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 (
{children}
);
};
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 (
);
};