import React, { useEffect, useRef, useState } from "react"; import { probablySupportsClipboardBlob } from "../clipboard"; import { canvasToBlob } from "../data/blob"; import { NonDeletedExcalidrawElement } from "../element/types"; import { t } from "../i18n"; import { getSelectedElements, isSomeElementSelected } from "../scene"; import { AppClassProperties, BinaryFiles, UIAppState } from "../types"; import { Dialog } from "./Dialog"; import { clipboard } from "./icons"; import Stack from "./Stack"; import OpenColor from "open-color"; import { CheckboxItem } from "./CheckboxItem"; import { DEFAULT_EXPORT_PADDING, EXPORT_IMAGE_TYPES, isFirefox, } from "../constants"; import { nativeFileSystemSupported } from "../data/filesystem"; import { ActionManager } from "../actions/manager"; import { exportToCanvas } from "../packages/utils"; import "./ExportDialog.scss"; const supportsContextFilters = "filter" in document.createElement("canvas").getContext("2d")!; export const ErrorCanvasPreview = () => { return (

{t("canvasError.cannotShowPreview")}

{t("canvasError.canvasTooBig")}

({t("canvasError.canvasTooBigTip")})
); }; export type ExportCB = ( elements: readonly NonDeletedExcalidrawElement[], scale?: number, ) => void; const ExportButton: React.FC<{ color: keyof OpenColor; onClick: () => void; title: string; shade?: number; children?: React.ReactNode; }> = ({ children, title, onClick, color, shade = 6 }) => { return ( ); }; const ImageExportModal = ({ elements, appState, files, actionManager, onExportImage, }: { appState: UIAppState; elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles; actionManager: ActionManager; onExportImage: AppClassProperties["onExportImage"]; }) => { const someElementIsSelected = isSomeElementSelected(elements, appState); const [exportSelected, setExportSelected] = useState(someElementIsSelected); 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; if (!maxWidth) { return; } exportToCanvas({ elements: exportedElements, appState, files, exportPadding: DEFAULT_EXPORT_PADDING, maxWidthOrHeight: maxWidth, }) .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 (
{renderError && }
{supportsContextFilters && actionManager.renderAction("exportWithDarkMode")}
{actionManager.renderAction("changeExportBackground")} {someElementIsSelected && ( setExportSelected(checked)} > {t("labels.onlySelected")} )} {actionManager.renderAction("changeExportEmbedScene")}
{actionManager.renderAction("changeExportScale")}

{t("buttons.scale")}

{!nativeFileSystemSupported && actionManager.renderAction("changeProjectName")}
onExportImage(EXPORT_IMAGE_TYPES.png, exportedElements) } > PNG onExportImage(EXPORT_IMAGE_TYPES.svg, exportedElements) } > SVG {/* firefox supports clipboard API under a flag, so let's throw and tell people what they can do */} {(probablySupportsClipboardBlob || isFirefox) && ( onExportImage(EXPORT_IMAGE_TYPES.clipboard, exportedElements) } color="gray" shade={7} > {clipboard} )}
); }; 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 ( ); };