Browse Source

fix: stop font `loadingdone` loop when rendering element SVGs (#5883)

* fix: stop font `loadingdone` loop when rendering element SVGs

* update snaps

* stop updating scene elements array if no change was made

* always re-render if invalidating element shape
David Luzar 2 years ago
parent
commit
bbe0c35f66

+ 14 - 12
src/components/App.tsx

@@ -730,18 +730,20 @@ class App extends React.Component<AppProps, AppState> {
   };
 
   private onFontLoaded = () => {
-    this.scene.replaceAllElements([
-      ...this.scene.getElementsIncludingDeleted().map((element) => {
-        if (isTextElement(element)) {
-          invalidateShapeForElement(element);
-          return newElementWith(element, {
-            ...refreshTextDimensions(element),
-          });
-        }
-        return element;
-      }),
-    ]);
-    this.onSceneUpdated();
+    let didUpdate = false;
+    this.scene.mapElements((element) => {
+      if (isTextElement(element)) {
+        invalidateShapeForElement(element);
+        didUpdate = true;
+        return newElementWith(element, {
+          ...refreshTextDimensions(element),
+        });
+      }
+      return element;
+    });
+    if (didUpdate) {
+      this.onSceneUpdated();
+    }
   };
 
   private resetHistory = () => {

+ 1 - 0
src/components/LibraryUnit.tsx

@@ -44,6 +44,7 @@ export const LibraryUnit = ({
         },
         null,
       );
+      svg.querySelector(".style-fonts")?.remove();
       node.innerHTML = svg.outerHTML;
     })();
 

+ 1 - 0
src/components/PasteChartDialog.tsx

@@ -46,6 +46,7 @@ const ChartPreviewBtn = (props: {
         },
         null, // files
       );
+      svg.querySelector(".style-fonts")?.remove();
       previewNode.replaceChildren();
       previewNode.appendChild(svg);
 

+ 29 - 0
src/scene/Scene.ts

@@ -79,6 +79,35 @@ class Scene {
     return null;
   }
 
+  /**
+   * A utility method to help with updating all scene elements, with the added
+   * performance optimization of not renewing the array if no change is made.
+   *
+   * Maps all current excalidraw elements, invoking the callback for each
+   * element. The callback should either return a new mapped element, or the
+   * original element if no changes are made. If no changes are made to any
+   * element, this results in a no-op. Otherwise, the newly mapped elements
+   * are set as the next scene's elements.
+   *
+   * @returns whether a change was made
+   */
+  mapElements(
+    iteratee: (element: ExcalidrawElement) => ExcalidrawElement,
+  ): boolean {
+    let didChange = false;
+    const newElements = this.elements.map((element) => {
+      const nextElement = iteratee(element);
+      if (nextElement !== element) {
+        didChange = true;
+      }
+      return nextElement;
+    });
+    if (didChange) {
+      this.replaceAllElements(newElements);
+    }
+    return didChange;
+  }
+
   replaceAllElements(nextElements: readonly ExcalidrawElement[]) {
     this.elements = nextElements;
     this.elementsMap.clear();

+ 1 - 1
src/scene/export.ts

@@ -139,7 +139,7 @@ export const exportToSvg = async (
   ${SVG_EXPORT_TAG}
   ${metadata}
   <defs>
-    <style>
+    <style class="style-fonts">
       @font-face {
         font-family: "Virgil";
         src: url("${assetPath}Virgil.woff2");

+ 1 - 1
src/tests/__snapshots__/export.test.tsx.snap

@@ -5,7 +5,7 @@ exports[`export exporting svg containing transformed images: svg export output 1
   <!-- svg-source:excalidraw -->
   
   <defs>
-    <style>
+    <style class=\\"style-fonts\\">
       @font-face {
         font-family: \\"Virgil\\";
         src: url(\\"https://excalidraw.com/Virgil.woff2\\");

+ 5 - 3
src/tests/scene/__snapshots__/export.test.ts.snap

@@ -17,7 +17,9 @@ exports[`exportToSvg with default arguments 1`] = `
   <defs>
     
     
-    <style>
+    <style
+      class="style-fonts"
+    >
       
       @font-face {
         font-family: "Virgil";
@@ -76,7 +78,7 @@ exports[`exportToSvg with elements that have a link 1`] = `
   <!-- svg-source:excalidraw -->
   
   <defs>
-    <style>
+    <style class=\\"style-fonts\\">
       @font-face {
         font-family: \\"Virgil\\";
         src: url(\\"https://excalidraw.com/Virgil.woff2\\");
@@ -95,7 +97,7 @@ exports[`exportToSvg with exportEmbedScene 1`] = `
   <!-- svg-source:excalidraw -->
   <!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1SPW/CMFx1MDAxMN35XHUwMDE1kbtcIpGk4aNstFRVpapcdTAwMWRcdTAwMTiQWnUw8YVYMbaxXHUwMDFkPoT477VccsRtxNpcclx1MDAwZpbu+b278907dKJcYpm9XHUwMDA0NI5cdTAwMTDscswoUXiLulx1MDAwZd+A0lRw+5T6WIta5Z5ZXHUwMDFhI8e9XHUwMDFlXHUwMDEzVlBcbm1OfGCwXHUwMDAybrRlfNk4ilx1MDAwZf62L5Q41Wau1lx1MDAxZpOiopyk63w1fJtOXj691JN2lpMlWVx1MDAxM+9d4fthXHUwMDEzbykxpcWSOG6wXHUwMDEy6LI0LVx1MDAxMPMlc21cdTAwMDZEXHUwMDFiJSp4XHUwMDEyTCjXyF3sTyi9wHm1VKLmJHCSPsaLXCJwXG7K2Mzs2WlcdTAwMDA4L2tcdTAwMDWoVWF+abGFNzot7ICDypZcXJZcdTAwMWO0/qNcdTAwMTFcdTAwMTLn1Oxbv3L9yVfip/vdzl9iJc95kHbBr85cdTAwMDCIT5Ulg/7wIVx1MDAxZTUvYb9JXHUwMDFht9F3wf2uk2Q0iuMsXHUwMDFkXHUwMDBlXHUwMDFhXHUwMDA21VO7auPTXHUwMDE2mGlcYnN0I3xcdTAwMGU24DVjzWMtXHQ+icJXXHUwMDE55VWbZ11VXcl9cSmheCU4QVx1MDAxZT92b0a7XHUwMDE57X+MXHUwMDA2jFGp4Ww0e/thICzlzNj8lnKyXHUwMDFk2lDYPl5ZbOGP03ubusWCa/Zw7Fx1MDAxY39cdTAwMDCLqmbvIn0=<!-- payload-end -->
   <defs>
-    <style>
+    <style class=\\"style-fonts\\">
       @font-face {
         font-family: \\"Virgil\\";
         src: url(\\"https://excalidraw.com/Virgil.woff2\\");