Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d6d0fed
Remove persist middleware
microbit-robert Dec 5, 2025
c28e542
Use IndexedDB storage for actions and recordings
microbit-robert Dec 5, 2025
a29ab2b
Settings in IndexedDB and making everything async
microbit-robert Dec 5, 2025
dd9b86a
Project data in IndexedDB
microbit-robert Dec 5, 2025
71166b9
Use transactions for adding and removing recordings
microbit-robert Dec 8, 2025
d468aa9
Store MakeCode project in IndexedDB
microbit-robert Dec 8, 2025
fdfc6ab
One transaction to load a project from storage
microbit-robert Dec 8, 2025
cfb5f2e
Move projectEdited into MakeCode store
microbit-robert Dec 8, 2025
bba6362
Tidy
microbit-robert Dec 8, 2025
661a278
Use loader function and fallback component
microbit-robert Dec 8, 2025
223de3a
Migrate from localStorage
microbit-robert Dec 8, 2025
3110c67
Use fake-indexeddb for tests
microbit-robert Dec 8, 2025
a3520d9
Attempt to fix now flaky test
microbit-robert Dec 8, 2025
9c5ddd9
Migrate ID to id and use uuid
microbit-robert Dec 10, 2025
0285b23
Fix tests
microbit-robert Dec 10, 2025
858357b
Use recent data for utils test and udpated snapshot
microbit-robert Dec 10, 2025
4ff7d06
Work around multiple loader calls
microbit-robert Dec 10, 2025
fcc6bfb
Keep open tabs up-to-date using broadcast channel
microbit-robert Dec 11, 2025
cc46c88
Sync model state across tabs
microbit-robert Dec 11, 2025
88ee85c
Add comment, tidy
microbit-robert Dec 11, 2025
3a86354
Remove outdated comment as we are no longer using persist middleware
microbit-robert Dec 11, 2025
b8fe124
Update the project when an action recording is added
microbit-robert Dec 12, 2025
170fa04
Set up DB for multiple projects
microbit-robert Dec 15, 2025
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
49 changes: 49 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@types/ejs": "^3.1.5",
"@types/lodash.camelcase": "^4.3.9",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.orderby": "^4.6.9",
"@types/lodash.upperfirst": "^4.3.9",
"@types/node": "^18.16.0",
"@types/react": "^18.3.3",
Expand Down Expand Up @@ -69,9 +70,12 @@
"@types/w3c-web-usb": "^1.0.6",
"bowser": "^2.11.0",
"chart.js": "^4.2.1",
"fake-indexeddb": "^6.2.5",
"framer-motion": "^10.2.4",
"idb": "^8.0.3",
"lodash.camelcase": "^4.3.0",
"lodash.debounce": "^4.0.8",
"lodash.orderby": "^4.6.0",
"lodash.upperfirst": "^4.3.1",
"ml4f": "git://github.com/microsoft/ml4f#v1.10.1",
"react": "^18.3.1",
Expand All @@ -81,6 +85,7 @@
"react-intl": "^6.6.8",
"react-router": "^6.24.0",
"react-router-dom": "^6.24.0",
"uuid": "^13.0.0",
"zustand": "^4.5.5"
}
}
38 changes: 28 additions & 10 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import {
createWebBluetoothConnection,
createWebUSBConnection,
} from "@microbit/microbit-connection";
import React, { ReactNode, useEffect, useMemo, useRef } from "react";
import React, { ReactNode, Suspense, useEffect, useMemo, useRef } from "react";
import { useIntl } from "react-intl";
import {
Await,
createBrowserRouter,
defer,
Outlet,
RouterProvider,
ScrollRestoration,
useLoaderData,
useNavigate,
} from "react-router-dom";
import "theme-package/fonts/fonts.css";
Expand Down Expand Up @@ -47,7 +50,7 @@ import ImportPage from "./pages/ImportPage";
import NewPage from "./pages/NewPage";
import TestingModelPage from "./pages/TestingModelPage";
import OpenSharedProjectPage from "./pages/OpenSharedProjectPage";
import { useStore } from "./store";
import { loadProjectFromStorage, useStore } from "./store";
import {
createCodePageUrl,
createDataSamplesPageUrl,
Expand All @@ -57,6 +60,7 @@ import {
createNewPageUrl,
createTestingModelPageUrl,
} from "./urls";
import LoadingPage from "./components/LoadingPage";

export interface ProviderLayoutProps {
children: ReactNode;
Expand Down Expand Up @@ -112,6 +116,7 @@ const Layout = () => {
const navigate = useNavigate();
const toast = useToast();
const intl = useIntl();
const { projectLoaded } = useLoaderData() as { projectLoaded: boolean };

useEffect(() => {
return useStore.subscribe(
Expand All @@ -138,23 +143,36 @@ const Layout = () => {
}, [intl, navigate, setPostImportDialogState, toast]);

return (
// We use this even though we have errorElement as this does logging.
<ErrorBoundary>
<ScrollRestoration />
<ProjectProvider driverRef={driverRef}>
<EditCodeDialog ref={driverRef} />
<Outlet />
</ProjectProvider>
</ErrorBoundary>
<Suspense fallback={<LoadingPage />}>
<Await resolve={projectLoaded}>
{/* We use this even though we have errorElement as this does logging. */}
<ErrorBoundary>
<ScrollRestoration />
<ProjectProvider driverRef={driverRef}>
<EditCodeDialog ref={driverRef} />
<Outlet />
</ProjectProvider>
</ErrorBoundary>
</Await>
</Suspense>
);
};

const createRouter = () => {
let loaderFuncCalled = false;
return createBrowserRouter([
{
id: "root",
path: "",
element: <Layout />,
loader: () => {
if (!loaderFuncCalled) {
loaderFuncCalled = true;
const projectLoaded = loadProjectFromStorage();
return defer({ projectLoaded });
}
return { projectLoaded: true };
},
// This one gets used for loader errors (typically offline)
// We set an error boundary inside the routes too that logs render-time errors.
// ErrorBoundary doesn't work properly in the loader case at least.
Expand Down
4 changes: 2 additions & 2 deletions src/components/ActionCertaintyCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface ActionCertaintyCardProps {
requiredConfidence?: number;
onThresholdChange: (val: number) => void;
actionName: string;
actionId: number;
actionId: string;
disabled?: boolean;
}

Expand All @@ -42,7 +42,7 @@ const ActionCertaintyCard = ({
const intl = useIntl();
const barWidth = 240;
const predictionResult = useStore((s) => s.predictionResult);
const isTriggered = predictionResult?.detected?.ID === actionId;
const isTriggered = predictionResult?.detected?.id === actionId;
const colorScheme = useMemo(
() => (isTriggered ? "brand2.500" : undefined),
[isTriggered]
Expand Down
28 changes: 14 additions & 14 deletions src/components/ActionDataSamplesCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ interface ActionDataSamplesCardProps {
selected: boolean;
onSelectRow?: () => void;
onRecord?: (recordingOptions: RecordingOptions) => void;
newRecordingId?: number;
newRecordingId?: string;
clearNewRecordingId?: () => void;
}

Expand Down Expand Up @@ -84,7 +84,7 @@ const ActionDataSamplesCard = ({
<DataSamplesRowCard
onSelectRow={onSelectRow}
selected={selected}
key={recording.ID}
key={recording.id}
>
<CloseButton
aria-label={intl.formatMessage(
Expand All @@ -105,15 +105,15 @@ const ActionDataSamplesCard = ({
zIndex={1}
borderColor="blackAlpha.500"
boxShadow="sm"
onClick={() => deleteActionRecording(value.ID, idx)}
onClick={() => deleteActionRecording(value.id, recording.id)}
/>
<DataSample
recording={recording}
numRecordings={value.recordings.length}
actionId={value.ID}
actionId={value.id}
actionName={value.name}
recordingIndex={idx}
isNew={newRecordingId === recording.ID}
isNew={newRecordingId === recording.id}
onNewAnimationEnd={clearNewRecordingId}
onDelete={deleteActionRecording}
view={view}
Expand Down Expand Up @@ -146,14 +146,14 @@ const ActionDataSamplesCard = ({
)}
{value.recordings.map((recording, idx) => (
<DataSample
key={recording.ID}
actionId={value.ID}
key={recording.id}
actionId={value.id}
actionName={value.name}
recordingIndex={idx}
hasClose={!preview}
recording={recording}
numRecordings={value.recordings.length}
isNew={newRecordingId === recording.ID}
isNew={newRecordingId === recording.id}
onDelete={deleteActionRecording}
onNewAnimationEnd={clearNewRecordingId}
view={view}
Expand Down Expand Up @@ -201,7 +201,7 @@ interface RecordingAreaProps extends BoxProps {
}

export const recordButtonId = (action: ActionData) =>
`record-button-${action.ID}`;
`record-button-${action.id}`;

const RecordingArea = ({
action,
Expand Down Expand Up @@ -319,12 +319,12 @@ const DataSample = ({
}: {
recording: RecordingData;
numRecordings: number;
actionId: number;
actionId: string;
actionName: string;
recordingIndex: number;
isNew: boolean;
onNewAnimationEnd?: () => void;
onDelete: (actionId: ActionData["ID"], recordingIdx: number) => void;
onDelete: (actionId: string, recordingId: string) => void;
view: DataSamplesView;
hasClose?: boolean;
}) => {
Expand All @@ -336,10 +336,10 @@ const DataSample = ({
view === DataSamplesView.GraphAndDataFeatures;
const intl = useIntl();
const handleDelete = useCallback(() => {
onDelete(actionId, recordingIndex);
}, [actionId, onDelete, recordingIndex]);
onDelete(actionId, recording.id);
}, [actionId, onDelete, recording.id]);
return (
<HStack key={recording.ID} position="relative">
<HStack key={recording.id} position="relative">
{hasClose && (
<CloseButton
position="absolute"
Expand Down
Loading