excalidraw/src/components/ExportDialog.tsx
2020-01-20 20:11:03 +02:00

185 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import "./ExportDialog.css";
import React, { useState, useEffect, useRef } from "react";
import { Modal } from "./Modal";
import { ToolIcon } from "./ToolIcon";
import { clipboard, exportFile, downloadFile, link } from "./icons";
import { Island } from "./Island";
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
import { getExportCanvasPreview } from "../scene/getExportCanvasPreview";
import { ActionsManagerInterface, UpdaterFn } from "../actions/types";
import Stack from "./Stack";
const probablySupportsClipboard =
"toBlob" in HTMLCanvasElement.prototype &&
"clipboard" in navigator &&
"write" in navigator.clipboard &&
"ClipboardItem" in window;
const scales = [1, 2, 3];
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
type ExportCB = (elements: readonly ExcalidrawElement[], scale: number) => void;
export function ExportDialog({
elements,
appState,
exportPadding = 10,
actionManager,
syncActionResult,
onExportToPng,
onExportToClipboard,
onExportToBackend
}: {
appState: AppState;
elements: readonly ExcalidrawElement[];
exportPadding?: number;
actionManager: ActionsManagerInterface;
syncActionResult: UpdaterFn;
onExportToPng: ExportCB;
onExportToClipboard: ExportCB;
onExportToBackend: ExportCB;
}) {
const someElementIsSelected = elements.some(element => element.isSelected);
const [modalIsShown, setModalIsShown] = useState(false);
const [scale, setScale] = useState(defaultScale);
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
const previeRef = useRef<HTMLDivElement>(null);
const { exportBackground, viewBackgroundColor } = appState;
const exportedElements = exportSelected
? elements.filter(element => element.isSelected)
: elements;
useEffect(() => {
setExportSelected(someElementIsSelected);
}, [someElementIsSelected]);
useEffect(() => {
const previewNode = previeRef.current;
const canvas = getExportCanvasPreview(exportedElements, {
exportBackground,
viewBackgroundColor,
exportPadding,
scale
});
previewNode?.appendChild(canvas);
return () => {
previewNode?.removeChild(canvas);
};
}, [
modalIsShown,
exportedElements,
exportBackground,
exportPadding,
viewBackgroundColor,
scale
]);
function handleClose() {
setModalIsShown(false);
setExportSelected(someElementIsSelected);
}
return (
<>
<ToolIcon
onClick={() => setModalIsShown(true)}
icon={exportFile}
type="button"
aria-label="Show export dialog"
title="Export"
/>
{modalIsShown && (
<Modal maxWidth={640} onCloseRequest={handleClose}>
<div className="ExportDialog__dialog">
<Island padding={4}>
<button className="ExportDialog__close" onClick={handleClose}>
</button>
<h2>Export</h2>
<div className="ExportDialog__preview" ref={previeRef}></div>
<div className="ExportDialog__actions">
<Stack.Row gap={2}>
<ToolIcon
type="button"
icon={downloadFile}
title="Export to PNG"
aria-label="Export to PNG"
onClick={() => onExportToPng(exportedElements, scale)}
/>
{probablySupportsClipboard && (
<ToolIcon
type="button"
icon={clipboard}
title="Copy to clipboard"
aria-label="Copy to clipboard"
onClick={() =>
onExportToClipboard(exportedElements, scale)
}
/>
)}
<ToolIcon
type="button"
icon={link}
title="Get shareable link"
aria-label="Get shareable link"
onClick={() => onExportToBackend(exportedElements, 1)}
/>
</Stack.Row>
{actionManager.renderAction(
"changeProjectName",
elements,
appState,
syncActionResult
)}
<Stack.Col gap={1}>
<div className="ExportDialog__scales">
<Stack.Row gap={1} align="baseline">
{scales.map(s => (
<ToolIcon
key={s}
size="s"
type="radio"
icon={"x" + s}
name="export-canvas-scale"
id="export-canvas-scale"
checked={scale === s}
onChange={() => setScale(s)}
/>
))}
</Stack.Row>
</div>
{actionManager.renderAction(
"changeExportBackground",
elements,
appState,
syncActionResult
)}
{someElementIsSelected && (
<div>
<label>
<input
type="checkbox"
checked={exportSelected}
onChange={e =>
setExportSelected(e.currentTarget.checked)
}
/>{" "}
Only selected
</label>
</div>
)}
</Stack.Col>
</div>
</Island>
</div>
</Modal>
)}
</>
);
}