diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx index 883482286..54bb87f4b 100644 --- a/excalidraw-app/App.tsx +++ b/excalidraw-app/App.tsx @@ -77,6 +77,7 @@ import { newElementWith } from "../packages/excalidraw/element/mutateElement"; import { isInitializedImageElement } from "../packages/excalidraw/element/typeChecks"; import { loadFilesFromFirebase } from "./data/firebase"; import { + isLocalStorageOverflowAtom, LibraryIndexedDBAdapter, LibraryLocalStorageMigrationAdapter, LocalData, @@ -364,6 +365,20 @@ const ExcalidrawWrapper = () => { }); const collabError = useAtomValue(collabErrorIndicatorAtom); + const isLocalStorageOverflow = useAtomValue(isLocalStorageOverflowAtom); + + useEffect(() => { + if (isLocalStorageOverflow) { + if (excalidrawAPI) { + excalidrawAPI.setToast({ + message: t("alerts.localStorageOverflow"), + closable: true, + duration: Infinity, + }); + } + } + }, [isLocalStorageOverflow, excalidrawAPI]); + useHandleLibrary({ excalidrawAPI, adapter: LibraryIndexedDBAdapter, diff --git a/excalidraw-app/data/LocalData.ts b/excalidraw-app/data/LocalData.ts index 2e524522c..bbc4f7996 100644 --- a/excalidraw-app/data/LocalData.ts +++ b/excalidraw-app/data/LocalData.ts @@ -43,6 +43,12 @@ import { FileManager } from "./FileManager"; import { Locker } from "./Locker"; import { updateBrowserStateVersion } from "./tabSync"; +// Global state to inform the app about local storage overflow +import { atom } from "jotai"; +import { appJotaiStore } from "../app-jotai"; + +export const isLocalStorageOverflowAtom = atom(false); + const filesStore = createStore("files-db", "files-store"); class LocalFileManager extends FileManager { @@ -68,7 +74,7 @@ class LocalFileManager extends FileManager { const saveDataStateToLocalStorage = ( elements: readonly ExcalidrawElement[], appState: AppState, -) => { +): boolean => { try { const _appState = clearAppStateForLocalStorage(appState); @@ -88,9 +94,18 @@ const saveDataStateToLocalStorage = ( JSON.stringify(_appState), ); updateBrowserStateVersion(STORAGE_KEYS.VERSION_DATA_STATE); + appJotaiStore.set(isLocalStorageOverflowAtom, false); + return true; } catch (error: any) { - // Unable to access window.localStorage - console.error(error); + // Quota exceeded error + if (error.name === "QuotaExceededError") { + // Set global state to inform the app about local storage overflow + appJotaiStore.set(isLocalStorageOverflowAtom, true); + } else { + console.error(error); + } + + return false; } }; @@ -104,7 +119,12 @@ export class LocalData { files: BinaryFiles, onFilesSaved: () => void, ) => { - saveDataStateToLocalStorage(elements, appState); + const didSave = saveDataStateToLocalStorage(elements, appState); + + // if saving to localStorage failed, don't attempt to save files + if (!didSave) { + return; + } await this.fileStorage.saveFiles({ elements, diff --git a/packages/excalidraw/locales/en.json b/packages/excalidraw/locales/en.json index 85c5076f3..e38839ac6 100644 --- a/packages/excalidraw/locales/en.json +++ b/packages/excalidraw/locales/en.json @@ -238,7 +238,8 @@ "resetLibrary": "This will clear your library. Are you sure?", "removeItemsFromsLibrary": "Delete {{count}} item(s) from library?", "invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.", - "collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!" + "collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!", + "localStorageOverflow": "Save failed. Too many items!" }, "errors": { "unsupportedFileType": "Unsupported file type.",