浏览代码

fix: display error message when local storage quota is exceeded (#9961)

* fix: display error message when local storage quota is exceeded

* add danger alert instead of toast

* tweak text

---------

Co-authored-by: dwelle <[email protected]>
Emil 2 周之前
父节点
当前提交
835eb8d2fd
共有 4 个文件被更改,包括 39 次插入5 次删除
  1. 9 1
      excalidraw-app/App.tsx
  2. 17 0
      excalidraw-app/data/LocalData.ts
  3. 11 3
      excalidraw-app/index.scss
  4. 2 1
      packages/excalidraw/locales/en.json

+ 9 - 1
excalidraw-app/App.tsx

@@ -119,6 +119,7 @@ import {
   LibraryIndexedDBAdapter,
   LibraryIndexedDBAdapter,
   LibraryLocalStorageMigrationAdapter,
   LibraryLocalStorageMigrationAdapter,
   LocalData,
   LocalData,
+  localStorageQuotaExceededAtom,
 } from "./data/LocalData";
 } from "./data/LocalData";
 import { isBrowserStorageStateNewer } from "./data/tabSync";
 import { isBrowserStorageStateNewer } from "./data/tabSync";
 import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
 import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
@@ -727,6 +728,8 @@ const ExcalidrawWrapper = () => {
 
 
   const isOffline = useAtomValue(isOfflineAtom);
   const isOffline = useAtomValue(isOfflineAtom);
 
 
+  const localStorageQuotaExceeded = useAtomValue(localStorageQuotaExceededAtom);
+
   const onCollabDialogOpen = useCallback(
   const onCollabDialogOpen = useCallback(
     () => setShareDialogState({ isOpen: true, type: "collaborationOnly" }),
     () => setShareDialogState({ isOpen: true, type: "collaborationOnly" }),
     [setShareDialogState],
     [setShareDialogState],
@@ -901,10 +904,15 @@ const ExcalidrawWrapper = () => {
 
 
         <TTDDialogTrigger />
         <TTDDialogTrigger />
         {isCollaborating && isOffline && (
         {isCollaborating && isOffline && (
-          <div className="collab-offline-warning">
+          <div className="alertalert--warning">
             {t("alerts.collabOfflineWarning")}
             {t("alerts.collabOfflineWarning")}
           </div>
           </div>
         )}
         )}
+        {localStorageQuotaExceeded && (
+          <div className="alert alert--danger">
+            {t("alerts.localStorageQuotaExceeded")}
+          </div>
+        )}
         {latestShareableLink && (
         {latestShareableLink && (
           <ShareableLinkDialog
           <ShareableLinkDialog
             link={latestShareableLink}
             link={latestShareableLink}

+ 17 - 0
excalidraw-app/data/LocalData.ts

@@ -27,6 +27,8 @@ import {
   get,
   get,
 } from "idb-keyval";
 } from "idb-keyval";
 
 
+import { appJotaiStore, atom } from "excalidraw-app/app-jotai";
+
 import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
 import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
 import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
 import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
 import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
 import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
@@ -45,6 +47,8 @@ import { updateBrowserStateVersion } from "./tabSync";
 
 
 const filesStore = createStore("files-db", "files-store");
 const filesStore = createStore("files-db", "files-store");
 
 
+export const localStorageQuotaExceededAtom = atom(false);
+
 class LocalFileManager extends FileManager {
 class LocalFileManager extends FileManager {
   clearObsoleteFiles = async (opts: { currentFileIds: FileId[] }) => {
   clearObsoleteFiles = async (opts: { currentFileIds: FileId[] }) => {
     await entries(filesStore).then((entries) => {
     await entries(filesStore).then((entries) => {
@@ -69,6 +73,9 @@ const saveDataStateToLocalStorage = (
   elements: readonly ExcalidrawElement[],
   elements: readonly ExcalidrawElement[],
   appState: AppState,
   appState: AppState,
 ) => {
 ) => {
+  const localStorageQuotaExceeded = appJotaiStore.get(
+    localStorageQuotaExceededAtom,
+  );
   try {
   try {
     const _appState = clearAppStateForLocalStorage(appState);
     const _appState = clearAppStateForLocalStorage(appState);
 
 
@@ -88,12 +95,22 @@ const saveDataStateToLocalStorage = (
       JSON.stringify(_appState),
       JSON.stringify(_appState),
     );
     );
     updateBrowserStateVersion(STORAGE_KEYS.VERSION_DATA_STATE);
     updateBrowserStateVersion(STORAGE_KEYS.VERSION_DATA_STATE);
+    if (localStorageQuotaExceeded) {
+      appJotaiStore.set(localStorageQuotaExceededAtom, false);
+    }
   } catch (error: any) {
   } catch (error: any) {
     // Unable to access window.localStorage
     // Unable to access window.localStorage
     console.error(error);
     console.error(error);
+    if (isQuotaExceededError(error) && !localStorageQuotaExceeded) {
+      appJotaiStore.set(localStorageQuotaExceededAtom, true);
+    }
   }
   }
 };
 };
 
 
+const isQuotaExceededError = (error: any) => {
+  return error instanceof DOMException && error.name === "QuotaExceededError";
+};
+
 type SavingLockTypes = "collaboration";
 type SavingLockTypes = "collaboration";
 
 
 export class LocalData {
 export class LocalData {

+ 11 - 3
excalidraw-app/index.scss

@@ -58,7 +58,7 @@
     }
     }
   }
   }
 
 
-  .collab-offline-warning {
+  .alert {
     pointer-events: none;
     pointer-events: none;
     position: absolute;
     position: absolute;
     top: 6.5rem;
     top: 6.5rem;
@@ -69,10 +69,18 @@
     text-align: center;
     text-align: center;
     line-height: 1.5;
     line-height: 1.5;
     border-radius: var(--border-radius-md);
     border-radius: var(--border-radius-md);
-    background-color: var(--color-warning);
-    color: var(--color-text-warning);
     z-index: 6;
     z-index: 6;
     white-space: pre;
     white-space: pre;
+
+    &--warning {
+      background-color: var(--color-warning);
+      color: var(--color-text-warning);
+    }
+
+    &--danger {
+      background-color: var(--color-danger-dark);
+      color: var(--color-danger-text);
+    }
   }
   }
 }
 }
 
 

+ 2 - 1
packages/excalidraw/locales/en.json

@@ -260,7 +260,8 @@
     "resetLibrary": "This will clear your library. Are you sure?",
     "resetLibrary": "This will clear your library. Are you sure?",
     "removeItemsFromsLibrary": "Delete {{count}} item(s) from library?",
     "removeItemsFromsLibrary": "Delete {{count}} item(s) from library?",
     "invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.",
     "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!",
+    "localStorageQuotaExceeded": "Browser storage quota exceeded. Changes will not be saved."
   },
   },
   "errors": {
   "errors": {
     "unsupportedFileType": "Unsupported file type.",
     "unsupportedFileType": "Unsupported file type.",