Parcourir la source

feat: support appState.exportEmbedScene to embed scene data in exportToSvg util (#3777)

* feat: add embedScene attribute to exportToSvg util

* fix

* return promise

* add docs and remove

* fix

* fix tests

* use

* fix

* fix

* remove metadata and use exportEmbedScene

* fix

* fix

* fix

* fix

* IIFE
Aakansha Doshi il y a 3 ans
Parent
commit
f861a9fdd0

+ 18 - 12
src/components/LibraryUnit.tsx

@@ -36,21 +36,27 @@ export const LibraryUnit = ({
     if (!elementsToRender) {
       return;
     }
-    const svg = exportToSvg(elementsToRender, {
-      exportBackground: false,
-      viewBackgroundColor: oc.white,
-    });
-    for (const child of ref.current!.children) {
-      if (child.tagName !== "svg") {
-        continue;
+    let svg: SVGSVGElement;
+    const current = ref.current!;
+
+    (async () => {
+      svg = await exportToSvg(elementsToRender, {
+        exportBackground: false,
+        viewBackgroundColor: oc.white,
+      });
+      for (const child of ref.current!.children) {
+        if (child.tagName !== "svg") {
+          continue;
+        }
+        current!.removeChild(child);
       }
-      ref.current!.removeChild(child);
-    }
-    ref.current!.appendChild(svg);
+      current!.appendChild(svg);
+    })();
 
-    const current = ref.current!;
     return () => {
-      current.removeChild(svg);
+      if (svg) {
+        current.removeChild(svg);
+      }
     };
   }, [elements, pendingElements]);
 

+ 12 - 10
src/components/PasteChartDialog.tsx

@@ -34,19 +34,21 @@ const ChartPreviewBtn = (props: {
       0,
     );
     setChartElements(elements);
-
-    const svg = exportToSvg(elements, {
-      exportBackground: false,
-      viewBackgroundColor: oc.white,
-    });
-
+    let svg: SVGSVGElement;
     const previewNode = previewRef.current!;
 
-    previewNode.appendChild(svg);
+    (async () => {
+      svg = await exportToSvg(elements, {
+        exportBackground: false,
+        viewBackgroundColor: oc.white,
+      });
 
-    if (props.selected) {
-      (previewNode.parentNode as HTMLDivElement).focus();
-    }
+      previewNode.appendChild(svg);
+
+      if (props.selected) {
+        (previewNode.parentNode as HTMLDivElement).focus();
+      }
+    })();
 
     return () => {
       previewNode.removeChild(svg);

+ 3 - 10
src/data/index.ts

@@ -35,20 +35,13 @@ export const exportCanvas = async (
     throw new Error(t("alerts.cannotExportEmptyCanvas"));
   }
   if (type === "svg" || type === "clipboard-svg") {
-    const tempSvg = exportToSvg(elements, {
+    const tempSvg = await exportToSvg(elements, {
       exportBackground,
       exportWithDarkMode: appState.exportWithDarkMode,
       viewBackgroundColor,
       exportPadding,
       exportScale: appState.exportScale,
-      metadata:
-        appState.exportEmbedScene && type === "svg"
-          ? await (
-              await import(/* webpackChunkName: "image" */ "./image")
-            ).encodeSvgMetadata({
-              text: serializeAsJSON(elements, appState),
-            })
-          : undefined,
+      exportEmbedScene: appState.exportEmbedScene && type === "svg",
     });
     if (type === "svg") {
       await fileSave(new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }), {
@@ -57,7 +50,7 @@ export const exportCanvas = async (
       });
       return;
     } else if (type === "clipboard-svg") {
-      copyTextToSystemClipboard(tempSvg.outerHTML);
+      await copyTextToSystemClipboard(tempSvg.outerHTML);
       return;
     }
   }

+ 1 - 1
src/data/json.ts

@@ -15,7 +15,7 @@ import Library from "./library";
 
 export const serializeAsJSON = (
   elements: readonly ExcalidrawElement[],
-  appState: AppState,
+  appState: Partial<AppState>,
 ): string => {
   const data: ExportedDataState = {
     type: EXPORT_DATA_TYPES.excalidraw,

+ 7 - 0
src/packages/excalidraw/CHANGELOG.md

@@ -19,6 +19,13 @@ Please add the latest change on the top under the correct section.
 
 ### Features
 
+- Support `appState.exportEmbedScene` attribute in [`exportToSvg`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToSvg) which allows to embed the scene data.
+
+  #### BREAKING CHANGE
+
+  - The attribute `metadata` is now removed as `metadata` was only used to embed scene data which is now supported with the `appState.exportEmbedScene` attribute.
+  - [`exportToSvg`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#exportToSvg) now resolves to a promise which resolves to `svg` of the exported drawing.
+
 - Expose [`loadLibraryFromBlob`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#loadLibraryFromBlobY), [`loadFromBlob`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#loadFromBlob), and [`getFreeDrawSvgPath`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#getFreeDrawSvgPath).
 
 - Expose [`FONT_FAMILY`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#FONT_FAMILY) so that consumer can use when passing `initialData.appState.currentItemFontFamily`.

+ 2 - 2
src/packages/excalidraw/README_NEXT.md

@@ -829,9 +829,8 @@ exportToSvg({
 | elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78) |  | The elements to exported as svg |
 | appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L42) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L11) | The app state of the scene |
 | exportPadding | number | 10 | The padding to be added on canvas |
-| metadata | string | '' | The metadata to be embedded in svg |
 
-This function returns a svg with the exported elements.
+This function returns a promise which resolves to svg of the exported drawing.
 
 ##### Additional attributes of appState for `export\*` APIs
 
@@ -840,6 +839,7 @@ This function returns a svg with the exported elements.
 | exportBackground | boolean | true | Indicates whether background should be exported |
 | viewBackgroundColor | string | #fff | The default background color |
 | exportWithDarkMode | boolean | false | Indicates whether to export with dark mode |
+| exportEmbedScene | boolean | false | Indicates whether scene data should be embedded in svg. This will increase the svg size. |
 
 ### FONT_FAMILY
 

+ 2 - 5
src/packages/utils.ts

@@ -74,15 +74,13 @@ export const exportToBlob = (
   });
 };
 
-export const exportToSvg = ({
+export const exportToSvg = async ({
   elements,
   appState = getDefaultAppState(),
   exportPadding,
-  metadata,
 }: Omit<ExportOpts, "getDimensions"> & {
   exportPadding?: number;
-  metadata?: string;
-}): SVGSVGElement => {
+}): Promise<SVGSVGElement> => {
   const { elements: restoredElements, appState: restoredAppState } = restore(
     { elements, appState },
     null,
@@ -90,7 +88,6 @@ export const exportToSvg = ({
   return _exportToSvg(getNonDeletedElements(restoredElements), {
     ...restoredAppState,
     exportPadding,
-    metadata,
   });
 };
 

+ 25 - 13
src/scene/export.ts

@@ -6,6 +6,7 @@ import { distance, SVG_NS } from "../utils";
 import { AppState } from "../types";
 import { DEFAULT_EXPORT_PADDING, THEME_FILTER } from "../constants";
 import { getDefaultAppState } from "../appState";
+import { serializeAsJSON } from "../data/json";
 
 export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
 
@@ -65,24 +66,35 @@ export const exportToCanvas = (
   return canvas;
 };
 
-export const exportToSvg = (
+export const exportToSvg = async (
   elements: readonly NonDeletedExcalidrawElement[],
-  {
-    exportBackground,
-    exportPadding = DEFAULT_EXPORT_PADDING,
-    viewBackgroundColor,
-    exportWithDarkMode,
-    exportScale = 1,
-    metadata = "",
-  }: {
+  appState: {
     exportBackground: boolean;
     exportPadding?: number;
     exportScale?: number;
     viewBackgroundColor: string;
     exportWithDarkMode?: boolean;
-    metadata?: string;
+    exportEmbedScene?: boolean;
   },
-): SVGSVGElement => {
+): Promise<SVGSVGElement> => {
+  const {
+    exportPadding = DEFAULT_EXPORT_PADDING,
+    viewBackgroundColor,
+    exportScale = 1,
+    exportEmbedScene,
+  } = appState;
+  let metadata = "";
+  if (exportEmbedScene) {
+    try {
+      metadata = await (
+        await import(/* webpackChunkName: "image" */ "../../src/data/image")
+      ).encodeSvgMetadata({
+        text: serializeAsJSON(elements, appState),
+      });
+    } catch (err) {
+      console.error(err);
+    }
+  }
   const [minX, minY, width, height] = getCanvasSize(elements, exportPadding);
 
   // initialze SVG root
@@ -92,7 +104,7 @@ export const exportToSvg = (
   svgRoot.setAttribute("viewBox", `0 0 ${width} ${height}`);
   svgRoot.setAttribute("width", `${width * exportScale}`);
   svgRoot.setAttribute("height", `${height * exportScale}`);
-  if (exportWithDarkMode) {
+  if (appState.exportWithDarkMode) {
     svgRoot.setAttribute("filter", THEME_FILTER);
   }
 
@@ -114,7 +126,7 @@ export const exportToSvg = (
   `;
 
   // render background rect
-  if (exportBackground && viewBackgroundColor) {
+  if (appState.exportBackground && viewBackgroundColor) {
     const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
     rect.setAttribute("x", "0");
     rect.setAttribute("y", "0");

+ 0 - 1
src/tests/packages/__snapshots__/utils.test.ts.snap

@@ -39,7 +39,6 @@ Object {
   "isResizing": false,
   "isRotating": false,
   "lastPointerDownWith": "mouse",
-  "metadata": undefined,
   "multiElement": null,
   "name": "name",
   "openMenu": null,

+ 20 - 12
src/tests/packages/utils.test.ts

@@ -73,11 +73,10 @@ describe("exportToSvg", () => {
   const mockedExportUtil = mockedSceneExportUtils.exportToSvg as jest.Mock;
   const passedElements = () => mockedExportUtil.mock.calls[0][0];
   const passedOptions = () => mockedExportUtil.mock.calls[0][1];
-
   afterEach(jest.resetAllMocks);
 
-  it("with default arguments", () => {
-    utils.exportToSvg({
+  it("with default arguments", async () => {
+    await utils.exportToSvg({
       ...diagramFactory({
         overrides: { appState: void 0 },
       }),
@@ -88,13 +87,12 @@ describe("exportToSvg", () => {
       // To avoid varying snapshots
       name: "name",
     };
-
     expect(passedElements().length).toBe(3);
     expect(passedOptionsWhenDefault).toMatchSnapshot();
   });
 
-  it("with deleted elements", () => {
-    utils.exportToSvg({
+  it("with deleted elements", async () => {
+    await utils.exportToSvg({
       ...diagramFactory({
         overrides: { appState: void 0 },
         elementOverrides: { isDeleted: true },
@@ -104,18 +102,28 @@ describe("exportToSvg", () => {
     expect(passedElements().length).toBe(0);
   });
 
-  it("with exportPadding and metadata", () => {
-    const METADATA = "some metada";
-
-    utils.exportToSvg({
+  it("with exportPadding", async () => {
+    await utils.exportToSvg({
       ...diagramFactory({ overrides: { appState: { name: "diagram name" } } }),
       exportPadding: 0,
-      metadata: METADATA,
     });
 
     expect(passedElements().length).toBe(3);
     expect(passedOptions()).toEqual(
-      expect.objectContaining({ exportPadding: 0, metadata: METADATA }),
+      expect.objectContaining({ exportPadding: 0 }),
     );
   });
+
+  it("with exportEmbedScene", async () => {
+    await utils.exportToSvg({
+      ...diagramFactory({
+        overrides: {
+          appState: { name: "diagram name", exportEmbedScene: true },
+        },
+      }),
+    });
+
+    expect(passedElements().length).toBe(3);
+    expect(passedOptions().exportEmbedScene).toBe(true);
+  });
 });

Fichier diff supprimé car celui-ci est trop grand
+ 17 - 0
src/tests/scene/__snapshots__/export.test.ts.snap


+ 18 - 12
src/tests/scene/export.test.ts

@@ -15,16 +15,16 @@ describe("exportToSvg", () => {
     viewBackgroundColor: "#ffffff",
   };
 
-  it("with default arguments", () => {
-    const svgElement = exportUtils.exportToSvg(ELEMENTS, DEFAULT_OPTIONS);
+  it("with default arguments", async () => {
+    const svgElement = await exportUtils.exportToSvg(ELEMENTS, DEFAULT_OPTIONS);
 
     expect(svgElement).toMatchSnapshot();
   });
 
-  it("with background color", () => {
+  it("with background color", async () => {
     const BACKGROUND_COLOR = "#abcdef";
 
-    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+    const svgElement = await exportUtils.exportToSvg(ELEMENTS, {
       ...DEFAULT_OPTIONS,
       exportBackground: true,
       viewBackgroundColor: BACKGROUND_COLOR,
@@ -36,8 +36,8 @@ describe("exportToSvg", () => {
     );
   });
 
-  it("with dark mode", () => {
-    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+  it("with dark mode", async () => {
+    const svgElement = await exportUtils.exportToSvg(ELEMENTS, {
       ...DEFAULT_OPTIONS,
       exportWithDarkMode: true,
     });
@@ -47,14 +47,12 @@ describe("exportToSvg", () => {
     );
   });
 
-  it("with exportPadding, metadata", () => {
-    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+  it("with exportPadding", async () => {
+    const svgElement = await exportUtils.exportToSvg(ELEMENTS, {
       ...DEFAULT_OPTIONS,
       exportPadding: 0,
-      metadata: "some metadata",
     });
 
-    expect(svgElement.innerHTML).toMatch(/some metadata/);
     expect(svgElement).toHaveAttribute("height", ELEMENT_HEIGHT.toString());
     expect(svgElement).toHaveAttribute("width", ELEMENT_WIDTH.toString());
     expect(svgElement).toHaveAttribute(
@@ -63,10 +61,10 @@ describe("exportToSvg", () => {
     );
   });
 
-  it("with scale", () => {
+  it("with scale", async () => {
     const SCALE = 2;
 
-    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+    const svgElement = await exportUtils.exportToSvg(ELEMENTS, {
       ...DEFAULT_OPTIONS,
       exportPadding: 0,
       exportScale: SCALE,
@@ -81,4 +79,12 @@ describe("exportToSvg", () => {
       (ELEMENT_WIDTH * SCALE).toString(),
     );
   });
+
+  it("with exportEmbedScene", async () => {
+    const svgElement = await exportUtils.exportToSvg(ELEMENTS, {
+      ...DEFAULT_OPTIONS,
+      exportEmbedScene: true,
+    });
+    expect(svgElement.innerHTML).toMatchSnapshot();
+  });
 });

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff