Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle local storage quota exceeding error #572

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lang/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 <link>raising a support request</link>.",
"description": "Support request link text"
Expand Down
11 changes: 9 additions & 2 deletions src/components/DefaultPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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 && <ConnectionDialogs />}
{!isNonConnectionDialogOpen && !isStorageQuotaExceededDialogOpen && (
<ConnectionDialogs />
)}
<Tour />
<SaveDialogs />
<NotCreateAiHexImportDialog
Expand All @@ -92,6 +96,9 @@ const DefaultPageLayout = ({
isOpen={postImportDialogState === PostImportDialogState.Error}
/>
<MakeCodeLoadErrorDialog />
<StorageQuotaExceededErrorDialog
isOpen={isStorageQuotaExceededDialogOpen}
/>
<FeedbackForm isOpen={isFeedbackOpen} onClose={closeDialog} />
<ProjectDropTarget
isEnabled={!isNonConnectionDialogOpen && !isConnectionDialogOpen}
Expand Down
64 changes: 64 additions & 0 deletions src/components/StorageQuotaExceededErrorDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* (c) 2024, Micro:bit Educational Foundation and contributors
*
* SPDX-License-Identifier: MIT
*/
import {
Button,
HStack,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
VStack,
} from "@chakra-ui/react";
import { FormattedMessage } from "react-intl";

interface StorageQuotaExceededErrorDialogProps {
isOpen: boolean;
}

const StorageQuotaExceededErrorDialog = ({
isOpen,
}: StorageQuotaExceededErrorDialogProps) => {
return (
<Modal
motionPreset="none"
isOpen={isOpen}
onClose={() => {}}
size="2xl"
isCentered
>
<ModalOverlay>
<ModalContent>
<ModalHeader>
<FormattedMessage id="storage-quota-exceeded-dialog-title" />
</ModalHeader>
<ModalBody>
<VStack textAlign="left" w="100%">
<Text w="100%">
<FormattedMessage id="storage-quota-exceeded-dialog-body" />
</Text>
</VStack>
</ModalBody>
<ModalFooter justifyContent="flex-end">
<HStack gap={5}>
<Button
onClick={() => window.location.reload()}
variant="primary"
size="lg"
>
<FormattedMessage id="reload-action" />
</Button>
</HStack>
</ModalFooter>
</ModalContent>
</ModalOverlay>
</Modal>
);
};

export default StorageQuotaExceededErrorDialog;
12 changes: 12 additions & 0 deletions src/messages/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 25 additions & 1 deletion src/store.ts
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is the best way of triggering the dialog, but thought it was the easiest way to wrap all the set actions in the store. Otherwise, I could write a wrapper to wrap state changes with a try-catch statement for catching quota exceeded errors, which I think is the more straightforward approach.

Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -1161,6 +1161,7 @@ const createMlStore = (logging: Logging) => {
{
version: 1,
name: "ml",
storage: createJSONStorage(() => mlStorage),
partialize: ({
actions,
project,
Expand Down Expand Up @@ -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 => {
Expand All @@ -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(
Expand Down
Loading