diff --git a/features.json b/features.json index 9b18c3739..c81b85e04 100644 --- a/features.json +++ b/features.json @@ -5,5 +5,7 @@ "makecode": true, "liveGraphInputValues": true, "recordingScrubberValues": true, - "modelValidation": true + "modelValidation": true, + "modelSettings": true, + "fingerprint": true } diff --git a/src/App.svelte b/src/App.svelte index 0d35998d5..b7b4760d5 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -30,7 +30,6 @@ import Router from './router/Router.svelte'; import { Feature, getFeature } from './lib/FeatureToggles'; import { welcomeLog } from './lib/utils/Logger'; - import { DeviceRequestStates, state } from './lib/stores/Stores'; import MediaQuery from './components/layout/MediaQuery.svelte'; import BottomBarMenuView from './components/layout/BottomBarMenuView.svelte'; import CookieBanner from './components/features/cookie-bannner/CookieBanner.svelte'; @@ -39,11 +38,17 @@ import OverlayView from './components/layout/OverlayView.svelte'; import SideBarMenuView from './components/layout/SideBarMenuView.svelte'; import PageContentView from './components/layout/PageContentView.svelte'; + import { stores } from './lib/stores/Stores'; + import { DeviceRequestStates } from './lib/domain/Devices'; + import { isLoading } from './lib/stores/ApplicationState'; + + const devices = stores.getDevices(); + welcomeLog(); if (CookieManager.isReconnectFlagSet()) { - $state.offerReconnect = true; - $state.reconnectState = DeviceRequestStates.INPUT; + $devices.offerReconnect = true; + $devices.reconnectState = DeviceRequestStates.INPUT; CookieManager.unsetReconnectFlag(); } @@ -56,7 +61,7 @@ {:else} - {#if $state.isLoading} + {#if $isLoading}
diff --git a/src/StaticConfiguration.ts b/src/StaticConfiguration.ts index 5aa3aa920..cb9bf868b 100644 --- a/src/StaticConfiguration.ts +++ b/src/StaticConfiguration.ts @@ -148,5 +148,10 @@ class StaticConfiguration { * Whether the knn model should be normalized by default */ public static readonly knnNormalizedDefault = false; + + /** + * Whether fingerprinting should be enabled by default + */ + public static readonly enableFingerprintByDefault: boolean = false; } export default StaticConfiguration; diff --git a/src/__tests__/csv/csv.test.ts b/src/__tests__/csv/csv.test.ts new file mode 100644 index 000000000..d864626eb --- /dev/null +++ b/src/__tests__/csv/csv.test.ts @@ -0,0 +1,116 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2023-2025, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { writable } from 'svelte/store'; +import type { RecordingData } from '../../lib/domain/RecordingData'; +import Gesture from '../../lib/domain/stores/gesture/Gesture'; +import type { PersistedGestureData } from '../../lib/domain/stores/gesture/Gestures'; +import type GestureConfidence from '../../lib/domain/stores/gesture/GestureConfidence'; +import { + serializeGestureRecordingsToCSV, + serializeRecordingToCsvWithoutGestureName, +} from '../../lib/utils/CSVUtils'; + +describe('CSV Test', () => { + // A crude way to enforce direction of dependencies, inspired by ArchUnit for java + test('Convert recording', () => { + const input: RecordingData = { + ID: 123, + labels: ['x', 'y', 'z'], + samples: [ + { + vector: [1, 2, 3], + }, + { + vector: [4, 5, 6], + }, + { + vector: [7, 8, 9], + }, + ], + }; + const data = writable({ + recordings: [input], + name: 'Test;Gesture', + } as PersistedGestureData); + const confidence = writable({}) as unknown as GestureConfidence; + const gesture: Gesture = new Gesture(data, confidence, () => void 0); + const result = serializeGestureRecordingsToCSV([gesture]); + expect(result).toBe( + 'gesture;sample;x;y;z\nTest\\;Gesture;0;1;2;3\nTest\\;Gesture;1;4;5;6\nTest\\;Gesture;2;7;8;9', + ); + }); + + test('Convert multiple gestures', () => { + const input1: RecordingData = { + ID: 123, + labels: ['x', 'y', 'z'], + samples: [ + { + vector: [1, 2, 3], + }, + { + vector: [4, 5, 6], + }, + ], + }; + const input2: RecordingData = { + ID: 456, + labels: ['x', 'y', 'z'], + samples: [ + { + vector: [7, 8, 9], + }, + { + vector: [10, 11, 12], + }, + ], + }; + const data1 = writable({ + recordings: [input1], + name: 'Gesture1', + } as PersistedGestureData); + const data2 = writable({ + recordings: [input2], + name: 'Gesture2', + } as PersistedGestureData); + const confidence = writable({}) as unknown as GestureConfidence; + const gesture1: Gesture = new Gesture(data1, confidence, () => void 0); + const gesture2: Gesture = new Gesture(data2, confidence, () => void 0); + const result = serializeGestureRecordingsToCSV([gesture1, gesture2]); + expect(result).toBe( + 'gesture;sample;x;y;z\n' + + 'Gesture1;0;1;2;3\n' + + 'Gesture1;1;4;5;6\n' + + 'Gesture2;0;7;8;9\n' + + 'Gesture2;1;10;11;12', + ); + }); + + test('Serialize recording without gesture name (with headers)', () => { + const input: RecordingData = { + ID: 123, + labels: ['x', 'y', 'z'], + samples: [ + { + vector: [1, 2, 3], + }, + { + vector: [4, 5, 6], + }, + { + vector: [7, 8, 9], + }, + ], + }; + + const result = serializeRecordingToCsvWithoutGestureName(input); + expect(result).toBe('sample;x;y;z\n' + '0;1;2;3\n' + '1;4;5;6\n' + '2;7;8;9'); + }); +}); diff --git a/src/__tests__/mocks/mlmodel/TestMLModelTrainer.ts b/src/__tests__/mocks/mlmodel/TestMLModelTrainer.ts index b32b8e397..faffbf0a0 100644 --- a/src/__tests__/mocks/mlmodel/TestMLModelTrainer.ts +++ b/src/__tests__/mocks/mlmodel/TestMLModelTrainer.ts @@ -3,12 +3,17 @@ * * SPDX-License-Identifier: MIT */ +import type { ModelInfo } from '../../../lib/domain/ModelRegistry'; +import ModelRegistry from '../../../lib/domain/ModelRegistry'; import type { ModelTrainer } from '../../../lib/domain/ModelTrainer'; import type { TrainingDataRepository } from '../../../lib/domain/TrainingDataRepository'; import TestMLModel from './TestMLModel'; class TestMLModelTrainer implements ModelTrainer { constructor(private numberOfGestures: number) {} + getModelInfo(): ModelInfo { + return ModelRegistry.NeuralNetwork; + } public trainModel(trainingData: TrainingDataRepository): Promise { return Promise.resolve(new TestMLModel(this.numberOfGestures)); } diff --git a/src/__viteBuildVariants__/ml-machine-simple/features.json b/src/__viteBuildVariants__/ml-machine-simple/features.json index 973184add..4ff547fdf 100644 --- a/src/__viteBuildVariants__/ml-machine-simple/features.json +++ b/src/__viteBuildVariants__/ml-machine-simple/features.json @@ -5,5 +5,7 @@ "makecode": false, "liveGraphInputValues": false, "recordingScrubberValues": false, - "modelValidation": false + "modelValidation": false, + "modelSettings": false, + "fingerprint": false } diff --git a/src/__viteBuildVariants__/ml-machine/features.json b/src/__viteBuildVariants__/ml-machine/features.json index d8623ddc3..a9bd93e99 100644 --- a/src/__viteBuildVariants__/ml-machine/features.json +++ b/src/__viteBuildVariants__/ml-machine/features.json @@ -5,5 +5,7 @@ "makecode": true, "liveGraphInputValues": true, "recordingScrubberValues": true, - "modelValidation": true + "modelValidation": true, + "modelSettings": true, + "fingerprint": true } diff --git a/src/__viteBuildVariants__/unbranded/features.json b/src/__viteBuildVariants__/unbranded/features.json index 9b18c3739..c81b85e04 100644 --- a/src/__viteBuildVariants__/unbranded/features.json +++ b/src/__viteBuildVariants__/unbranded/features.json @@ -5,5 +5,7 @@ "makecode": true, "liveGraphInputValues": true, "recordingScrubberValues": true, - "modelValidation": true + "modelValidation": true, + "modelSettings": true, + "fingerprint": true } diff --git a/src/assets/messages/ui.da.json b/src/assets/messages/ui.da.json index cf724cbc2..8ff2b6301 100644 --- a/src/assets/messages/ui.da.json +++ b/src/assets/messages/ui.da.json @@ -50,6 +50,7 @@ "content.data.noData.exampleName.shake": "Ryste", "content.data.noData.exampleName.still": "Stille", "content.data.noData.exampleName.circle": "Cirkel", + "content.data.tooltip.remove": "Fjern", "content.trainer.failure.header": "Træning mislykkedes", "content.trainer.failure.body": "Træningen resulterede ikke i en brugbar model. Grunden til dette ligger sandsynligvis i dataet. Hvis dataet i forskellige klasser minder for meget om hinanden, kan dette resultere i nogle forskellige problemer i træningsprocessen, der ikke gør det muligt at træne modellen ordentligt.", "content.trainer.failure.todo": "Gå tilbage til datasiden og ændr i din data.", @@ -111,8 +112,9 @@ "content.validation.infobox.classesContent": "Her kan du se de klasser, du har defineret på datasiden.", "content.validation.noGestures.title": "Ingen klasser...", "content.validation.noGestures.description": "Du har endnu ikke tilføjet nogen klasser. Gå til datatrinnet i menuen til venstre og tilføj nogle.", - "content.validation.tutorial.title": "Validierungssätze", - "content.validation.tutorial.description": "Erstelle einen separaten Datensatz, um die Leistung deines Modells zu bewerten. Das Modell wird nicht mit diesen Daten trainiert.", + "content.validation.tutorial.title": "Valideringssæt", + "content.validation.tutorial.description": "Opret et separat datasæt til at evaluere dit models ydeevne. Modellen vil ikke blive trænet på disse data.", + "content.validation.tutorial.trainmodelfirst": "Zuerst ein Modell trainieren", "footer.connectButtonNotConnected": "Tilslut din BBC micro:bit", "footer.disconnectButton": "Frakobl", "footer.helpHeader": "Live graf", diff --git a/src/assets/messages/ui.de.json b/src/assets/messages/ui.de.json index f5fb6bd88..37f092b7a 100644 --- a/src/assets/messages/ui.de.json +++ b/src/assets/messages/ui.de.json @@ -50,6 +50,7 @@ "content.data.noData.exampleName.shake": "Schütteln", "content.data.noData.exampleName.still": "Still", "content.data.noData.exampleName.circle": "Kreis", + "content.data.tooltip.remove": "Entfernen", "content.trainer.failure.header": "Training fehlgeschlagen", "content.trainer.failure.body": "Das Training führte nicht zu einem brauchbaren Modell. Der Grund dafür sind höchstwahrscheinlich die für das Training verwendeten Daten. Wenn die Daten für verschiedene Klassen zu ähnlich sind, kann dies zu Problemen im Trainingsprozess führen.", "content.trainer.failure.todo": "Gehe zur Datenseite zurück und ändere die Daten.", @@ -111,8 +112,9 @@ "content.validation.infobox.classesContent": "Hier siehst du die Klassen, die du auf der Datenseite definiert hast.", "content.validation.noGestures.title": "Keine Gesten...", "content.validation.noGestures.description": "Du hast noch keine Gesten hinzugefügt. Gehe zum Schritt „Daten“ im Menü auf der linken Seite und füge welche hinzu.", - "content.validation.tutorial.title": "Valideringssæt", - "content.validation.tutorial.description": "Opret et separat datasæt til at evaluere dit models ydeevne. Modellen vil ikke blive trænet på disse data.", + "content.validation.tutorial.title": "Validierungssätze", + "content.validation.tutorial.description": "Erstelle einen separaten Datensatz, um die Leistung deines Modells zu bewerten. Das Modell wird nicht mit diesen Daten trainiert.", + "content.validation.tutorial.trainmodelfirst": "Zuerst ein Modell trainieren", "footer.connectButtonNotConnected": "Verbinde deinen micro:bit", "footer.disconnectButton": "Trennen", "footer.helpHeader": "Live-Diagramm", diff --git a/src/assets/messages/ui.en.json b/src/assets/messages/ui.en.json index 7f15fe567..ea0ec33ab 100644 --- a/src/assets/messages/ui.en.json +++ b/src/assets/messages/ui.en.json @@ -50,6 +50,7 @@ "content.data.noData.exampleName.shake": "Shake", "content.data.noData.exampleName.still": "Still", "content.data.noData.exampleName.circle": "Circle", + "content.data.tooltip.remove": "Remove", "content.trainer.failure.header": "Training Failed", "content.trainer.failure.body": "The training did not result in a usable model. The reason for this is most likely the data used for training. If the data for different classes are too similar, this can result in issues in the training process.", "content.trainer.failure.todo": "Return to the data page and change your data.", @@ -113,6 +114,7 @@ "content.validation.noGestures.description": "You haven't added any gestures yet. Go to the data step in the menu to the left and add some.", "content.validation.tutorial.title": "Validation sets", "content.validation.tutorial.description": "Create a separate dataset to evaluate the performance of your model. The model will not be trained on this data.", + "content.validation.tutorial.trainmodelfirst": "Train a model first", "footer.connectButtonNotConnected": "Connect your micro:bit", "footer.disconnectButton": "Disconnect", "footer.helpHeader": "Live graph", diff --git a/src/components/features/OutdatedMicrobitWarning.svelte b/src/components/features/OutdatedMicrobitWarning.svelte index f4a20270d..2f18e41d9 100644 --- a/src/components/features/OutdatedMicrobitWarning.svelte +++ b/src/components/features/OutdatedMicrobitWarning.svelte @@ -14,8 +14,8 @@ import StaticConfiguration from '../../StaticConfiguration'; import Microbits from '../../lib/microbit-interfacing/Microbits'; import { HexOrigin } from '../../lib/microbit-interfacing/HexOrigin'; - import { DeviceRequestStates } from '../../lib/stores/Stores'; import StandardButton from '../ui/buttons/StandardButton.svelte'; + import { DeviceRequestStates } from '../../lib/domain/Devices'; let hasBeenClosed = false; export let targetRole: 'INPUT' | 'OUTPUT'; let showMakeCodeUpdateMessage = diff --git a/src/components/features/PleaseConnect.svelte b/src/components/features/PleaseConnect.svelte index eebf28970..efd838f37 100644 --- a/src/components/features/PleaseConnect.svelte +++ b/src/components/features/PleaseConnect.svelte @@ -16,10 +16,10 @@
-

+

{$t('menu.trainer.notConnected1')}

-

+

{$t('menu.trainer.notConnected2')}

diff --git a/src/components/features/ReconnectPrompt.svelte b/src/components/features/ReconnectPrompt.svelte index 8d3681d78..844d2025f 100644 --- a/src/components/features/ReconnectPrompt.svelte +++ b/src/components/features/ReconnectPrompt.svelte @@ -10,12 +10,15 @@ import { btPatternInput, btPatternOutput } from '../../lib/stores/connectionStore'; import Microbits from '../../lib/microbit-interfacing/Microbits'; import { MBSpecs } from 'microbyte'; - import { DeviceRequestStates, state } from '../../lib/stores/Stores'; import StandardButton from '../ui/buttons/StandardButton.svelte'; + import { stores } from '../../lib/stores/Stores'; + import { DeviceRequestStates } from '../../lib/domain/Devices'; + + const devices = stores.getDevices(); let reconnectText: string; let reconnectButtonText: string; - state.subscribe(s => { + devices.subscribe(s => { if (s.reconnectState === DeviceRequestStates.INPUT) { reconnectText = $t('popup.disconnectedWarning.input'); reconnectButtonText = $t('popup.disconnectedWarning.reconnectButton.input'); @@ -26,7 +29,7 @@ }); // When disconnected by lost connection, offer the option to attempt to reconnect let hideReconnectMessageAfterTimeout = false; - state.subscribe(s => { + devices.subscribe(s => { if (s.offerReconnect) { hideReconnectMessageAfterTimeout = true; } @@ -47,7 +50,7 @@ }; void connect().then(() => { - $state.offerReconnect = false; + $devices.offerReconnect = false; }); }; @@ -59,7 +62,7 @@

{reconnectText}

- reconnect($state.reconnectState)}> + reconnect($devices.reconnectState)}> {reconnectButtonText}
diff --git a/src/components/features/bottom/BottomPanel.svelte b/src/components/features/bottom/BottomPanel.svelte index 6d71055a1..27c53142c 100644 --- a/src/components/features/bottom/BottomPanel.svelte +++ b/src/components/features/bottom/BottomPanel.svelte @@ -10,13 +10,19 @@ import LiveGraphInformationSection from './LiveGraphInformationSection.svelte'; import { tr } from '../../../i18n'; import ConnectDialogContainer from '../../features/connection-prompt/ConnectDialogContainer.svelte'; - import { state } from '../../../lib/stores/Stores'; import { startConnectionProcess } from '../../../lib/stores/connectDialogStore'; import Microbits from '../../../lib/microbit-interfacing/Microbits'; import View3DLive from '../3d-inspector/View3DLive.svelte'; import BaseDialog from '../../ui/dialogs/BaseDialog.svelte'; import MicrobitLiveGraph from '../graphs/MicrobitLiveGraph.svelte'; import StandardButton from '../../ui/buttons/StandardButton.svelte'; + import { stores } from '../../../lib/stores/Stores'; + import LiveDataFingerprint from './LiveDataFingerprint.svelte'; + import { Feature, hasFeature } from '../../../lib/FeatureToggles'; + import Switch from '../../ui/Switch.svelte'; + + const devices = stores.getDevices(); + const enableFingerprint = stores.getEnableFingerprint(); let componentWidth: number; let connectDialogReference: ConnectDialogContainer; @@ -34,15 +40,20 @@ }; let isLive3DOpen = false; + + $: isFingerprintEnabled = $enableFingerprint && hasFeature(Feature.FINGERPRINT); + const toggleEnabled = (event: any) => { + enableFingerprint.set(event.target.checked); + };
+ class:bg-gray-300={$devices.isInputAssigned && !$devices.isInputReady}> - {#if !$state.isInputAssigned} + {#if !$devices.isInputAssigned}
@@ -53,9 +64,9 @@
- +
- {#if $state.isInputInitializing} + {#if $devices.isInputInitializing}
@@ -64,23 +75,41 @@
{/if}
+ class="h-full p-0 m-0 absolute top-0 left-0 right-45 border-r border-solid border-black border-opacity-60">
-
+
-
(isLive3DOpen = true)}> - + + +
+ {#if isFingerprintEnabled} +
+

Fingerprint:

+ enableFingerprint.set(e.detail.checked)} /> +
+
+ +
+ {/if} + +
(isLive3DOpen = true)}> + +
+ (isLive3DOpen = false)}>
import { tr } from '../../../i18n'; - import { state, stores } from '../../../lib/stores/Stores'; + import { stores } from '../../../lib/stores/Stores'; import TypingUtils from '../../../lib/TypingUtils'; import StandardButton from '../../ui/buttons/StandardButton.svelte'; + const devices = stores.getDevices(); export let onOutputDisconnectButtonClicked: () => void; export let onOutputConnectButtonClicked: () => void; @@ -19,33 +20,33 @@
- {#if $model.hasModel || $model.isTraining || $state.isOutputConnected} - {#if $state.isOutputAssigned} + {#if $model.hasModel || $model.isTraining || $devices.isOutputConnected} + {#if $devices.isOutputAssigned} - {#if !$state.isOutputConnected || $state.isOutputReady} + {#if !$devices.isOutputConnected || $devices.isOutputReady} - + {$tr('menu.model.disconnect')} {:else} - + loading {/if} {:else} - + {$tr('menu.model.connectOutputButton')} {/if} {/if}
- {#if !$state.isInputConnected || $state.isInputReady} + {#if !$devices.isInputConnected || $devices.isInputReady} - {$tr('footer.disconnectButton')} {:else} - + loading {/if} diff --git a/src/components/features/bottom/LiveDataFingerprint.svelte b/src/components/features/bottom/LiveDataFingerprint.svelte new file mode 100644 index 000000000..26e123da8 --- /dev/null +++ b/src/components/features/bottom/LiveDataFingerprint.svelte @@ -0,0 +1,52 @@ + + + +
+ {#if !!filteredNormalizedInput} + + {/if} +
diff --git a/src/components/features/bottom/LiveGraphInformationSection.svelte b/src/components/features/bottom/LiveGraphInformationSection.svelte index 50f509db8..2eb01f513 100644 --- a/src/components/features/bottom/LiveGraphInformationSection.svelte +++ b/src/components/features/bottom/LiveGraphInformationSection.svelte @@ -7,9 +7,10 @@
@@ -23,8 +24,8 @@

Live

+ class:text-red-500={$devices.isInputReady} + class:text-gray-500={!$devices.isInputReady}> •

{#if hasFeature(Feature.LIVE_GRAPH_INPUT_VALUES)} diff --git a/src/components/features/connection-prompt/ConnectDialogContainer.svelte b/src/components/features/connection-prompt/ConnectDialogContainer.svelte index ed03dc105..7f930ec0d 100644 --- a/src/components/features/connection-prompt/ConnectDialogContainer.svelte +++ b/src/components/features/connection-prompt/ConnectDialogContainer.svelte @@ -21,7 +21,7 @@ import { btPatternInput, btPatternOutput } from '../../../lib/stores/connectionStore'; import BrokenFirmwareDetected from './usb/BrokenFirmwareDetected.svelte'; import { MBSpecs } from 'microbyte'; - import { DeviceRequestStates } from '../../../lib/stores/Stores'; + import { DeviceRequestStates } from '../../../lib/domain/Devices'; let flashProgress = 0; diff --git a/src/components/features/connection-prompt/bluetooth/BluetoothConnectDialog.svelte b/src/components/features/connection-prompt/bluetooth/BluetoothConnectDialog.svelte index 1df306232..cf10fe99f 100644 --- a/src/components/features/connection-prompt/bluetooth/BluetoothConnectDialog.svelte +++ b/src/components/features/connection-prompt/bluetooth/BluetoothConnectDialog.svelte @@ -19,10 +19,12 @@ import StaticConfiguration from '../../../../StaticConfiguration'; import Logger from '../../../../lib/utils/Logger'; import { MBSpecs } from 'microbyte'; - import { DeviceRequestStates, state } from '../../../../lib/stores/Stores'; import StandardButton from '../../../ui/buttons/StandardButton.svelte'; + import { DeviceRequestStates } from '../../../../lib/domain/Devices'; + import { stores } from '../../../../lib/stores/Stores'; + + const devices = stores.getDevices(); - // callbacks export let deviceState: DeviceRequestStates; export let onBluetoothConnected: () => void; @@ -101,7 +103,7 @@ onMount(() => { // Resets the bluetooth connection prompt for cancelled device requests - $state.requestDeviceWasCancelled = false; + $devices.requestDeviceWasCancelled = false; }); const handleSearchWithoutName = () => { @@ -114,7 +116,7 @@ {$t('popup.connectMB.bluetooth.heading')} - {#if $state.requestDeviceWasCancelled && !isConnecting} + {#if $devices.requestDeviceWasCancelled && !isConnecting}

{$t('popup.connectMB.bluetooth.cancelledConnection')}

{$t('popup.connectMB.bluetooth.cancelledConnection.noNameDescription')} diff --git a/src/components/features/datacollection/Gesture.svelte b/src/components/features/datacollection/Gesture.svelte index 550c11a01..dc11668e2 100644 --- a/src/components/features/datacollection/Gesture.svelte +++ b/src/components/features/datacollection/Gesture.svelte @@ -14,13 +14,13 @@ MicrobitInteractions, chosenGesture, } from '../../../lib/stores/uiStore'; - import Recording from '../../ui/Recording.svelte'; + import Recording from '../../ui/recording/Recording.svelte'; import { t } from '../../../i18n'; import ImageSkeleton from '../../ui/skeletonloading/ImageSkeleton.svelte'; import GestureCard from '../../ui/Card.svelte'; import StaticConfiguration from '../../../StaticConfiguration'; import Gesture from '../../../lib/domain/stores/gesture/Gesture'; - import { state, stores } from '../../../lib/stores/Stores'; + import { stores } from '../../../lib/stores/Stores'; import type { RecordingData } from '../../../lib/domain/RecordingData'; import { startRecording } from '../../../lib/utils/Recording'; import GestureDot from '../../ui/GestureDot.svelte'; @@ -28,11 +28,12 @@ export let onNoMicrobitSelect: () => void; export let gesture: Gesture; + const devices = stores.getDevices(); const gestures = stores.getGestures(); - $: liveData = $stores.liveData; const defaultNewName = $t('content.data.classPlaceholderNewClass'); const recordingDuration = StaticConfiguration.recordingDuration; + const enableFingerprint = stores.getEnableFingerprint(); let isThisRecording = false; @@ -86,7 +87,7 @@ // If gesture is already selected, the selection is removed. // If bluetooth is not connected, open connection prompt by calling callback function selectClicked(): void { - if (!$state.isInputConnected) { + if (!$devices.isInputConnected) { chosenGesture.update(gesture => { gesture = null; return gesture; @@ -218,7 +219,12 @@

{#each $gesture.recordings as recording (String($gesture.ID) + String(recording.ID))} - + {/each}
diff --git a/src/components/features/graphs/LiveGraph.svelte b/src/components/features/graphs/LiveGraph.svelte index b18b379ab..c1928e386 100644 --- a/src/components/features/graphs/LiveGraph.svelte +++ b/src/components/features/graphs/LiveGraph.svelte @@ -9,11 +9,11 @@ import { type Unsubscriber } from 'svelte/store'; import { SmoothieChart, TimeSeries } from 'smoothie'; import DimensionLabels from './DimensionLabels.svelte'; - import { state, stores } from '../../../lib/stores/Stores'; import type { LiveData } from '../../../lib/domain/stores/LiveData'; import type { LiveDataVector } from '../../../lib/domain/stores/LiveDataVector'; import StaticConfiguration from '../../../StaticConfiguration'; import SmoothedLiveData from '../../../lib/livedata/SmoothedLiveData'; + import { stores } from '../../../lib/stores/Stores'; /** * TimesSeries, but with the data array added. @@ -32,6 +32,7 @@ let axisColors = StaticConfiguration.graphColors; const highlightedAxes = stores.getHighlightedAxes(); + const devices = stores.getDevices(); // Smoothes real-time data by using the 3 most recent data points let smoothedLiveData = new SmoothedLiveData(liveData, 3); @@ -98,7 +99,7 @@ const model = classifier.getModel(); $: { if (chart !== undefined) { - if ($state.isInputReady) { + if ($devices.isInputReady) { if (!$model.isTraining) { chart.start(); } else { @@ -114,7 +115,7 @@ // The jagged edges problem is caused by repeating the recordingStarted function. // We will simply block the recording from starting, while it's recording let blockRecordingStart = false; - $: recordingStarted($state.isRecording); + $: recordingStarted($devices.isRecording); // Function to clearly diplay the area in which users are recording function recordingStarted(isRecording: boolean): void { @@ -135,15 +136,15 @@ }, StaticConfiguration.recordingDuration); } - // When state changes, update the state of the canvas + // When devices changes, update the devices of the canvas $: { - const isConnected = $state.isInputReady; + const isConnected = $devices.isInputReady; updateCanvas(isConnected); } let unsubscribeFromData: Unsubscriber | undefined; - // If state is connected. Start updating the graph whenever there is new data + // If devices is connected. Start updating the graph whenever there is new data // From the Micro:Bit function updateCanvas(isConnected: boolean) { if (isConnected || !unsubscribeFromData) { @@ -180,7 +181,7 @@ {#key cnt}