refactor: simplify ImageExportDialog (#6578)

This commit is contained in:
David Luzar 2023-05-13 22:58:35 +02:00 committed by GitHub
parent b1b325b9a7
commit f6f9ed0396
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 91 deletions

View File

@ -59,6 +59,7 @@ import {
ELEMENT_TRANSLATE_AMOUNT,
ENV,
EVENT,
EXPORT_IMAGE_TYPES,
GRID_SIZE,
IMAGE_MIME_TYPES,
IMAGE_RENDER_TIMEOUT,
@ -82,7 +83,7 @@ import {
VERTICAL_ALIGN,
ZOOM_STEP,
} from "../constants";
import { loadFromBlob } from "../data";
import { exportCanvas, loadFromBlob } from "../data";
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
import { restore, restoreElements } from "../data/restore";
import {
@ -237,6 +238,7 @@ import {
getShortcutKey,
isTransparent,
easeToValuesRAF,
muteFSAbortError,
} from "../utils";
import {
ContextMenu,
@ -251,6 +253,7 @@ import {
generateIdFromFile,
getDataURL,
getFileFromEvent,
isImageFileHandle,
isSupportedImageFile,
loadSceneOrLibraryFromBlob,
normalizeFile,
@ -618,6 +621,7 @@ class App extends React.Component<AppProps, AppState> {
}
UIOptions={this.props.UIOptions}
onImageAction={this.onImageAction}
onExportImage={this.onExportImage}
renderWelcomeScreen={
!this.state.isLoading &&
this.state.showWelcomeScreen &&
@ -688,6 +692,37 @@ class App extends React.Component<AppProps, AppState> {
});
};
public onExportImage = async (
type: keyof typeof EXPORT_IMAGE_TYPES,
elements: readonly NonDeletedExcalidrawElement[],
) => {
trackEvent("export", type, "ui");
const fileHandle = await exportCanvas(
type,
elements,
this.state,
this.files,
{
exportBackground: this.state.exportBackground,
name: this.state.name,
viewBackgroundColor: this.state.viewBackgroundColor,
},
)
.catch(muteFSAbortError)
.catch((error) => {
console.error(error);
this.setState({ errorMessage: error.message });
});
if (
this.state.exportEmbedScene &&
fileHandle &&
isImageFileHandle(fileHandle)
) {
this.setState({ fileHandle });
}
};
private syncActionResult = withBatchedUpdates(
(actionResult: ActionResult) => {
if (this.unmounted || actionResult === false) {

View File

@ -4,13 +4,17 @@ import { canvasToBlob } from "../data/blob";
import { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { BinaryFiles, UIAppState } from "../types";
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, isFirefox } from "../constants";
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";
@ -65,21 +69,14 @@ const ImageExportModal = ({
elements,
appState,
files,
exportPadding = DEFAULT_EXPORT_PADDING,
actionManager,
onExportToPng,
onExportToSvg,
onExportToClipboard,
onExportImage,
}: {
appState: UIAppState;
elements: readonly NonDeletedExcalidrawElement[];
files: BinaryFiles;
exportPadding?: number;
actionManager: ActionManager;
onExportToPng: ExportCB;
onExportToSvg: ExportCB;
onExportToClipboard: ExportCB;
onCloseRequest: () => void;
onExportImage: AppClassProperties["onExportImage"];
}) => {
const someElementIsSelected = isSomeElementSelected(elements, appState);
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
@ -90,10 +87,6 @@ const ImageExportModal = ({
? getSelectedElements(elements, appState, true)
: elements;
useEffect(() => {
setExportSelected(someElementIsSelected);
}, [someElementIsSelected]);
useEffect(() => {
const previewNode = previewRef.current;
if (!previewNode) {
@ -107,7 +100,7 @@ const ImageExportModal = ({
elements: exportedElements,
appState,
files,
exportPadding,
exportPadding: DEFAULT_EXPORT_PADDING,
maxWidthOrHeight: maxWidth,
})
.then((canvas) => {
@ -122,7 +115,7 @@ const ImageExportModal = ({
console.error(error);
setRenderError(error);
});
}, [appState, files, exportedElements, exportPadding]);
}, [appState, files, exportedElements]);
return (
<div className="ExportDialog">
@ -177,7 +170,9 @@ const ImageExportModal = ({
color="indigo"
title={t("buttons.exportToPng")}
aria-label={t("buttons.exportToPng")}
onClick={() => onExportToPng(exportedElements)}
onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.png, exportedElements)
}
>
PNG
</ExportButton>
@ -185,7 +180,9 @@ const ImageExportModal = ({
color="red"
title={t("buttons.exportToSvg")}
aria-label={t("buttons.exportToSvg")}
onClick={() => onExportToSvg(exportedElements)}
onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.svg, exportedElements)
}
>
SVG
</ExportButton>
@ -194,7 +191,9 @@ const ImageExportModal = ({
{(probablySupportsClipboardBlob || isFirefox) && (
<ExportButton
title={t("buttons.copyPngToClipboard")}
onClick={() => onExportToClipboard(exportedElements)}
onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.clipboard, exportedElements)
}
color="gray"
shade={7}
>
@ -209,45 +208,31 @@ const ImageExportModal = ({
export const ImageExportDialog = ({
elements,
appState,
setAppState,
files,
exportPadding = DEFAULT_EXPORT_PADDING,
actionManager,
onExportToPng,
onExportToSvg,
onExportToClipboard,
onExportImage,
onCloseRequest,
}: {
appState: UIAppState;
setAppState: React.Component<any, UIAppState>["setState"];
elements: readonly NonDeletedExcalidrawElement[];
files: BinaryFiles;
exportPadding?: number;
actionManager: ActionManager;
onExportToPng: ExportCB;
onExportToSvg: ExportCB;
onExportToClipboard: ExportCB;
onExportImage: AppClassProperties["onExportImage"];
onCloseRequest: () => void;
}) => {
const handleClose = React.useCallback(() => {
setAppState({ openDialog: null });
}, [setAppState]);
if (appState.openDialog !== "imageExport") {
return null;
}
return (
<>
{appState.openDialog === "imageExport" && (
<Dialog onCloseRequest={handleClose} title={t("buttons.exportImage")}>
<Dialog onCloseRequest={onCloseRequest} title={t("buttons.exportImage")}>
<ImageExportModal
elements={elements}
appState={appState}
files={files}
exportPadding={exportPadding}
actionManager={actionManager}
onExportToPng={onExportToPng}
onExportToSvg={onExportToSvg}
onExportToClipboard={onExportToClipboard}
onCloseRequest={handleClose}
onExportImage={onExportImage}
/>
</Dialog>
)}
</>
);
};

View File

@ -2,23 +2,22 @@ import clsx from "clsx";
import React from "react";
import { ActionManager } from "../actions/manager";
import { CLASSES, DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_WIDTH } from "../constants";
import { exportCanvas } from "../data";
import { isTextElement, showSelectedShapeActions } from "../element";
import { NonDeletedExcalidrawElement } from "../element/types";
import { Language, t } from "../i18n";
import { calculateScrollCenter } from "../scene";
import { ExportType } from "../scene/types";
import {
AppProps,
AppState,
ExcalidrawProps,
BinaryFiles,
UIAppState,
AppClassProperties,
} from "../types";
import { capitalizeString, isShallowEqual, muteFSAbortError } from "../utils";
import { capitalizeString, isShallowEqual } from "../utils";
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
import { ErrorDialog } from "./ErrorDialog";
import { ExportCB, ImageExportDialog } from "./ImageExportDialog";
import { ImageExportDialog } from "./ImageExportDialog";
import { FixedSideContainer } from "./FixedSideContainer";
import { HintViewer } from "./HintViewer";
import { Island } from "./Island";
@ -31,7 +30,6 @@ import { HelpDialog } from "./HelpDialog";
import Stack from "./Stack";
import { UserList } from "./UserList";
import { JSONExportDialog } from "./JSONExportDialog";
import { isImageFileHandle } from "../data/blob";
import { PenModeButton } from "./PenModeButton";
import { trackEvent } from "../analytics";
import { useDevice } from "../components/App";
@ -69,6 +67,7 @@ interface LayerUIProps {
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
UIOptions: AppProps["UIOptions"];
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
onExportImage: AppClassProperties["onExportImage"];
renderWelcomeScreen: boolean;
children?: React.ReactNode;
}
@ -114,6 +113,7 @@ const LayerUI = ({
renderCustomStats,
UIOptions,
onImageAction,
onExportImage,
renderWelcomeScreen,
children,
}: LayerUIProps) => {
@ -143,47 +143,14 @@ const LayerUI = ({
return null;
}
const createExporter =
(type: ExportType): ExportCB =>
async (exportedElements) => {
trackEvent("export", type, "ui");
const fileHandle = await exportCanvas(
type,
exportedElements,
// FIXME once we split UI canvas from element canvas
appState as AppState,
files,
{
exportBackground: appState.exportBackground,
name: appState.name,
viewBackgroundColor: appState.viewBackgroundColor,
},
)
.catch(muteFSAbortError)
.catch((error) => {
console.error(error);
setAppState({ errorMessage: error.message });
});
if (
appState.exportEmbedScene &&
fileHandle &&
isImageFileHandle(fileHandle)
) {
setAppState({ fileHandle });
}
};
return (
<ImageExportDialog
elements={elements}
appState={appState}
setAppState={setAppState}
files={files}
actionManager={actionManager}
onExportToPng={createExporter("png")}
onExportToSvg={createExporter("svg")}
onExportToClipboard={createExporter("clipboard")}
onExportImage={onExportImage}
onCloseRequest={() => setAppState({ openDialog: null })}
/>
);
};

View File

@ -131,6 +131,12 @@ export const MIME_TYPES = {
...IMAGE_MIME_TYPES,
} as const;
export const EXPORT_IMAGE_TYPES = {
png: "png",
svg: "svg",
clipboard: "clipboard",
} as const;
export const EXPORT_DATA_TYPES = {
excalidraw: "excalidraw",
excalidrawClipboard: "excalidraw/clipboard",

View File

@ -442,6 +442,7 @@ export type AppClassProperties = {
pasteFromClipboard: App["pasteFromClipboard"];
id: App["id"];
onInsertElements: App["onInsertElements"];
onExportImage: App["onExportImage"];
};
export type PointerDownState = Readonly<{