refactor: simplify ImageExportDialog
(#6578)
This commit is contained in:
parent
b1b325b9a7
commit
f6f9ed0396
@ -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) {
|
||||
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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 })}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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",
|
||||
|
@ -442,6 +442,7 @@ export type AppClassProperties = {
|
||||
pasteFromClipboard: App["pasteFromClipboard"];
|
||||
id: App["id"];
|
||||
onInsertElements: App["onInsertElements"];
|
||||
onExportImage: App["onExportImage"];
|
||||
};
|
||||
|
||||
export type PointerDownState = Readonly<{
|
||||
|
Loading…
x
Reference in New Issue
Block a user