Skip to content

Handle opening project from microbit.org (first cut for testing) #388

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

Merged
merged 25 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9f4cc04
WIP locally working open in createAI flow
microbit-grace Oct 14, 2024
c6a0097
Add import page
microbit-grace Oct 14, 2024
089aa45
Remove not needed commented out block
microbit-grace Oct 14, 2024
a7c3c9f
Remove activitiesBaseUrl
microbit-grace Oct 15, 2024
8d72fb1
Merge branch 'main' of https://github.com/microbit-foundation/ml-trai…
microbit-grace Oct 15, 2024
f80fb61
Revert Homepage.tsx
microbit-grace Oct 15, 2024
8bbdc60
Fix merge
microbit-grace Oct 15, 2024
34160b0
Temp activitiesBaseUrl
microbit-grace Oct 15, 2024
0ab41d4
Hardcode activitiesBaseUrl
microbit-grace Oct 15, 2024
f3b0fd6
Remove debug logging
microbit-grace Oct 15, 2024
f0370fe
Temp hardcoding for local testing
microbit-grace Oct 15, 2024
d94db35
Change hardcoded url to preview url
microbit-grace Oct 15, 2024
2ba8c6b
Use deployment activitiesBaseUrl
microbit-grace Oct 15, 2024
6c3960f
Update ml-trainer-microbit version
microbit-grace Oct 15, 2024
33152af
Updated testing-library/react lib
microbit-grace Oct 15, 2024
fa5dca4
Add loadProject store test
microbit-grace Oct 15, 2024
92dc85a
Revert "Updated testing-library/react lib"
microbit-grace Oct 16, 2024
f1e1577
Revert "Add loadProject store test"
microbit-grace Oct 16, 2024
c44bb77
Tweak copy
microbit-matt-hillsdon Oct 16, 2024
a2833ab
Tweaks
microbit-matt-hillsdon Oct 16, 2024
7fa21b2
Add timestamp on loading a project
microbit-matt-hillsdon Oct 16, 2024
628faf2
Comment
microbit-matt-hillsdon Oct 16, 2024
0c2d83e
Merge branch 'main' into open-in-createai
microbit-matt-hillsdon Oct 16, 2024
b7f5b13
Share code to get gestures; loadProject as a new session
microbit-matt-hillsdon Oct 16, 2024
04c8ef9
Lint
microbit-matt-hillsdon Oct 16, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npm install --no-save @microbit-foundation/[email protected].10 @microbit-foundation/[email protected] @microbit-foundation/[email protected]
- run: npm install --no-save @microbit-foundation/[email protected].13 @microbit-foundation/[email protected] @microbit-foundation/[email protected]
if: github.repository_owner == 'microbit-foundation'
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
4 changes: 4 additions & 0 deletions lang/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@
"defaultMessage": "Close",
"description": "Close button text or label"
},
"code-download-error": {
"defaultMessage": "Error downloading the project code",
"description": "Title for error message relating to project loading"
},
"coming-soon": {
"defaultMessage": "Coming soon",
"description": "Placeholder text for future projects"
Expand Down
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import { LoggingProvider } from "./logging/logging-hooks";
import TranslationProvider from "./messages/TranslationProvider";
import DataSamplesPage from "./pages/DataSamplesPage";
import HomePage from "./pages/HomePage";
import ImportPage from "./pages/ImportPage";
import NewPage from "./pages/NewPage";
import TestingModelPage from "./pages/TestingModelPage";
import {
createDataSamplesPageUrl,
createHomePageUrl,
createImportPageUrl,
createNewPageUrl,
createTestingModelPageUrl,
} from "./urls";
Expand Down Expand Up @@ -96,6 +98,7 @@ const createRouter = () => {
path: createNewPageUrl(),
element: <NewPage />,
},
{ path: createImportPageUrl(), element: <ImportPage /> },
{
path: createDataSamplesPageUrl(),
element: <DataSamplesPage />,
Expand Down
1 change: 1 addition & 0 deletions src/deployment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface DeploymentConfig {

termsOfUseLink?: string;
privacyPolicyLink?: string;
activitiesBaseUrl?: string;

logging: Logging;
}
Expand Down
6 changes: 6 additions & 0 deletions src/messages/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@
"value": "Close"
}
],
"code-download-error": [
{
"type": 0,
"value": "Error downloading the project code"
}
],
"coming-soon": [
{
"type": 0,
Expand Down
25 changes: 25 additions & 0 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,28 @@ export enum TourId {
CollectDataToTrainModel = "collectDataToTrainModel",
TestModelPage = "testModelPage",
}

/**
* Information passed omn the URL from microbit.org.
* We call back into microbit.org to grab a JSON file with
* full details.
*/
export type MicrobitOrgResource = {
/**
* ID that can be used when fetching the code from microbit.org.
*/
id: string;

/**
* Name of the microbit.org project or lesson.
*
* We use this to load the target code.
*/
project: string;

/**
* Name of the actual code snippet.
* Due to a data issue this can often be the same as the project name.
*/
name: string;
};
87 changes: 87 additions & 0 deletions src/pages/ImportPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Project } from "@microbit/makecode-embed/react";
import { useEffect } from "react";
import { IntlShape, useIntl } from "react-intl";
import { useNavigate } from "react-router";
import { useSearchParams } from "react-router-dom";
import { useDeployment } from "../deployment";
import { MicrobitOrgResource } from "../model";
import { useStore } from "../store";
import { createDataSamplesPageUrl } from "../urls";

const ImportPage = () => {
const navigate = useNavigate();
const intl = useIntl();
const { activitiesBaseUrl } = useDeployment();
const resource = useMicrobitResourceSearchParams();
const loadProject = useStore((s) => s.loadProject);

useEffect(() => {
const updateAsync = async () => {
if (!resource || !activitiesBaseUrl) {
return;
}
const code = await fetchMicrobitOrgResourceTargetCode(
activitiesBaseUrl,
resource,
intl
);
loadProject(code);
navigate(createDataSamplesPageUrl());
};
void updateAsync();
}, [activitiesBaseUrl, intl, loadProject, navigate, resource]);

return <></>;
};

const useMicrobitResourceSearchParams = (): MicrobitOrgResource | undefined => {
const [params] = useSearchParams();
const id = params.get("id");
const project = params.get("project");
const name = params.get("name");
return id && name && project ? { id, project, name } : undefined;
};

const isValidProject = (content: Project): content is Project => {
return (
content &&
typeof content === "object" &&
"text" in content &&
!!content.text
);
};

const fetchMicrobitOrgResourceTargetCode = async (
activitiesBaseUrl: string,
resource: MicrobitOrgResource,
intl: IntlShape
): Promise<Project> => {
const url = `${activitiesBaseUrl}${encodeURIComponent(
resource.id
)}-makecode.json`;
let json;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Unexpected response ${response.status}`);
}
json = (await response.json()) as object;
} catch (e) {
const rethrow = new Error(
intl.formatMessage({ id: "code-download-error" })
);
rethrow.stack = e instanceof Error ? e.stack : undefined;
throw rethrow;
}
if (
!("editorContent" in json) ||
typeof json.editorContent !== "object" ||
!json.editorContent ||
!isValidProject(json.editorContent)
) {
throw new Error(intl.formatMessage({ id: "code-format-error" }));
}
return json.editorContent;
};

export default ImportPage;
40 changes: 33 additions & 7 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
} from "./makecode/utils";
import { trainModel } from "./ml";
import {
DatasetEditorJsonFormat,
DownloadState,
DownloadStep,
Gesture,
Expand Down Expand Up @@ -131,6 +130,7 @@ export interface Actions {
downloadDataset(): void;
dataCollectionMicrobitConnected(): void;
loadDataset(gestures: GestureData[]): void;
loadProject(project: Project): void;
setEditorOpen(open: boolean): void;
recordingStarted(): void;
recordingStopped(): void;
Expand Down Expand Up @@ -410,6 +410,25 @@ export const useStore = create<Store>()(
});
},

/**
* Generally project loads go via MakeCode as it reads the hex but when we open projects
* from microbit.org we have the JSON already and use this route.
*/
loadProject(project: Project) {
set(() => {
const timestamp = Date.now();
return {
gestures: getGesturesFromProject(project),
model: undefined,
project,
projectEdited: true,
appEditNeedsFlushToEditor: true,
timestamp,
projectLoadTimestamp: timestamp,
};
});
},

closeTrainModelDialogs() {
set({
trainModelDialogStage: TrainModelDialogStage.Closed,
Expand Down Expand Up @@ -547,19 +566,14 @@ export const useStore = create<Store>()(
// It's a new project. Thanks user. We'll update our state.
// This will cause another write to MakeCode but that's OK as it gives us
// a chance to validate/update the project
const datasetString = newProject.text?.[filenames.datasetJson];
const dataset = datasetString
? (JSON.parse(datasetString) as DatasetEditorJsonFormat)
: { data: [] };

const timestamp = Date.now();
return {
project: newProject,
projectLoadTimestamp: timestamp,
timestamp,
// New project loaded externally so we can't know whether its edited.
projectEdited: true,
gestures: dataset.data,
gestures: getGesturesFromProject(newProject),
model: undefined,
isEditorOpen: false,
};
Expand Down Expand Up @@ -765,3 +779,15 @@ const gestureIcon = ({
}
return useableIcons[0];
};

const getGesturesFromProject = (project: Project): GestureData[] => {
const { text } = project;
if (text === undefined || !("dataset.json" in text)) {
return [];
}
const dataset = JSON.parse(text["dataset.json"]) as object;
if (typeof dataset !== "object" || !("data" in dataset)) {
return [];
}
return dataset.data as GestureData[];
};
2 changes: 2 additions & 0 deletions src/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const createHomePageUrl = () => `${basepath}`;

export const createNewPageUrl = () => `${basepath}new`;

export const createImportPageUrl = () => `${basepath}import`;

export const createDataSamplesPageUrl = () => `${basepath}data-samples`;

export const createTestingModelPageUrl = () => `${basepath}testing-model`;
Loading