From 835eb8d2fdf21afd93175367838e6b9ecd9e9271 Mon Sep 17 00:00:00 2001
From: Emil <73137047+h0lm1@users.noreply.github.com>
Date: Tue, 30 Sep 2025 23:54:43 +0200
Subject: [PATCH] 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 <5153846+dwelle@users.noreply.github.com>
---
excalidraw-app/App.tsx | 10 +++++++++-
excalidraw-app/data/LocalData.ts | 17 +++++++++++++++++
excalidraw-app/index.scss | 14 +++++++++++---
packages/excalidraw/locales/en.json | 3 ++-
4 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx
index b972e6e5b0..a5d01769cc 100644
--- a/excalidraw-app/App.tsx
+++ b/excalidraw-app/App.tsx
@@ -119,6 +119,7 @@ import {
LibraryIndexedDBAdapter,
LibraryLocalStorageMigrationAdapter,
LocalData,
+ localStorageQuotaExceededAtom,
} from "./data/LocalData";
import { isBrowserStorageStateNewer } from "./data/tabSync";
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
@@ -727,6 +728,8 @@ const ExcalidrawWrapper = () => {
const isOffline = useAtomValue(isOfflineAtom);
+ const localStorageQuotaExceeded = useAtomValue(localStorageQuotaExceededAtom);
+
const onCollabDialogOpen = useCallback(
() => setShareDialogState({ isOpen: true, type: "collaborationOnly" }),
[setShareDialogState],
@@ -901,10 +904,15 @@ const ExcalidrawWrapper = () => {
{isCollaborating && isOffline && (
-
+
{t("alerts.collabOfflineWarning")}
)}
+ {localStorageQuotaExceeded && (
+
+ {t("alerts.localStorageQuotaExceeded")}
+
+ )}
{latestShareableLink && (
{
await entries(filesStore).then((entries) => {
@@ -69,6 +73,9 @@ const saveDataStateToLocalStorage = (
elements: readonly ExcalidrawElement[],
appState: AppState,
) => {
+ const localStorageQuotaExceeded = appJotaiStore.get(
+ localStorageQuotaExceededAtom,
+ );
try {
const _appState = clearAppStateForLocalStorage(appState);
@@ -88,12 +95,22 @@ const saveDataStateToLocalStorage = (
JSON.stringify(_appState),
);
updateBrowserStateVersion(STORAGE_KEYS.VERSION_DATA_STATE);
+ if (localStorageQuotaExceeded) {
+ appJotaiStore.set(localStorageQuotaExceededAtom, false);
+ }
} catch (error: any) {
// Unable to access window.localStorage
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";
export class LocalData {
diff --git a/excalidraw-app/index.scss b/excalidraw-app/index.scss
index cfaaf9cea2..9f320775be 100644
--- a/excalidraw-app/index.scss
+++ b/excalidraw-app/index.scss
@@ -58,7 +58,7 @@
}
}
- .collab-offline-warning {
+ .alert {
pointer-events: none;
position: absolute;
top: 6.5rem;
@@ -69,10 +69,18 @@
text-align: center;
line-height: 1.5;
border-radius: var(--border-radius-md);
- background-color: var(--color-warning);
- color: var(--color-text-warning);
z-index: 6;
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);
+ }
}
}
diff --git a/packages/excalidraw/locales/en.json b/packages/excalidraw/locales/en.json
index 4bd76fe876..feebe6da0f 100644
--- a/packages/excalidraw/locales/en.json
+++ b/packages/excalidraw/locales/en.json
@@ -260,7 +260,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!",
+ "localStorageQuotaExceeded": "Browser storage quota exceeded. Changes will not be saved."
},
"errors": {
"unsupportedFileType": "Unsupported file type.",