diff --git a/packages/excalidraw/actions/actionExport.tsx b/packages/excalidraw/actions/actionExport.tsx index e47a5bb84c..453a0be1f7 100644 --- a/packages/excalidraw/actions/actionExport.tsx +++ b/packages/excalidraw/actions/actionExport.tsx @@ -300,7 +300,8 @@ export const actionExportWithDarkMode = register< name: "exportWithDarkMode", label: "imageExportDialog.label.darkMode", trackEvent: { category: "export", action: "toggleTheme" }, - perform: (_elements, appState, value) => { + perform: (_elements, appState, value, app) => { + app.sessionExportThemeOverride = value ? THEME.DARK : THEME.LIGHT; return { appState: { ...appState, exportWithDarkMode: value }, captureUpdate: CaptureUpdateAction.EVENTUALLY, diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index 0b361e0e70..75b850f8c2 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -595,6 +595,7 @@ const gesture: Gesture = { class App extends React.Component { canvas: AppClassProperties["canvas"]; interactiveCanvas: AppClassProperties["interactiveCanvas"] = null; + public sessionExportThemeOverride: AppState["theme"] | undefined; rc: RoughCanvas; unmounted: boolean = false; actionManager: ActionManager; @@ -710,6 +711,7 @@ class App extends React.Component { this.state = { ...defaultAppState, theme, + exportWithDarkMode: theme === THEME.DARK, isLoading: true, ...this.getCanvasOffsets(), viewModeEnabled, @@ -3241,6 +3243,13 @@ class App extends React.Component { const elements = this.scene.getElementsIncludingDeleted(); const elementsMap = this.scene.getElementsMapIncludingDeleted(); + const shouldExportWithDarkMode = + (this.sessionExportThemeOverride ?? this.state.theme) === THEME.DARK; + + if (this.state.exportWithDarkMode !== shouldExportWithDarkMode) { + this.setState({ exportWithDarkMode: shouldExportWithDarkMode }); + } + if (!this.state.showWelcomeScreen && !elements.length) { this.setState({ showWelcomeScreen: true }); } diff --git a/packages/excalidraw/components/ImageExportDialog.tsx b/packages/excalidraw/components/ImageExportDialog.tsx index 18831fa08e..48afbaa966 100644 --- a/packages/excalidraw/components/ImageExportDialog.tsx +++ b/packages/excalidraw/components/ImageExportDialog.tsx @@ -59,6 +59,7 @@ type ImageExportModalProps = { actionManager: ActionManager; onExportImage: AppClassProperties["onExportImage"]; name: string; + exportWithDarkMode: boolean; }; const ImageExportModal = ({ @@ -68,6 +69,7 @@ const ImageExportModal = ({ actionManager, onExportImage, name, + exportWithDarkMode, }: ImageExportModalProps) => { const hasSelection = isSomeElementSelected( elementsSnapshot, @@ -79,15 +81,13 @@ const ImageExportModal = ({ const [exportWithBackground, setExportWithBackground] = useState( appStateSnapshot.exportBackground, ); - const [exportDarkMode, setExportDarkMode] = useState( - appStateSnapshot.exportWithDarkMode, - ); const [embedScene, setEmbedScene] = useState( appStateSnapshot.exportEmbedScene, ); const [exportScale, setExportScale] = useState(appStateSnapshot.exportScale); const previewRef = useRef(null); + const previewRenderRequestIdRef = useRef(0); const [renderError, setRenderError] = useState(null); const { onCopy, copyStatus, resetCopyStatus } = useCopyStatus(); @@ -99,7 +99,7 @@ const ImageExportModal = ({ }, [ projectName, exportWithBackground, - exportDarkMode, + exportWithDarkMode, exportScale, embedScene, resetCopyStatus, @@ -122,13 +122,18 @@ const ImageExportModal = ({ return; } + const requestId = ++previewRenderRequestIdRef.current; + const isStaleRequest = () => { + return requestId !== previewRenderRequestIdRef.current; + }; + exportToCanvas({ elements: exportedElements, appState: { ...appStateSnapshot, name: projectName, exportBackground: exportWithBackground, - exportWithDarkMode: exportDarkMode, + exportWithDarkMode, exportScale, exportEmbedScene: embedScene, }, @@ -137,25 +142,41 @@ const ImageExportModal = ({ maxWidthOrHeight: Math.max(maxWidth, maxHeight), exportingFrame, }) - .then((canvas) => { + .then(async (canvas) => { + if (isStaleRequest()) { + return; + } + + // If converting to blob fails, there's some problem that will likely + // prevent preview and export (e.g. canvas too big). + try { + await canvasToBlob(canvas); + } catch (error: any) { + if (error.name === "CANVAS_POSSIBLY_TOO_BIG") { + throw new Error(t("canvasError.canvasTooBig")); + } + throw error; + } + + if (isStaleRequest()) { + return; + } + setRenderError(null); - // if converting to blob fails, there's some problem that will - // likely prevent preview and export (e.g. canvas too big) - return canvasToBlob(canvas) - .then(() => { - previewNode.replaceChildren(canvas); - }) - .catch((e) => { - if (e.name === "CANVAS_POSSIBLY_TOO_BIG") { - throw new Error(t("canvasError.canvasTooBig")); - } - throw e; - }); + previewNode.replaceChildren(canvas); }) .catch((error) => { + if (isStaleRequest()) { + return; + } + console.error(error); setRenderError(error); }); + + return () => { + previewRenderRequestIdRef.current += 1; + }; }, [ appStateSnapshot, files, @@ -163,7 +184,7 @@ const ImageExportModal = ({ exportingFrame, projectName, exportWithBackground, - exportDarkMode, + exportWithDarkMode, exportScale, embedScene, ]); @@ -233,9 +254,8 @@ const ImageExportModal = ({ > { - setExportDarkMode(checked); actionManager.executeAction( actionExportWithDarkMode, "ui", @@ -399,6 +419,7 @@ export const ImageExportDialog = ({ actionManager={actionManager} onExportImage={onExportImage} name={name} + exportWithDarkMode={appState.exportWithDarkMode} /> ); diff --git a/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap index ef0fb47e64..2ed8bc1c53 100644 --- a/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap @@ -2,7 +2,7 @@ exports[` > > should render main menu with host menu items if passed from host 1`] = `