feat: clearing library cache (#6621)

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Arnost Pleskot 2023-05-29 16:01:44 +02:00 committed by GitHub
parent 08563e7d7b
commit a91e401554
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 86 additions and 41 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ import {
DEFAULT_SIDEBAR, DEFAULT_SIDEBAR,
LIBRARY_SIDEBAR_TAB, LIBRARY_SIDEBAR_TAB,
} from "../constants"; } from "../constants";
import { libraryItemSvgsCache } from "../hooks/useLibraryItemSvg";
export const libraryItemsAtom = atom<{ export const libraryItemsAtom = atom<{
status: "loading" | "loaded"; 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 = () => { resetLibrary = () => {
return this.setLibrary([]); return this.setLibrary([]);
}; };

View File

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