diff --git a/lang/ui.en.json b/lang/ui.en.json index 86bcd4c85..0cd91d6a8 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -1447,6 +1447,14 @@ "defaultMessage": "Stop recording", "description": "Button label to stop recording movement data while recording multiple samples" }, + "storage-quota-exceeded-dialog-body": { + "defaultMessage": "You have reached your session's storage limit. Recent changes made on your session cannot be saved. Please reload the page to continue your session.", + "description": "Body of storage error dialog" + }, + "storage-quota-exceeded-dialog-title": { + "defaultMessage": "Error auto-saving your session", + "description": "Title of storage error dialog" + }, "support-request": { "defaultMessage": "Please consider raising a support request.", "description": "Support request link text" diff --git a/src/components/DefaultPageLayout.tsx b/src/components/DefaultPageLayout.tsx index a0ee4be52..28a39e5a6 100644 --- a/src/components/DefaultPageLayout.tsx +++ b/src/components/DefaultPageLayout.tsx @@ -24,7 +24,7 @@ import { useProject } from "../hooks/project-hooks"; import { keyboardShortcuts, useShortcut } from "../keyboard-shortcut-hooks"; import { PostImportDialogState } from "../model"; import Tour from "../pages/Tour"; -import { useStore } from "../store"; +import { isStorageQuotaExceeded, useStore } from "../store"; import { createHomePageUrl } from "../urls"; import ActionBar from "./ActionBar/ActionBar"; import ItemsRight from "./ActionBar/ActionBarItemsRight"; @@ -37,6 +37,7 @@ import NotCreateAiHexImportDialog from "./NotCreateAiHexImportDialog"; import PreReleaseNotice from "./PreReleaseNotice"; import ProjectDropTarget from "./ProjectDropTarget"; import SaveDialogs from "./SaveDialogs"; +import StorageQuotaExceededErrorDialog from "./StorageQuotaExceededErrorDialog"; interface DefaultPageLayoutProps { titleId?: string; @@ -76,11 +77,14 @@ const DefaultPageLayout = ({ const isFeedbackOpen = useStore((s) => s.isFeedbackFormOpen); const closeDialog = useStore((s) => s.closeDialog); + const isStorageQuotaExceededDialogOpen = isStorageQuotaExceeded(); return ( <> {/* Suppress dialogs to prevent overlapping dialogs */} - {!isNonConnectionDialogOpen && } + {!isNonConnectionDialogOpen && !isStorageQuotaExceededDialogOpen && ( + + )} + { + return ( + {}} + size="2xl" + isCentered + > + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default StorageQuotaExceededErrorDialog; diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 1353ebc11..7e14df28a 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -2563,6 +2563,18 @@ "value": "Stop recording" } ], + "storage-quota-exceeded-dialog-body": [ + { + "type": 0, + "value": "You have reached your session's storage limit. Recent changes made on your session cannot be saved. Please reload the page to continue your session." + } + ], + "storage-quota-exceeded-dialog-title": [ + { + "type": 0, + "value": "Error auto-saving your session" + } + ], "support-request": [ { "type": 0, diff --git a/src/store.ts b/src/store.ts index c0bc7f84e..4567e7bf5 100644 --- a/src/store.ts +++ b/src/store.ts @@ -7,7 +7,7 @@ import { Project } from "@microbit/makecode-embed/react"; import * as tf from "@tensorflow/tfjs"; import { create } from "zustand"; -import { devtools, persist } from "zustand/middleware"; +import { createJSONStorage, devtools, persist } from "zustand/middleware"; import { useShallow } from "zustand/react/shallow"; import { deployment } from "./deployment"; import { flags } from "./flags"; @@ -1161,6 +1161,7 @@ const createMlStore = (logging: Logging) => { { version: 1, name: "ml", + storage: createJSONStorage(() => mlStorage), partialize: ({ actions, project, @@ -1211,6 +1212,26 @@ const createMlStore = (logging: Logging) => { ); }; +const storageQuotaExceededKey = "QuotaExceededError"; +const mlStorage = { + getItem: localStorage.getItem, + setItem: (name: string, value: string) => { + try { + localStorage.setItem(name, value); + } catch (e) { + if ((e as Error).name === "QuotaExceededError") { + return localStorage.setItem(storageQuotaExceededKey, "1"); + } + throw e; + } + }, + removeItem: localStorage.removeItem, +}; + +export const isStorageQuotaExceeded = () => { + return localStorage.getItem(storageQuotaExceededKey) === "1"; +}; + export const useStore = createMlStore(deployment.logging); const getDataWindowFromActions = (actions: ActionData[]): DataWindow => { @@ -1220,6 +1241,9 @@ const getDataWindowFromActions = (actions: ActionData[]): DataWindow => { : currentDataWindow; }; +// Reset storage quota exceeded state +localStorage.setItem(storageQuotaExceededKey, "0"); + // Get data window from actions on app load. const { actions } = useStore.getState(); useStore.setState(