import "./ExportDialog.css"; import React, { useState, useEffect, useRef } from "react"; import { Modal } from "./Modal"; import { ToolButton } from "./ToolButton"; import { clipboard, exportFile, link } from "./icons"; import { Island } from "./Island"; import { ExcalidrawElement } from "../element/types"; import { AppState } from "../types"; import { exportToCanvas } from "../scene/export"; import { ActionsManagerInterface } from "../actions/types"; import Stack from "./Stack"; import { t } from "../i18n"; import { KEYS } from "../keys"; import { probablySupportsClipboardBlob } from "../clipboard"; import { getSelectedElements, isSomeElementSelected } from "../scene"; import useIsMobile from "../is-mobile"; const scales = [1, 2, 3]; const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1; type ExportCB = ( elements: readonly ExcalidrawElement[], scale?: number, ) => void; function ExportModal({ elements, appState, exportPadding = 10, actionManager, onExportToPng, onExportToSvg, onExportToClipboard, onExportToBackend, onCloseRequest, }: { appState: AppState; elements: readonly ExcalidrawElement[]; exportPadding?: number; actionManager: ActionsManagerInterface; onExportToPng: ExportCB; onExportToSvg: ExportCB; onExportToClipboard: ExportCB; onExportToBackend: ExportCB; onCloseRequest: () => void; }) { const someElementIsSelected = isSomeElementSelected(elements); const [scale, setScale] = useState(defaultScale); const [exportSelected, setExportSelected] = useState(someElementIsSelected); const previewRef = useRef(null); const { exportBackground, viewBackgroundColor } = appState; const pngButton = useRef(null); const closeButton = useRef(null); const onlySelectedInput = useRef(null); const exportedElements = exportSelected ? getSelectedElements(elements) : elements; useEffect(() => { setExportSelected(someElementIsSelected); }, [someElementIsSelected]); useEffect(() => { const previewNode = previewRef.current; const canvas = exportToCanvas(exportedElements, { exportBackground, viewBackgroundColor, exportPadding, scale, }); previewNode?.appendChild(canvas); return () => { previewNode?.removeChild(canvas); }; }, [ exportedElements, exportBackground, exportPadding, viewBackgroundColor, scale, ]); useEffect(() => { pngButton.current?.focus(); }, []); function handleKeyDown(e: React.KeyboardEvent) { if (e.key === KEYS.TAB) { const { activeElement } = document; if (e.shiftKey) { if (activeElement === pngButton.current) { closeButton.current?.focus(); e.preventDefault(); } } else { if (activeElement === closeButton.current) { pngButton.current?.focus(); e.preventDefault(); } if (activeElement === onlySelectedInput.current) { closeButton.current?.focus(); e.preventDefault(); } } } } return (

{t("buttons.export")}

onExportToPng(exportedElements, scale)} ref={pngButton} /> onExportToSvg(exportedElements, scale)} /> {probablySupportsClipboardBlob && ( onExportToClipboard(exportedElements, scale)} /> )} onExportToBackend(exportedElements)} /> {actionManager.renderAction("changeProjectName")}
{scales.map(s => ( setScale(s)} /> ))}
{actionManager.renderAction("changeExportBackground")} {someElementIsSelected && (
)}
); } export function ExportDialog({ elements, appState, exportPadding = 10, actionManager, onExportToPng, onExportToSvg, onExportToClipboard, onExportToBackend, }: { appState: AppState; elements: readonly ExcalidrawElement[]; exportPadding?: number; actionManager: ActionsManagerInterface; onExportToPng: ExportCB; onExportToSvg: ExportCB; onExportToClipboard: ExportCB; onExportToBackend: ExportCB; }) { const [modalIsShown, setModalIsShown] = useState(false); const triggerButton = useRef(null); const handleClose = React.useCallback(() => { setModalIsShown(false); triggerButton.current?.focus(); }, []); return ( <> setModalIsShown(true)} icon={exportFile} type="button" aria-label={t("buttons.export")} showAriaLabel={useIsMobile()} title={t("buttons.export")} ref={triggerButton} /> {modalIsShown && ( )} ); }