Skip to content

Commit

Permalink
Merge branch 'main' into graph-settings
Browse files Browse the repository at this point in the history
  • Loading branch information
microbit-robert committed Nov 13, 2024
2 parents 3f155bb + 6ed35d9 commit 697f96e
Show file tree
Hide file tree
Showing 33 changed files with 823 additions and 370 deletions.
70 changes: 45 additions & 25 deletions lang/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@
"defaultMessage": "These are the MakeCode blocks for each detected action when you transfer your ML model and code to a micro:bit",
"description": "Code column heading tooltip"
},
"coming-soon": {
"defaultMessage": "Coming soon",
"description": "Placeholder text for future projects"
},
"confirm-action": {
"defaultMessage": "Confirm",
"description": "Confirm button text"
Expand Down Expand Up @@ -335,6 +331,10 @@
"defaultMessage": "Connect to test your ML model",
"description": "Live graph disconnected micro:bit status message for test model page"
},
"connect-to-tour-body": {
"defaultMessage": "You need to connect a data collection micro:bit to view the tour.",
"description": "Prompt to connect the app to a micro:bit"
},
"connect-troubleshoot": {
"defaultMessage": "Troubleshoot problems with connecting to your micro:bit",
"description": "Link to support article for troubleshootling"
Expand Down Expand Up @@ -1351,36 +1351,52 @@
"defaultMessage": "Testing model",
"description": "Testing model page title"
},
"tour-collectData-addActions-content": {
"tour-action": {
"defaultMessage": "Tour",
"description": "Button label that starts a tour of the app's features"
},
"tour-collect-addActions-content": {
"defaultMessage": "Decide what your other actions will be and what you will name them. You need at least 2 actions with 3 data samples to train the model. Your data samples are only stored on your computer, they do not get sent to anyone else.",
"description": "Tour step description"
},
"tour-collectData-addActions-title": {
"tour-collect-addActions-title": {
"defaultMessage": "Add more actions",
"description": "Tour step title"
},
"tour-collectData-afterFirst-content": {
"tour-collect-afterFirst-content": {
"defaultMessage": "To train the machine learning model you need at least 3 data samples for 2 different actions.",
"description": "Tour step description"
},
"tour-collectData-afterFirst-title": {
"defaultMessage": "You’ve recorded your first sample!",
"description": "Tour step title"
"tour-collect-afterFirst-title": {
"defaultMessage": "You’ve recorded your first {recordingCount, plural, one {sample} other {samples}}!",
"description": "Tour step title. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format."
},
"tour-collect-collectMore-explanation-content": {
"defaultMessage": "Collecting more samples should result in a better machine learning model.",
"description": "Tour step description"
},
"tour-collect-collectMore-hasRecordings-content": {
"defaultMessage": "Record data samples for your actions.",
"description": "Tour step description"
},
"tour-collectData-collectMore-content": {
"defaultMessage": "Record more samples for this action. Collecting more samples should result in a better machine learning model.",
"tour-collect-collectMore-noRecordings-content": {
"defaultMessage": "Record more samples for this action.",
"description": "Tour step description"
},
"tour-collectData-collectMore-title": {
"tour-collect-collectMore-title": {
"defaultMessage": "Collect more data samples",
"description": "Tour step title"
},
"tour-collectData-trainModel-content": {
"tour-collect-trainModel-content": {
"defaultMessage": "When you have collected enough data samples you can train the machine learning model. You can come back later to remove or add more data and re-train to make the model more reliable.",
"description": "Tour step description"
},
"tour-dataSamples-actions-content": {
"defaultMessage": "An action is the type of movement you want the machine learning model to recognise e.g. ‘waving’ or ‘clapping’. Decide what your first action will be, name it and then start recording data samples.",
"tour-dataSamples-actions-common-content": {
"defaultMessage": "An action is the type of movement you want the machine learning model to recognise e.g. ‘waving’ or ‘clapping’.",
"description": "Tour step content"
},
"tour-dataSamples-actions-noRecordings-content": {
"defaultMessage": "Decide what your first action will be, name it and then start recording data samples.",
"description": "Tour step content"
},
"tour-dataSamples-connected-content": {
Expand Down Expand Up @@ -1411,35 +1427,39 @@
"defaultMessage": "Microsoft MakeCode",
"description": "Tour step title"
},
"tour-testModel-afterTrain-content": {
"tour-trainModel-afterTrain-alreadyConnected-title": {
"defaultMessage": "You’ve trained an ML model!",
"description": "Tour step title"
},
"tour-trainModel-afterTrain-content": {
"defaultMessage": "Now test your machine learning model. Try each action by moving your data collection micro:bit. Does the model correctly estimate each action?",
"description": "Tour step description"
},
"tour-testModel-afterTrain-title": {
"defaultMessage": "You’ve trained an ML model!",
"tour-trainModel-afterTrain-delayedUntilConnected-title": {
"defaultMessage": "Test your ML model",
"description": "Tour step title"
},
"tour-testModel-certaintyRecognition-content": {
"tour-trainModel-certaintyRecognition-content": {
"defaultMessage": "The bar graph shows how confident the model is that you are doing each action. Move the slider to adjust the recognition point, or threshold.",
"description": "Tour step description"
},
"tour-testModel-certaintyRecognition-title": {
"tour-trainModel-certaintyRecognition-title": {
"defaultMessage": "Certainty and recognition point",
"description": "Tour step title"
},
"tour-testModel-editInMakeCode-content": {
"tour-trainModel-editInMakeCode-content": {
"defaultMessage": "Open your project in MakeCode to download the program and your machine learning model to a micro:bit. You can add more blocks to create your own programs using your model.",
"description": "Tour step description"
},
"tour-testModel-estimatedAction-content": {
"tour-trainModel-estimatedAction-content": {
"defaultMessage": "The action the model estimates you are currently doing is shown below the micro:bit icon for that action.",
"description": "Tour step description"
},
"tour-testModel-makeCodeBlocks-content": {
"tour-trainModel-makeCodeBlocks-content": {
"defaultMessage": "These MakeCode blocks will show icons for each action detected when you transfer your code and model to a micro:bit.",
"description": "Tour step description"
},
"tour-testModel-makeCodeBlocks-title": {
"tour-trainModel-makeCodeBlocks-title": {
"defaultMessage": "Microsoft MakeCode blocks",
"description": "Tour step title"
},
Expand Down
29 changes: 21 additions & 8 deletions src/components/ActionDataSamplesCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,19 @@ const ActionDataSamplesCard = ({
<DataSamplesRowCard
onSelectRow={onSelectRow}
selected={selected}
className={tourElClassname.recordDataSamplesCard}
// Otherwise we put the tour class on the recording area
className={
value.recordings.length === 0
? tourElClassname.recordDataSamplesCard
: undefined
}
>
<RecordingArea action={value} selected={selected} onRecord={onRecord} />
<RecordingArea
className={tourElClassname.recordDataSamplesCard}
action={value}
selected={selected}
onRecord={onRecord}
/>
{value.recordings.map((recording, idx) => (
<DataSample
key={recording.ID}
Expand Down Expand Up @@ -155,18 +165,21 @@ const DataSamplesRowCard = ({
);
};

interface RecordingAreaProps extends BoxProps {
action: ActionData;
selected: boolean;
onRecord: (recordingOptions: RecordingOptions) => void;
}

const RecordingArea = ({
action,
selected,
onRecord,
}: {
action: ActionData;
selected: boolean;
onRecord: (recordingOptions: RecordingOptions) => void;
}) => {
...props
}: RecordingAreaProps) => {
const intl = useIntl();
return (
<VStack w="8.25rem" justifyContent="center">
<VStack w="8.25rem" justifyContent="center" {...props}>
<Menu>
<ButtonGroup isAttached>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,27 @@ import {
} from "@chakra-ui/react";
import { ComponentProps, useCallback, useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";
import { ConnectionStatus } from "../connect-status-hooks";
import {
ConnectionFlowStep,
useConnectionStage,
} from "../connection-stage-hooks";
import { ConnectionStatus } from "../connect-status-hooks";
import { ConnectOptions } from "../store";

interface ConnectFirstDialogProps
extends Omit<ComponentProps<typeof Modal>, "children"> {
explanationTextId: string;
onChooseConnect?: () => void;
options?: ConnectOptions;
}

const ConnectToRecordDialog = ({
const ConnectFirstDialog = ({
explanationTextId,
options,
onClose,
onChooseConnect,
...rest
}: Omit<ComponentProps<typeof Modal>, "children">) => {
}: ConnectFirstDialogProps) => {
const {
actions,
status: connStatus,
Expand All @@ -34,13 +45,14 @@ const ConnectToRecordDialog = ({
}, [onClose]);

const handleConnect = useCallback(async () => {
onChooseConnect?.();
switch (connStatus) {
case ConnectionStatus.FailedToConnect:
case ConnectionStatus.FailedToReconnectTwice:
case ConnectionStatus.FailedToSelectBluetoothDevice:
case ConnectionStatus.NotConnected: {
// Start connection flow.
actions.startConnect();
actions.startConnect(options);
return handleOnClose();
}
case ConnectionStatus.ConnectionLost:
Expand All @@ -65,7 +77,7 @@ const ConnectToRecordDialog = ({
return handleOnClose();
}
}
}, [connStatus, actions, handleOnClose]);
}, [onChooseConnect, connStatus, actions, options, handleOnClose]);

useEffect(() => {
if (
Expand Down Expand Up @@ -96,7 +108,7 @@ const ConnectToRecordDialog = ({
<ModalBody>
<ModalCloseButton />
<Text>
<FormattedMessage id="connect-to-record-body" />
<FormattedMessage id={explanationTextId} />
</Text>
</ModalBody>
<ModalFooter justifyContent="flex-end">
Expand All @@ -114,4 +126,4 @@ const ConnectToRecordDialog = ({
);
};

export default ConnectToRecordDialog;
export default ConnectFirstDialog;
37 changes: 22 additions & 15 deletions src/components/DataSamplesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ import {
import { FormattedMessage } from "react-intl";
import { useConnectActions } from "../connect-actions-hooks";
import { useConnectionStage } from "../connection-stage-hooks";
import { ActionData, TourId } from "../model";
import { ActionData } from "../model";
import { useStore } from "../store";
import ConnectToRecordDialog from "./ConnectToRecordDialog";
import ConnectFirstDialog from "./ConnectFirstDialog";
import DataSamplesMenu from "./DataSamplesMenu";
import DataSamplesTableRow from "./DataSamplesTableRow";
import HeadingGrid, { GridColumnHeadingItemProps } from "./HeadingGrid";
import LoadProjectInput, { LoadProjectInputRef } from "./LoadProjectInput";
import RecordingDialog, { RecordingOptions } from "./RecordingDialog";
import RecordingDialog, {
RecordingCompleteDetail,
RecordingOptions,
} from "./RecordingDialog";
import ShowGraphsCheckbox from "./ShowGraphsCheckbox";

const gridCommonProps: Partial<GridProps> = {
Expand Down Expand Up @@ -85,6 +88,10 @@ const DataSamplesTable = ({
undefined
);

const handleConnect = useCallback(() => {
connActions.startConnect();
}, [connActions]);

useEffect(() => {
const listener = (e: ButtonEvent) => {
if (!recordingDialogDisclosure.isOpen) {
Expand Down Expand Up @@ -114,28 +121,28 @@ const DataSamplesTable = ({
);

const tourStart = useStore((s) => s.tourStart);
useEffect(() => {
if (
!recordingDialogDisclosure.isOpen &&
actions.length === 1 &&
actions[0].recordings.length === 1
) {
tourStart(TourId.CollectDataToTrainModel);
}
}, [actions, recordingDialogDisclosure.isOpen, tourStart]);
const handleRecordingComplete = useCallback(
({ mostRecentRecordingId, recordingCount }: RecordingCompleteDetail) => {
setNewRecordingId(mostRecentRecordingId);
tourStart({ name: "DataSamplesRecorded", recordingCount });
},
[tourStart]
);

return (
<>
<ConnectToRecordDialog
<ConnectFirstDialog
isOpen={connectToRecordDialogDisclosure.isOpen}
onClose={connectToRecordDialogDisclosure.onClose}
explanationTextId="connect-to-record-body"
/>
{selectedAction && (
<RecordingDialog
actionId={selectedAction.ID}
isOpen={recordingDialogDisclosure.isOpen}
onClose={recordingDialogDisclosure.onClose}
actionName={selectedAction.name}
onRecordingComplete={setNewRecordingId}
onRecordingComplete={handleRecordingComplete}
recordingOptions={recordingOptions}
/>
)}
Expand Down Expand Up @@ -166,7 +173,7 @@ const DataSamplesTable = ({
fontSize="lg"
color="brand.600"
variant="link"
onClick={connActions.startConnect}
onClick={handleConnect}
>
{chunks}
</Button>
Expand Down
3 changes: 1 addition & 2 deletions src/components/EditCodeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from "@chakra-ui/react";
import { MakeCodeFrameDriver } from "@microbit/makecode-embed/react";
import { forwardRef, memo, useEffect, useRef } from "react";
import { TourId } from "../model";
import { useStore } from "../store";
import Editor from "./Editor";

Expand All @@ -21,7 +20,7 @@ const EditCodeDialog = forwardRef<MakeCodeFrameDriver, EditCodeDialogProps>(
const tourStart = useStore((s) => s.tourStart);
useEffect(() => {
if (isOpen) {
tourStart(TourId.MakeCode);
tourStart({ name: "MakeCode" });
}
}, [isOpen, tourStart]);
return (
Expand Down
15 changes: 3 additions & 12 deletions src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import {
MakeCodeFrame,
MakeCodeFrameDriver,
} from "@microbit/makecode-embed/react";
import React, { forwardRef, useCallback } from "react";
import { getMakeCodeLang } from "../settings";
import React, { forwardRef } from "react";
import { useProject } from "../hooks/project-hooks";
import { getMakeCodeLang } from "../settings";
import { useSettings } from "../store";
import { useLogging } from "../logging/logging-hooks";

const controllerId = "MicrobitMachineLearningTool";

Expand All @@ -18,21 +17,13 @@ const Editor = forwardRef<MakeCodeFrameDriver, EditorProps>(function Editor(
props,
ref
) {
const logging = useLogging();
const { project, editorCallbacks } = useProject();
const initialProjects = useCallback(() => {
logging.log(
`[MakeCode] Initialising with header ID: ${project.header?.id}`
);
return Promise.resolve([project]);
}, [logging, project]);
const { editorCallbacks } = useProject();
const [{ languageId }] = useSettings();
return (
<MakeCodeFrame
ref={ref}
controllerId={controllerId}
controller={2}
initialProjects={initialProjects}
lang={getMakeCodeLang(languageId)}
loading="eager"
{...editorCallbacks}
Expand Down
Loading

0 comments on commit 697f96e

Please sign in to comment.