import React, { useEffect, useRef, useState } from "react"; import type { ActionManager } from "../actions/manager"; import type { AppClassProperties, BinaryFiles, UIAppState } from "../types"; import { actionExportWithDarkMode, actionChangeExportBackground, actionChangeExportEmbedScene, actionChangeExportScale, actionChangeProjectName, } from "../actions/actionExport"; import { probablySupportsClipboardBlob } from "../clipboard"; import { DEFAULT_EXPORT_PADDING, EXPORT_IMAGE_TYPES, isFirefox, EXPORT_SCALES, } from "../constants"; import { canvasToBlob } from "../data/blob"; import { nativeFileSystemSupported } from "../data/filesystem"; import { NonDeletedExcalidrawElement } from "../element/types"; import { t } from "../i18n"; import { getSelectedElements, isSomeElementSelected } from "../scene"; import { exportToCanvas } from "../packages/utils"; import { copyIcon, downloadIcon, helpIcon } from "./icons"; import { Button } from "./Button"; import { Dialog } from "./Dialog"; import { RadioGroup } from "./RadioGroup"; import { Switch } from "./Switch"; import { Tooltip } from "./Tooltip"; import "./ImageExportDialog.scss"; import { useAppProps } from "./App"; const supportsContextFilters = "filter" in document.createElement("canvas").getContext("2d")!; export const ErrorCanvasPreview = () => { return (

{t("canvasError.cannotShowPreview")}

{t("canvasError.canvasTooBig")}

({t("canvasError.canvasTooBigTip")})
); }; type ImageExportModalProps = { appState: UIAppState; elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles; actionManager: ActionManager; onExportImage: AppClassProperties["onExportImage"]; }; const ImageExportModal = ({ appState, elements, files, actionManager, onExportImage, }: ImageExportModalProps) => { const appProps = useAppProps(); const [projectName, setProjectName] = useState(appState.name); const someElementIsSelected = isSomeElementSelected(elements, appState); const [exportSelected, setExportSelected] = useState(someElementIsSelected); const [exportWithBackground, setExportWithBackground] = useState( appState.exportBackground, ); const [exportDarkMode, setExportDarkMode] = useState( appState.exportWithDarkMode, ); const [embedScene, setEmbedScene] = useState(appState.exportEmbedScene); const [exportScale, setExportScale] = useState(appState.exportScale); const previewRef = useRef(null); const [renderError, setRenderError] = useState(null); const exportedElements = exportSelected ? getSelectedElements(elements, appState, true) : elements; useEffect(() => { const previewNode = previewRef.current; if (!previewNode) { return; } const maxWidth = previewNode.offsetWidth; const maxHeight = previewNode.offsetHeight; if (!maxWidth) { return; } exportToCanvas({ elements: exportedElements, appState, files, exportPadding: DEFAULT_EXPORT_PADDING, maxWidthOrHeight: Math.max(maxWidth, maxHeight), }) .then((canvas) => { 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((error) => { console.error(error); setRenderError(error); }); }, [appState, files, exportedElements]); return (

{t("imageExportDialog.header")}

{renderError && }
{!nativeFileSystemSupported && ( { setProjectName(event.target.value); actionManager.executeAction( actionChangeProjectName, "ui", event.target.value, ); }} /> )}

{t("imageExportDialog.header")}

{someElementIsSelected && ( { setExportSelected(checked); }} /> )} { setExportWithBackground(checked); actionManager.executeAction( actionChangeExportBackground, "ui", checked, ); }} /> {supportsContextFilters && ( { setExportDarkMode(checked); actionManager.executeAction( actionExportWithDarkMode, "ui", checked, ); }} /> )} { setEmbedScene(checked); actionManager.executeAction( actionChangeExportEmbedScene, "ui", checked, ); }} /> { setExportScale(scale); actionManager.executeAction(actionChangeExportScale, "ui", scale); }} choices={EXPORT_SCALES.map((scale) => ({ value: scale, label: `${scale}\u00d7`, }))} />
{(probablySupportsClipboardBlob || isFirefox) && ( )}
); }; type ExportSettingProps = { label: string; children: React.ReactNode; tooltip?: string; name?: string; }; const ExportSetting = ({ label, children, tooltip, name, }: ExportSettingProps) => { return (
{children}
); }; export const ImageExportDialog = ({ elements, appState, files, actionManager, onExportImage, onCloseRequest, }: { appState: UIAppState; elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles; actionManager: ActionManager; onExportImage: AppClassProperties["onExportImage"]; onCloseRequest: () => void; }) => { if (appState.openDialog !== "imageExport") { return null; } return ( ); };