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(