Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5ef648e
Brief recording complete state
microbit-grace Oct 25, 2024
e21c473
Get is basically working mechanically
microbit-robert Oct 30, 2024
8da0971
Working with dodgy looking 'more' button
microbit-robert Oct 30, 2024
79daa32
Theme hack to make button red
microbit-robert Oct 31, 2024
f865096
Add text for num recordings remaining
microbit-robert Oct 31, 2024
ace13ad
Merge branch 'main' into recording-options
microbit-robert Oct 31, 2024
8a50c27
Merge branch 'recording-done' into recording-options
microbit-robert Oct 31, 2024
c8f26a4
Update done text and reduce time shown
microbit-robert Oct 31, 2024
7633bd0
More hacking to get the counter right on dialog open
microbit-robert Oct 31, 2024
1c741e8
Tweak recording button and available options
microbit-robert Oct 31, 2024
662acb8
Translatable text, update icon
microbit-robert Oct 31, 2024
b8010c7
Fix time difference
microbit-robert Oct 31, 2024
9bed1b3
Refactor
microbit-robert Oct 31, 2024
902be47
Another icon
microbit-robert Oct 31, 2024
9bbafd2
Update text
microbit-robert Oct 31, 2024
3e15a6f
Tweak text again
microbit-robert Oct 31, 2024
6f3618c
Text tweaks and sample count
microbit-robert Oct 31, 2024
39ccec2
User select none on recording status text
microbit-robert Oct 31, 2024
00e8072
Use isRecording state for live graph overlays
microbit-robert Oct 31, 2024
27bc39c
Fix overlays for continous recording
microbit-robert Oct 31, 2024
2b17f6f
Pass RecordingOptions object through components
microbit-robert Nov 1, 2024
4e902a9
More recording options aria label text
microbit-robert Nov 1, 2024
8bfd1f2
Merge branch 'main' into recording-options
microbit-robert Nov 1, 2024
357f8aa
Bump theme version and add button variant to default theme
microbit-robert Nov 1, 2024
b3b471d
Add stop recording text for continuous recording
microbit-robert Nov 1, 2024
f64efa9
Trigger tour only once recording dialog has closed
microbit-robert Nov 1, 2024
9f5a970
Bump theme version and tweak default recordOutline variant
microbit-robert Nov 1, 2024
82c7434
Spacing tweak (#455)
microbit-matt-hillsdon Nov 1, 2024
3a82936
Additional spacing tweak
microbit-robert Nov 1, 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/ml-trainer-microbit@0.2.0-dev.22 @microbit-foundation/website-deploy-aws@0.6 @microbit-foundation/website-deploy-aws-config@0.9
- run: npm install --no-save @microbit-foundation/ml-trainer-microbit@0.2.0-dev.27 @microbit-foundation/website-deploy-aws@0.6 @microbit-foundation/website-deploy-aws-config@0.9
if: github.repository_owner == 'microbit-foundation'
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
32 changes: 32 additions & 0 deletions lang/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -999,14 +999,38 @@
"defaultMessage": "Press to record a data sample or press button B on your data collection micro:bit.",
"description": "Hint when you have named the first action but not recorded any data samples"
},
"record-samples": {
"defaultMessage": "Record {numSamples} samples",
"description": "Additional recording action text"
},
"record-samples-help": {
"defaultMessage": "Each sample has its own countdown",
"description": "Help text for additional recording action"
},
"record-seconds": {
"defaultMessage": "Record for {numSeconds} seconds",
"description": "Additional recording action text"
},
"record-seconds-help": {
"defaultMessage": "Move continuously to get {numSamples} samples",
"description": "Help text for additional recording action"
},
"recording": {
"defaultMessage": "Recording",
"description": "Shown during recording"
},
"recording-complete": {
"defaultMessage": "Done",
"description": "Text shown when data recording complete"
},
"recording-data-for": {
"defaultMessage": "Record data for action \"{action}\"",
"description": "Text in recording dialog"
},
"recording-data-for-numbered": {
"defaultMessage": "Record {sample} of {numSamples} for action \"{action}\"",
"description": "Text in recording dialog"
},
"recording-fingerprint-label": {
"defaultMessage": "heatmap showing micro:bit x, y, z accelerometer data features for one recording",
"description": "Label for recording fingerprint heatmap"
Expand All @@ -1015,6 +1039,10 @@
"defaultMessage": "graph showing micro:bit x, y, z accelerometer data for one recording",
"description": "Label for recording graph"
},
"recording-options-aria": {
"defaultMessage": "More recording options",
"description": "Aria label for more recording options button"
},
"reload-action": {
"defaultMessage": "Click to reload the page",
"description": "Reload page button text"
Expand Down Expand Up @@ -1103,6 +1131,10 @@
"defaultMessage": "Train",
"description": "Step in home page diagram"
},
"stop-recording-action": {
"defaultMessage": "Stop recording",
"description": "Button label to stop recording movement data while recording multiple samples"
},
"support-request": {
"defaultMessage": "Please consider <link>raising a support request</link>.",
"description": "Support request link text"
Expand Down
101 changes: 86 additions & 15 deletions src/components/ActionDataSamplesCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,30 @@ import {
Box,
BoxProps,
Button,
ButtonGroup,
Card,
CardBody,
CloseButton,
HStack,
Icon,
keyframes,
Menu,
MenuItem,
MenuList,
Portal,
Text,
VStack,
} from "@chakra-ui/react";
import { ReactNode, useCallback } from "react";
import { RiHashtag, RiTimerLine } from "react-icons/ri";
import { FormattedMessage, useIntl } from "react-intl";
import { DataSamplesView, GestureData, RecordingData } from "../model";
import { useStore } from "../store";
import { tourElClassname } from "../tours";
import MoreMenuButton from "./MoreMenuButton";
import RecordingFingerprint from "./RecordingFingerprint";
import RecordingGraph from "./RecordingGraph";
import { RecordingOptions } from "./RecordingDialog";

const flash = keyframes({
"0%, 10%": {
Expand All @@ -29,7 +38,7 @@ interface ActionDataSamplesCardProps {
value: GestureData;
selected: boolean;
onSelectRow?: () => void;
onRecord: () => void;
onRecord: (recordingOptions: RecordingOptions) => void;
newRecordingId?: number;
clearNewRecordingId?: () => void;
}
Expand Down Expand Up @@ -153,28 +162,90 @@ const RecordingArea = ({
}: {
action: GestureData;
selected: boolean;
onRecord: () => void;
onRecord: (recordingOptions: RecordingOptions) => void;
}) => {
const intl = useIntl();
return (
<VStack w="8.25rem" justifyContent="center">
<Button
variant={selected ? "solid" : "outline"}
colorScheme="red"
onClick={onRecord}
aria-label={intl.formatMessage(
{ id: "record-action-aria" },
{ action: action.name }
)}
>
<FormattedMessage id="record-action" />
</Button>
<Menu>
<ButtonGroup isAttached>
<Button
pr={2}
variant={selected ? "record" : "recordOutline"}
borderRight="none"
onClick={() =>
onRecord({ recordingsToCapture: 1, continuousRecording: false })
}
aria-label={intl.formatMessage(
{ id: "record-action-aria" },
{ action: action.name }
)}
>
<FormattedMessage id="record-action" />
</Button>
<MoreMenuButton
minW={8}
variant={selected ? "record" : "recordOutline"}
aria-label={intl.formatMessage({ id: "recording-options-aria" })}
/>
<Portal>
<MenuList>
<MenuItem
onClick={() =>
onRecord({
recordingsToCapture: 10,
continuousRecording: false,
})
}
icon={<Icon as={RiHashtag} h={5} w={5} />}
>
<Text fontSize="md">
<FormattedMessage
id="record-samples"
values={{ numSamples: 10 }}
/>
</Text>
<Text fontSize="xs">
<FormattedMessage id="record-samples-help" />
</Text>
</MenuItem>
<MenuItem
onClick={() =>
onRecord({
recordingsToCapture: 10,
continuousRecording: true,
})
}
icon={<Icon as={RiTimerLine} h={5} w={5} />}
>
<Text fontSize="md">
<FormattedMessage
id="record-seconds"
values={{ numSeconds: 10 }}
/>
</Text>
<Text fontSize="xs">
<FormattedMessage
id="record-seconds-help"
values={{ numSamples: 10 }}
/>
</Text>
</MenuItem>
</MenuList>
</Portal>
</ButtonGroup>
</Menu>
{action.recordings.length < 3 ? (
<Text fontSize="xs" textAlign="center" fontWeight="bold">
<Text
fontSize="xs"
textAlign="center"
fontWeight="bold"
userSelect="none"
>
<FormattedMessage id="data-samples-status-not-enough" />
</Text>
) : (
<Text fontSize="xs" textAlign="center">
<Text fontSize="xs" textAlign="center" userSelect="none">
<FormattedMessage
id="data-samples-status-count"
values={{ numSamples: action.recordings.length }}
Expand Down
46 changes: 37 additions & 9 deletions src/components/DataSamplesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,25 @@ import {
VStack,
} from "@chakra-ui/react";
import { ButtonEvent } from "@microbit/microbit-connection";
import { ReactNode, useEffect, useMemo, useRef, useState } from "react";
import {
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { FormattedMessage } from "react-intl";
import { useConnectActions } from "../connect-actions-hooks";
import { useConnectionStage } from "../connection-stage-hooks";
import { GestureData } from "../model";
import { GestureData, TourId } from "../model";
import { useStore } from "../store";
import ConnectToRecordDialog from "./ConnectToRecordDialog";
import DataSamplesTableRow from "./DataSamplesTableRow";
import DataSamplesMenu from "./DataSamplesMenu";
import DataSamplesTableRow from "./DataSamplesTableRow";
import HeadingGrid, { GridColumnHeadingItemProps } from "./HeadingGrid";
import LoadProjectInput, { LoadProjectInputRef } from "./LoadProjectInput";
import RecordingDialog from "./RecordingDialog";
import RecordingDialog, { RecordingOptions } from "./RecordingDialog";
import ShowGraphsCheckbox from "./ShowGraphsCheckbox";

const gridCommonProps: Partial<GridProps> = {
Expand Down Expand Up @@ -93,6 +100,30 @@ const DataSamplesTable = ({
};
}, [connection, recordingDialogDisclosure]);

const [recordingOptions, setRecordingOptions] = useState<RecordingOptions>({
continuousRecording: false,
recordingsToCapture: 1,
});
const handleRecord = useCallback(
(recordingOptions: RecordingOptions) => {
setRecordingOptions(recordingOptions);
isConnected
? recordingDialogDisclosure.onOpen()
: connectToRecordDialogDisclosure.onOpen();
},
[connectToRecordDialogDisclosure, isConnected, recordingDialogDisclosure]
);

const tourStart = useStore((s) => s.tourStart);
useEffect(() => {
if (
!recordingDialogDisclosure.isOpen &&
gestures.length === 1 &&
gestures[0].recordings.length === 1
) {
tourStart(TourId.CollectDataToTrainModel);
}
}, [gestures, recordingDialogDisclosure.isOpen, tourStart]);
Comment on lines +118 to +126
Copy link
Author

Choose a reason for hiding this comment

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

Would have prefered to do this on recording dialog close, but the latest version of the gesture recordings aren't available at that point.

return (
<>
<ConnectToRecordDialog
Expand All @@ -106,6 +137,7 @@ const DataSamplesTable = ({
onClose={recordingDialogDisclosure.onClose}
actionName={selectedGesture.name}
onRecordingComplete={setNewRecordingId}
recordingOptions={recordingOptions}
/>
)}
<HeadingGrid
Expand Down Expand Up @@ -173,11 +205,7 @@ const DataSamplesTable = ({
clearNewRecordingId={() => setNewRecordingId(undefined)}
selected={selectedGesture.ID === g.ID}
onSelectRow={() => setSelectedGestureIdx(idx)}
onRecord={
isConnected
? recordingDialogDisclosure.onOpen
: connectToRecordDialogDisclosure.onOpen
}
onRecord={handleRecord}
showHints={showHints}
/>
))}
Expand Down
3 changes: 2 additions & 1 deletion src/components/DataSamplesTableHints.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { GestureData } from "../model";
import ActionDataSamplesCard from "./ActionDataSamplesCard";
import GreetingEmojiWithArrow from "./GreetingEmojiWithArrow";
import UpCurveArrow from "./UpCurveArrow";
import { RecordingOptions } from "./RecordingDialog";

interface DataSamplesTableHintsProps {
gesture: GestureData;
onRecord: () => void;
onRecord: (recordingOptions: RecordingOptions) => void;
}

const DataSamplesTableHints = ({
Expand Down
3 changes: 2 additions & 1 deletion src/components/DataSamplesTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {
ConnectionFlowStep,
useConnectionStage,
} from "../connection-stage-hooks";
import { RecordingOptions } from "./RecordingDialog";

interface DataSamplesTableRowProps {
gesture: GestureData;
selected: boolean;
onSelectRow: () => void;
onRecord: () => void;
onRecord: (recordingOptions: RecordingOptions) => void;
showHints: boolean;
newRecordingId?: number;
clearNewRecordingId: () => void;
Expand Down
42 changes: 16 additions & 26 deletions src/components/LiveGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Box, HStack, Icon, Text } from "@chakra-ui/react";
import { useSize } from "@chakra-ui/react-use-size";
import { useEffect, useMemo, useRef, useState } from "react";
import { AccelerometerDataEvent } from "@microbit/microbit-connection";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { RiArrowDropLeftFill } from "react-icons/ri";
import { SmoothieChart, TimeSeries } from "smoothie";
import { useConnectActions } from "../connect-actions-hooks";
import { useConnectionStage } from "../connection-stage-hooks";
import { AccelerometerDataEvent } from "@microbit/microbit-connection";
import { ConnectionStatus } from "../connect-status-hooks";
import { RiArrowDropLeftFill } from "react-icons/ri";
import React from "react";
import { useConnectionStage } from "../connection-stage-hooks";
import { LabelConfig, getUpdatedLabelConfig } from "../live-graph-label-config";
import { useStore } from "../store";

Expand Down Expand Up @@ -85,30 +84,21 @@ const LiveGraph = () => {
}
}, [chart, isConnected, status]);

// Draw on graph to display that users are recording
// Ideally we'd do this without timing the recording again!
const [isTimingRecording, setIsTimingRecording] = useState<boolean>(false);
const dataWindow = useStore((s) => s.dataWindow);
// Draw on graph to display that users are recording.
const isRecording = useStore((s) => s.isRecording);
useEffect(() => {
if (isRecording && !isTimingRecording) {
{
// Set the start recording line
const now = new Date().getTime();
recordLines.append(now - 1, -2, false);
recordLines.append(now, 2.3, false);
setIsTimingRecording(true);
}

setTimeout(() => {
// Set the end recording line
const now = new Date().getTime();
recordLines.append(now - 1, 2.3, false);
recordLines.append(now, -2, false);
setIsTimingRecording(false);
}, dataWindow.duration);
if (isRecording) {
// Set the start recording line
const now = new Date().getTime();
Copy link

@microbit-matt-hillsdon microbit-matt-hillsdon Nov 1, 2024

Choose a reason for hiding this comment

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

I'd feel better about this if this could get the timestamps from the code that controls the recording.
Maybe we have a way to listen for recording events that include timestamps rather than using React state.

const recorder = useRecorder()
useEffect(() => {
  // With clean-up omitted, events carry a timestamp
  recorder.addEventListener("recordingstart", ...)
  recorder.addEventListener("recordingstop", ...)
}, [recorder]);

We can look at this in a follow up PR.

recordLines.append(now - 1, -2, false);
recordLines.append(now, 2.3, false);
} else {
// Set the end recording line
const now = new Date().getTime();
recordLines.append(now - 1, 2.3, false);
recordLines.append(now, -2, false);
}
}, [isTimingRecording, recordLines, isRecording, dataWindow.duration]);
}, [isRecording, recordLines]);

const [labelConfigs, setLabelConfigs] =
useState<LabelConfig[]>(initialLabelConfigs);
Expand Down
Loading
Loading