From a91e401554388b72741efc19b27478f7964d939a Mon Sep 17 00:00:00 2001 From: Arnost Pleskot Date: Mon, 29 May 2023 16:01:44 +0200 Subject: [PATCH] feat: clearing library cache (#6621) Co-authored-by: dwelle --- src/components/App.tsx | 1 + src/components/LibraryMenuHeaderContent.tsx | 63 +++++++++++---------- src/components/LibraryMenuItems.tsx | 4 ++ src/components/LibraryMenuSection.tsx | 9 ++- src/components/LibraryUnit.tsx | 6 +- src/data/library.ts | 15 +++++ src/hooks/useLibraryItemSvg.ts | 29 ++++++++-- 7 files changed, 86 insertions(+), 41 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 49919c95..0b730fc4 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1072,6 +1072,7 @@ class App extends React.Component { this.unmounted = true; this.removeEventListeners(); this.scene.destroy(); + this.library.destroy(); clearTimeout(touchTimeout); touchTimeout = 0; } diff --git a/src/components/LibraryMenuHeaderContent.tsx b/src/components/LibraryMenuHeaderContent.tsx index d51f651f..56c8fe6d 100644 --- a/src/components/LibraryMenuHeaderContent.tsx +++ b/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<{

{content}

); - }, [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") }); + }); - const resetLibrary = useCallback(() => { + deleteItemsFromLibraryCache(selectedItems); + + onSelectItems([]); + }; + + const resetLibrary = () => { library.resetLibrary(); - }, [library]); + clearLibraryCache(); + }; return ( ([]); + 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 ? (
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 ( @@ -47,6 +48,7 @@ function LibraryRow({ selected={isItemSelected(item.id)} onToggle={onItemSelectToggle} onDrag={onItemDrag} + svgCache={svgCache} /> ))} @@ -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} /> ) : ( diff --git a/src/components/LibraryUnit.tsx b/src/components/LibraryUnit.tsx index 68fdec14..97f73f80 100644 --- a/src/components/LibraryUnit.tsx +++ b/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(null); - const svg = useLibraryItemSvg(id, elements); + const svg = useLibraryItemSvg(id, elements, svgCache); useEffect(() => { const node = ref.current; diff --git a/src/data/library.ts b/src/data/library.ts index b9033bac..381caed1 100644 --- a/src/data/library.ts +++ b/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([]); }; diff --git a/src/hooks/useLibraryItemSvg.ts b/src/hooks/useLibraryItemSvg.ts index d0a0f532..ba980219 100644 --- a/src/hooks/useLibraryItemSvg.ts +++ b/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>( - new Map(), -); +export type SvgCache = Map; + +export const libraryItemSvgsCache = atom(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(); 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, + }; +};