Quellcode durchsuchen

feat: clearing library cache (#6621)

Co-authored-by: dwelle <[email protected]>
Arnost Pleskot vor 2 Jahren
Ursprung
Commit
a91e401554

+ 1 - 0
src/components/App.tsx

@@ -1072,6 +1072,7 @@ class App extends React.Component<AppProps, AppState> {
     this.unmounted = true;
     this.removeEventListeners();
     this.scene.destroy();
+    this.library.destroy();
     clearTimeout(touchTimeout);
     touchTimeout = 0;
   }

+ 33 - 30
src/components/LibraryMenuHeaderContent.tsx

@@ -24,6 +24,7 @@ import DropdownMenu from "./dropdownMenu/DropdownMenu";
 import { isLibraryMenuOpenAtom } from "./LibraryMenu";
 import { useUIAppState } from "../context/ui-appState";
 import clsx from "clsx";
+import { useLibraryCache } from "../hooks/useLibraryItemSvg";
 
 const getSelectedItems = (
   libraryItems: LibraryItems,
@@ -55,7 +56,7 @@ export const LibraryDropdownMenuButton: React.FC<{
     jotaiScope,
   );
 
-  const renderRemoveLibAlert = useCallback(() => {
+  const renderRemoveLibAlert = () => {
     const content = selectedItems.length
       ? t("alerts.removeItemsFromsLibrary", { count: selectedItems.length })
       : t("alerts.resetLibrary");
@@ -80,7 +81,7 @@ export const LibraryDropdownMenuButton: React.FC<{
         <p>{content}</p>
       </ConfirmDialog>
     );
-  }, [selectedItems, onRemoveFromLibrary, resetLibrary]);
+  };
 
   const [showRemoveLibAlert, setShowRemoveLibAlert] = useState(false);
 
@@ -136,20 +137,20 @@ export const LibraryDropdownMenuButton: React.FC<{
     );
   }, [setPublishLibSuccess, publishLibSuccess]);
 
-  const onPublishLibSuccess = useCallback(
-    (data: { url: string; authorName: string }, libraryItems: LibraryItems) => {
-      setShowPublishLibraryDialog(false);
-      setPublishLibSuccess({ url: data.url, authorName: data.authorName });
-      const nextLibItems = libraryItems.slice();
-      nextLibItems.forEach((libItem) => {
-        if (selectedItems.includes(libItem.id)) {
-          libItem.status = "published";
-        }
-      });
-      library.setLibrary(nextLibItems);
-    },
-    [setShowPublishLibraryDialog, setPublishLibSuccess, selectedItems, library],
-  );
+  const onPublishLibSuccess = (
+    data: { url: string; authorName: string },
+    libraryItems: LibraryItems,
+  ) => {
+    setShowPublishLibraryDialog(false);
+    setPublishLibSuccess({ url: data.url, authorName: data.authorName });
+    const nextLibItems = libraryItems.slice();
+    nextLibItems.forEach((libItem) => {
+      if (selectedItems.includes(libItem.id)) {
+        libItem.status = "published";
+      }
+    });
+    library.setLibrary(nextLibItems);
+  };
 
   const onLibraryImport = async () => {
     try {
@@ -280,27 +281,29 @@ export const LibraryDropdownMenu = ({
   className?: string;
 }) => {
   const { library } = useApp();
+  const { clearLibraryCache, deleteItemsFromLibraryCache } = useLibraryCache();
   const appState = useUIAppState();
   const setAppState = useExcalidrawSetAppState();
 
   const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
 
-  const removeFromLibrary = useCallback(
-    async (libraryItems: LibraryItems) => {
-      const nextItems = libraryItems.filter(
-        (item) => !selectedItems.includes(item.id),
-      );
-      library.setLibrary(nextItems).catch(() => {
-        setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") });
-      });
-      onSelectItems([]);
-    },
-    [library, setAppState, selectedItems, onSelectItems],
-  );
+  const removeFromLibrary = async (libraryItems: LibraryItems) => {
+    const nextItems = libraryItems.filter(
+      (item) => !selectedItems.includes(item.id),
+    );
+    library.setLibrary(nextItems).catch(() => {
+      setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") });
+    });
+
+    deleteItemsFromLibraryCache(selectedItems);
 
-  const resetLibrary = useCallback(() => {
+    onSelectItems([]);
+  };
+
+  const resetLibrary = () => {
     library.resetLibrary();
-  }, [library]);
+    clearLibraryCache();
+  };
 
   return (
     <LibraryDropdownMenuButton

+ 4 - 0
src/components/LibraryMenuItems.tsx

@@ -15,6 +15,7 @@ import { duplicateElements } from "../element/newElement";
 import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
 import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent";
 import LibraryMenuSection from "./LibraryMenuSection";
+import { useLibraryCache } from "../hooks/useLibraryItemSvg";
 
 import "./LibraryMenuItems.scss";
 
@@ -38,6 +39,7 @@ export default function LibraryMenuItems({
   id: string;
 }) {
   const [selectedItems, setSelectedItems] = useState<LibraryItem["id"][]>([]);
+  const { svgCache } = useLibraryCache();
 
   const unpublishedItems = libraryItems.filter(
     (item) => item.status !== "published",
@@ -224,6 +226,7 @@ export default function LibraryMenuItems({
               onItemDrag={onItemDrag}
               onClick={onItemClick}
               isItemSelected={isItemSelected}
+              svgCache={svgCache}
             />
           )}
         </>
@@ -243,6 +246,7 @@ export default function LibraryMenuItems({
               onItemDrag={onItemDrag}
               onClick={onItemClick}
               isItemSelected={isItemSelected}
+              svgCache={svgCache}
             />
           ) : unpublishedItems.length > 0 ? (
             <div

+ 6 - 3
src/components/LibraryMenuSection.tsx

@@ -4,8 +4,7 @@ import { LibraryItem } from "../types";
 import Stack from "./Stack";
 import clsx from "clsx";
 import { ExcalidrawElement, NonDeleted } from "../element/types";
-import { useAtom } from "jotai";
-import { libraryItemSvgsCache } from "../hooks/useLibraryItemSvg";
+import { SvgCache } from "../hooks/useLibraryItemSvg";
 import { useTransition } from "../hooks/useTransition";
 
 const ITEMS_PER_ROW = 4;
@@ -26,6 +25,7 @@ interface Props {
   onItemSelectToggle: (id: LibraryItem["id"], event: React.MouseEvent) => void;
   onItemDrag: (id: LibraryItem["id"], event: React.DragEvent) => void;
   isItemSelected: (id: LibraryItem["id"] | null) => boolean;
+  svgCache: SvgCache;
 }
 
 function LibraryRow({
@@ -34,6 +34,7 @@ function LibraryRow({
   onItemDrag,
   isItemSelected,
   onClick,
+  svgCache,
 }: Props) {
   return (
     <Stack.Row className="library-menu-items-container__row">
@@ -47,6 +48,7 @@ function LibraryRow({
             selected={isItemSelected(item.id)}
             onToggle={onItemSelectToggle}
             onDrag={onItemDrag}
+            svgCache={svgCache}
           />
         </Stack.Col>
       ))}
@@ -68,11 +70,11 @@ function LibraryMenuSection({
   onItemDrag,
   isItemSelected,
   onClick,
+  svgCache,
 }: Props) {
   const rows = Math.ceil(items.length / ITEMS_PER_ROW);
   const [, startTransition] = useTransition();
   const [index, setIndex] = useState(0);
-  const [svgCache] = useAtom(libraryItemSvgsCache);
 
   const rowsRenderedPerBatch = useMemo(() => {
     return svgCache.size === 0
@@ -99,6 +101,7 @@ function LibraryMenuSection({
             onItemDrag={onItemDrag}
             onClick={onClick}
             isItemSelected={isItemSelected}
+            svgCache={svgCache}
           />
         ) : (
           <EmptyLibraryRow key={i} />

+ 4 - 2
src/components/LibraryUnit.tsx

@@ -5,7 +5,7 @@ import { LibraryItem } from "../types";
 import "./LibraryUnit.scss";
 import { CheckboxItem } from "./CheckboxItem";
 import { PlusIcon } from "./icons";
-import { useLibraryItemSvg } from "../hooks/useLibraryItemSvg";
+import { SvgCache, useLibraryItemSvg } from "../hooks/useLibraryItemSvg";
 
 export const LibraryUnit = ({
   id,
@@ -15,6 +15,7 @@ export const LibraryUnit = ({
   selected,
   onToggle,
   onDrag,
+  svgCache,
 }: {
   id: LibraryItem["id"] | /** for pending item */ null;
   elements?: LibraryItem["elements"];
@@ -23,9 +24,10 @@ export const LibraryUnit = ({
   selected: boolean;
   onToggle: (id: string, event: React.MouseEvent) => void;
   onDrag: (id: string, event: React.DragEvent) => void;
+  svgCache: SvgCache;
 }) => {
   const ref = useRef<HTMLDivElement | null>(null);
-  const svg = useLibraryItemSvg(id, elements);
+  const svg = useLibraryItemSvg(id, elements, svgCache);
 
   useEffect(() => {
     const node = ref.current;

+ 15 - 0
src/data/library.ts

@@ -22,6 +22,7 @@ import {
   DEFAULT_SIDEBAR,
   LIBRARY_SIDEBAR_TAB,
 } from "../constants";
+import { libraryItemSvgsCache } from "../hooks/useLibraryItemSvg";
 
 export const libraryItemsAtom = atom<{
   status: "loading" | "loaded";
@@ -115,6 +116,20 @@ class Library {
     }
   };
 
+  /** call on excalidraw instance unmount */
+  destroy = () => {
+    this.isInitialized = false;
+    this.updateQueue = [];
+    this.lastLibraryItems = [];
+    jotaiStore.set(libraryItemSvgsCache, new Map());
+    // TODO uncomment after/if we make jotai store scoped to each excal instance
+    // jotaiStore.set(libraryItemsAtom, {
+    //   status: "loading",
+    //   isInitialized: false,
+    //   libraryItems: [],
+    // });
+  };
+
   resetLibrary = () => {
     return this.setLibrary([]);
   };

+ 23 - 6
src/hooks/useLibraryItemSvg.ts

@@ -1,12 +1,13 @@
 import { atom, useAtom } from "jotai";
 import { useEffect, useState } from "react";
 import { COLOR_PALETTE } from "../colors";
+import { jotaiScope } from "../jotai";
 import { exportToSvg } from "../packages/utils";
 import { LibraryItem } from "../types";
 
-export const libraryItemSvgsCache = atom<Map<LibraryItem["id"], SVGSVGElement>>(
-  new Map(),
-);
+export type SvgCache = Map<LibraryItem["id"], SVGSVGElement>;
+
+export const libraryItemSvgsCache = atom<SvgCache>(new Map());
 
 const exportLibraryItemToSvg = async (elements: LibraryItem["elements"]) => {
   return await exportToSvg({
@@ -22,8 +23,8 @@ const exportLibraryItemToSvg = async (elements: LibraryItem["elements"]) => {
 export const useLibraryItemSvg = (
   id: LibraryItem["id"] | null,
   elements: LibraryItem["elements"] | undefined,
+  svgCache: SvgCache,
 ): SVGSVGElement | undefined => {
-  const [svgCache, setSvgCache] = useAtom(libraryItemSvgsCache);
   const [svg, setSvg] = useState<SVGSVGElement>();
 
   useEffect(() => {
@@ -40,7 +41,7 @@ export const useLibraryItemSvg = (
             const exportedSvg = await exportLibraryItemToSvg(elements);
 
             if (exportedSvg) {
-              setSvgCache(svgCache.set(id, exportedSvg));
+              svgCache.set(id, exportedSvg);
               setSvg(exportedSvg);
             }
           })();
@@ -53,7 +54,23 @@ export const useLibraryItemSvg = (
         })();
       }
     }
-  }, [id, elements, svgCache, setSvgCache, setSvg]);
+  }, [id, elements, svgCache, setSvg]);
 
   return svg;
 };
+
+export const useLibraryCache = () => {
+  const [svgCache] = useAtom(libraryItemSvgsCache, jotaiScope);
+
+  const clearLibraryCache = () => svgCache.clear();
+
+  const deleteItemsFromLibraryCache = (items: LibraryItem["id"][]) => {
+    items.forEach((item) => svgCache.delete(item));
+  };
+
+  return {
+    clearLibraryCache,
+    deleteItemsFromLibraryCache,
+    svgCache,
+  };
+};