From 7286613131beb1bc5e36439192ef5e566b2280c0 Mon Sep 17 00:00:00 2001 From: P SRIKAR NAIDU Date: Fri, 30 Oct 2026 21:37:52 +0530 Subject: [PATCH 1/2] fix(editor): export local images without API URLs --- src/components/editor/EditorApp.tsx | 4 ++-- src/components/editor/ExportBar.tsx | 32 ++++++++++++++++++++------- src/components/editor/export-utils.ts | 22 ++++++++++++++++++ tests/components/export-utils.test.ts | 14 ++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 src/components/editor/export-utils.ts create mode 100644 tests/components/export-utils.test.ts diff --git a/src/components/editor/EditorApp.tsx b/src/components/editor/EditorApp.tsx index 34df60a..4f5e61f 100644 --- a/src/components/editor/EditorApp.tsx +++ b/src/components/editor/EditorApp.tsx @@ -236,7 +236,7 @@ function EditorAppInner({ initialCategory }: EditorAppProps) { {currentTemplate.name} - + {/* Mobile Tabs */} @@ -278,7 +278,7 @@ function EditorAppInner({ initialCategory }: EditorAppProps) { {/* The topbar ExportBar always mounts and owns the shortcuts, so this second one opts out. */} - + ; templateId: string; + svg: string | null; /** * Whether this bar owns the keyboard shortcuts. Pass false on a second * ExportBar so Ctrl/Cmd+S and Ctrl/Cmd+Shift+C fire once, not once per bar. @@ -24,22 +26,31 @@ export function ExportBar({ downloadUrl, params, templateId, + svg, shortcuts = true, }: ExportBarProps) { const [downloading, setDownloading] = useState(false); const [copied, setCopied] = useState(null); const downloadRef = useRef(downloadUrl); const apiUrlRef = useRef(apiUrl); + const svgRef = useRef(svg); + const hasLocalImage = containsLocalImage(params); downloadRef.current = downloadUrl; apiUrlRef.current = apiUrl; + svgRef.current = svg; const handleDownload = useCallback(async () => { setDownloading(true); try { - const res = await fetch(downloadRef.current); - if (!res.ok) throw new Error('Download failed'); - const blob = await res.blob(); + let blob: Blob; + if (hasLocalImage) { + blob = await svgToPngBlob(svgRef.current || ''); + } else { + const res = await fetch(downloadRef.current); + if (!res.ok) throw new Error('Download failed'); + blob = await res.blob(); + } const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; @@ -55,7 +66,11 @@ export function ExportBar({ } finally { setDownloading(false); } - }, [templateId]); + }, [hasLocalImage, templateId]); + + const showLocalImageWarning = useCallback(() => { + window.showToast?.('Local images cannot be included in a shareable URL.', 'error'); + }, []); const copyToClipboard = useCallback(async (text: string, label: string) => { try { @@ -96,13 +111,14 @@ export function ExportBar({ // Ctrl/Cmd+Shift+C → Copy API URL if (e.key === 'C' && e.shiftKey) { e.preventDefault(); - copyToClipboard(apiUrlRef.current, 'url'); + if (hasLocalImage) showLocalImageWarning(); + else copyToClipboard(apiUrlRef.current, 'url'); } } document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); - }, [handleDownload, copyToClipboard, shortcuts]); + }, [handleDownload, copyToClipboard, hasLocalImage, showLocalImageWarning, shortcuts]); const metaTags = generateMetaTags({ title: params.title ? String(params.title) : undefined, @@ -126,7 +142,7 @@ export function ExportBar({