Explorar el Código

refactor: simplify `ImageExportDialog` (#6578)

David Luzar hace 2 años
padre
commit
f6f9ed0396
Se han modificado 5 ficheros con 85 adiciones y 91 borrados
  1. 36 1
      src/components/App.tsx
  2. 35 50
      src/components/ImageExportDialog.tsx
  3. 7 40
      src/components/LayerUI.tsx
  4. 6 0
      src/constants.ts
  5. 1 0
      src/types.ts

+ 36 - 1
src/components/App.tsx

@@ -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) {

+ 35 - 50
src/components/ImageExportDialog.tsx

@@ -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")}>
-          <ImageExportModal
-            elements={elements}
-            appState={appState}
-            files={files}
-            exportPadding={exportPadding}
-            actionManager={actionManager}
-            onExportToPng={onExportToPng}
-            onExportToSvg={onExportToSvg}
-            onExportToClipboard={onExportToClipboard}
-            onCloseRequest={handleClose}
-          />
-        </Dialog>
-      )}
-    </>
+    <Dialog onCloseRequest={onCloseRequest} title={t("buttons.exportImage")}>
+      <ImageExportModal
+        elements={elements}
+        appState={appState}
+        files={files}
+        actionManager={actionManager}
+        onExportImage={onExportImage}
+      />
+    </Dialog>
   );
 };

+ 7 - 40
src/components/LayerUI.tsx

@@ -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 })}
       />
     );
   };

+ 6 - 0
src/constants.ts

@@ -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",

+ 1 - 0
src/types.ts

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