From 753d01a05f90501c9804d35d0c5c2d990dc3efd8 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Tue, 25 Jun 2024 14:25:34 +0100 Subject: [PATCH 001/172] Skeleton with messages/images `npm run ci` now works --- .eslintrc.cjs | 74 +- .github/workflows/build.yml | 4 +- .prettierrc.cjs | 6 +- azure-pipelines.yml | 15 - babel.config.cjs | 5 - index.html | 4 +- package-lock.json | 13218 ++++++++++------ package.json | 86 +- public/css/all.min.css | 5 - public/css/global.css | 76 - public/models/license.txt | 11 - public/models/microbit.bin | Bin 2409448 -> 0 bytes public/models/microbit.gltf | 130 - public/sounds/congratulations.wav | Bin 44194 -> 0 bytes public/sounds/high_pitch.wav | Bin 17568 -> 0 bytes public/sounds/huge_mistake.wav | Bin 88048 -> 0 bytes public/sounds/looser.wav | Bin 176460 -> 0 bytes public/sounds/low_pitch.wav | Bin 43052 -> 0 bytes public/sounds/mistake.mp3 | Bin 7853 -> 0 bytes public/webfonts/fa-brands-400.eot | Bin 134294 -> 0 bytes public/webfonts/fa-brands-400.svg | 3717 ----- public/webfonts/fa-brands-400.ttf | Bin 133988 -> 0 bytes public/webfonts/fa-brands-400.woff | Bin 89988 -> 0 bytes public/webfonts/fa-brands-400.woff2 | Bin 76736 -> 0 bytes public/webfonts/fa-regular-400.eot | Bin 34034 -> 0 bytes public/webfonts/fa-regular-400.svg | 801 - public/webfonts/fa-regular-400.ttf | Bin 33736 -> 0 bytes public/webfonts/fa-regular-400.woff | Bin 16276 -> 0 bytes public/webfonts/fa-regular-400.woff2 | Bin 13224 -> 0 bytes public/webfonts/fa-solid-900.eot | Bin 203030 -> 0 bytes public/webfonts/fa-solid-900.svg | 5034 ------ public/webfonts/fa-solid-900.ttf | Bin 202744 -> 0 bytes public/webfonts/fa-solid-900.woff | Bin 101648 -> 0 bytes public/webfonts/fa-solid-900.woff2 | Bin 78268 -> 0 bytes src/App.svelte | 129 - src/App.tsx | 3 + src/StaticConfiguration.ts | 57 - src/__tests__/cookie.test.ts | 31 - src/__tests__/datafunctions.test.ts | 20 - src/__tests__/default-build-config.test.ts | 18 - .../fixtures/gesture-data-bad-labels.json | 1024 -- src/__tests__/fixtures/gesture-data.json | 1024 -- .../fixtures/test-data-shake-still.json | 573 - src/__tests__/getPrediction.test.ts | 87 - src/__tests__/i18n.test.ts | 78 - src/__tests__/license-identifiers.test.ts | 98 - src/__tests__/microbit-usb-conection.test.ts | 52 - src/__tests__/ml.test.ts | 98 - .../mock-bluetooth-accelerometer-service.ts | 218 - .../mock-bluetooth-gattcharacteristic.ts | 119 - .../mocks/mock-bluetooth-gattservice.ts | 122 - .../mocks/mock-bluetooth-info-service.ts | 234 - src/__tests__/mocks/mock-bluetooth.ts | 19 - .../mocks/mock-microbit-bluetooth.ts | 217 - src/__tests__/mocks/mock-usb.ts | 160 - src/__tests__/patternMatrixTransforms.test.ts | 123 - src/__tests__/serialProtocol.test.ts | 391 - src/__tests__/smoothenXYZData.test.ts | 34 - src/__tests__/translations.test.ts | 45 - src/__tests__/unusedTranslations.test.ts | 95 - .../ml-machine/windi.config.js | 34 - .../3d-inspector/RecordingInspector.svelte | 37 - src/components/3d-inspector/View3D.svelte | 31 - src/components/3d-inspector/View3DLive.svelte | 26 - .../3d-inspector/View3DUnsafe.svelte | 212 - src/components/3d-inspector/View3DUtility.ts | 158 - src/components/DialogHeading.svelte | 7 - src/components/FrontPageContentTile.svelte | 10 - src/components/Gesture.svelte | 388 - src/components/GestureTilePart.svelte | 26 - src/components/HtmlFormattedMessage.svelte | 83 - src/components/HtmlFormattedMessage.test.ts | 91 - src/components/IconButton.svelte | 52 - src/components/LinkOverlay.svelte | 54 - src/components/LinkOverlayContainer.svelte | 16 - src/components/LoadingBar.svelte | 12 - src/components/LoadingBlobs.svelte | 69 - src/components/LoadingSpinner.svelte | 9 - src/components/MediaQuery.svelte | 45 - src/components/MenuTransition.svelte | 21 - src/components/NewGestureButton.svelte | 30 - src/components/PatternBox.svelte | 60 - src/components/PatternColumnInput.svelte | 46 - src/components/PatternMatrix.svelte | 112 - src/components/PleaseConnectFirst.svelte | 48 - src/components/PrototypeVersionWarning.svelte | 16 - src/components/ReconnectHelp.svelte | 135 - src/components/Recording.svelte | 31 - src/components/ResourcePageLayout.svelte | 87 - src/components/StandardButton.svelte | 69 - src/components/StartResumeActions.svelte | 98 - src/components/TrainModelFirstTitle.svelte | 50 - src/components/TrainingStatusSection.svelte | 23 - src/components/bottom/BottomPanel.svelte | 69 - .../bottom/ConnectedLiveGraphButtons.svelte | 75 - .../bottom/LiveGraphInformationSection.svelte | 10 - .../ConnectDialogContainer.svelte | 405 - .../ConnectSameDialog.svelte | 43 - .../WebBluetoothTryAgain.svelte | 28 - .../connection-prompt/WebUsbTryAgain.svelte | 47 - .../WhatYouWillNeedDialog.svelte | 69 - .../connection-prompt/arrows/ArrowOne.svelte | 21 - .../connection-prompt/arrows/ArrowTwo.svelte | 27 - .../bluetooth/BluetoothConnectDialog.svelte | 74 - .../BluetoothConnectingDialog.svelte | 21 - .../bluetooth/ConnectBatteryDialog.svelte | 44 - .../bluetooth/ConnectCableDialog.svelte | 49 - .../SelectMicrobitDialogBluetooth.svelte | 52 - .../bluetooth/StartBluetoothDialog.svelte | 47 - .../radio/ConnectingMicrobits.svelte | 21 - .../radio/StartRadioDialog.svelte | 47 - .../usb/BrokenFirmwareDetected.svelte | 60 - .../usb/DownloadingDialog.svelte | 33 - .../usb/SelectMicrobitDialogUsb.svelte | 51 - .../usb/manual/ManualInstallTutorial.svelte | 84 - src/components/control-bar/ControlBar.svelte | 10 - .../control-bar-items/AboutDialog.svelte | 71 - .../ExpandableControlBarMenu.svelte | 29 - .../control-bar-items/HelpMenu.svelte | 90 - .../control-bar-items/LanguageDialog.svelte | 42 - .../control-bar-items/MenuItem.svelte | 29 - .../control-bar-items/MenuItems.svelte | 25 - .../SelectLanguageControlBarDropdown.svelte | 15 - .../control-bar-items/SettingsMenu.svelte | 55 - .../datacollection/DataPageControlBar.svelte | 42 - .../datacollection/DataPageMenu.svelte | 56 - .../RecordInformationContent.svelte | 37 - .../dialogs/AppVersionRedirectDialog.svelte | 52 - src/components/dialogs/BaseDialog.svelte | 50 - .../dialogs/CompatibilityWarningDialog.svelte | 37 - .../dialogs/PerformanceWarningDialog.svelte | 46 - src/components/dialogs/SignUpDialog.svelte | 113 - src/components/dialogs/StandardDialog.svelte | 128 - .../UnsupportedMicrobitWarningDialog.svelte | 70 - src/components/graphs/DimensionLabels.svelte | 93 - src/components/graphs/LiveGraph.svelte | 98 - src/components/graphs/RecordingGraph.svelte | 251 - src/components/information/Information.svelte | 113 - .../InformationComponentUtility.ts | 23 - src/components/output/OutputGesture.svelte | 51 - .../output/OutputGestureStack.svelte | 288 - .../output/OutputGestureTile.svelte | 80 - src/components/output/OutputMatrix.svelte | 112 - .../output/OutputSoundSelector.svelte | 110 - src/components/output/PinSelector.svelte | 132 - src/components/output/PinSelectorUtil.ts | 10 - ...LiveDataBufferUtilizationPercentage.svelte | 20 - .../playground/PlaygroundContext.ts | 40 - .../playground/PlaygroundGestureView.svelte | 25 - .../playground/PlaygroundLog.svelte | 17 - .../AccelerometerDataSynthesizer.ts | 99 - .../inputSynthesizer/IntervalSlider.svelte | 21 - ...icrobitAccelerometerDataSynthesizer.svelte | 28 - .../inputSynthesizer/Visualizer.svelte | 61 - .../skeletonloading/ImageSkeleton.svelte | 57 - src/global.d.ts | 2 - src/i18n.ts | 76 - src/main.ts | 14 - .../domain/ClassifierInput.ts => main.tsx} | 10 +- src/menus/DataMenu.svelte | 22 - src/menus/MenuButton.svelte | 54 - src/menus/ModelMenu.svelte | 55 - src/menus/TrainingMenu.svelte | 39 - src/pages/DataPage.svelte | 148 - src/pages/GetStartedPage.svelte | 108 - src/pages/Homepage.svelte | 116 - src/pages/IntroducingToolPage.svelte | 120 - src/pages/PlaygroundPage.svelte | 120 - src/pages/filter/D3Plot.svelte | 312 - src/pages/filter/FilterPage.svelte | 57 - src/pages/filter/FilterToggler.svelte | 108 - .../NewFeaturesTile.svelte | 33 - .../WhatIsMachineLearningTile.svelte | 49 - src/pages/model/ModelPage.svelte | 22 - .../model/stackview/ModelPageStackView.svelte | 102 - .../ModelPageStackViewContent.svelte | 156 - .../model/tileview/ModelPageTileView.svelte | 108 - .../tileview/ModelPageTileViewTiles.svelte | 114 - src/pages/training/TrainingButton.svelte | 23 - src/pages/training/TrainingPage.svelte | 106 - src/placeholder.test.ts | 3 + src/router/Router.svelte | 107 - src/router/paths.ts | 54 - src/script/ControlledStorage.ts | 56 - src/script/CookieManager.ts | 37 - src/script/TypingUtils.ts | 19 - .../compatibility/CompatibilityChecker.ts | 47 - src/script/compatibility/CompatibilityList.ts | 262 - src/script/datafunctions.ts | 245 - src/script/domain/Axes.ts | 12 - src/script/domain/BindableValue.ts | 48 - src/script/domain/Classifier.ts | 51 - src/script/domain/ClassifierFactory.ts | 66 - src/script/domain/Engine.ts | 17 - src/script/domain/Filter.ts | 10 - src/script/domain/Filters.ts | 29 - src/script/domain/Gesture.ts | 131 - src/script/domain/GestureConfidence.ts | 75 - src/script/domain/Gestures.ts | 101 - src/script/domain/LiveData.ts | 14 - src/script/domain/LiveDataBuffer.ts | 90 - src/script/domain/MLModel.ts | 10 - src/script/domain/MLModelFactory.ts | 19 - src/script/domain/Model.ts | 81 - src/script/domain/ModelTrainer.ts | 20 - src/script/engine/PollingPredictorEngine.ts | 74 - src/script/environment.ts | 19 - src/script/filters/FilterWithMaths.ts | 25 - src/script/filters/MaxFilter.ts | 13 - src/script/filters/MeanFilter.ts | 14 - src/script/filters/MinFilter.ts | 14 - src/script/filters/PeaksFilter.ts | 59 - src/script/filters/RootMeanSquareFilter.ts | 14 - src/script/filters/StandardDeviationFilter.ts | 17 - src/script/filters/TotalAccFilter.ts | 14 - src/script/filters/ZeroCrossingRateFilter.ts | 23 - src/script/flags.ts | 76 - src/script/getPrediction.ts | 20 - .../livedata/MicrobitAccelerometerData.ts | 49 - src/script/microbit-interfacing/MBSpecs.ts | 353 - .../microbit-interfacing/MicrobitBluetooth.ts | 565 - .../MicrobitConnection.ts | 24 - .../microbit-interfacing/MicrobitSerial.ts | 324 - .../microbit-interfacing/MicrobitUSB.ts | 196 - src/script/microbit-interfacing/Microbits.ts | 136 - .../microbit-interfacing/change-listeners.ts | 89 - src/script/microbit-interfacing/constants.ts | 17 - .../microbit-interfacing/serialProtocol.ts | 228 - .../microbit-interfacing/state-updaters.ts | 222 - src/script/ml.ts | 349 - .../mlmodels/AccelerometerClassifierInput.ts | 27 - src/script/mlmodels/LayersMLModel.ts | 26 - src/script/mlmodels/LayersModelTrainer.ts | 64 - src/script/mlmodels/NoneMLModel.ts | 14 - src/script/navigation/Menus.ts | 36 - src/script/patternMatrixTransforms.ts | 58 - src/script/repository/ClassifierRepository.ts | 125 - src/script/repository/GestureRepository.ts | 120 - src/script/repository/Repositories.ts | 39 - src/script/smoothenXYZData.ts | 34 - src/script/stores/Stores.ts | 26 - src/script/stores/complianceStore.ts | 102 - src/script/stores/connectDialogStore.ts | 63 - src/script/stores/connectionStore.ts | 39 - src/script/stores/mlStore.ts | 276 - src/script/stores/storeUtil.ts | 155 - src/script/stores/uiStore.ts | 190 - src/script/transitions.ts | 61 - src/script/utils/Smoother.ts | 46 - src/script/utils/api.ts | 57 - src/script/utils/logging.ts | 93 - src/script/utils/reconnect.ts | 64 - src/setup_tests.ts | 43 - src/svelte-skeleton.d.ts | 7 - src/views/IncompatiblePlatformView.svelte | 20 - src/views/OverlayView.svelte | 73 - src/views/PageContentView.svelte | 11 - src/views/TabView.svelte | 42 - src/views/currentComponentStore.ts | 12 - src/vite-env.d.ts | 2 - staticwebapp.config.json | 6 - svelte.config.js | 12 - tsconfig.json | 55 +- tsconfig.node.json | 10 + vite.config.ts | 60 +- windi.config.js | 57 - 266 files changed, 8772 insertions(+), 34509 deletions(-) delete mode 100644 azure-pipelines.yml delete mode 100644 babel.config.cjs delete mode 100644 public/css/all.min.css delete mode 100644 public/css/global.css delete mode 100644 public/models/license.txt delete mode 100644 public/models/microbit.bin delete mode 100644 public/models/microbit.gltf delete mode 100644 public/sounds/congratulations.wav delete mode 100644 public/sounds/high_pitch.wav delete mode 100644 public/sounds/huge_mistake.wav delete mode 100644 public/sounds/looser.wav delete mode 100644 public/sounds/low_pitch.wav delete mode 100644 public/sounds/mistake.mp3 delete mode 100644 public/webfonts/fa-brands-400.eot delete mode 100644 public/webfonts/fa-brands-400.svg delete mode 100644 public/webfonts/fa-brands-400.ttf delete mode 100644 public/webfonts/fa-brands-400.woff delete mode 100644 public/webfonts/fa-brands-400.woff2 delete mode 100644 public/webfonts/fa-regular-400.eot delete mode 100644 public/webfonts/fa-regular-400.svg delete mode 100644 public/webfonts/fa-regular-400.ttf delete mode 100644 public/webfonts/fa-regular-400.woff delete mode 100644 public/webfonts/fa-regular-400.woff2 delete mode 100644 public/webfonts/fa-solid-900.eot delete mode 100644 public/webfonts/fa-solid-900.svg delete mode 100644 public/webfonts/fa-solid-900.ttf delete mode 100644 public/webfonts/fa-solid-900.woff delete mode 100644 public/webfonts/fa-solid-900.woff2 delete mode 100644 src/App.svelte create mode 100644 src/App.tsx delete mode 100644 src/StaticConfiguration.ts delete mode 100644 src/__tests__/cookie.test.ts delete mode 100644 src/__tests__/datafunctions.test.ts delete mode 100644 src/__tests__/default-build-config.test.ts delete mode 100644 src/__tests__/fixtures/gesture-data-bad-labels.json delete mode 100644 src/__tests__/fixtures/gesture-data.json delete mode 100644 src/__tests__/fixtures/test-data-shake-still.json delete mode 100644 src/__tests__/getPrediction.test.ts delete mode 100644 src/__tests__/i18n.test.ts delete mode 100644 src/__tests__/license-identifiers.test.ts delete mode 100644 src/__tests__/microbit-usb-conection.test.ts delete mode 100644 src/__tests__/ml.test.ts delete mode 100644 src/__tests__/mocks/mock-bluetooth-accelerometer-service.ts delete mode 100644 src/__tests__/mocks/mock-bluetooth-gattcharacteristic.ts delete mode 100644 src/__tests__/mocks/mock-bluetooth-gattservice.ts delete mode 100644 src/__tests__/mocks/mock-bluetooth-info-service.ts delete mode 100644 src/__tests__/mocks/mock-bluetooth.ts delete mode 100644 src/__tests__/mocks/mock-microbit-bluetooth.ts delete mode 100644 src/__tests__/mocks/mock-usb.ts delete mode 100644 src/__tests__/patternMatrixTransforms.test.ts delete mode 100644 src/__tests__/serialProtocol.test.ts delete mode 100644 src/__tests__/smoothenXYZData.test.ts delete mode 100644 src/__tests__/translations.test.ts delete mode 100644 src/__tests__/unusedTranslations.test.ts delete mode 100644 src/__viteBuildVariants__/ml-machine/windi.config.js delete mode 100644 src/components/3d-inspector/RecordingInspector.svelte delete mode 100644 src/components/3d-inspector/View3D.svelte delete mode 100644 src/components/3d-inspector/View3DLive.svelte delete mode 100644 src/components/3d-inspector/View3DUnsafe.svelte delete mode 100644 src/components/3d-inspector/View3DUtility.ts delete mode 100644 src/components/DialogHeading.svelte delete mode 100644 src/components/FrontPageContentTile.svelte delete mode 100644 src/components/Gesture.svelte delete mode 100644 src/components/GestureTilePart.svelte delete mode 100644 src/components/HtmlFormattedMessage.svelte delete mode 100644 src/components/HtmlFormattedMessage.test.ts delete mode 100644 src/components/IconButton.svelte delete mode 100644 src/components/LinkOverlay.svelte delete mode 100644 src/components/LinkOverlayContainer.svelte delete mode 100644 src/components/LoadingBar.svelte delete mode 100644 src/components/LoadingBlobs.svelte delete mode 100644 src/components/LoadingSpinner.svelte delete mode 100644 src/components/MediaQuery.svelte delete mode 100644 src/components/MenuTransition.svelte delete mode 100644 src/components/NewGestureButton.svelte delete mode 100644 src/components/PatternBox.svelte delete mode 100644 src/components/PatternColumnInput.svelte delete mode 100644 src/components/PatternMatrix.svelte delete mode 100644 src/components/PleaseConnectFirst.svelte delete mode 100644 src/components/PrototypeVersionWarning.svelte delete mode 100644 src/components/ReconnectHelp.svelte delete mode 100644 src/components/Recording.svelte delete mode 100644 src/components/ResourcePageLayout.svelte delete mode 100644 src/components/StandardButton.svelte delete mode 100644 src/components/StartResumeActions.svelte delete mode 100644 src/components/TrainModelFirstTitle.svelte delete mode 100644 src/components/TrainingStatusSection.svelte delete mode 100644 src/components/bottom/BottomPanel.svelte delete mode 100644 src/components/bottom/ConnectedLiveGraphButtons.svelte delete mode 100644 src/components/bottom/LiveGraphInformationSection.svelte delete mode 100644 src/components/connection-prompt/ConnectDialogContainer.svelte delete mode 100644 src/components/connection-prompt/ConnectSameDialog.svelte delete mode 100644 src/components/connection-prompt/WebBluetoothTryAgain.svelte delete mode 100644 src/components/connection-prompt/WebUsbTryAgain.svelte delete mode 100644 src/components/connection-prompt/WhatYouWillNeedDialog.svelte delete mode 100644 src/components/connection-prompt/arrows/ArrowOne.svelte delete mode 100644 src/components/connection-prompt/arrows/ArrowTwo.svelte delete mode 100644 src/components/connection-prompt/bluetooth/BluetoothConnectDialog.svelte delete mode 100644 src/components/connection-prompt/bluetooth/BluetoothConnectingDialog.svelte delete mode 100644 src/components/connection-prompt/bluetooth/ConnectBatteryDialog.svelte delete mode 100644 src/components/connection-prompt/bluetooth/ConnectCableDialog.svelte delete mode 100644 src/components/connection-prompt/bluetooth/SelectMicrobitDialogBluetooth.svelte delete mode 100644 src/components/connection-prompt/bluetooth/StartBluetoothDialog.svelte delete mode 100644 src/components/connection-prompt/radio/ConnectingMicrobits.svelte delete mode 100644 src/components/connection-prompt/radio/StartRadioDialog.svelte delete mode 100644 src/components/connection-prompt/usb/BrokenFirmwareDetected.svelte delete mode 100644 src/components/connection-prompt/usb/DownloadingDialog.svelte delete mode 100644 src/components/connection-prompt/usb/SelectMicrobitDialogUsb.svelte delete mode 100644 src/components/connection-prompt/usb/manual/ManualInstallTutorial.svelte delete mode 100644 src/components/control-bar/ControlBar.svelte delete mode 100644 src/components/control-bar/control-bar-items/AboutDialog.svelte delete mode 100644 src/components/control-bar/control-bar-items/ExpandableControlBarMenu.svelte delete mode 100644 src/components/control-bar/control-bar-items/HelpMenu.svelte delete mode 100644 src/components/control-bar/control-bar-items/LanguageDialog.svelte delete mode 100644 src/components/control-bar/control-bar-items/MenuItem.svelte delete mode 100644 src/components/control-bar/control-bar-items/MenuItems.svelte delete mode 100644 src/components/control-bar/control-bar-items/SelectLanguageControlBarDropdown.svelte delete mode 100644 src/components/control-bar/control-bar-items/SettingsMenu.svelte delete mode 100644 src/components/datacollection/DataPageControlBar.svelte delete mode 100644 src/components/datacollection/DataPageMenu.svelte delete mode 100644 src/components/datacollection/RecordInformationContent.svelte delete mode 100644 src/components/dialogs/AppVersionRedirectDialog.svelte delete mode 100644 src/components/dialogs/BaseDialog.svelte delete mode 100644 src/components/dialogs/CompatibilityWarningDialog.svelte delete mode 100644 src/components/dialogs/PerformanceWarningDialog.svelte delete mode 100644 src/components/dialogs/SignUpDialog.svelte delete mode 100644 src/components/dialogs/StandardDialog.svelte delete mode 100644 src/components/dialogs/UnsupportedMicrobitWarningDialog.svelte delete mode 100644 src/components/graphs/DimensionLabels.svelte delete mode 100644 src/components/graphs/LiveGraph.svelte delete mode 100644 src/components/graphs/RecordingGraph.svelte delete mode 100644 src/components/information/Information.svelte delete mode 100644 src/components/information/InformationComponentUtility.ts delete mode 100644 src/components/output/OutputGesture.svelte delete mode 100644 src/components/output/OutputGestureStack.svelte delete mode 100644 src/components/output/OutputGestureTile.svelte delete mode 100644 src/components/output/OutputMatrix.svelte delete mode 100644 src/components/output/OutputSoundSelector.svelte delete mode 100644 src/components/output/PinSelector.svelte delete mode 100644 src/components/output/PinSelectorUtil.ts delete mode 100644 src/components/playground/LiveDataBufferUtilizationPercentage.svelte delete mode 100644 src/components/playground/PlaygroundContext.ts delete mode 100644 src/components/playground/PlaygroundGestureView.svelte delete mode 100644 src/components/playground/PlaygroundLog.svelte delete mode 100644 src/components/playground/inputSynthesizer/AccelerometerDataSynthesizer.ts delete mode 100644 src/components/playground/inputSynthesizer/IntervalSlider.svelte delete mode 100644 src/components/playground/inputSynthesizer/MicrobitAccelerometerDataSynthesizer.svelte delete mode 100644 src/components/playground/inputSynthesizer/Visualizer.svelte delete mode 100644 src/components/skeletonloading/ImageSkeleton.svelte delete mode 100644 src/i18n.ts delete mode 100644 src/main.ts rename src/{script/domain/ClassifierInput.ts => main.tsx} (50%) delete mode 100644 src/menus/DataMenu.svelte delete mode 100644 src/menus/MenuButton.svelte delete mode 100644 src/menus/ModelMenu.svelte delete mode 100644 src/menus/TrainingMenu.svelte delete mode 100644 src/pages/DataPage.svelte delete mode 100644 src/pages/GetStartedPage.svelte delete mode 100644 src/pages/Homepage.svelte delete mode 100644 src/pages/IntroducingToolPage.svelte delete mode 100644 src/pages/PlaygroundPage.svelte delete mode 100644 src/pages/filter/D3Plot.svelte delete mode 100644 src/pages/filter/FilterPage.svelte delete mode 100644 src/pages/filter/FilterToggler.svelte delete mode 100644 src/pages/home-page-content-tiles/NewFeaturesTile.svelte delete mode 100644 src/pages/home-page-content-tiles/WhatIsMachineLearningTile.svelte delete mode 100644 src/pages/model/ModelPage.svelte delete mode 100644 src/pages/model/stackview/ModelPageStackView.svelte delete mode 100644 src/pages/model/stackview/ModelPageStackViewContent.svelte delete mode 100644 src/pages/model/tileview/ModelPageTileView.svelte delete mode 100644 src/pages/model/tileview/ModelPageTileViewTiles.svelte delete mode 100644 src/pages/training/TrainingButton.svelte delete mode 100644 src/pages/training/TrainingPage.svelte create mode 100644 src/placeholder.test.ts delete mode 100644 src/router/Router.svelte delete mode 100644 src/router/paths.ts delete mode 100644 src/script/ControlledStorage.ts delete mode 100644 src/script/CookieManager.ts delete mode 100644 src/script/TypingUtils.ts delete mode 100644 src/script/compatibility/CompatibilityChecker.ts delete mode 100644 src/script/compatibility/CompatibilityList.ts delete mode 100644 src/script/datafunctions.ts delete mode 100644 src/script/domain/Axes.ts delete mode 100644 src/script/domain/BindableValue.ts delete mode 100644 src/script/domain/Classifier.ts delete mode 100644 src/script/domain/ClassifierFactory.ts delete mode 100644 src/script/domain/Engine.ts delete mode 100644 src/script/domain/Filter.ts delete mode 100644 src/script/domain/Filters.ts delete mode 100644 src/script/domain/Gesture.ts delete mode 100644 src/script/domain/GestureConfidence.ts delete mode 100644 src/script/domain/Gestures.ts delete mode 100644 src/script/domain/LiveData.ts delete mode 100644 src/script/domain/LiveDataBuffer.ts delete mode 100644 src/script/domain/MLModel.ts delete mode 100644 src/script/domain/MLModelFactory.ts delete mode 100644 src/script/domain/Model.ts delete mode 100644 src/script/domain/ModelTrainer.ts delete mode 100644 src/script/engine/PollingPredictorEngine.ts delete mode 100644 src/script/environment.ts delete mode 100644 src/script/filters/FilterWithMaths.ts delete mode 100644 src/script/filters/MaxFilter.ts delete mode 100644 src/script/filters/MeanFilter.ts delete mode 100644 src/script/filters/MinFilter.ts delete mode 100644 src/script/filters/PeaksFilter.ts delete mode 100644 src/script/filters/RootMeanSquareFilter.ts delete mode 100644 src/script/filters/StandardDeviationFilter.ts delete mode 100644 src/script/filters/TotalAccFilter.ts delete mode 100644 src/script/filters/ZeroCrossingRateFilter.ts delete mode 100644 src/script/flags.ts delete mode 100644 src/script/getPrediction.ts delete mode 100644 src/script/livedata/MicrobitAccelerometerData.ts delete mode 100644 src/script/microbit-interfacing/MBSpecs.ts delete mode 100644 src/script/microbit-interfacing/MicrobitBluetooth.ts delete mode 100644 src/script/microbit-interfacing/MicrobitConnection.ts delete mode 100644 src/script/microbit-interfacing/MicrobitSerial.ts delete mode 100644 src/script/microbit-interfacing/MicrobitUSB.ts delete mode 100644 src/script/microbit-interfacing/Microbits.ts delete mode 100644 src/script/microbit-interfacing/change-listeners.ts delete mode 100644 src/script/microbit-interfacing/constants.ts delete mode 100644 src/script/microbit-interfacing/serialProtocol.ts delete mode 100644 src/script/microbit-interfacing/state-updaters.ts delete mode 100644 src/script/ml.ts delete mode 100644 src/script/mlmodels/AccelerometerClassifierInput.ts delete mode 100644 src/script/mlmodels/LayersMLModel.ts delete mode 100644 src/script/mlmodels/LayersModelTrainer.ts delete mode 100644 src/script/mlmodels/NoneMLModel.ts delete mode 100644 src/script/navigation/Menus.ts delete mode 100644 src/script/patternMatrixTransforms.ts delete mode 100644 src/script/repository/ClassifierRepository.ts delete mode 100644 src/script/repository/GestureRepository.ts delete mode 100644 src/script/repository/Repositories.ts delete mode 100644 src/script/smoothenXYZData.ts delete mode 100644 src/script/stores/Stores.ts delete mode 100644 src/script/stores/complianceStore.ts delete mode 100644 src/script/stores/connectDialogStore.ts delete mode 100644 src/script/stores/connectionStore.ts delete mode 100644 src/script/stores/mlStore.ts delete mode 100644 src/script/stores/storeUtil.ts delete mode 100644 src/script/stores/uiStore.ts delete mode 100644 src/script/transitions.ts delete mode 100644 src/script/utils/Smoother.ts delete mode 100644 src/script/utils/api.ts delete mode 100644 src/script/utils/logging.ts delete mode 100644 src/script/utils/reconnect.ts delete mode 100644 src/setup_tests.ts delete mode 100644 src/svelte-skeleton.d.ts delete mode 100644 src/views/IncompatiblePlatformView.svelte delete mode 100644 src/views/OverlayView.svelte delete mode 100644 src/views/PageContentView.svelte delete mode 100644 src/views/TabView.svelte delete mode 100644 src/views/currentComponentStore.ts delete mode 100644 staticwebapp.config.json delete mode 100644 svelte.config.js create mode 100644 tsconfig.node.json delete mode 100644 windi.config.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs index d03e7400e..c3536e1aa 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,51 +1,45 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ module.exports = { root: true, - parser: '@typescript-eslint/parser', + env: { browser: true, es2020: true }, extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - 'plugin:svelte/recommended', + "eslint:recommended", + "plugin:@typescript-eslint/recommended-type-checked", + "plugin:react-hooks/recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", ], + ignorePatterns: [ + "dist", + ".eslintrc.cjs", + "deployment.cjs", + "bin/**/*.js", + "bootstrap-template.js", + "playwright.config.ts", + "CMakeFiles", + ], + parser: "@typescript-eslint/parser", parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', + ecmaVersion: "latest", + sourceType: "module", + project: ["./tsconfig.json", "./tsconfig.node.json"], tsconfigRootDir: __dirname, - project: ['./tsconfig.json'], - extraFileExtensions: ['.svelte'], + }, + plugins: ["react-refresh"], + settings: { + react: { + version: "18", + }, }, rules: { - "@typescript-eslint/no-unused-vars": [ - warn, - { - argsIgnorePattern: "^_" - } + // More trouble than it's worth + "react/no-unescaped-entities": "off", + // False positives from library imports from Chakra UI + "@typescript-eslint/unbound-method": "off", + "@typescript-eslint/no-misused-promises": [ + "error", + { + checksVoidReturn: false, + }, ], }, - env: { - browser: true, - es6: true, - node: true, - }, - overrides: [ - { - files: ['*.svelte'], - parser: 'svelte-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - }, - }, - ], - plugins: ['@typescript-eslint'], - ignorePatterns: [ - 'node_modules', - 'svelte.config.js', - '.eslintrc.cjs', - 'babel.config.cjs', - ], }; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2029a1620..bafb55774 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,12 +32,12 @@ jobs: node-version: 20 cache: 'npm' registry-url: 'https://npm.pkg.github.com' + - uses: microbit-foundation/npm-package-versioner-action@v1 - run: npm ci - - run: npm install --no-save @microbit-foundation/website-deploy-aws@0.6 @microbit-foundation/website-deploy-aws-config@0.9 @microbit-foundation/npm-package-versioner@2 + - run: npm install --no-save @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 }} - - run: npx update-ci-version - run: node ./bin/print-ci-env.cjs >> $GITHUB_ENV - run: npm run test - run: npm run check diff --git a/.prettierrc.cjs b/.prettierrc.cjs index 476b1aa8e..2c7a883e7 100644 --- a/.prettierrc.cjs +++ b/.prettierrc.cjs @@ -7,12 +7,8 @@ module.exports = { arrowParens: 'avoid', singleQuote: true, printWidth: 90, - plugins: ['prettier-plugin-svelte'], + plugins: [], semi: true, - svelteSortOrder: 'options-styles-scripts-markup', - svelteStrictMode: false, bracketSameLine: true, - svelteAllowShorthand: true, - svelteIndentScriptAndStyle: true, trailingComma: 'all', }; diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 6cb65a983..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,15 +0,0 @@ -trigger: - - main - -pool: - vmImage: ubuntu-latest - -steps: - - checkout: self - submodules: true - - task: AzureStaticWebApp@0 - inputs: - app_location: '/' - api_location: 'api' - output_location: '/public' - azure_static_web_apps_api_token: $(deployment_token) \ No newline at end of file diff --git a/babel.config.cjs b/babel.config.cjs deleted file mode 100644 index 49d6c2a62..000000000 --- a/babel.config.cjs +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - presets: [ - ['@babel/preset-env', {targets: {node: 'current'}}], - ], - }; \ No newline at end of file diff --git a/index.html b/index.html index 893a90f74..27b6b9644 100644 --- a/index.html +++ b/index.html @@ -26,8 +26,6 @@ - - @@ -40,7 +38,7 @@ window.dataLayer.push(arguments); }; - + diff --git a/package-lock.json b/package-lock.json index 0e5d43ab4..63f66fe99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,80 +1,75 @@ { - "name": "ml-machine", + "name": "ml-tool", "version": "0.6.0-local", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ml-machine", + "name": "ml-tool", "version": "0.6.0-local", "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-web": "^3.0.0", - "@sentry/svelte": "^7.100.1", + "@chakra-ui/icons": "^2.1.1", + "@chakra-ui/react": "^2.8.2", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", + "@vitejs/plugin-react": "^4.3.1", "bowser": "^2.11.0", "browser-lang": "^0.2.1", "chart.js": "^4.2.1", "d3": "^7.8.5", "dapjs": "^2.3.0", + "framer-motion": "^10.2.4", "js-cookie": "^3.0.4", "postcss": "^8.4.23", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-icons": "^4.12.0", + "react-intl": "^6.6.8", "smoothie": "^1.36.1", - "svelte-i18n": "^4.0.0", - "svelte-skeleton": "^1.3.1", - "svelte-transition": "^0.0.10", "three": "^0.152.2", "uuid4": "^2.0.3" }, "devDependencies": { - "@babel/preset-env": "^7.23.5", - "@bulatdashiev/svelte-slider": "^1.0.3", - "@iconify-json/mdi": "^1.1.63", - "@iconify-json/ri": "^1.1.17", - "@melt-ui/pp": "^0.3.0", - "@melt-ui/svelte": "^0.70.0", - "@sveltejs/vite-plugin-svelte": "^3.0.1", - "@testing-library/jest-dom": "^6.2.0", - "@testing-library/svelte": "^4.0.5", - "@tsconfig/svelte": "^4.0.1", + "@chakra-ui/cli": "^2.4.1", + "@formatjs/cli": "^6.2.7", + "@playwright/test": "^1.42.1", + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", "@types/browser-lang": "^0.1.1", "@types/d3": "^7.4.1", - "@types/js-cookie": "^3.0.3", + "@types/ejs": "^3.1.5", + "@types/file-saver": "^2.0.3", "@types/node": "^18.16.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@types/three": "^0.152.0", "@types/w3c-web-usb": "^1.0.6", "@types/web-bluetooth": "^0.0.17", - "@typescript-eslint/eslint-plugin": "^5.59.0", - "@typescript-eslint/parser": "^5.59.0", - "eslint": "^8.43.0", - "eslint-plugin-svelte": "^2.32.1", - "jsdom": "^23.2.0", - "prettier": "^3.1.0", - "prettier-plugin-svelte": "^3.1.2", - "svelte": "^4.0.0", - "svelte-check": "^3.6.2", - "svelte-preprocess": "^5.0.3", - "svelte-windicss-preprocess": "^4.2.2", - "tslib": "^2.5.0", - "typescript": "^5.0.4", - "unplugin-icons": "^0.18.1", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "cross-env": "^7.0.3", + "ejs": "^3.1.9", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "jsdom": "^24.0.0", + "playwright": "^1.42.1", + "prettier": "2.3.2", + "typescript": "^5.4.2", "vite": "^5.0.7", - "vite-plugin-windicss": "^1.9.3", - "vitest": "^1.0.4", + "vite-plugin-pwa": "^0.19.8", + "vite-plugin-svgr": "^4.2.0", + "vitest": "^1.3.1", "vitest-dom": "^0.1.1" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@adobe/css-tools": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", @@ -85,6 +80,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -93,80 +89,43 @@ "node": ">=6.0.0" } }, - "node_modules/@antfu/install-pkg": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.3.1.tgz", - "integrity": "sha512-A3zWY9VeTPnxlMiZtsGHw2lSd3ghwvL8s9RiGOtqvDxhhFfZ781ynsGBa/iUnDJ5zBrmTFQrJDud3TGgRISaxw==", - "dev": true, - "dependencies": { - "execa": "^8.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@antfu/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-UU8TLr/EoXdg7OjMp0h9oDoIAVr+Z/oW9cpOxQQyrsz6Qzd2ms/1CdWx8fl2OQdFpxGmq5Vc4TwfLHId6nAZjA==", - "dev": true, - "dependencies": { - "@types/throttle-debounce": "^2.1.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-2.0.1.tgz", - "integrity": "sha512-QJAJffmCiymkv6YyQ7voyQb5caCth6jzZsQncYCpHXrJ7RqdYG5y43+is8mnFcYubdOkr7cn1+na9BdFMxqw7w==", - "dev": true, - "dependencies": { - "bidi-js": "^1.0.3", - "css-tree": "^2.3.1", - "is-potential-custom-element-name": "^1.0.1" - } - }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", - "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.6", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", - "@babel/types": "^7.23.6", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -182,15 +141,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "dev": true, - "peer": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -222,13 +179,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -294,34 +251,34 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -340,28 +297,28 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -383,9 +340,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -426,12 +383,13 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -450,39 +408,36 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true, "engines": { "node": ">=6.9.0" @@ -503,39 +458,36 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", - "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, - "peer": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.6", - "@babel/types": "^7.23.6" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1441,6 +1393,36 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-regenerator": { "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", @@ -1729,7 +1711,6 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1740,38 +1721,34 @@ "node_modules/@babel/runtime/node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", - "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1780,32 +1757,169 @@ } }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", - "dev": true, + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@bulatdashiev/svelte-slider": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@bulatdashiev/svelte-slider/-/svelte-slider-1.0.3.tgz", - "integrity": "sha512-GkVyqIjzQnqy0FQIm8wqRLIOFQBQ/Ewd381Y7KwABsSwiSu0eMYO0/8s4q/wlMDDb2/+U66rXlalaUokUfw8Vg==", - "dev": true + "node_modules/@chakra-ui/accordion": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/accordion/-/accordion-2.3.1.tgz", + "integrity": "sha512-FSXRm8iClFyU+gVaXisOSEw0/4Q+qZbFRiuhIAkVU6Boj0FxAMrlo9a8AV5TuF77rgaHytCdHk0Ng+cyUijrag==", + "dependencies": { + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", - "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", + "node_modules/@chakra-ui/alert": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/alert/-/alert-2.2.2.tgz", + "integrity": "sha512-jHg4LYMRNOJH830ViLuicjb3F+v6iriE/2G5T+Sd0Hna04nukNJ1MxUmBPE+vI22me2dIflfelu2v9wdB6Pojw==", + "dependencies": { + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/anatomy": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/anatomy/-/anatomy-2.2.2.tgz", + "integrity": "sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==" + }, + "node_modules/@chakra-ui/avatar": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/avatar/-/avatar-2.3.0.tgz", + "integrity": "sha512-8gKSyLfygnaotbJbDMHDiJoF38OHXUYVme4gGxZ1fLnQEdPVEaIWfH+NndIjOM0z8S+YEFnT9KyGMUtvPrBk3g==", + "dependencies": { + "@chakra-ui/image": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/breadcrumb": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/breadcrumb/-/breadcrumb-2.2.0.tgz", + "integrity": "sha512-4cWCG24flYBxjruRi4RJREWTGF74L/KzI2CognAW/d/zWR0CjiScuJhf37Am3LFbCySP6WSoyBOtTIoTA4yLEA==", + "dependencies": { + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/breakpoint-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.8.tgz", + "integrity": "sha512-Pq32MlEX9fwb5j5xx8s18zJMARNHlQZH2VH1RZgfgRDpp7DcEgtRW5AInfN5CfqdHLO1dGxA7I3MqEuL5JnIsA==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + } + }, + "node_modules/@chakra-ui/button": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/button/-/button-2.1.0.tgz", + "integrity": "sha512-95CplwlRKmmUXkdEp/21VkEWgnwcx2TOBG6NfYlsuLBDHSLlo5FKIiE2oSi4zXc4TLcopGcWPNcm/NDaSC5pvA==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/spinner": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/card": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/card/-/card-2.2.0.tgz", + "integrity": "sha512-xUB/k5MURj4CtPAhdSoXZidUbm8j3hci9vnc+eZJVDqhDOShNlD6QeniQNRPRys4lWAQLCbFcrwL29C8naDi6g==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/checkbox": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/checkbox/-/checkbox-2.3.2.tgz", + "integrity": "sha512-85g38JIXMEv6M+AcyIGLh7igNtfpAN6KGQFYxY9tBj0eWvWk4NKQxvqqyVta0bSAyIl1rixNIIezNpNWk2iO4g==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/visually-hidden": "2.2.0", + "@zag-js/focus-visible": "0.16.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/cli": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/cli/-/cli-2.4.1.tgz", + "integrity": "sha512-GZZuHUA1cXJWpmYNiVTLPihvY4VhIssRl+AXgw/0IbeodTMop3jWlIioPKLAQeXu5CwvRA6iESyGjnu1V8Zykg==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.3", + "cli-check-node": "^1.3.4", + "cli-handle-unhandled": "^1.1.1", + "cli-welcome": "^2.2.2", + "commander": "^9.3.0", + "esbuild": "^0.17.18", + "prettier": "^2.8.8" + }, + "bin": { + "chakra-cli": "bin/index.js" + } + }, + "node_modules/@chakra-ui/cli/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -1814,13 +1928,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", - "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -1829,13 +1944,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", - "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" @@ -1844,13 +1960,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", - "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1859,13 +1976,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", - "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1874,13 +1992,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", - "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -1889,13 +2008,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", - "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -1904,13 +2024,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", - "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1919,13 +2040,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", - "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1934,13 +2056,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", - "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1949,13 +2072,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", - "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1964,13 +2088,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", - "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1979,13 +2104,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", - "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1994,13 +2120,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", - "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2009,13 +2136,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", - "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2024,13 +2152,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", - "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2039,13 +2168,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", - "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -2054,13 +2184,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", - "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -2069,13 +2200,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", - "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "sunos" @@ -2084,13 +2216,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", - "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -2099,13 +2232,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", - "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -2114,13 +2248,14 @@ "node": ">=12" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", - "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", + "node_modules/@chakra-ui/cli/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -2129,1208 +2264,1953 @@ "node": ">=12" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "node_modules/@chakra-ui/cli/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": "^12.20.0 || >=14" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@chakra-ui/cli/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=12" }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/@chakra-ui/cli/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" + "bin": { + "prettier": "bin-prettier.js" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node_modules/@chakra-ui/clickable": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.1.0.tgz", + "integrity": "sha512-flRA/ClPUGPYabu+/GLREZVZr9j2uyyazCAUHAdrTUEdDYCr31SVGhgh7dgKdtq23bOvAQJpIJjw/0Bs0WvbXw==", + "dependencies": { + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@floating-ui/core": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.3.tgz", - "integrity": "sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==", - "dev": true, + "node_modules/@chakra-ui/close-button": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/close-button/-/close-button-2.1.1.tgz", + "integrity": "sha512-gnpENKOanKexswSVpVz7ojZEALl2x5qjLYNqSQGbxz+aP9sOXPfUS56ebyBrre7T7exuWGiFeRwnM0oVeGPaiw==", "dependencies": { - "@floating-ui/utils": "^0.2.0" + "@chakra-ui/icon": "3.2.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@floating-ui/dom": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.4.tgz", - "integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==", - "dev": true, + "node_modules/@chakra-ui/color-mode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/color-mode/-/color-mode-2.2.0.tgz", + "integrity": "sha512-niTEA8PALtMWRI9wJ4LL0CSBDo8NBfLNp4GD6/0hstcm3IlbBHTVKxN6HwSaoNYfphDQLxCjT4yG+0BJA5tFpg==", "dependencies": { - "@floating-ui/core": "^1.5.3", - "@floating-ui/utils": "^0.2.0" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==", - "dev": true + "node_modules/@chakra-ui/control-box": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/control-box/-/control-box-2.1.0.tgz", + "integrity": "sha512-gVrRDyXFdMd8E7rulL0SKeoljkLQiPITFnsyMO8EFHNZ+AHt5wK4LIguYVEq88APqAGZGfHFWXr79RYrNiE3Mg==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz", - "integrity": "sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA==", + "node_modules/@chakra-ui/counter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/counter/-/counter-2.1.0.tgz", + "integrity": "sha512-s6hZAEcWT5zzjNz2JIWUBzRubo9la/oof1W7EKZVVfPYHERnl5e16FmBC79Yfq8p09LQ+aqFKm/etYoJMMgghw==", "dependencies": { - "@formatjs/intl-localematcher": "0.5.2", - "tslib": "^2.4.0" + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@formatjs/fast-memoize": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", - "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", - "dependencies": { - "tslib": "^2.4.0" + "node_modules/@chakra-ui/css-reset": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-2.3.0.tgz", + "integrity": "sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==", + "peerDependencies": { + "@emotion/react": ">=10.0.35", + "react": ">=18" } }, - "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz", - "integrity": "sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw==", + "node_modules/@chakra-ui/descendant": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/descendant/-/descendant-3.1.0.tgz", + "integrity": "sha512-VxCIAir08g5w27klLyi7PVo8BxhW4tgU/lxQyujkmi4zx7hT9ZdrcQLAted/dAa+aSIZ14S1oV0Q9lGjsAdxUQ==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/icu-skeleton-parser": "1.7.0", - "tslib": "^2.4.0" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz", - "integrity": "sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==", + "node_modules/@chakra-ui/dom-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/dom-utils/-/dom-utils-2.1.0.tgz", + "integrity": "sha512-ZmF2qRa1QZ0CMLU8M1zCfmw29DmPNtfjR9iTo74U5FPr3i1aoAh7fbJ4qAlZ197Xw9eAW28tvzQuoVWeL5C7fQ==" + }, + "node_modules/@chakra-ui/editable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/editable/-/editable-3.1.0.tgz", + "integrity": "sha512-j2JLrUL9wgg4YA6jLlbU88370eCRyor7DZQD9lzpY95tSOXpTljeg3uF9eOmDnCs6fxp3zDWIfkgMm/ExhcGTg==", "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "tslib": "^2.4.0" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz", - "integrity": "sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==", + "node_modules/@chakra-ui/event-utils": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@chakra-ui/event-utils/-/event-utils-2.0.8.tgz", + "integrity": "sha512-IGM/yGUHS+8TOQrZGpAKOJl/xGBrmRYJrmbHfUE7zrG3PpQyXvbLDP1M+RggkCFVgHlJi2wpYIf0QtQlU0XZfw==" + }, + "node_modules/@chakra-ui/focus-lock": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/focus-lock/-/focus-lock-2.1.0.tgz", + "integrity": "sha512-EmGx4PhWGjm4dpjRqM4Aa+rCWBxP+Rq8Uc/nAVnD4YVqkEhBkrPTpui2lnjsuxqNaZ24fIAZ10cF1hlpemte/w==", "dependencies": { - "tslib": "^2.4.0" + "@chakra-ui/dom-utils": "2.1.0", + "react-focus-lock": "^2.9.4" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, + "node_modules/@chakra-ui/form-control": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/form-control/-/form-control-2.2.0.tgz", + "integrity": "sha512-wehLC1t4fafCVJ2RvJQT2jyqsAwX7KymmiGqBu7nQoQz8ApTkGABWpo/QwDh3F/dBLrouHDoOvGmYTqft3Mirw==", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, - "engines": { - "node": ">=10.10.0" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" + "node_modules/@chakra-ui/hooks": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/hooks/-/hooks-2.2.1.tgz", + "integrity": "sha512-RQbTnzl6b1tBjbDPf9zGRo9rf/pQMholsOudTxjy4i9GfTfz6kgp5ValGjQm2z7ng6Z31N1cnjZ1AlSzQ//ZfQ==", + "dependencies": { + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/utils": "2.0.15", + "compute-scroll-into-view": "3.0.3", + "copy-to-clipboard": "3.3.3" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true - }, - "node_modules/@iconify-json/mdi": { - "version": "1.1.63", - "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.1.63.tgz", - "integrity": "sha512-b07dRM5LYdwJqd/lm+1+JDNDNSWHvpZ7ujb73oJxx3lmK1J0giDS0BSc4yia0hixNV0Z2q/4GyxAu/A8XqDnbg==", - "dev": true, + "node_modules/@chakra-ui/icon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.2.0.tgz", + "integrity": "sha512-xxjGLvlX2Ys4H0iHrI16t74rG9EBcpFvJ3Y3B7KMQTrnW34Kf7Da/UC8J67Gtx85mTHW020ml85SVPKORWNNKQ==", "dependencies": { - "@iconify/types": "*" + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@iconify-json/ri": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/@iconify-json/ri/-/ri-1.1.17.tgz", - "integrity": "sha512-FN6dynUF7GjVsDU9twvzreN+3gpWiJwpD7FJelHFVPjHNW7U4CDAnw4mEWLaqscB7npJVXLidYKdI03Mh0apTQ==", - "dev": true, + "node_modules/@chakra-ui/icons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", "dependencies": { - "@iconify/types": "*" + "@chakra-ui/icon": "3.2.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@iconify/json": { - "version": "1.1.432", - "resolved": "https://registry.npmjs.org/@iconify/json/-/json-1.1.432.tgz", - "integrity": "sha512-ZcQKCnJXmeKDKxSu4vOFCYkCv8sjX1OASTMQpzqVzt3ivBBDudAYyXJlO4hgk0X4B2R0IMHRNvRgCAYkKDa2eQ==", - "dev": true - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "dev": true + "node_modules/@chakra-ui/image": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", + "integrity": "sha512-bskumBYKLiLMySIWDGcz0+D9Th0jPvmX6xnRMs4o92tT3Od/bW26lahmV2a2Op2ItXeCmRMY+XxJH5Gy1i46VA==", + "dependencies": { + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } }, - "node_modules/@iconify/utils": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.13.tgz", - "integrity": "sha512-6uWvJIo715xYRy1KmCCyZYW0YYkLjaojEExoEkxpOHKhi9cyHW8hVKo+m8zrxzNVSqjUx9OuVRa2BWXeXfkp5A==", - "dev": true, + "node_modules/@chakra-ui/input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/input/-/input-2.1.2.tgz", + "integrity": "sha512-GiBbb3EqAA8Ph43yGa6Mc+kUPjh4Spmxp1Pkelr8qtudpc3p2PJOOebLpd90mcqw8UePPa+l6YhhPtp6o0irhw==", "dependencies": { - "@antfu/install-pkg": "^0.1.1", - "@antfu/utils": "^0.7.5", - "@iconify/types": "^2.0.0", - "debug": "^4.3.4", - "kolorist": "^1.8.0", - "local-pkg": "^0.4.3" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@iconify/utils/node_modules/@antfu/install-pkg": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.1.1.tgz", - "integrity": "sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==", - "dev": true, + "node_modules/@chakra-ui/layout": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/layout/-/layout-2.3.1.tgz", + "integrity": "sha512-nXuZ6WRbq0WdgnRgLw+QuxWAHuhDtVX8ElWqcTK+cSMFg/52eVP47czYBE5F35YhnoW2XBwfNoNgZ7+e8Z01Rg==", "dependencies": { - "execa": "^5.1.1", - "find-up": "^5.0.0" + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@iconify/utils/node_modules/@antfu/utils": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", - "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" + "node_modules/@chakra-ui/lazy-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/lazy-utils/-/lazy-utils-2.0.5.tgz", + "integrity": "sha512-UULqw7FBvcckQk2n3iPO56TMJvDsNv0FKZI6PlUNJVaGsPbsYxK/8IQ60vZgaTVPtVcjY6BE+y6zg8u9HOqpyg==" + }, + "node_modules/@chakra-ui/live-region": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/live-region/-/live-region-2.1.0.tgz", + "integrity": "sha512-ZOxFXwtaLIsXjqnszYYrVuswBhnIHHP+XIgK1vC6DePKtyK590Wg+0J0slDwThUAd4MSSIUa/nNX84x1GMphWw==", + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@iconify/utils/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, + "node_modules/@chakra-ui/media-query": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/media-query/-/media-query-3.3.0.tgz", + "integrity": "sha512-IsTGgFLoICVoPRp9ykOgqmdMotJG0CnPsKvGQeSFOB/dZfIujdVb14TYxDU4+MURXry1MhJ7LzZhv+Ml7cr8/g==", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@chakra-ui/breakpoint-utils": "2.0.8", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/menu": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/menu/-/menu-2.2.1.tgz", + "integrity": "sha512-lJS7XEObzJxsOwWQh7yfG4H8FzFPRP5hVPN/CL+JzytEINCSBvsCDHrYPQGp7jzpCi8vnTqQQGQe0f8dwnXd2g==", + "dependencies": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-outside-click": "2.2.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/modal": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/modal/-/modal-2.3.1.tgz", + "integrity": "sha512-TQv1ZaiJMZN+rR9DK0snx/OPwmtaGH1HbZtlYt4W4s6CzyK541fxLRTjIXfEzIGpvNW+b6VFuFjbcR78p4DEoQ==", + "dependencies": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/transition": "2.1.0", + "aria-hidden": "^1.2.3", + "react-remove-scroll": "^2.5.6" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" } }, - "node_modules/@iconify/utils/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" + "node_modules/@chakra-ui/number-input": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-input/-/number-input-2.1.2.tgz", + "integrity": "sha512-pfOdX02sqUN0qC2ysuvgVDiws7xZ20XDIlcNhva55Jgm095xjm8eVdIBfNm3SFbSUNxyXvLTW/YQanX74tKmuA==", + "dependencies": { + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-interval": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@iconify/utils/node_modules/human-signals": { + "node_modules/@chakra-ui/number-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/number-utils/-/number-utils-2.0.7.tgz", + "integrity": "sha512-yOGxBjXNvLTBvQyhMDqGU0Oj26s91mbAlqKHiuw737AXHt0aPllOthVUqQMeaYLwLCjGMg0jtI7JReRzyi94Dg==" + }, + "node_modules/@chakra-ui/object-utils": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" + "resolved": "https://registry.npmjs.org/@chakra-ui/object-utils/-/object-utils-2.1.0.tgz", + "integrity": "sha512-tgIZOgLHaoti5PYGPTwK3t/cqtcycW0owaiOXoZOcpwwX/vlVb+H1jFsQyWiiwQVPt9RkoSLtxzXamx+aHH+bQ==" + }, + "node_modules/@chakra-ui/pin-input": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/pin-input/-/pin-input-2.1.0.tgz", + "integrity": "sha512-x4vBqLStDxJFMt+jdAHHS8jbh294O53CPQJoL4g228P513rHylV/uPscYUHrVJXRxsHfRztQO9k45jjTYaPRMw==", + "dependencies": { + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@iconify/utils/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/@chakra-ui/popover": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/popover/-/popover-2.2.1.tgz", + "integrity": "sha512-K+2ai2dD0ljvJnlrzesCDT9mNzLifE3noGKZ3QwLqd/K34Ym1W/0aL1ERSynrcG78NKoXS54SdEzkhCZ4Gn/Zg==", + "dependencies": { + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-animation-state": "2.1.0", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-focus-effect": "2.1.0", + "@chakra-ui/react-use-focus-on-pointer-down": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" } }, - "node_modules/@iconify/utils/node_modules/local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", - "dev": true, - "engines": { - "node": ">=14" + "node_modules/@chakra-ui/popper": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/popper/-/popper-3.1.0.tgz", + "integrity": "sha512-ciDdpdYbeFG7og6/6J8lkTFxsSvwTdMLFkpVylAF6VNC22jssiWfquj2eyD4rJnzkRFPvIWJq8hvbfhsm+AjSg==", + "dependencies": { + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@popperjs/core": "^2.9.3" }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@iconify/utils/node_modules/mimic-fn": { + "node_modules/@chakra-ui/portal": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" + "resolved": "https://registry.npmjs.org/@chakra-ui/portal/-/portal-2.1.0.tgz", + "integrity": "sha512-9q9KWf6SArEcIq1gGofNcFPSWEyl+MfJjEUg/un1SMlQjaROOh3zYr+6JAwvcORiX7tyHosnmWC3d3wI2aPSQg==", + "dependencies": { + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" } }, - "node_modules/@iconify/utils/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, + "node_modules/@chakra-ui/progress": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/progress/-/progress-2.2.0.tgz", + "integrity": "sha512-qUXuKbuhN60EzDD9mHR7B67D7p/ZqNS2Aze4Pbl1qGGZfulPW0PY8Rof32qDtttDQBkzQIzFGE8d9QpAemToIQ==", "dependencies": { - "path-key": "^3.0.0" + "@chakra-ui/react-context": "2.1.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@iconify/utils/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, + "node_modules/@chakra-ui/provider": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/provider/-/provider-2.4.2.tgz", + "integrity": "sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==", "dependencies": { - "mimic-fn": "^2.1.0" + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/utils": "2.0.15" }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18", + "react-dom": ">=18" } }, - "node_modules/@iconify/utils/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "node_modules/@chakra-ui/radio": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/radio/-/radio-2.1.2.tgz", + "integrity": "sha512-n10M46wJrMGbonaghvSRnZ9ToTv/q76Szz284gv4QUWvyljQACcGrXIONUnQ3BIwbOfkRqSk7Xl/JgZtVfll+w==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@zag-js/focus-visible": "0.16.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-2.8.2.tgz", + "integrity": "sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==", + "dependencies": { + "@chakra-ui/accordion": "2.3.1", + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/avatar": "2.3.0", + "@chakra-ui/breadcrumb": "2.2.0", + "@chakra-ui/button": "2.1.0", + "@chakra-ui/card": "2.2.0", + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/control-box": "2.1.0", + "@chakra-ui/counter": "2.1.0", + "@chakra-ui/css-reset": "2.3.0", + "@chakra-ui/editable": "3.1.0", + "@chakra-ui/focus-lock": "2.1.0", + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/hooks": "2.2.1", + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/image": "2.1.0", + "@chakra-ui/input": "2.1.2", + "@chakra-ui/layout": "2.3.1", + "@chakra-ui/live-region": "2.1.0", + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/menu": "2.2.1", + "@chakra-ui/modal": "2.3.1", + "@chakra-ui/number-input": "2.1.2", + "@chakra-ui/pin-input": "2.1.0", + "@chakra-ui/popover": "2.2.1", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/progress": "2.2.0", + "@chakra-ui/provider": "2.4.2", + "@chakra-ui/radio": "2.1.2", + "@chakra-ui/react-env": "3.1.0", + "@chakra-ui/select": "2.1.2", + "@chakra-ui/skeleton": "2.1.0", + "@chakra-ui/skip-nav": "2.1.0", + "@chakra-ui/slider": "2.1.0", + "@chakra-ui/spinner": "2.1.0", + "@chakra-ui/stat": "2.1.1", + "@chakra-ui/stepper": "2.3.1", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/switch": "2.1.2", + "@chakra-ui/system": "2.6.2", + "@chakra-ui/table": "2.1.0", + "@chakra-ui/tabs": "3.0.0", + "@chakra-ui/tag": "3.1.1", + "@chakra-ui/textarea": "2.1.2", + "@chakra-ui/theme": "3.3.1", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/toast": "7.0.2", + "@chakra-ui/tooltip": "2.3.1", + "@chakra-ui/transition": "2.1.0", + "@chakra-ui/utils": "2.0.15", + "@chakra-ui/visually-hidden": "2.2.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/react-children-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-children-utils/-/react-children-utils-2.0.6.tgz", + "integrity": "sha512-QVR2RC7QsOsbWwEnq9YduhpqSFnZGvjjGREV8ygKi8ADhXh93C8azLECCUVgRJF2Wc+So1fgxmjLcbZfY2VmBA==", + "peerDependencies": { + "react": ">=18" + } }, - "node_modules/@iconify/utils/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" + "node_modules/@chakra-ui/react-context": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-context/-/react-context-2.1.0.tgz", + "integrity": "sha512-iahyStvzQ4AOwKwdPReLGfDesGG+vWJfEsn0X/NoGph/SkN+HXtv2sCfYFFR9k7bb+Kvc6YfpLlSuLvKMHi2+w==", + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@internationalized/date": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.1.tgz", - "integrity": "sha512-LUQIfwU9e+Fmutc/DpRTGXSdgYZLBegi4wygCWDSVmUdLTaMHsQyASDiJtREwanwKuQLq0hY76fCJ9J/9I2xOQ==", - "dev": true, + "node_modules/@chakra-ui/react-env": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-env/-/react-env-3.1.0.tgz", + "integrity": "sha512-Vr96GV2LNBth3+IKzr/rq1IcnkXv+MLmwjQH6C8BRtn3sNskgDFD5vLkVXcEhagzZMCh8FR3V/bzZPojBOyNhw==", "dependencies": { - "@swc/helpers": "^0.5.0" + "@chakra-ui/react-use-safe-layout-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, + "node_modules/@chakra-ui/react-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-types/-/react-types-2.0.7.tgz", + "integrity": "sha512-12zv2qIZ8EHwiytggtGvo4iLT0APris7T0qaAWqzpUGS0cdUtR8W+V1BJ5Ocq+7tA6dzQ/7+w5hmXih61TuhWQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-animation-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.1.0.tgz", + "integrity": "sha512-CFZkQU3gmDBwhqy0vC1ryf90BVHxVN8cTLpSyCpdmExUEtSEInSCGMydj2fvn7QXsz/za8JNdO2xxgJwxpLMtg==", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "node_modules/@chakra-ui/react-use-callback-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.1.0.tgz", + "integrity": "sha512-efnJrBtGDa4YaxDzDE90EnKD3Vkh5a1t3w7PhnRQmsphLy3g2UieasoKTlT2Hn118TwDjIv5ZjHJW6HbzXA9wQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-controllable-state": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.1.0.tgz", + "integrity": "sha512-QR/8fKNokxZUs4PfxjXuwl0fj/d71WPrmLJvEpCTkHjnzu7LnYvzoe2wB867IdooQJL0G1zBxl0Dq+6W1P3jpg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@chakra-ui/react-use-callback-ref": "2.1.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "engines": { - "node": ">=6.0.0" + "node_modules/@chakra-ui/react-use-disclosure": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.1.0.tgz", + "integrity": "sha512-Ax4pmxA9LBGMyEZJhhUZobg9C0t3qFE4jVF1tGBsrLDcdBeLR9fwOogIPY9Hf0/wqSlAryAimICbr5hkpa5GSw==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "engines": { - "node": ">=6.0.0" + "node_modules/@chakra-ui/react-use-event-listener": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.1.0.tgz", + "integrity": "sha512-U5greryDLS8ISP69DKDsYcsXRtAdnTQT+jjIlRYZ49K/XhUR/AqVZCK5BkR1spTDmO9H8SPhgeNKI70ODuDU/Q==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "node_modules/@chakra-ui/react-use-focus-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.1.0.tgz", + "integrity": "sha512-xzVboNy7J64xveLcxTIJ3jv+lUJKDwRM7Szwn9tNzUIPD94O3qwjV7DDCUzN2490nSYDF4OBMt/wuDBtaR3kUQ==", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "node_modules/@chakra-ui/react-use-focus-on-pointer-down": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.1.0.tgz", + "integrity": "sha512-2jzrUZ+aiCG/cfanrolsnSMDykCAbv9EK/4iUyZno6BYb3vziucmvgKuoXbMPAzWNtwUwtuMhkby8rc61Ue+Lg==", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@chakra-ui/react-use-event-listener": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + "node_modules/@chakra-ui/react-use-interval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-interval/-/react-use-interval-2.1.0.tgz", + "integrity": "sha512-8iWj+I/+A0J08pgEXP1J1flcvhLBHkk0ln7ZvGIyXiEyM6XagOTJpwNhiu+Bmk59t3HoV/VyvyJTa+44sEApuw==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" + }, + "peerDependencies": { + "react": ">=18" + } }, - "node_modules/@melt-ui/pp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@melt-ui/pp/-/pp-0.3.0.tgz", - "integrity": "sha512-b07Bdh8l2KcwKVCXOY+SoBw1dk9eWvQfMSi6SoacpRVyVmmfpi0kV4oGt3HYF0tUCB3sEmVicxse50ZzZxEzEA==", - "dev": true, + "node_modules/@chakra-ui/react-use-latest-ref": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-latest-ref/-/react-use-latest-ref-2.1.0.tgz", + "integrity": "sha512-m0kxuIYqoYB0va9Z2aW4xP/5b7BzlDeWwyXCH6QpT2PpW3/281L3hLCm1G0eOUcdVlayqrQqOeD6Mglq+5/xoQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-merge-refs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.1.0.tgz", + "integrity": "sha512-lERa6AWF1cjEtWSGjxWTaSMvneccnAVH4V4ozh8SYiN9fSPZLlSG3kNxfNzdFvMEhM7dnP60vynF7WjGdTgQbQ==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-outside-click": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.2.0.tgz", + "integrity": "sha512-PNX+s/JEaMneijbgAM4iFL+f3m1ga9+6QK0E5Yh4s8KZJQ/bLwZzdhMz8J/+mL+XEXQ5J0N8ivZN28B82N1kNw==", "dependencies": { - "estree-walker": "^3.0.3", - "magic-string": "^0.30.5" + "@chakra-ui/react-use-callback-ref": "2.1.0" }, - "engines": { - "pnpm": ">=8.6.3" + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-pan-event": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.1.0.tgz", + "integrity": "sha512-xmL2qOHiXqfcj0q7ZK5s9UjTh4Gz0/gL9jcWPA6GVf+A0Od5imEDa/Vz+533yQKWiNSm1QGrIj0eJAokc7O4fg==", + "dependencies": { + "@chakra-ui/event-utils": "2.0.8", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "framesync": "6.1.2" }, "peerDependencies": { - "@melt-ui/svelte": ">= 0.29.0", - "svelte": "^3.55.0 || ^4.0.0 || ^5.0.0-next.1" + "react": ">=18" } }, - "node_modules/@melt-ui/svelte": { - "version": "0.70.0", - "resolved": "https://registry.npmjs.org/@melt-ui/svelte/-/svelte-0.70.0.tgz", - "integrity": "sha512-ni14892MHJMAxSl2cz1pcgfnLR7fee1nNDJmx47hV19ewxSs8eQ8iguPrfx1ONtgjbp2YYVZhlpERi7szd30cA==", - "dev": true, + "node_modules/@chakra-ui/react-use-previous": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-previous/-/react-use-previous-2.1.0.tgz", + "integrity": "sha512-pjxGwue1hX8AFcmjZ2XfrQtIJgqbTF3Qs1Dy3d1krC77dEsiCUbQ9GzOBfDc8pfd60DrB5N2tg5JyHbypqh0Sg==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-safe-layout-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.1.0.tgz", + "integrity": "sha512-Knbrrx/bcPwVS1TorFdzrK/zWA8yuU/eaXDkNj24IrKoRlQrSBFarcgAEzlCHtzuhufP3OULPkELTzz91b0tCw==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-use-size": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-size/-/react-use-size-2.1.0.tgz", + "integrity": "sha512-tbLqrQhbnqOjzTaMlYytp7wY8BW1JpL78iG7Ru1DlV4EWGiAmXFGvtnEt9HftU0NJ0aJyjgymkxfVGI55/1Z4A==", "dependencies": { - "@floating-ui/core": "^1.3.1", - "@floating-ui/dom": "^1.4.5", - "@internationalized/date": "^3.5.0", - "dequal": "^2.0.3", - "focus-trap": "^7.5.2", - "nanoid": "^5.0.4" + "@zag-js/element-size": "0.10.5" }, "peerDependencies": { - "svelte": ">=3 <5" + "react": ">=18" } }, - "node_modules/@melt-ui/svelte/node_modules/nanoid": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.4.tgz", - "integrity": "sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.js" + "node_modules/@chakra-ui/react-use-timeout": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-timeout/-/react-use-timeout-2.1.0.tgz", + "integrity": "sha512-cFN0sobKMM9hXUhyCofx3/Mjlzah6ADaEl/AXl5Y+GawB5rgedgAcu2ErAgarEkwvsKdP6c68CKjQ9dmTQlJxQ==", + "dependencies": { + "@chakra-ui/react-use-callback-ref": "2.1.0" }, - "engines": { - "node": "^18 || >=20" + "peerDependencies": { + "react": ">=18" } }, - "node_modules/@microsoft/applicationinsights-analytics-js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-3.0.7.tgz", - "integrity": "sha512-HOcFA4JJUTz+KOqVQoE5g1ExL7Wm+sL2czLKQq1hDkmU4PeO4Oq/pi9SeR6iHDQpXCenMTvAkzvZ9A41ZKIOQA==", + "node_modules/@chakra-ui/react-use-update-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.1.0.tgz", + "integrity": "sha512-ND4Q23tETaR2Qd3zwCKYOOS1dfssojPLJMLvUtUbW5M9uW1ejYWgGUobeAiOVfSplownG8QYMmHTP86p/v0lbA==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@chakra-ui/react-utils": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@chakra-ui/react-utils/-/react-utils-2.0.12.tgz", + "integrity": "sha512-GbSfVb283+YA3kA8w8xWmzbjNWk14uhNpntnipHCftBibl0lxtQ9YqMFQLwuFOO0U2gYVocszqqDWX+XNKq9hw==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.7", - "@microsoft/applicationinsights-core-js": "3.0.7", - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@chakra-ui/utils": "2.0.15" }, "peerDependencies": { - "tslib": "*" + "react": ">=18" } }, - "node_modules/@microsoft/applicationinsights-cfgsync-js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-cfgsync-js/-/applicationinsights-cfgsync-js-3.0.7.tgz", - "integrity": "sha512-lp+FP5mg35KO1LuCss2wEOR3dYTFLeDbBkqIoQ9TnJje7Yt3gNKHANl9/b8nMmJyL9u2Lp0pc3wzM+InfO5xHw==", + "node_modules/@chakra-ui/select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/select/-/select-2.1.2.tgz", + "integrity": "sha512-ZwCb7LqKCVLJhru3DXvKXpZ7Pbu1TDZ7N0PdQ0Zj1oyVLJyrpef1u9HR5u0amOpqcH++Ugt0f5JSmirjNlctjA==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.7", - "@microsoft/applicationinsights-core-js": "3.0.7", - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "tslib": "*" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.7.tgz", - "integrity": "sha512-3y8ct8V2bGo7QaYVrfQcWZeOci2tUZhXkme3k7nKa2P7upSX/1d+dPF12EelxrtWVLxtfCQJkk+2W4M1AyejGQ==", + "node_modules/@chakra-ui/shared-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz", + "integrity": "sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==" + }, + "node_modules/@chakra-ui/skeleton": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skeleton/-/skeleton-2.1.0.tgz", + "integrity": "sha512-JNRuMPpdZGd6zFVKjVQ0iusu3tXAdI29n4ZENYwAJEMf/fN0l12sVeirOxkJ7oEL0yOx2AgEYFSKdbcAgfUsAQ==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.7", - "@microsoft/applicationinsights-core-js": "3.0.7", - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@chakra-ui/media-query": "3.3.0", + "@chakra-ui/react-use-previous": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "tslib": "*" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.7.tgz", - "integrity": "sha512-boumvLA7LZu0NmwT9ThpTAI64BNYUlOkFNcjUbYeKNEaE6CBPGX/z25XXlYu+j4hHldDaCn9zC1LuN7AuoMJSA==", + "node_modules/@chakra-ui/skip-nav": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/skip-nav/-/skip-nav-2.1.0.tgz", + "integrity": "sha512-Hk+FG+vadBSH0/7hwp9LJnLjkO0RPGnx7gBJWI4/SpoJf3e4tZlWYtwGj0toYY4aGKl93jVghuwGbDBEMoHDug==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/slider": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/slider/-/slider-2.1.0.tgz", + "integrity": "sha512-lUOBcLMCnFZiA/s2NONXhELJh6sY5WtbRykPtclGfynqqOo47lwWJx+VP7xaeuhDOPcWSSecWc9Y1BfPOCz9cQ==", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.7", - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@chakra-ui/number-utils": "2.0.7", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-callback-ref": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-latest-ref": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-pan-event": "2.1.0", + "@chakra-ui/react-use-size": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0" }, "peerDependencies": { - "tslib": "*" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.7.tgz", - "integrity": "sha512-sVnnVW4fWXzZdtUTVjuwH3xGa1cj+tW7r72voMZzyuNOZ41fBOCK9AqoV0nKP5VCgNjySwn6Rpbw82I4TKKosQ==", + "node_modules/@chakra-ui/spinner": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/spinner/-/spinner-2.1.0.tgz", + "integrity": "sha512-hczbnoXt+MMv/d3gE+hjQhmkzLiKuoTo42YhUG7Bs9OSv2lg1fZHW1fGNRFP3wTi6OIbD044U1P9HK+AOgFH3g==", "dependencies": { - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "tslib": "*" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@microsoft/applicationinsights-dependencies-js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-3.0.7.tgz", - "integrity": "sha512-nylC373IWXHUoz3FS9LclMIvGvX/KEeFFCPzZ0i1vjEsBwIB4LJ/5XwvtEk4F/kAXPZr6wFNoxecMhS9nKOfNg==", + "node_modules/@chakra-ui/stat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stat/-/stat-2.1.1.tgz", + "integrity": "sha512-LDn0d/LXQNbAn2KaR3F1zivsZCewY4Jsy1qShmfBMKwn6rI8yVlbvu6SiA3OpHS0FhxbsZxQI6HefEoIgtqY6Q==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.7", - "@microsoft/applicationinsights-core-js": "3.0.7", - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "tslib": "*" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@microsoft/applicationinsights-properties-js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-3.0.7.tgz", - "integrity": "sha512-vWSs16AJ7bsZxsT34Cv81d/5+SjjniNxNhI+XNzKSBnjhxITrhmRTEbhBbYQAP9118qQsEYW9liQBeLnxg6QSg==", + "node_modules/@chakra-ui/stepper": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/stepper/-/stepper-2.3.1.tgz", + "integrity": "sha512-ky77lZbW60zYkSXhYz7kbItUpAQfEdycT0Q4bkHLxfqbuiGMf8OmgZOQkOB9uM4v0zPwy2HXhe0vq4Dd0xa55Q==", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.7", - "@microsoft/applicationinsights-core-js": "3.0.7", - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "tslib": "*" + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@microsoft/applicationinsights-shims": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", - "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "node_modules/@chakra-ui/styled-system": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/styled-system/-/styled-system-2.9.2.tgz", + "integrity": "sha512-To/Z92oHpIE+4nk11uVMWqo2GGRS86coeMmjxtpnErmWRdLcp1WVCVRAvn+ZwpLiNR+reWFr2FFqJRsREuZdAg==", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@chakra-ui/shared-utils": "2.0.5", + "csstype": "^3.1.2", + "lodash.mergewith": "4.6.2" } }, - "node_modules/@microsoft/applicationinsights-web": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-3.0.7.tgz", - "integrity": "sha512-UfX/Fl4Fe2Q72/MXJLCb5MBwTGdMBXmB/JW+eF2dDt6zDZa2OOe8u7daGXjh2ewFdBqb4feb02PDCSDg+nsBeA==", + "node_modules/@chakra-ui/switch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/switch/-/switch-2.1.2.tgz", + "integrity": "sha512-pgmi/CC+E1v31FcnQhsSGjJnOE2OcND4cKPyTE+0F+bmGm48Q/b5UmKD9Y+CmZsrt/7V3h8KNczowupfuBfIHA==", "dependencies": { - "@microsoft/applicationinsights-analytics-js": "3.0.7", - "@microsoft/applicationinsights-cfgsync-js": "3.0.7", - "@microsoft/applicationinsights-channel-js": "3.0.7", - "@microsoft/applicationinsights-common": "3.0.7", - "@microsoft/applicationinsights-core-js": "3.0.7", - "@microsoft/applicationinsights-dependencies-js": "3.0.7", - "@microsoft/applicationinsights-properties-js": "3.0.7", - "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@chakra-ui/checkbox": "2.3.2", + "@chakra-ui/shared-utils": "2.0.5" }, "peerDependencies": { - "tslib": "*" + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18" } }, - "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "node_modules/@chakra-ui/system": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/system/-/system-2.6.2.tgz", + "integrity": "sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@chakra-ui/color-mode": "2.2.0", + "@chakra-ui/object-utils": "2.1.0", + "@chakra-ui/react-utils": "2.0.12", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme-utils": "2.0.21", + "@chakra-ui/utils": "2.0.15", + "react-fast-compare": "3.2.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0", + "@emotion/styled": "^11.0.0", + "react": ">=18" } }, - "node_modules/@nevware21/ts-async": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.4.0.tgz", - "integrity": "sha512-dbV826TTehQIBIJjh8GDSbwn1Z6+cnkyNbRlpcpdBPH8mROD2zabIUKqWcw9WRdTjjUIm21K+OR4DXWlAyOVTQ==", + "node_modules/@chakra-ui/table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/table/-/table-2.1.0.tgz", + "integrity": "sha512-o5OrjoHCh5uCLdiUb0Oc0vq9rIAeHSIRScc2ExTC9Qg/uVZl2ygLrjToCaKfaaKl1oQexIeAcZDKvPG8tVkHyQ==", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@nevware21/ts-utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", - "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + "node_modules/@chakra-ui/tabs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/tabs/-/tabs-3.0.0.tgz", + "integrity": "sha512-6Mlclp8L9lqXmsGWF5q5gmemZXOiOYuh0SGT/7PgJVNPz3LXREXlXg2an4MBUD8W5oTkduCX+3KTMCwRrVrDYw==", + "dependencies": { + "@chakra-ui/clickable": "2.1.0", + "@chakra-ui/descendant": "3.1.0", + "@chakra-ui/lazy-utils": "2.0.5", + "@chakra-ui/react-children-utils": "2.0.6", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-controllable-state": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/react-use-safe-layout-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "node_modules/@chakra-ui/tag": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tag/-/tag-3.1.1.tgz", + "integrity": "sha512-Bdel79Dv86Hnge2PKOU+t8H28nm/7Y3cKd4Kfk9k3lOpUh4+nkSGe58dhRzht59lEqa4N9waCgQiBdkydjvBXQ==", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@chakra-ui/icon": "3.2.0", + "@chakra-ui/react-context": "2.1.0" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" + "node_modules/@chakra-ui/textarea": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/textarea/-/textarea-2.1.2.tgz", + "integrity": "sha512-ip7tvklVCZUb2fOHDb23qPy/Fr2mzDOGdkrpbNi50hDCiV4hFX02jdQJdi3ydHZUyVgZVBKPOJ+lT9i7sKA2wA==", + "dependencies": { + "@chakra-ui/form-control": "2.2.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@chakra-ui/theme": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme/-/theme-3.3.1.tgz", + "integrity": "sha512-Hft/VaT8GYnItGCBbgWd75ICrIrIFrR7lVOhV/dQnqtfGqsVDlrztbSErvMkoPKt0UgAkd9/o44jmZ6X4U2nZQ==", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/theme-tools": "2.1.2" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.8.0" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.0.tgz", - "integrity": "sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] + "node_modules/@chakra-ui/theme-tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-tools/-/theme-tools-2.1.2.tgz", + "integrity": "sha512-Qdj8ajF9kxY4gLrq7gA+Azp8CtFHGO9tWMN2wfF9aQNgG9AuMhPrUzMq9AMQ0MXiYcgNq/FD3eegB43nHVmXVA==", + "dependencies": { + "@chakra-ui/anatomy": "2.2.2", + "@chakra-ui/shared-utils": "2.0.5", + "color2k": "^2.0.2" + }, + "peerDependencies": { + "@chakra-ui/styled-system": ">=2.0.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.0.tgz", - "integrity": "sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] + "node_modules/@chakra-ui/theme-utils": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@chakra-ui/theme-utils/-/theme-utils-2.0.21.tgz", + "integrity": "sha512-FjH5LJbT794r0+VSCXB3lT4aubI24bLLRWB+CuRKHijRvsOg717bRdUN/N1fEmEpFnRVrbewttWh/OQs0EWpWw==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1", + "lodash.mergewith": "4.6.2" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.0.tgz", - "integrity": "sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@chakra-ui/toast": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@chakra-ui/toast/-/toast-7.0.2.tgz", + "integrity": "sha512-yvRP8jFKRs/YnkuE41BVTq9nB2v/KDRmje9u6dgDmE5+1bFt3bwjdf9gVbif4u5Ve7F7BGk5E093ARRVtvLvXA==", + "dependencies": { + "@chakra-ui/alert": "2.2.2", + "@chakra-ui/close-button": "2.1.1", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-context": "2.1.0", + "@chakra-ui/react-use-timeout": "2.1.0", + "@chakra-ui/react-use-update-effect": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5", + "@chakra-ui/styled-system": "2.9.2", + "@chakra-ui/theme": "3.3.1" + }, + "peerDependencies": { + "@chakra-ui/system": "2.6.2", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.0.tgz", - "integrity": "sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==", - "cpu": [ - "x64" - ], + "node_modules/@chakra-ui/tooltip": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/tooltip/-/tooltip-2.3.1.tgz", + "integrity": "sha512-Rh39GBn/bL4kZpuEMPPRwYNnccRCL+w9OqamWHIB3Qboxs6h8cOyXfIdGxjo72lvhu1QI/a4KFqkM3St+WfC0A==", + "dependencies": { + "@chakra-ui/dom-utils": "2.1.0", + "@chakra-ui/popper": "3.1.0", + "@chakra-ui/portal": "2.1.0", + "@chakra-ui/react-types": "2.0.7", + "@chakra-ui/react-use-disclosure": "2.1.0", + "@chakra-ui/react-use-event-listener": "2.1.0", + "@chakra-ui/react-use-merge-refs": "2.1.0", + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "framer-motion": ">=4.0.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@chakra-ui/transition": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/transition/-/transition-2.1.0.tgz", + "integrity": "sha512-orkT6T/Dt+/+kVwJNy7zwJ+U2xAZ3EU7M3XCs45RBvUnZDr/u9vdmaM/3D/rOpmQJWgQBwKPJleUXrYWUagEDQ==", + "dependencies": { + "@chakra-ui/shared-utils": "2.0.5" + }, + "peerDependencies": { + "framer-motion": ">=4.0.0", + "react": ">=18" + } + }, + "node_modules/@chakra-ui/utils": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.15.tgz", + "integrity": "sha512-El4+jL0WSaYYs+rJbuYFDbjmfCcfGDmRY95GO4xwzit6YAPZBLcR65rOEwLps+XWluZTy1xdMrusg/hW0c1aAA==", + "dependencies": { + "@types/lodash.mergewith": "4.6.7", + "css-box-model": "1.2.1", + "framesync": "6.1.2", + "lodash.mergewith": "4.6.2" + } + }, + "node_modules/@chakra-ui/visually-hidden": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/visually-hidden/-/visually-hidden-2.2.0.tgz", + "integrity": "sha512-KmKDg01SrQ7VbTD3+cPWf/UfpF5MSwm3v7MWi0n5t8HnnadT13MF0MJCDSXbBWnzLv1ZKJ6zlyAOeARWX+DpjQ==", + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", + "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", + "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.2", + "@emotion/serialize": "^1.1.4", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", + "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", + "cpu": [ + "arm" + ], "dev": true, "optional": true, "os": [ - "darwin" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.0.tgz", - "integrity": "sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", + "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", "cpu": [ - "arm" + "arm64" ], "dev": true, "optional": true, "os": [ - "linux" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.0.tgz", - "integrity": "sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==", + "node_modules/@esbuild/android-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", + "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", "cpu": [ - "arm64" + "x64" ], "dev": true, "optional": true, "os": [ - "linux" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.0.tgz", - "integrity": "sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", + "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", "cpu": [ "arm64" ], "dev": true, "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.0.tgz", - "integrity": "sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", + "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", "cpu": [ - "riscv64" + "x64" ], "dev": true, "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.0.tgz", - "integrity": "sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", + "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", "cpu": [ - "x64" + "arm64" ], "dev": true, "optional": true, "os": [ - "linux" - ] + "freebsd" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.0.tgz", - "integrity": "sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", + "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", "cpu": [ "x64" ], "dev": true, "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", + "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.0.tgz", - "integrity": "sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", + "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", "cpu": [ "arm64" ], "dev": true, "optional": true, "os": [ - "win32" - ] + "linux" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.0.tgz", - "integrity": "sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", + "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", "cpu": [ "ia32" ], "dev": true, "optional": true, "os": [ - "win32" - ] + "linux" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.0.tgz", - "integrity": "sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", + "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", "cpu": [ - "x64" + "loong64" ], "dev": true, "optional": true, "os": [ - "win32" - ] - }, - "node_modules/@sentry-internal/feedback": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.100.1.tgz", - "integrity": "sha512-yqcRVnjf+qS+tC4NxOKLJOaSJ+csHmh/dHUzvCTkf5rLsplwXYRnny2r0tqGTQ4tuXMxwgSMKPYwicg81P+xuw==", - "dependencies": { - "@sentry/core": "7.100.1", - "@sentry/types": "7.100.1", - "@sentry/utils": "7.100.1" - }, + "linux" + ], "engines": { "node": ">=12" } }, - "node_modules/@sentry-internal/replay-canvas": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.100.1.tgz", - "integrity": "sha512-TnqxqJGhbFhhYRhTG2WLFer+lVieV7mNGeIxFBiw1L4kuj8KGl+C0sknssKyZSRVJFSahhHIosHJGRMkkD//7g==", - "dependencies": { - "@sentry/core": "7.100.1", - "@sentry/replay": "7.100.1", - "@sentry/types": "7.100.1", - "@sentry/utils": "7.100.1" - }, - "engines": { + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", + "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { "node": ">=12" } }, - "node_modules/@sentry-internal/tracing": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.100.1.tgz", - "integrity": "sha512-+u9RRf5eL3StiyiRyAHZmdkAR7GTSGx4Mt4Lmi5NEtCcWlTGZ1QgW2r8ZbhouVmTiJkjhQgYCyej3cojtazeJg==", - "dependencies": { - "@sentry/core": "7.100.1", - "@sentry/types": "7.100.1", - "@sentry/utils": "7.100.1" - }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", + "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@sentry/browser": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.100.1.tgz", - "integrity": "sha512-IxHQ08ixf0bmaWpe4yt1J4UUsOpg02fxax9z3tOQYXw5MSzz5pDXn8M8DFUVJB3wWuyXhHXTub9yD3VIP9fnoA==", - "dependencies": { - "@sentry-internal/feedback": "7.100.1", - "@sentry-internal/replay-canvas": "7.100.1", - "@sentry-internal/tracing": "7.100.1", - "@sentry/core": "7.100.1", - "@sentry/replay": "7.100.1", - "@sentry/types": "7.100.1", - "@sentry/utils": "7.100.1" - }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", + "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@sentry/core": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.100.1.tgz", - "integrity": "sha512-f+ItUge/o9AjlveQq0ZUbQauKlPH1FIJbC1TRaYLJ4KNfOdrsh8yZ29RmWv0cFJ/e+FGTr603gWpRPObF5rM8Q==", - "dependencies": { - "@sentry/types": "7.100.1", - "@sentry/utils": "7.100.1" - }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", + "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@sentry/replay": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.100.1.tgz", - "integrity": "sha512-B1NFjzGEFaqejxBRdUyEzH8ChXc2kfiqlA/W/Lg0aoWIl2/7nuMk+l4ld9gW5F5bIAXDTVd5vYltb1lWEbpr7w==", - "dependencies": { - "@sentry-internal/tracing": "7.100.1", - "@sentry/core": "7.100.1", - "@sentry/types": "7.100.1", - "@sentry/utils": "7.100.1" - }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", + "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=12" } }, - "node_modules/@sentry/svelte": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry/svelte/-/svelte-7.100.1.tgz", - "integrity": "sha512-2oZJNFZYfXeDX05mvBBLtve1KRfCI0DgrxZYe9qbYQIe7snobNSydTK+wIHX/SJeVUz3Z+qyovPqFzjkJPTFnw==", - "dependencies": { - "@sentry/browser": "7.100.1", - "@sentry/core": "7.100.1", - "@sentry/types": "7.100.1", - "@sentry/utils": "7.100.1", - "magic-string": "^0.30.0" - }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", + "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" - }, - "peerDependencies": { - "svelte": "3.x || 4.x" + "node": ">=12" } }, - "node_modules/@sentry/types": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.100.1.tgz", - "integrity": "sha512-fLM+LedHuKzOd8IhXBqaQuym+AA519MGjeczBa5kGakes/BbAsUMwsNfjsKQedp7Kh44RgYF99jwoRPK2oDrXw==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", + "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@sentry/utils": { - "version": "7.100.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.100.1.tgz", - "integrity": "sha512-Ve6dXr1o6xiBe3VCoJgiutmBKrugryI65EZAbYto5XI+t+PjiLLf9wXtEMF24ZrwImo4Lv3E9Uqza+fWkEbw6A==", - "dependencies": { - "@sentry/types": "7.100.1" - }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", + "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", + "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.1.tgz", - "integrity": "sha512-CGURX6Ps+TkOovK6xV+Y2rn8JKa8ZPUHPZ/NKgCxAmgBrXReavzFl8aOSCj3kQ1xqT7yGJj53hjcV/gqwDAaWA==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", + "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", + "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0-next.0 || ^2.0.0", - "debug": "^4.3.4", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "svelte-hmr": "^0.15.3", - "vitefu": "^0.2.5" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^18.0.0 || >=20" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.0.0.tgz", - "integrity": "sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==", + "node_modules/@eslint-community/regexpp": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { - "debug": "^4.3.4" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.0.0 || >=20" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@swc/helpers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.3.tgz", - "integrity": "sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { - "tslib": "^2.4.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@tensorflow/tfjs": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.15.0.tgz", - "integrity": "sha512-SdhKYAx/UiMJuKYxf3aXMOuK4j+rwEGRNlwAMIYPYJAFMySdqZ7hC4ZV6mB8D4LAjkgk35y3zOJ/3MWamstKdg==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.15.0", - "@tensorflow/tfjs-backend-webgl": "4.15.0", - "@tensorflow/tfjs-converter": "4.15.0", - "@tensorflow/tfjs-core": "4.15.0", - "@tensorflow/tfjs-data": "4.15.0", - "@tensorflow/tfjs-layers": "4.15.0", - "argparse": "^1.0.10", - "chalk": "^4.1.0", - "core-js": "3.29.1", - "regenerator-runtime": "^0.13.5", - "yargs": "^16.0.3" + "type-fest": "^0.20.2" }, - "bin": { - "tfjs-custom-module": "dist/tools/custom_module/cli.js" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tensorflow/tfjs-backend-cpu": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.15.0.tgz", - "integrity": "sha512-w3ecwXOdp+usf0s8lkLVl7+hCkKJY5Fm4LxqJ2Oy5MeeMNbwka8fDt8xyW5gOf0/gAaeG3qMKkh0lo6rr9fRlw==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { - "@types/seedrandom": "^2.4.28", - "seedrandom": "^3.0.5" + "brace-expansion": "^1.1.7" }, "engines": { - "yarn": ">= 1.3.2" + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" }, - "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@tensorflow/tfjs-backend-webgl": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.15.0.tgz", - "integrity": "sha512-FoOva3KjKvWVHAXWAW5Ojboz1IbM1K9i8ggNG7czJgE0La4JHMo814UHSoE6Rc0hkRoOpvDUa+FxsqYOBEhuzQ==", - "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.15.0", - "@types/offscreencanvas": "~2019.3.0", - "@types/seedrandom": "^2.4.28", - "seedrandom": "^3.0.5" + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@formatjs/cli": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.2.12.tgz", + "integrity": "sha512-bt1NEgkeYN8N9zWcpsPu3fZ57vv+biA+NtIQBlyOZnCp1bcvh+vNTXvmwF4C5qxqDtCylpOIb3yi3Ktgp4v0JQ==", + "dev": true, + "bin": { + "formatjs": "bin/formatjs" }, "engines": { - "yarn": ">= 1.3.2" + "node": ">= 16" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0" + "@glimmer/env": "^0.1.7", + "@glimmer/reference": "^0.91.1 || ^0.92.0", + "@glimmer/syntax": "^0.92.0", + "@glimmer/validator": "^0.92.0", + "@vue/compiler-core": "^3.4.0", + "content-tag": "^2.0.1", + "ember-template-recast": "^6.1.4", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "@glimmer/env": { + "optional": true + }, + "@glimmer/reference": { + "optional": true + }, + "@glimmer/syntax": { + "optional": true + }, + "@glimmer/validator": { + "optional": true + }, + "@vue/compiler-core": { + "optional": true + }, + "content-tag": { + "optional": true + }, + "ember-template-recast": { + "optional": true + }, + "vue": { + "optional": true + } } }, - "node_modules/@tensorflow/tfjs-converter": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.15.0.tgz", - "integrity": "sha512-694igyzRcJYf/l6F12l3Sz6hw0xPjihlxXEpIpypYieoq/8WBIoDgigMA/gv0NylkJSfPwwuYs7GF2/zKH2Tmg==", - "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0" + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" } }, - "node_modules/@tensorflow/tfjs-core": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.15.0.tgz", - "integrity": "sha512-0D9olf5cdMNvJKpmY4yiN0Br1pabOyYDwRyBpl02/Hf6MxiOAi+pXqs/Xa1342g9H2CzqeL1oNxz7nRKa71GyA==", + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", "dependencies": { - "@types/long": "^4.0.1", - "@types/offscreencanvas": "~2019.7.0", - "@types/seedrandom": "^2.4.28", - "@webgpu/types": "0.1.38", - "long": "4.0.0", - "node-fetch": "~2.6.1", - "seedrandom": "^3.0.5" - }, - "engines": { - "yarn": ">= 1.3.2" + "tslib": "^2.4.0" } }, - "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { - "version": "2019.7.3", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", - "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==" + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", + "tslib": "^2.4.0" + } }, - "node_modules/@tensorflow/tfjs-data": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.15.0.tgz", - "integrity": "sha512-9iDWloyW/tfw11UlVhsAan+ekfGDoPYg2yS5f+43ixdwbfe0jWc/azDhIXoJALMfe7TTLmbMsx3A64e43RoeOw==", + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", "dependencies": { - "@types/node-fetch": "^2.1.2", - "node-fetch": "~2.6.1", - "string_decoder": "^1.3.0" + "@formatjs/ecma402-abstract": "2.0.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.4.tgz", + "integrity": "sha512-56483O+HVcL0c7VucAS2tyH020mt9XTozZO67cwtGg0a7KWDukS/FzW3OnvaHmTHDuYsoPIzO+ZHVfU6fT/bJw==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/intl-displaynames": "6.6.8", + "@formatjs/intl-listformat": "7.5.7", + "intl-messageformat": "10.5.14", + "tslib": "^2.4.0" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0", - "seedrandom": "^3.0.5" + "typescript": "^4.7 || 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@tensorflow/tfjs-layers": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.15.0.tgz", - "integrity": "sha512-PwIXB7UtGyKVmIcbonqdtArcBgva8DOxeZqklyvb/zfg17GpWeh2++eUHKSpAl5K0mdO5Y2pL4ssD9p6AQqk9w==", - "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0" + "node_modules/@formatjs/intl-displaynames": { + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.6.8.tgz", + "integrity": "sha512-Lgx6n5KxN16B3Pb05z3NLEBQkGoXnGjkTBNCZI+Cn17YjHJ3fhCeEJJUqRlIZmJdmaXQhjcQVDp6WIiNeRYT5g==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" } }, - "node_modules/@tensorflow/tfjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@formatjs/intl-listformat": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.5.7.tgz", + "integrity": "sha512-MG2TSChQJQT9f7Rlv+eXwUFiG24mKSzmF144PLb8m8OixyXqn4+YWU+5wZracZGCgVTVmx8viCf7IH3QXoiB2g==", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" } }, - "node_modules/@tensorflow/tfjs/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "tslib": "^2.4.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=10.10.0" } }, - "node_modules/@tensorflow/tfjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=7.0.0" + "node": "*" } }, - "node_modules/@tensorflow/tfjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, - "node_modules/@tensorflow/tfjs/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@tensorflow/tfjs/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@testing-library/dom": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", - "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "node_modules/@jest/types/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -3345,7 +4225,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@testing-library/dom/node_modules/chalk": { + "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -3361,7 +4241,7 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@testing-library/dom/node_modules/color-convert": { + "node_modules/@jest/types/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -3373,13 +4253,13 @@ "node": ">=7.0.0" } }, - "node_modules/@testing-library/dom/node_modules/color-name": { + "node_modules/@jest/types/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@testing-library/dom/node_modules/has-flag": { + "node_modules/@jest/types/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", @@ -3388,7 +4268,7 @@ "node": ">=8" } }, - "node_modules/@testing-library/dom/node_modules/supports-color": { + "node_modules/@jest/types/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", @@ -3400,1234 +4280,3534 @@ "node": ">=8" } }, - "node_modules/@testing-library/jest-dom": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", - "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", - "dev": true, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@adobe/css-tools": "^4.3.2", - "@babel/runtime": "^7.9.2", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "lodash": "^4.17.15", - "redent": "^3.0.0" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - }, - "peerDependencies": { - "@jest/globals": ">= 28", - "@types/jest": ">= 28", - "jest": ">= 28", - "vitest": ">= 0.32" - }, - "peerDependenciesMeta": { - "@jest/globals": { - "optional": true - }, - "@types/jest": { - "optional": true - }, - "jest": { - "optional": true - }, - "vitest": { - "optional": true - } + "node": ">=6.0.0" } }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=6.0.0" } }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, - "node_modules/@testing-library/jest-dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/@testing-library/jest-dom/node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/@testing-library/jest-dom/node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@playwright/test": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", + "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "playwright": "1.45.0" + }, + "bin": { + "playwright": "cli.js" }, "engines": { - "node": ">=8" + "node": ">=18" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" } }, - "node_modules/@testing-library/svelte": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-4.0.5.tgz", - "integrity": "sha512-P7X3mpYv/My4hBZfxVxTFV5KcA+EoWfRCguWP7WQdYj6HMdg/L+LiwG4ocvLe+hupedrC7dwcy85JlxKplJp2g==", + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", "dev": true, "dependencies": { - "@testing-library/dom": "^9.3.1" + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" }, "engines": { - "node": ">= 10" + "node": ">=14.0.0" }, "peerDependencies": { - "svelte": "^3 || ^4" + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@tsconfig/svelte": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-4.0.1.tgz", - "integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==", - "dev": true - }, - "node_modules/@tweenjs/tween.js": { - "version": "18.6.4", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", - "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==", - "dev": true - }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true - }, - "node_modules/@types/browser-lang": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/browser-lang/-/browser-lang-0.1.2.tgz", - "integrity": "sha512-F6UrLn++11o967g8+G4c0mILIuSuyhpE9N989T4Rr+sgHqYpdrAbPgp3KOPPf4AA2A09dQDUB8/3K1GBG9Daeg==", - "dev": true - }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", "dev": true, "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@types/d3-array": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", - "dev": true - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", "dev": true, "dependencies": { - "@types/d3-selection": "*" + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.0.tgz", + "integrity": "sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "dev": true - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "dev": true + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.0.tgz", + "integrity": "sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@types/d3-array": "*", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.0.tgz", + "integrity": "sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.0.tgz", + "integrity": "sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.0.tgz", + "integrity": "sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.0.tgz", + "integrity": "sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.0.tgz", + "integrity": "sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.0.tgz", + "integrity": "sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.0.tgz", + "integrity": "sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.0.tgz", + "integrity": "sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.0.tgz", + "integrity": "sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.0.tgz", + "integrity": "sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.0.tgz", + "integrity": "sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dev": true, + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@tensorflow/tfjs": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.15.0.tgz", + "integrity": "sha512-SdhKYAx/UiMJuKYxf3aXMOuK4j+rwEGRNlwAMIYPYJAFMySdqZ7hC4ZV6mB8D4LAjkgk35y3zOJ/3MWamstKdg==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.15.0", + "@tensorflow/tfjs-backend-webgl": "4.15.0", + "@tensorflow/tfjs-converter": "4.15.0", + "@tensorflow/tfjs-core": "4.15.0", + "@tensorflow/tfjs-data": "4.15.0", + "@tensorflow/tfjs-layers": "4.15.0", + "argparse": "^1.0.10", + "chalk": "^4.1.0", + "core-js": "3.29.1", + "regenerator-runtime": "^0.13.5", + "yargs": "^16.0.3" + }, + "bin": { + "tfjs-custom-module": "dist/tools/custom_module/cli.js" + } + }, + "node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.15.0.tgz", + "integrity": "sha512-w3ecwXOdp+usf0s8lkLVl7+hCkKJY5Fm4LxqJ2Oy5MeeMNbwka8fDt8xyW5gOf0/gAaeG3qMKkh0lo6rr9fRlw==", + "dependencies": { + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.15.0" + } + }, + "node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.15.0.tgz", + "integrity": "sha512-FoOva3KjKvWVHAXWAW5Ojboz1IbM1K9i8ggNG7czJgE0La4JHMo814UHSoE6Rc0hkRoOpvDUa+FxsqYOBEhuzQ==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.15.0", + "@types/offscreencanvas": "~2019.3.0", + "@types/seedrandom": "^2.4.28", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.15.0" + } + }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.15.0.tgz", + "integrity": "sha512-694igyzRcJYf/l6F12l3Sz6hw0xPjihlxXEpIpypYieoq/8WBIoDgigMA/gv0NylkJSfPwwuYs7GF2/zKH2Tmg==", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.15.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.15.0.tgz", + "integrity": "sha512-0D9olf5cdMNvJKpmY4yiN0Br1pabOyYDwRyBpl02/Hf6MxiOAi+pXqs/Xa1342g9H2CzqeL1oNxz7nRKa71GyA==", + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.38", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==" + }, + "node_modules/@tensorflow/tfjs-data": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.15.0.tgz", + "integrity": "sha512-9iDWloyW/tfw11UlVhsAan+ekfGDoPYg2yS5f+43ixdwbfe0jWc/azDhIXoJALMfe7TTLmbMsx3A64e43RoeOw==", + "dependencies": { + "@types/node-fetch": "^2.1.2", + "node-fetch": "~2.6.1", + "string_decoder": "^1.3.0" + }, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.15.0", + "seedrandom": "^3.0.5" + } + }, + "node_modules/@tensorflow/tfjs-layers": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.15.0.tgz", + "integrity": "sha512-PwIXB7UtGyKVmIcbonqdtArcBgva8DOxeZqklyvb/zfg17GpWeh2++eUHKSpAl5K0mdO5Y2pL4ssD9p6AQqk9w==", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.15.0" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@tensorflow/tfjs/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@tensorflow/tfjs/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.2.0.tgz", + "integrity": "sha512-CytIvb6tVOADRngTHGWNxH8LPgO/3hi/BdCEHOf7Qd2GvZVClhVP0Wo/QHzWhpki49Bk0b4VT6xpt3fx8HTSIw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", + "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/jest-dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", + "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/@testing-library/dom": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/react/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/react/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/react/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/react/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@testing-library/react/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==", + "dev": true + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/browser-lang": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/browser-lang/-/browser-lang-0.1.2.tgz", + "integrity": "sha512-F6UrLn++11o967g8+G4c0mILIuSuyhpE9N989T4Rr+sgHqYpdrAbPgp3KOPPf4AA2A09dQDUB8/3K1GBG9Daeg==", + "dev": true + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", + "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "dependencies": { "@types/geojson": "*" } }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "dev": true + "node_modules/@types/d3-hierarchy": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", + "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.2.tgz", + "integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dev": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true + }, + "node_modules/@types/geojson": { + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", + "dev": true + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@types/jest/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==" + }, + "node_modules/@types/lodash.mergewith": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", + "integrity": "sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, + "node_modules/@types/node": { + "version": "18.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", + "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-hid": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/node-hid/-/node-hid-1.3.4.tgz", + "integrity": "sha512-0ootpsYetN9vjqkDSwm/cA4fk/9yGM/PO0X8SLPE/BzXlUaBelImMWMymtF9QEoEzxY0pnhcROIJM0CNSUqO8w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.3.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", + "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@types/seedrandom": { + "version": "2.4.34", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", + "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/stats.js": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", + "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", + "dev": true + }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.9", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", + "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", + "dev": true, + "dependencies": { + "@types/jest": "*" + } + }, + "node_modules/@types/three": { + "version": "0.152.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.1.tgz", + "integrity": "sha512-PMOCQnx9JRmq+2OUGTPoY9h1hTWD2L7/nmuW/SyNq1Vbq3Lwt3MNdl3wYSa4DvLTGv62NmIXD9jYdAOwohwJyw==", + "dev": true, + "dependencies": { + "@tweenjs/tween.js": "~18.6.4", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.9", + "lil-gui": "~0.17.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true + }, + "node_modules/@types/usb": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/usb/-/usb-1.5.4.tgz", + "integrity": "sha512-NOUza/8yuswu6RoECQyPHEjA34qpDaeONQ72fm+bCnnN2DJjDePAY+NsmV17H88oIlq4JlJ2mD5Kh5d6R2MwTQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/w3c-web-serial": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/w3c-web-serial/-/w3c-web-serial-1.0.6.tgz", + "integrity": "sha512-5IlDdQ2C56sCVwc7CUlqT9Axxw+0V/FbWRbErklYIzZ5mKL9s4l7epXHygn+4X7L2nmAPnVvRl55XUVo0760Rg==" + }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz", + "integrity": "sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz", + "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==", + "dev": true + }, + "node_modules/@types/webxr": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.10.tgz", + "integrity": "sha512-n3u5sqXQJhf1CS68mw3Wf16FQ4cRPNBBwdYLFzq3UddiADOim1Pn3Y6PBdDilz1vOJF3ybLxJ8ZEDlLIzrOQZg==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz", + "integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/type-utils": "7.14.1", + "@typescript-eslint/utils": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz", + "integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz", + "integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz", + "integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/utils": "7.14.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz", + "integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz", + "integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz", + "integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz", + "integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.14.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", + "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.5", + "@babel/plugin-transform-react-jsx-self": "^7.24.5", + "@babel/plugin-transform-react-jsx-source": "^7.24.1", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", + "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", + "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.6.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", + "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/snapshot/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@vitest/spy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", + "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@webgpu/types": { + "version": "0.1.38", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", + "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==" + }, + "node_modules/@zag-js/dom-query": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.16.0.tgz", + "integrity": "sha512-Oqhd6+biWyKnhKwFFuZrrf6lxBz2tX2pRQe6grUnYwO6HJ8BcbqZomy2lpOdr+3itlaUqx+Ywj5E5ZZDr/LBfQ==" + }, + "node_modules/@zag-js/element-size": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.10.5.tgz", + "integrity": "sha512-uQre5IidULANvVkNOBQ1tfgwTQcGl4hliPSe69Fct1VfYb2Fd0jdAcGzqQgPhfrXFpR62MxLPB7erxJ/ngtL8w==" + }, + "node_modules/@zag-js/focus-visible": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.16.0.tgz", + "integrity": "sha512-a7U/HSopvQbrDU4GLerpqiMcHKEkQkNPeDZJWz38cw/6Upunh41GjHetq5TB84hxyCaDzJ6q2nEdNoBQfC0FKA==", + "dependencies": { + "@zag-js/dom-query": "0.16.0" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", - "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", - "dev": true + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, "dependencies": { - "@types/d3-selection": "*" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "@types/d3-dsv": "*" + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-lang": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/browser-lang/-/browser-lang-0.2.1.tgz", + "integrity": "sha512-+xmtsTxVZKWrKHoNUQp4Tm7BEXlnMwOMAHZAh1SSot1+n04qHLFIH0K5anX52k5BkcauggbaNlWT8f3bVwDh/Q==" + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/@types/d3-force": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", - "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==", - "dev": true - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, - "dependencies": { - "@types/geojson": "*" + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", - "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==", - "dev": true + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { - "@types/d3-color": "*" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/d3-path": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.2.tgz", - "integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==", - "dev": true - }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", - "dev": true - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", - "dev": true - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", - "dev": true + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } }, - "node_modules/@types/d3-scale": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", - "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "dependencies": { - "@types/d3-time": "*" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", - "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", - "dev": true - }, - "node_modules/@types/d3-selection": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", - "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==", - "dev": true + "node_modules/caniuse-lite": { + "version": "1.0.30001570", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", + "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] }, - "node_modules/@types/d3-shape": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", - "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "dependencies": { - "@types/d3-path": "*" + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@types/d3-time": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", - "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", - "dev": true - }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", - "dev": true + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "dev": true + "node_modules/chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } }, - "node_modules/@types/d3-transition": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", - "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, "dependencies": { - "@types/d3-selection": "*" + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" } }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" - }, - "node_modules/@types/geojson": { - "version": "7946.0.13", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", - "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", - "dev": true - }, - "node_modules/@types/js-cookie": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", - "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", - "dev": true + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/clear-any-console": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/clear-any-console/-/clear-any-console-1.16.2.tgz", + "integrity": "sha512-OL/7wZpNy9x0GBSzz3poWja84Nr7iaH8aYNsJ5Uet2BVLj6Lm1zvWpZN/yH46Vv3ae7YfHmLLMmfHj911fshJg==", "dev": true }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + "node_modules/cli-check-node": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/cli-check-node/-/cli-check-node-1.3.4.tgz", + "integrity": "sha512-iLGgQXm82iP8eH3R67qbOWs5qqUOLmNnMy5Lzl/RybcMh3y+H2zWU5POzuQ6oDUOdz4XWuxcFhP75szqd6frLg==", + "dev": true, + "dependencies": { + "chalk": "^3.0.0", + "log-symbols": "^3.0.0" + } }, - "node_modules/@types/node": { - "version": "18.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", - "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", + "node_modules/cli-check-node/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==", + "node_modules/cli-check-node/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/node-hid": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@types/node-hid/-/node-hid-1.3.4.tgz", - "integrity": "sha512-0ootpsYetN9vjqkDSwm/cA4fk/9yGM/PO0X8SLPE/BzXlUaBelImMWMymtF9QEoEzxY0pnhcROIJM0CNSUqO8w==", + "node_modules/cli-check-node/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { - "@types/node": "*" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@types/offscreencanvas": { - "version": "2019.3.0", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz", - "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==" - }, - "node_modules/@types/pug": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", - "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", - "dev": true - }, - "node_modules/@types/seedrandom": { - "version": "2.4.34", - "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", - "integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==" - }, - "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "node_modules/cli-check-node/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@types/stats.js": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", - "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", - "dev": true + "node_modules/cli-check-node/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "node_modules/@types/three": { - "version": "0.152.1", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.1.tgz", - "integrity": "sha512-PMOCQnx9JRmq+2OUGTPoY9h1hTWD2L7/nmuW/SyNq1Vbq3Lwt3MNdl3wYSa4DvLTGv62NmIXD9jYdAOwohwJyw==", + "node_modules/cli-check-node/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@tweenjs/tween.js": "~18.6.4", - "@types/stats.js": "*", - "@types/webxr": "*", - "fflate": "~0.6.9", - "lil-gui": "~0.17.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/throttle-debounce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", - "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==", - "dev": true - }, - "node_modules/@types/usb": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/usb/-/usb-1.5.4.tgz", - "integrity": "sha512-NOUza/8yuswu6RoECQyPHEjA34qpDaeONQ72fm+bCnnN2DJjDePAY+NsmV17H88oIlq4JlJ2mD5Kh5d6R2MwTQ==", + "node_modules/cli-handle-error": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cli-handle-error/-/cli-handle-error-4.4.0.tgz", + "integrity": "sha512-RyBCnKlc7xVr79cKb9RfBq+4fjwQeX8HKeNzIPnI/W+DWWIUUKh2ur576DpwJ3kZt2UGHlIAOF7N9txy+mgZsA==", + "dev": true, "dependencies": { - "@types/node": "*" + "chalk": "^3.0.0", + "log-symbols": "^3.0.0" } }, - "node_modules/@types/w3c-web-serial": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/w3c-web-serial/-/w3c-web-serial-1.0.6.tgz", - "integrity": "sha512-5IlDdQ2C56sCVwc7CUlqT9Axxw+0V/FbWRbErklYIzZ5mKL9s4l7epXHygn+4X7L2nmAPnVvRl55XUVo0760Rg==" - }, - "node_modules/@types/w3c-web-usb": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz", - "integrity": "sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==" - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz", - "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==", - "dev": true - }, - "node_modules/@types/webxr": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.10.tgz", - "integrity": "sha512-n3u5sqXQJhf1CS68mw3Wf16FQ4cRPNBBwdYLFzq3UddiADOim1Pn3Y6PBdDilz1vOJF3ybLxJ8ZEDlLIzrOQZg==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "node_modules/cli-handle-error/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/cli-handle-error/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/cli-handle-error/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "color-name": "~1.1.4" }, "engines": { - "node": ">=10" + "node": ">=7.0.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/cli-handle-error/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "node_modules/cli-handle-error/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "node_modules/cli-handle-error/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "has-flag": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=8" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "node_modules/cli-handle-unhandled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cli-handle-unhandled/-/cli-handle-unhandled-1.1.1.tgz", + "integrity": "sha512-Em91mJvU7VdgT2MxQpyY633vW1tDzRjPDbii6ZjEBHHLLh0xDoVkFt/wjvi9nSvJcz9rJmvtJSK8KL/hvF0Stg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "cli-handle-error": "^4.1.0" + } + }, + "node_modules/cli-welcome": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/cli-welcome/-/cli-welcome-2.2.3.tgz", + "integrity": "sha512-hxaOpahLk5PAYJj4tOcv8vaNMaBQHeMzeLQTAHq2EoGGTKVYV/MPCSlg5EEsKZ7y8WDGS2ScQtnITw02ZNukMQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "clear-any-console": "^1.16.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.0.3.tgz", + "integrity": "sha512-nadqwNxghAGTamwIqQSG433W6OADZx2vCo3UXHNrzTRHK/htu+7+L0zhjEoaeaQVNAi3YgqWDv8+tzf0hRfR+A==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" } }, - "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, + "node_modules/core-js": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", + "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "hasInstallScript": true, "funding": { "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/core-js" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "node_modules/core-js-compat": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", + "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "browserslist": "^4.22.2" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://opencollective.com/core-js" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "dependencies": { - "yallist": "^4.0.0" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" }, "engines": { "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" + "cross-spawn": "^7.0.1" }, "bin": { - "semver": "bin/semver.js" + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" }, "engines": { - "node": ">=10" + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node": ">= 8" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "tiny-invariant": "^1.0.6" } }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" + "rrweb-cssom": "^0.6.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=18" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, - "node_modules/@vitest/expect": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.0.4.tgz", - "integrity": "sha512-/NRN9N88qjg3dkhmFcCBwhn/Ie4h064pY3iv7WLRsDJW7dXnEgeoa8W9zy7gIPluhz6CkgqiB3HmpIXgmEY5dQ==", - "dev": true, + "node_modules/d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", "dependencies": { - "@vitest/spy": "1.0.4", - "@vitest/utils": "1.0.4", - "chai": "^4.3.10" + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/runner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.0.4.tgz", - "integrity": "sha512-rhOQ9FZTEkV41JWXozFM8YgOqaG9zA7QXbhg5gy6mFOVqh4PcupirIJ+wN7QjeJt8S8nJRYuZH1OjJjsbxAXTQ==", - "dev": true, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "dependencies": { - "@vitest/utils": "1.0.4", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "internmap": "1 - 2" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "dependencies": { - "yocto-queue": "^1.0.0" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" }, "engines": { - "node": ">=18" + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "engines": { - "node": ">=12.20" + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/snapshot": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.0.4.tgz", - "integrity": "sha512-vkfXUrNyNRA/Gzsp2lpyJxh94vU2OHT1amoD6WuvUAA12n32xeVZQ0KjjQIf8F6u7bcq2A2k969fMVxEsxeKYA==", - "dev": true, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "delaunator": "5" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", "engines": { - "node": ">=10" + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/snapshot/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@vitest/snapshot/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } }, - "node_modules/@vitest/spy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.0.4.tgz", - "integrity": "sha512-9ojTFRL1AJVh0hvfzAQpm0QS6xIS+1HFIw94kl/1ucTfGCaj1LV/iuJU4Y6cdR03EzPDygxTHwE1JOm+5RCcvA==", - "dev": true, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "dependencies": { - "tinyspy": "^2.2.0" + "d3-dsv": "1 - 3" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/utils": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.0.4.tgz", - "integrity": "sha512-gsswWDXxtt0QvtK/y/LWukN7sGMYmnCcv1qv05CsY6cU/Y1zpGX1QuvLs+GO1inczpE6Owixeel3ShkjhYtGfA==", - "dev": true, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "dependencies": { - "diff-sequences": "^29.6.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=12" } }, - "node_modules/@vitest/utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/@vitest/utils/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "d3-array": "2.5.0 - 3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@vitest/utils/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/@webgpu/types": { - "version": "0.1.38", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz", - "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==" + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } }, - "node_modules/@windicss/config": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@windicss/config/-/config-1.9.3.tgz", - "integrity": "sha512-u8GUjsfC9r5X1AGYhzb1lX3zZj8wqk6SH1DYex8XUGmZ1M2UpvnUPOFi63XFViduspQ6l2xTX84QtG+lUzhEoQ==", - "dev": true, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "dependencies": { - "debug": "^4.3.4", - "jiti": "^1.18.2", - "windicss": "^3.5.6" + "d3-color": "1 - 3" }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "engines": { + "node": ">=12" } }, - "node_modules/@windicss/config/node_modules/windicss": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/windicss/-/windicss-3.5.6.tgz", - "integrity": "sha512-P1mzPEjgFMZLX0ZqfFht4fhV/FX8DTG7ERG1fBLiWvd34pTLVReS5CVsewKn9PApSgXnVfPWwvq+qUsRwpnwFA==", - "dev": true, - "bin": { - "windicss": "cli/index.js" - }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "engines": { - "node": ">= 12" + "node": ">=12" } }, - "node_modules/@windicss/plugin-utils": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-1.9.3.tgz", - "integrity": "sha512-3VG5HEGeuIfG/9iTwLyzWWm/aGKNTbtSVkpkAabdRuDP/2lEmf6Hpo4uo5drwE+2O9gXfc6nSYgAwBjotx5CfQ==", - "dev": true, - "dependencies": { - "@antfu/utils": "^0.7.2", - "@windicss/config": "1.9.3", - "debug": "^4.3.4", - "fast-glob": "^3.2.12", - "magic-string": "^0.30.0", - "micromatch": "^4.0.5", - "windicss": "^3.5.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" } }, - "node_modules/@windicss/plugin-utils/node_modules/@antfu/utils": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", - "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" } }, - "node_modules/@windicss/plugin-utils/node_modules/windicss": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/windicss/-/windicss-3.5.6.tgz", - "integrity": "sha512-P1mzPEjgFMZLX0ZqfFht4fhV/FX8DTG7ERG1fBLiWvd34pTLVReS5CVsewKn9PApSgXnVfPWwvq+qUsRwpnwFA==", - "dev": true, - "bin": { - "windicss": "cli/index.js" - }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", "engines": { - "node": ">= 12" + "node": ">=12" } }, - "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "bin": { - "acorn": "bin/acorn" + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" }, "engines": { - "node": ">=0.4.0" + "node": ">=12" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node_modules/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/acorn-walk": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", - "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", - "dev": true, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "engines": { - "node": ">=0.4.0" + "node": ">=12" } }, - "node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "dependencies": { - "debug": "^4.3.4" + "d3-path": "^3.1.0" }, "engines": { - "node": ">= 14" + "node": ">=12" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "d3-array": "2 - 3" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "dependencies": { - "color-convert": "^1.9.0" + "d3-time": "1 - 3" }, "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "dependencies": { - "sprintf-js": "~1.0.2" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "dependencies": { - "deep-equal": "^2.0.5" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, + "node_modules/dapjs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/dapjs/-/dapjs-2.3.0.tgz", + "integrity": "sha512-quanzq7+2xnqgGqqYgARz9o3iBcZ3Ir5r5mTA7WPsjrp9ilEqqCToSFGTL+8HuGP35dUIL7O+yMBloYHhHgZDA==", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "@types/node-hid": "^1.2.0", + "@types/usb": "^1.5.1", + "@types/w3c-web-usb": "^1.0.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, "engines": { - "node": ">=8" + "node": ">=8.14.0" } }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, "engines": { - "node": "*" + "node": ">=18" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, "engines": { "node": ">= 0.4" }, @@ -4635,1998 +7815,2074 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axobject-query": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", - "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", - "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.4", - "semver": "^6.3.1" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", - "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4", - "core-js-compat": "^3.33.1" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", - "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.4" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "require-from-string": "^2.0.2" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, + "ms": "2.1.2" + }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "type-detect": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/browser-lang": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/browser-lang/-/browser-lang-0.2.1.tgz", - "integrity": "sha512-+xmtsTxVZKWrKHoNUQp4Tm7BEXlnMwOMAHZAh1SSot1+n04qHLFIH0K5anX52k5BkcauggbaNlWT8f3bVwDh/Q==" - }, - "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "engines": { - "node": "*" + "node": ">=0.10.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "dependencies": { + "robust-predicates": "^3.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "engines": { "node": ">=6" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001570", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", - "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "path-type": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "esutils": "^2.0.2" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/chart.js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", - "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=7" + "no-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "dependencies": { - "get-func-name": "^2.0.2" + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" }, "engines": { - "node": "*" + "node": ">=0.10.0" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "node_modules/electron-to-chromium": { + "version": "1.4.613", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.613.tgz", + "integrity": "sha512-r4x5+FowKG6q+/Wj0W9nidx7QO31BJwmR2uEo+Qh3YLGQ8SbBAFuDFpTxzly/I2gsbrFwBuIjrMp423L3O5U3w==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, "engines": { - "node": ">= 8.10.0" + "node": ">=0.12" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cli-color": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz", - "integrity": "sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ==", + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.61", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.15", - "timers-ext": "^0.1.7" + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">=0.10" + "node": ">= 0.4" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" } }, - "node_modules/code-red": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1", - "acorn": "^8.10.0", - "estree-walker": "^3.0.3", - "periscopic": "^3.1.0" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", "dev": true, "dependencies": { - "color-name": "1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, "dependencies": { - "delayed-stream": "~1.0.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, "engines": { - "node": ">= 10" + "node": ">= 0.4" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, - "peer": true - }, - "node_modules/core-js": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", - "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "dependencies": { + "hasown": "^2.0.0" } }, - "node_modules/core-js-compat": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", - "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "dependencies": { - "browserslist": "^4.22.2" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/esbuild": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", + "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 8" + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.9", + "@esbuild/android-arm64": "0.19.9", + "@esbuild/android-x64": "0.19.9", + "@esbuild/darwin-arm64": "0.19.9", + "@esbuild/darwin-x64": "0.19.9", + "@esbuild/freebsd-arm64": "0.19.9", + "@esbuild/freebsd-x64": "0.19.9", + "@esbuild/linux-arm": "0.19.9", + "@esbuild/linux-arm64": "0.19.9", + "@esbuild/linux-ia32": "0.19.9", + "@esbuild/linux-loong64": "0.19.9", + "@esbuild/linux-mips64el": "0.19.9", + "@esbuild/linux-ppc64": "0.19.9", + "@esbuild/linux-riscv64": "0.19.9", + "@esbuild/linux-s390x": "0.19.9", + "@esbuild/linux-x64": "0.19.9", + "@esbuild/netbsd-x64": "0.19.9", + "@esbuild/openbsd-x64": "0.19.9", + "@esbuild/sunos-x64": "0.19.9", + "@esbuild/win32-arm64": "0.19.9", + "@esbuild/win32-ia32": "0.19.9", + "@esbuild/win32-x64": "0.19.9" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "node": ">=6" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, "bin": { - "cssesc": "bin/cssesc" + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/cssstyle": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", - "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "node_modules/eslint-plugin-react": { + "version": "7.34.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", + "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", "dev": true, "dependencies": { - "rrweb-cssom": "^0.6.0" + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.hasown": "^1.1.4", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11" }, "engines": { - "node": ">=18" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, - "node_modules/d3": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", - "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz", + "integrity": "sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, "dependencies": { - "internmap": "1 - 2" + "esutils": "^2.0.2" }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, "dependencies": { - "d3-path": "1 - 3" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "dependencies": { - "d3-array": "^3.2.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { - "delaunator": "5" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "engines": { - "node": ">=12" + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" + "color-name": "~1.1.4" }, "engines": { - "node": ">=12" + "node": ">=7.0.0" } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "dependencies": { - "d3-dsv": "1 - 3" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=12" + "node": ">=10.13.0" } }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { - "d3-array": "2.5.0 - 3" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "d3-color": "1 - 3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=12" + "node": ">=0.10" } }, - "node_modules/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=12" + "node": ">=4.0" } }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "engines": { - "node": ">=12" + "node": ">=4.0" } }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "dependencies": { - "d3-path": "^3.1.0" - }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, "dependencies": { - "d3-array": "2 - 3" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=12" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, "dependencies": { - "d3-time": "1 - 3" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=12" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "engines": { - "node": ">=12" - } + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" + "node": ">=8.6.0" } }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" + "reusify": "^1.0.4" } }, - "node_modules/dapjs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/dapjs/-/dapjs-2.3.0.tgz", - "integrity": "sha512-quanzq7+2xnqgGqqYgARz9o3iBcZ3Ir5r5mTA7WPsjrp9ilEqqCToSFGTL+8HuGP35dUIL7O+yMBloYHhHgZDA==", + "node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, "dependencies": { - "@types/node-hid": "^1.2.0", - "@types/usb": "^1.5.1", - "@types/w3c-web-usb": "^1.0.4" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=8.14.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=18" + "node": ">=10" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { - "ms": "2.1.2" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "type-detect": "^4.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/focus-lock": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.5.tgz", + "integrity": "sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==", + "dependencies": { + "tslib": "^2.0.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "is-callable": "^1.1.3" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 6" } }, - "node_modules/defu": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/defu/-/defu-5.0.1.tgz", - "integrity": "sha512-EPS1carKg+dkEVy3qNTqIdp2qV7mUP08nIsupfwQpz++slCVRw7qbQyWvSTig+kFPwz2XXp5/kIIkH+CwrJKkQ==", - "dev": true - }, - "node_modules/delaunator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", - "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "node_modules/framer-motion": { + "version": "10.18.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz", + "integrity": "sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==", "dependencies": { - "robust-predicates": "^3.0.0" + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "engines": { - "node": ">=6" - } + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "dependencies": { + "tslib": "2.4.0" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "node_modules/framesync/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "dependencies": { - "path-type": "^4.0.0" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, "engines": { - "node": ">=6.0.0" + "node": ">= 10.0.0" } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.613", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.613.tgz", - "integrity": "sha512-r4x5+FowKG6q+/Wj0W9nidx7QO31BJwmR2uEo+Qh3YLGQ8SbBAFuDFpTxzly/I2gsbrFwBuIjrMp423L3O5U3w==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "hasInstallScript": true, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", - "dev": true - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esbuild": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", - "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.19.9", - "@esbuild/android-arm64": "0.19.9", - "@esbuild/android-x64": "0.19.9", - "@esbuild/darwin-arm64": "0.19.9", - "@esbuild/darwin-x64": "0.19.9", - "@esbuild/freebsd-arm64": "0.19.9", - "@esbuild/freebsd-x64": "0.19.9", - "@esbuild/linux-arm": "0.19.9", - "@esbuild/linux-arm64": "0.19.9", - "@esbuild/linux-ia32": "0.19.9", - "@esbuild/linux-loong64": "0.19.9", - "@esbuild/linux-mips64el": "0.19.9", - "@esbuild/linux-ppc64": "0.19.9", - "@esbuild/linux-riscv64": "0.19.9", - "@esbuild/linux-s390x": "0.19.9", - "@esbuild/linux-x64": "0.19.9", - "@esbuild/netbsd-x64": "0.19.9", - "@esbuild/openbsd-x64": "0.19.9", - "@esbuild/sunos-x64": "0.19.9", - "@esbuild/win32-arm64": "0.19.9", - "@esbuild/win32-ia32": "0.19.9", - "@esbuild/win32-x64": "0.19.9" + "node": ">=6.9.0" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "engines": { - "node": ">=6" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { - "node": ">=0.8.0" + "node": "*" } }, - "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-compat-utils": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", - "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "engines": { - "node": ">=12" + "node": ">=16" }, - "peerDependencies": { - "eslint": ">=6.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-svelte": { - "version": "2.35.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.35.1.tgz", - "integrity": "sha512-IF8TpLnROSGy98Z3NrsKXWDSCbNY2ReHDcrYTuXZMbfX7VmESISR78TWgO9zdg4Dht1X8coub5jKwHzP0ExRug==", + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@jridgewell/sourcemap-codec": "^1.4.14", - "debug": "^4.3.1", - "eslint-compat-utils": "^0.1.2", - "esutils": "^2.0.3", - "known-css-properties": "^0.29.0", - "postcss": "^8.4.5", - "postcss-load-config": "^3.1.4", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.11", - "semver": "^7.5.3", - "svelte-eslint-parser": ">=0.33.0 <1.0.0" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0-0", - "svelte": "^3.37.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "svelte": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-svelte/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=10" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-plugin-svelte/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/eslint-plugin-svelte/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8.0.0" + "node": "*" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=4" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "get-intrinsic": "^1.1.3" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, - "engines": { - "node": ">=10" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "es-define-property": "^1.0.0" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 14" } }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, "dependencies": { - "estraverse": "^5.1.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=0.10" + "node": ">= 14" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=16.17.0" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { - "estraverse": "^5.2.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=4.0" + "node": ">=0.10.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">= 4" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, "engines": { - "node": ">=4.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dependencies": { - "@types/estree": "^1.0.0" + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dependencies": { - "type": "^2.7.2" + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" } }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + "node_modules/intl-messageformat": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", + "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "tslib": "^2.4.0" + } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=8.6.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", "dev": true, "dependencies": { - "reusify": "^1.0.4" + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fflate": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", - "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", - "dev": true - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "dependencies": { - "flat-cache": "^3.0.4" + "has-bigints": "^1.0.1" }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "dependencies": { - "to-regex-range": "^5.0.1" + "binary-extensions": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "dev": true, "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "builtin-modules": "^3.3.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/focus-trap": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", - "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "dependencies": { - "tabbable": "^6.2.0" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "is-callable": "^1.1.3" + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "peer": true, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": "*" + "node": ">=0.10.0" } }, - "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "dependencies": { - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { - "node": ">=16" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.12.0" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "dependencies": { - "is-glob": "^4.0.3" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", "dev": true, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/globalyzer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==" - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" - }, - "node_modules/gopd": { + "node_modules/is-potential-custom-element-name": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.3" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "engines": { - "node": ">= 0.4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -6634,10 +9890,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "dependencies": { "has-symbols": "^1.0.2" @@ -6649,505 +9905,646 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "function-bind": "^1.1.2" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "call-bind": "^1.0.2" }, - "engines": { - "node": ">= 14" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">= 14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" } }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "node_modules/jake": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, "engines": { - "node": ">= 4" + "node": ">=10" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "engines": { - "node": ">=0.8.19" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "node_modules/jake/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 0.4" + "node": "*" } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/intl-messageformat": { - "version": "10.5.8", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.8.tgz", - "integrity": "sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, "dependencies": { - "@formatjs/ecma402-abstract": "1.18.0", - "@formatjs/fast-memoize": "2.2.0", - "@formatjs/icu-messageformat-parser": "2.7.3", - "tslib": "^2.4.0" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "has-bigints": "^1.0.1" + "color-name": "~1.1.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=7.0.0" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "is-extglob": "^2.1.1" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">=8" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { - "@types/estree": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=7.0.0" } }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "color-name": "~1.1.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=7.0.0" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "bin": { - "jiti": "bin/jiti.js" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/js-cookie": { @@ -7161,8 +10558,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -7183,31 +10579,31 @@ "dev": true }, "node_modules/jsdom": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.2.0.tgz", - "integrity": "sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", + "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", "dev": true, "dependencies": { - "@asamuzakjp/dom-selector": "^2.0.1", "cssstyle": "^4.0.1", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.10", "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", + "rrweb-cssom": "^0.7.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.3", + "tough-cookie": "^4.1.4", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", - "ws": "^8.16.0", + "ws": "^8.17.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -7222,12 +10618,16 @@ } } }, + "node_modules/jsdom/node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -7241,6 +10641,17 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -7258,7 +10669,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -7272,6 +10682,51 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", "dev": true }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7281,27 +10736,15 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "engines": { "node": ">=6" } }, - "node_modules/known-css-properties": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz", - "integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==", - "dev": true - }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dev": true - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7321,14 +10764,10 @@ "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==", "dev": true }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/local-pkg": { "version": "0.5.0", @@ -7346,11 +10785,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7390,11 +10824,45 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -7404,6 +10872,15 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7413,14 +10890,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", - "dependencies": { - "es5-ext": "~0.10.2" - } - }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -7431,34 +10900,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" - }, - "node_modules/memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" } }, "node_modules/merge-stream": { @@ -7477,12 +10924,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -7525,41 +10972,23 @@ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=4" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { - "minimist": "^1.2.6" + "brace-expansion": "^2.0.1" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mlly": { @@ -7574,19 +11003,10 @@ "ufo": "^1.3.0" } }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -7611,16 +11031,15 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } }, "node_modules/node-fetch": { "version": "2.6.13", @@ -7702,23 +11121,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nwsapi": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -7754,6 +11190,72 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", + "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7779,17 +11281,17 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -7829,7 +11331,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -7837,6 +11338,23 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -7879,14 +11397,12 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -7906,16 +11422,6 @@ "node": "*" } }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7944,83 +11450,63 @@ "pathe": "^1.1.0" } }, - "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/playwright": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", + "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", + "dev": true, "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "playwright-core": "1.45.0" + }, + "bin": { + "playwright": "cli.js" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" } }, - "node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "node_modules/playwright-core": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", + "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", "dev": true, - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" + "bin": { + "playwright-core": "cli.js" }, "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } + "node": ">=18" } }, - "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.3.3" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/postcss-scss": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", - "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "funding": [ { "type": "opencollective", @@ -8028,31 +11514,20 @@ }, { "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-scss" + "url": "https://tidelift.com/funding/github/npm/postcss" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.4.29" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" }, "engines": { - "node": ">=4" + "node": "^10 || ^12 || >=14" } }, "node_modules/prelude-ls": { @@ -8065,28 +11540,27 @@ } }, "node_modules/prettier": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", - "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", "dev": true, "bin": { - "prettier": "bin/prettier.cjs" + "prettier": "bin-prettier.js" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "node": ">=10.13.0" } }, - "node_modules/prettier-plugin-svelte": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.1.2.tgz", - "integrity": "sha512-7xfMZtwgAWHMT0iZc8jN4o65zgbAQ3+O32V6W7pXrqNvKnHnkoyQCGCbKeUyXKZLbYE0YhFRnamfxfkEGxm8qA==", + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", "dev": true, - "peerDependencies": { - "prettier": "^3.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pretty-format": { @@ -8115,6 +11589,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -8154,13 +11644,192 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-clientside-effect": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz", + "integrity": "sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg==", + "dependencies": { + "@babel/runtime": "^7.12.13" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-focus-lock": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.12.1.tgz", + "integrity": "sha512-lfp8Dve4yJagkHiFrC1bGtib3mF2ktqwPJw4/WGcgPW+pJ/AVQA5X2vI7xgp13FcxFEpYBBHpXai/N2DBNC0Jw==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "focus-lock": "^1.3.5", + "prop-types": "^15.6.2", + "react-clientside-effect": "^1.2.6", + "use-callback-ref": "^1.3.2", + "use-sidecar": "^1.1.2" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-intl": { + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.8.tgz", + "integrity": "sha512-M0pkhzcgV31h++2901BiRXWl69hp2zPyLxRrSwRjd1ErXbNoubz/f4M6DrRTd4OiSUrT4ajRQzrmtS5plG4FtA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "@formatjs/intl": "2.10.4", + "@formatjs/intl-displaynames": "6.6.8", + "@formatjs/intl-listformat": "7.5.7", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/react": "16 || 17 || 18", + "hoist-non-react-statics": "^3.3.2", + "intl-messageformat": "10.5.14", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "react": "^16.6.0 || 17 || 18", + "typescript": "^4.7 || 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz", + "integrity": "sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, "node_modules/readdirp": { "version": "3.6.0", @@ -8205,6 +11874,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -8238,14 +11928,15 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -8319,7 +12010,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -8336,7 +12026,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -8355,6 +12044,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -8434,15 +12124,22 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, "dependencies": { - "mri": "^1.1.0" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" }, "engines": { - "node": ">=6" + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/safe-buffer": { @@ -8464,35 +12161,28 @@ } ] }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sander": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", - "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==", - "dev": true, - "dependencies": { - "es6-promise": "^3.1.2", - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - } - }, - "node_modules/sander/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -8505,6 +12195,14 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/seedrandom": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", @@ -8519,30 +12217,42 @@ "semver": "bin/semver.js" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8570,14 +12280,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8610,24 +12324,33 @@ "node": ">=8" } }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true + }, "node_modules/smoothie": { "version": "1.36.1", "resolved": "https://registry.npmjs.org/smoothie/-/smoothie-1.36.1.tgz", "integrity": "sha512-499Vr2od6TicP8s7ykcyTfddh/6n11BB41G9RE7gqQRyfoPIAYotUTzwAxQpAfOdVOb+BvcG2qla+hjyqwe+PA==" }, - "node_modules/sorcery": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz", - "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==", + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.14", - "buffer-crc32": "^0.2.5", - "minimist": "^1.2.0", - "sander": "^0.5.0" - }, - "bin": { - "sorcery": "bin/sorcery" + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" } }, "node_modules/source-map-js": { @@ -8638,11 +12361,58 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -8688,82 +12458,74 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" }, "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, "dependencies": { - "min-indent": "^1.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "dependencies": { - "acorn": "^8.10.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8771,289 +12533,194 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svelte": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.8.tgz", - "integrity": "sha512-hU6dh1MPl8gh6klQZwK/n73GiAHiR95IkFsesLPbMeEZi36ydaXL/ZAb4g9sayT0MXzpxyZjR28yderJHxcmYA==", + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@jridgewell/trace-mapping": "^0.3.18", - "acorn": "^8.9.0", - "aria-query": "^5.3.0", - "axobject-query": "^3.2.1", - "code-red": "^1.0.3", - "css-tree": "^2.3.1", - "estree-walker": "^3.0.3", - "is-reference": "^3.0.1", - "locate-character": "^3.0.0", - "magic-string": "^0.30.4", - "periscopic": "^3.1.0" + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" }, "engines": { - "node": ">=16" + "node": ">=4" } }, - "node_modules/svelte-check": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.2.tgz", - "integrity": "sha512-E6iFh4aUCGJLRz6QZXH3gcN/VFfkzwtruWSRmlKrLWQTiO6VzLsivR6q02WYLGNAGecV3EocqZuCDrC2uttZ0g==", - "dev": true, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "chokidar": "^3.4.1", - "fast-glob": "^3.2.7", - "import-fresh": "^3.2.1", - "picocolors": "^1.0.0", - "sade": "^1.7.4", - "svelte-preprocess": "^5.1.0", - "typescript": "^5.0.3" - }, - "bin": { - "svelte-check": "bin/svelte-check" + "ansi-regex": "^5.0.1" }, - "peerDependencies": { - "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" + "engines": { + "node": ">=8" } }, - "node_modules/svelte-eslint-parser": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.33.1.tgz", - "integrity": "sha512-vo7xPGTlKBGdLH8T5L64FipvTrqv3OQRx9d2z5X05KKZDlF4rQk8KViZO4flKERY+5BiVdOh7zZ7JGJWo5P0uA==", + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", "dev": true, - "dependencies": { - "eslint-scope": "^7.0.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "postcss": "^8.4.29", - "postcss-scss": "^4.0.8" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "svelte": "^3.37.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "svelte": { - "optional": true - } + "node": ">=10" } }, - "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/svelte-eslint-parser/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, "engines": { - "node": ">=4.0" + "node": ">=8" } }, - "node_modules/svelte-hmr": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", - "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "engines": { - "node": "^12.20 || ^14.13.1 || >= 16" + "node": ">=8" }, - "peerDependencies": { - "svelte": "^3.19.0 || ^4.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/svelte-i18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/svelte-i18n/-/svelte-i18n-4.0.0.tgz", - "integrity": "sha512-4vivjKZADUMRIhTs38JuBNy3unbnh9AFRxWFLxq62P4NHic+/BaIZZlAsvqsCdnp7IdJf5EoSiH6TNdItcjA6g==", + "node_modules/strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", + "dev": true, "dependencies": { - "cli-color": "^2.0.3", - "deepmerge": "^4.2.2", - "esbuild": "^0.19.2", - "estree-walker": "^2", - "intl-messageformat": "^10.5.3", - "sade": "^1.8.1", - "tiny-glob": "^0.2.9" - }, - "bin": { - "svelte-i18n": "dist/cli.js" - }, - "engines": { - "node": ">= 16" + "js-tokens": "^9.0.0" }, - "peerDependencies": { - "svelte": "^3 || ^4" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/svelte-i18n/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true }, - "node_modules/svelte-preprocess": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.2.tgz", - "integrity": "sha512-XF0aliMAcYnP4hLETvB6HRAMnaL09ASYT1Z2I1Gwu0nz6xbdg/dSgAEthtFZJA4AKrNhFDFdmUDO+H9d/6xg5g==", - "dev": true, - "hasInstallScript": true, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dependencies": { - "@types/pug": "^2.0.6", - "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", - "sorcery": "^0.11.0", - "strip-indent": "^3.0.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" - }, - "peerDependencies": { - "@babel/core": "^7.10.2", - "coffeescript": "^2.5.1", - "less": "^3.11.3 || ^4.0.0", - "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", - "pug": "^3.0.0", - "sass": "^1.26.8", - "stylus": "^0.55.0", - "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", - "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "coffeescript": { - "optional": true - }, - "less": { - "optional": true - }, - "postcss": { - "optional": true - }, - "postcss-load-config": { - "optional": true - }, - "pug": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "typescript": { - "optional": true - } + "node": ">=4" } }, - "node_modules/svelte-preprocess/node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svelte-skeleton": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/svelte-skeleton/-/svelte-skeleton-1.3.1.tgz", - "integrity": "sha512-/dVf4Am8Rolo9DjQxmy8X8xXSKDzZWyGjf84CekWdQqvP+Y57lW1O8dxHUxqT1bGeER8ZMAOuyGEzx+gADEVWg==" + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true }, - "node_modules/svelte-transition": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/svelte-transition/-/svelte-transition-0.0.10.tgz", - "integrity": "sha512-BN8XDA7dKyuh+Lmdn3vxCzJd3M7L4BLdRziIAJew2AiBFMcrJJg8srEMYYoTCOLtYJ2Oqlv3+3/K5b6uHM8LSg==", - "peerDependencies": { - "svelte": "^3.59.1 || ^4.0.0" - } + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true }, - "node_modules/svelte-windicss-preprocess": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/svelte-windicss-preprocess/-/svelte-windicss-preprocess-4.2.8.tgz", - "integrity": "sha512-Z6pmFbHqJ19SgCiXiVRC/hlRBgZ/5LksMjPF3ilF/1HESP2L+secuaPjr3xOjJW67iZQpT2YdXzGe+MvdsJ6OQ==", + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "dev": true, - "dependencies": { - "@iconify/json": "1.1.432", - "fast-glob": "3.2.7", - "unconfig": "0.2.2", - "windicss": "3.5.4", - "windicss-runtime-dom": "3.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/svelte-windicss-preprocess/node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/svelte-windicss-preprocess/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/tempy/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/svelte/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "node_modules/terser": { + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", + "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", + "dev": true, "dependencies": { - "dequal": "^2.0.3" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "node_modules/text-table": { @@ -9067,23 +12734,10 @@ "resolved": "https://registry.npmjs.org/three/-/three-0.152.2.tgz", "integrity": "sha512-Ff9zIpSfkkqcBcpdiFo2f35vA9ZucO+N8TNacJOqaEE6DrB0eufItVMib8bK8Pcju/ZNT6a7blE1GhTpkdsILw==" }, - "node_modules/timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, - "node_modules/tiny-glob": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", - "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", - "dependencies": { - "globalyzer": "0.1.0", - "globrex": "^0.1.2" - } + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" }, "node_modules/tinybench": { "version": "2.5.1", @@ -9092,18 +12746,18 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.1.tgz", - "integrity": "sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", "dev": true, "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", "dev": true, "engines": { "node": ">=14.0.0" @@ -9113,7 +12767,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, "engines": { "node": ">=4" } @@ -9130,10 +12783,15 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "dependencies": { "psl": "^1.1.33", @@ -9157,36 +12815,22 @@ "node": ">=18" } }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, "engines": { - "node": ">= 6" + "node": ">=16" }, "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "typescript": ">=4.2.0" } }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type-check": { "version": "0.4.0", @@ -9210,9 +12854,9 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", "dev": true, "engines": { "node": ">=10" @@ -9221,11 +12865,84 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9240,18 +12957,19 @@ "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", "dev": true }, - "node_modules/unconfig": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.2.2.tgz", - "integrity": "sha512-JN1MeYJ/POnjBj7NgOJJxPp6+NcD6Nd0hEuK0D89kjm9GvQQUq8HeE2Eb7PZgtu+64mWkDiqeJn1IZoLH7htPg==", + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "dependencies": { - "@antfu/utils": "^0.3.0", - "defu": "^5.0.0", - "jiti": "^1.12.9" + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/undici-types": { @@ -9299,76 +13017,35 @@ "node": ">=4" } }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true, "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unplugin": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.5.1.tgz", - "integrity": "sha512-0QkvG13z6RD+1L1FoibQqnvTwVBXvS4XSPwAyinVgoOCl2jAgwzdUKmEj05o4Lt8xwQI85Hb6mSyYkcAGwZPew==", - "dev": true, - "dependencies": { - "acorn": "^8.11.2", - "chokidar": "^3.5.3", - "webpack-sources": "^3.2.3", - "webpack-virtual-modules": "^0.6.0" - } - }, - "node_modules/unplugin-icons": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.18.1.tgz", - "integrity": "sha512-WzKu/eoq74YC7vyEAGsFebkRzsZrRkR4FUzLU6gbpfa7WRaVVpQS2n7LSxE1iRUN0scKL5b9bq+i0wucR+ttFQ==", - "dev": true, - "dependencies": { - "@antfu/install-pkg": "^0.3.0", - "@antfu/utils": "^0.7.6", - "@iconify/utils": "^2.1.12", - "debug": "^4.3.4", - "kolorist": "^1.8.0", - "local-pkg": "^0.5.0", - "unplugin": "^1.5.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@svgr/core": ">=7.0.0", - "@svgx/core": "^1.0.1", - "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", - "vue-template-compiler": "^2.6.12", - "vue-template-es2015-compiler": "^1.9.0" - }, - "peerDependenciesMeta": { - "@svgr/core": { - "optional": true - }, - "@svgx/core": { - "optional": true - }, - "@vue/compiler-sfc": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - }, - "vue-template-es2015-compiler": { - "optional": true - } + "node": ">= 4.0.0" } }, - "node_modules/unplugin-icons/node_modules/@antfu/utils": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", - "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" + "engines": { + "node": ">=4", + "yarn": "*" } }, "node_modules/update-browserslist-db": { @@ -9420,11 +13097,46 @@ "requires-port": "^1.0.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, "node_modules/uuid4": { "version": "2.0.3", @@ -9487,9 +13199,9 @@ } }, "node_modules/vite-node": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.0.4.tgz", - "integrity": "sha512-9xQQtHdsz5Qn8hqbV7UKqkm8YkJhzT/zr41Dmt5N7AlD8hJXw/Z7y0QiD5I8lnTthV9Rvcvi0QW7PI0Fq83ZPg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", + "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -9508,63 +13220,62 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-plugin-windicss": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-1.9.3.tgz", - "integrity": "sha512-PqNiIsrEftCrgn0xIpj8ZMSdpz8NZn+OJ3gKXnOF+hFzbHFrKGJA49ViOUKCHDOquxoGBZMmTjepWr8GrftKcQ==", + "node_modules/vite-plugin-pwa": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.19.8.tgz", + "integrity": "sha512-e1oK0dfhzhDhY3VBuML6c0h8Xfx6EkOVYqolj7g+u8eRfdauZe5RLteCIA/c5gH0CBQ0CNFAuv/AFTx4Z7IXTw==", "dev": true, "dependencies": { - "@windicss/plugin-utils": "1.9.3", "debug": "^4.3.4", - "kolorist": "^1.8.0", - "windicss": "^3.5.6" + "fast-glob": "^3.3.2", + "pretty-bytes": "^6.1.1", + "workbox-build": "^7.0.0", + "workbox-window": "^7.0.0" + }, + "engines": { + "node": ">=16.0.0" }, "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "vite": "^2.0.1 || ^3.0.0 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/vite-plugin-windicss/node_modules/windicss": { - "version": "3.5.6", - "resolved": "https://registry.npmjs.org/windicss/-/windicss-3.5.6.tgz", - "integrity": "sha512-P1mzPEjgFMZLX0ZqfFht4fhV/FX8DTG7ERG1fBLiWvd34pTLVReS5CVsewKn9PApSgXnVfPWwvq+qUsRwpnwFA==", - "dev": true, - "bin": { - "windicss": "cli/index.js" + "@vite-pwa/assets-generator": "^0.2.4", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", + "workbox-build": "^7.0.0", + "workbox-window": "^7.0.0" }, - "engines": { - "node": ">= 12" + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } } }, - "node_modules/vitefu": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", - "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "node_modules/vite-plugin-svgr": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", + "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", "dev": true, - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + "dependencies": { + "@rollup/pluginutils": "^5.0.5", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } + "peerDependencies": { + "vite": "^2.6.0 || 3 || 4 || 5" } }, "node_modules/vitest": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.0.4.tgz", - "integrity": "sha512-s1GQHp/UOeWEo4+aXDOeFBJwFzL6mjycbQwwKWX2QcYfh/7tIerS59hWQ20mxzupTJluA2SdwiBuWwQHH67ckg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", + "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", "dev": true, "dependencies": { - "@vitest/expect": "1.0.4", - "@vitest/runner": "1.0.4", - "@vitest/snapshot": "1.0.4", - "@vitest/spy": "1.0.4", - "@vitest/utils": "1.0.4", - "acorn-walk": "^8.3.0", - "cac": "^6.7.14", + "@vitest/expect": "1.6.0", + "@vitest/runner": "1.6.0", + "@vitest/snapshot": "1.6.0", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", "execa": "^8.0.1", @@ -9573,11 +13284,11 @@ "pathe": "^1.1.1", "picocolors": "^1.0.0", "std-env": "^3.5.0", - "strip-literal": "^1.3.0", + "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.1", + "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.0.4", + "vite-node": "1.6.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -9592,8 +13303,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "^1.0.0", - "@vitest/ui": "^1.0.0", + "@vitest/browser": "1.6.0", + "@vitest/ui": "1.6.0", "happy-dom": "*", "jsdom": "*" }, @@ -9635,15 +13346,6 @@ "vitest": ">=0.31.0" } }, - "node_modules/vitest-dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/vitest-dom/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -9683,21 +13385,6 @@ "node": ">=12" } }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", - "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", - "dev": true - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -9763,32 +13450,61 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9813,25 +13529,382 @@ "node": ">=8" } }, - "node_modules/windicss": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/windicss/-/windicss-3.5.4.tgz", - "integrity": "sha512-x2Iu0a69dtNiKHMkR886lx0WKbZI5GqvXyvGBCJ2VA6rcjKYjnzCA/Ljd6hNQBfqlkSum8J+qAVcCfLzQFI4rQ==", + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "bin": { - "windicss": "cli/index.js" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-background-sync": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.1.0.tgz", + "integrity": "sha512-rMbgrzueVWDFcEq1610YyDW71z0oAXLfdRHRQcKw4SGihkfOK0JUEvqWHFwA6rJ+6TClnMIn7KQI5PNN1XQXwQ==", + "dev": true, + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.1.0.tgz", + "integrity": "sha512-O36hIfhjej/c5ar95pO67k1GQw0/bw5tKP7CERNgK+JdxBANQhDmIuOXZTNvwb2IHBx9hj2kxvcDyRIh5nzOgQ==", + "dev": true, + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-build": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.1.1.tgz", + "integrity": "sha512-WdkVdC70VMpf5NBCtNbiwdSZeKVuhTEd5PV3mAwpTQCGAB5XbOny1P9egEgNdetv4srAMmMKjvBk4RD58LpooA==", + "dev": true, + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.1.0", + "workbox-broadcast-update": "7.1.0", + "workbox-cacheable-response": "7.1.0", + "workbox-core": "7.1.0", + "workbox-expiration": "7.1.0", + "workbox-google-analytics": "7.1.0", + "workbox-navigation-preload": "7.1.0", + "workbox-precaching": "7.1.0", + "workbox-range-requests": "7.1.0", + "workbox-recipes": "7.1.0", + "workbox-routing": "7.1.0", + "workbox-strategies": "7.1.0", + "workbox-streams": "7.1.0", + "workbox-sw": "7.1.0", + "workbox-window": "7.1.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dev": true, + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" }, "engines": { - "node": ">= 12" + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" } }, - "node_modules/windicss-runtime-dom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/windicss-runtime-dom/-/windicss-runtime-dom-3.0.0.tgz", - "integrity": "sha512-a12Uhu71yT1U8w0PzJ3amF9xmC8b1rWFLgXEfI/UyuwUi6D1vUACOO6vb0iY4T4OtP/bJAjQMM7lv3hMWSwLuQ==", + "node_modules/workbox-build/node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, "funding": { - "url": "https://github.com/sponsors/antfu" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/workbox-build/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-build/node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.1.0.tgz", + "integrity": "sha512-iwsLBll8Hvua3xCuBB9h92+/e0wdsmSVgR2ZlvcfjepZWwhd3osumQB3x9o7flj+FehtWM2VHbZn8UJeBXXo6Q==", + "dev": true, + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-core": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.1.0.tgz", + "integrity": "sha512-5KB4KOY8rtL31nEF7BfvU7FMzKT4B5TkbYa2tzkS+Peqj0gayMT9SytSFtNzlrvMaWgv6y/yvP9C0IbpFjV30Q==", + "dev": true + }, + "node_modules/workbox-expiration": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.1.0.tgz", + "integrity": "sha512-m5DcMY+A63rJlPTbbBNtpJ20i3enkyOtSgYfv/l8h+D6YbbNiA0zKEkCUaMsdDlxggla1oOfRkyqTvl5Ni5KQQ==", + "dev": true, + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.1.0.tgz", + "integrity": "sha512-FvE53kBQHfVTcZyczeBVRexhh7JTkyQ8HAvbVY6mXd2n2A7Oyz/9fIwnY406ZcDhvE4NFfKGjW56N4gBiqkrew==", + "dev": true, + "dependencies": { + "workbox-background-sync": "7.1.0", + "workbox-core": "7.1.0", + "workbox-routing": "7.1.0", + "workbox-strategies": "7.1.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.1.0.tgz", + "integrity": "sha512-4wyAbo0vNI/X0uWNJhCMKxnPanNyhybsReMGN9QUpaePLTiDpKxPqFxl4oUmBNddPwIXug01eTSLVIFXimRG/A==", + "dev": true, + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-precaching": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.1.0.tgz", + "integrity": "sha512-LyxzQts+UEpgtmfnolo0hHdNjoB7EoRWcF7EDslt+lQGd0lW4iTvvSe3v5JiIckQSB5KTW5xiCqjFviRKPj1zA==", + "dev": true, + "dependencies": { + "workbox-core": "7.1.0", + "workbox-routing": "7.1.0", + "workbox-strategies": "7.1.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.1.0.tgz", + "integrity": "sha512-m7+O4EHolNs5yb/79CrnwPR/g/PRzMFYEdo01LqwixVnc/sbzNSvKz0d04OE3aMRel1CwAAZQheRsqGDwATgPQ==", + "dev": true, + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-recipes": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.1.0.tgz", + "integrity": "sha512-NRrk4ycFN9BHXJB6WrKiRX3W3w75YNrNrzSX9cEZgFB5ubeGoO8s/SDmOYVrFYp9HMw6sh1Pm3eAY/1gVS8YLg==", + "dev": true, + "dependencies": { + "workbox-cacheable-response": "7.1.0", + "workbox-core": "7.1.0", + "workbox-expiration": "7.1.0", + "workbox-precaching": "7.1.0", + "workbox-routing": "7.1.0", + "workbox-strategies": "7.1.0" + } + }, + "node_modules/workbox-routing": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.1.0.tgz", + "integrity": "sha512-oOYk+kLriUY2QyHkIilxUlVcFqwduLJB7oRZIENbqPGeBP/3TWHYNNdmGNhz1dvKuw7aqvJ7CQxn27/jprlTdg==", + "dev": true, + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-strategies": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.1.0.tgz", + "integrity": "sha512-/UracPiGhUNehGjRm/tLUQ+9PtWmCbRufWtV0tNrALuf+HZ4F7cmObSEK+E4/Bx1p8Syx2tM+pkIrvtyetdlew==", + "dev": true, + "dependencies": { + "workbox-core": "7.1.0" + } + }, + "node_modules/workbox-streams": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.1.0.tgz", + "integrity": "sha512-WyHAVxRXBMfysM8ORwiZnI98wvGWTVAq/lOyBjf00pXFvG0mNaVz4Ji+u+fKa/mf1i2SnTfikoYKto4ihHeS6w==", + "dev": true, + "dependencies": { + "workbox-core": "7.1.0", + "workbox-routing": "7.1.0" + } + }, + "node_modules/workbox-sw": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.1.0.tgz", + "integrity": "sha512-Hml/9+/njUXBglv3dtZ9WBKHI235AQJyLBV1G7EFmh4/mUdSQuXui80RtjDeVRrXnm/6QWgRUEHG3/YBVbxtsA==", + "dev": true + }, + "node_modules/workbox-window": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz", + "integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==", + "dev": true, + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.1.0" } }, "node_modules/wrap-ansi": { @@ -9887,9 +13960,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -9940,7 +14013,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } diff --git a/package.json b/package.json index dae6107e1..37a324467 100644 --- a/package.json +++ b/package.json @@ -1,72 +1,82 @@ { - "name": "ml-machine", + "name": "ml-tool", "version": "0.6.0-local", "author": "Center for Computational Thinking and Design at Aarhus University", "private": true, "type": "module", "license": "MIT", "scripts": { - "build": "VITE_VERSION=$npm_package_version vite build", - "dev": "VITE_VERSION=$npm_package_version vite", - "start": "vite dist --host", - "check": "svelte-check --tsconfig ./tsconfig.json", + "build": "tsc && cross-env VITE_VERSION=$npm_package_version vite build", + "ci": "npm run test && npm run lint && npm run build", + "deploy": "website-deploy-aws", + "dev": "cross-env VITE_VERSION=$npm_package_version vite", + "invalidate": "invalidate-cloudfront-distribution", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview", + "start": "npm run dev", "test": "vitest", - "prettier": "prettier --write src/." + "test:e2e": "playwright test --headed", + "test:e2e:headless": "playwright test", + "i18n:compile": "node bin/tidy-lang.cjs && formatjs compile-folder --ast lang src/messages" }, "devDependencies": { - "@babel/preset-env": "^7.23.5", - "@bulatdashiev/svelte-slider": "^1.0.3", - "@iconify-json/mdi": "^1.1.63", - "@iconify-json/ri": "^1.1.17", - "@melt-ui/pp": "^0.3.0", - "@melt-ui/svelte": "^0.70.0", - "@sveltejs/vite-plugin-svelte": "^3.0.1", - "@testing-library/jest-dom": "^6.2.0", - "@testing-library/svelte": "^4.0.5", - "@tsconfig/svelte": "^4.0.1", + "@chakra-ui/cli": "^2.4.1", + "@formatjs/cli": "^6.2.7", + "@playwright/test": "^1.42.1", + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", "@types/browser-lang": "^0.1.1", "@types/d3": "^7.4.1", - "@types/js-cookie": "^3.0.3", + "@types/ejs": "^3.1.5", + "@types/file-saver": "^2.0.3", "@types/node": "^18.16.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@types/three": "^0.152.0", "@types/w3c-web-usb": "^1.0.6", "@types/web-bluetooth": "^0.0.17", - "@typescript-eslint/eslint-plugin": "^5.59.0", - "@typescript-eslint/parser": "^5.59.0", - "eslint": "^8.43.0", - "eslint-plugin-svelte": "^2.32.1", - "jsdom": "^23.2.0", - "prettier": "^3.1.0", - "prettier-plugin-svelte": "^3.1.2", - "svelte": "^4.0.0", - "svelte-check": "^3.6.2", - "svelte-preprocess": "^5.0.3", - "svelte-windicss-preprocess": "^4.2.2", - "tslib": "^2.5.0", - "typescript": "^5.0.4", - "unplugin-icons": "^0.18.1", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "cross-env": "^7.0.3", + "ejs": "^3.1.9", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "jsdom": "^24.0.0", + "playwright": "^1.42.1", + "prettier": "2.3.2", + "typescript": "^5.4.2", "vite": "^5.0.7", - "vite-plugin-windicss": "^1.9.3", - "vitest": "^1.0.4", + "vite-plugin-pwa": "^0.19.8", + "vite-plugin-svgr": "^4.2.0", + "vitest": "^1.3.1", "vitest-dom": "^0.1.1" }, "dependencies": { - "@microsoft/applicationinsights-web": "^3.0.0", - "@sentry/svelte": "^7.100.1", + "@chakra-ui/icons": "^2.1.1", + "@chakra-ui/react": "^2.8.2", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", + "@vitejs/plugin-react": "^4.3.1", "bowser": "^2.11.0", "browser-lang": "^0.2.1", "chart.js": "^4.2.1", "d3": "^7.8.5", "dapjs": "^2.3.0", + "framer-motion": "^10.2.4", "js-cookie": "^3.0.4", "postcss": "^8.4.23", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-icons": "^4.12.0", + "react-intl": "^6.6.8", "smoothie": "^1.36.1", - "svelte-i18n": "^4.0.0", - "svelte-skeleton": "^1.3.1", - "svelte-transition": "^0.0.10", "three": "^0.152.2", "uuid4": "^2.0.3" } diff --git a/public/css/all.min.css b/public/css/all.min.css deleted file mode 100644 index ac76ff191..000000000 --- a/public/css/all.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bacteria:before{content:"\e059"}.fa-bacterium:before{content:"\e05a"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\e05b"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudflare:before{content:"\e07d"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\e052"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-deezer:before{content:"\e077"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edge-legacy:before{content:"\e078"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-faucet:before{content:"\e005"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\e007"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-pay:before{content:"\e079"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guilded:before{content:"\e07e"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\e05d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-hands-wash:before{content:"\e05e"}.fa-handshake:before{content:"\f2b5"}.fa-handshake-alt-slash:before{content:"\e05f"}.fa-handshake-slash:before{content:"\e060"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-head-side-cough:before{content:"\e061"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-head-side-mask:before{content:"\e063"}.fa-head-side-virus:before{content:"\e064"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hive:before{content:"\e07f"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-house-user:before{content:"\e065"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\e013"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-innosoft:before{content:"\e080"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\e055"}.fa-instalod:before{content:"\e081"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-house:before{content:"\e066"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\e067"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\e01a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\e056"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-octopus-deploy:before{content:"\e082"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-arrows:before{content:"\e068"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-perbyte:before{content:"\e083"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\e01e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\e069"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pump-medical:before{content:"\e06a"}.fa-pump-soap:before{content:"\e06b"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-rust:before{content:"\e07a"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-shield-virus:before{content:"\e06c"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\e057"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sink:before{content:"\e06d"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\e06e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\e06f"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-store-alt-slash:before{content:"\e070"}.fa-store-slash:before{content:"\e071"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-tiktok:before{content:"\e07b"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\e041"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-uncharted:before{content:"\e084"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\e049"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-unsplash:before{content:"\e07c"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-users-slash:before{content:"\e073"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-vest:before{content:"\e085"}.fa-vest-patches:before{content:"\e086"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-virus:before{content:"\e074"}.fa-virus-slash:before{content:"\e075"}.fa-viruses:before{content:"\e076"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-watchman-monitoring:before{content:"\e087"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wodu:before{content:"\e088"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.fab,.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/public/css/global.css b/public/css/global.css deleted file mode 100644 index 957033c10..000000000 --- a/public/css/global.css +++ /dev/null @@ -1,76 +0,0 @@ -html { - -webkit-font-smoothing: antialiased; -} - -.scrollable { - -ms-overflow-style: none; - scrollbar-width: none; -} - -.scrollable::-webkit-scrollbar { - width: 6px; - margin-right: 0; - height: 8px; -} - -.scrollable::-webkit-scrollbar-thumb { - background: rgba(160, 160, 160, 0.473); - border-radius: 3px; -} - -.scrollinvis { - -ms-overflow-style: none; - scrollbar-width: none; -} - -.scrollinvis::-webkit-scrollbar { - width: 0px; - margin-right: 0; - height: 0px; -} - -.scrollinvis::-webkit-scrollbar-thumb { - background: rgba(160, 160, 160, 0.473); - border-radius: 3px; -} - -.autoscaling { - transform: scale(0.6) translate(-35%, -70%); -} - -@media screen and (min-width: 1200px) { - .autoscaling { - transform: scale(0.7) translate(-22%, -50%); - } -} - -@media screen and (min-width: 1400px) { - .autoscaling { - transform: scale(0.85) translate(-10%, -30%); - } -} - -@media screen and (min-width: 1600px) { - .autoscaling { - transform: scale(1) translate(0, -15%); - ; - } -} - -@media screen and (min-width: 1900px) { - .autoscaling { - transform: scale(1.25) translate(10%, -5%); - } -} - -@media screen and (min-width: 2300px) { - .autoscaling { - transform: scale(1.5) translate(18%, 5%); - } -} - -@media screen and (min-width: 2700px) { - .autoscaling { - transform: scale(2) translate(25%, 10%); - } -} \ No newline at end of file diff --git a/public/models/license.txt b/public/models/license.txt deleted file mode 100644 index c3e6c5d1e..000000000 --- a/public/models/license.txt +++ /dev/null @@ -1,11 +0,0 @@ -Model Information: -* title: Microbit Assemby -* source: https://sketchfab.com/3d-models/microbit-assemby-f0c6ec58eefc47afaab08d8de07e1158 -* author: jezd (https://sketchfab.com/jezd) - -Model License: -* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) -* requirements: Author must be credited. Commercial use is allowed. - -If you use this 3D model in your project be sure to copy paste this credit wherever you share it: -This work is based on "Microbit Assemby" (https://sketchfab.com/3d-models/microbit-assemby-f0c6ec58eefc47afaab08d8de07e1158) by jezd (https://sketchfab.com/jezd) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) \ No newline at end of file diff --git a/public/models/microbit.bin b/public/models/microbit.bin deleted file mode 100644 index 681681c2bca0674c1e2ef2daeb81c8983f4192cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2409448 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�i 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

V|7Ak@Sw|s^KAD+@p2Z=rj`qT-AB=hlS!DFqeA0 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⩔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 diff --git a/public/models/microbit.gltf b/public/models/microbit.gltf deleted file mode 100644 index aca43b8b0..000000000 --- a/public/models/microbit.gltf +++ /dev/null @@ -1,130 +0,0 @@ -{ - "accessors": [ - { - "bufferView": 1, - "componentType": 5126, - "count": 45508, - "max": [2.160778522491455, -0.026219289749860764, 2.158750057220459], - "min": [-2.9392216205596924, -0.8878952264785767, -2.158750057220459], - "type": "VEC3" - }, - { - "bufferView": 1, - "byteOffset": 546096, - "componentType": 5126, - "count": 45508, - "max": [1.0, 1.0, 1.0], - "min": [-1.0, -1.0, -1.0], - "type": "VEC3" - }, - { - "bufferView": 2, - "componentType": 5126, - "count": 45508, - "max": [0.9354838728904724, 0.9354838728904724, 1.0, 1.0], - "min": [0.0, 0.0, 0.0, 1.0], - "type": "VEC4" - }, - { - "bufferView": 0, - "componentType": 5125, - "count": 147282, - "type": "SCALAR" - } - ], - "asset": { - "extras": { - "author": "jezd (https://sketchfab.com/jezd)", - "license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)", - "source": "https://sketchfab.com/3d-models/microbit-assemby-f0c6ec58eefc47afaab08d8de07e1158", - "title": "Microbit Assemby" - }, - "generator": "Sketchfab-12.68.0", - "version": "2.0" - }, - "bufferViews": [ - { - "buffer": 0, - "byteLength": 589128, - "name": "floatBufferViews", - "target": 34963 - }, - { - "buffer": 0, - "byteLength": 1092192, - "byteOffset": 589128, - "byteStride": 12, - "name": "floatBufferViews", - "target": 34962 - }, - { - "buffer": 0, - "byteLength": 728128, - "byteOffset": 1681320, - "byteStride": 16, - "name": "floatBufferViews", - "target": 34962 - } - ], - "buffers": [ - { - "byteLength": 2409448, - "uri": "microbit.bin" - } - ], - "materials": [ - { - "doubleSided": true, - "name": "Scene_-_Root", - "pbrMetallicRoughness": { - "baseColorFactor": [0.5, 0.5, 0.5, 1.0], - "metallicFactor": 0.0, - "roughnessFactor": 0.6 - } - } - ], - "meshes": [ - { - "name": "Object_0", - "primitives": [ - { - "attributes": { - "COLOR_0": 2, - "NORMAL": 1, - "POSITION": 0 - }, - "indices": 3, - "material": 0, - "mode": 4 - } - ] - } - ], - "nodes": [ - { - "children": [1], - "matrix": [ - 0.5346093107466956, 0.032277692794475926, -0.11701840776204618, 0.0, - -0.11969750498639442, 0.22807762221336791, -0.4839373542253646, 0.0, - 0.020190712799171395, 0.4974746554924749, 0.22946370187784748, 0.0, - -0.10945520550012589, 0.21306870877742767, -0.029492583125829648, 1.0 - ], - "name": "Sketchfab_model" - }, - { - "children": [2], - "name": "microbit assemby.stl.cleaner.gles" - }, - { - "mesh": 0, - "name": "Object_2" - } - ], - "scene": 0, - "scenes": [ - { - "name": "Sketchfab_Scene", - "nodes": [0] - } - ] -} diff --git a/public/sounds/congratulations.wav b/public/sounds/congratulations.wav deleted file mode 100644 index 70bb3ff325d623d0811f3e754763b31b6cfdf952..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44194 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 diff --git a/public/sounds/huge_mistake.wav b/public/sounds/huge_mistake.wav deleted file mode 100644 index e95f7d6149497ad7d22be4ade5fb953a4a3b282a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88048 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⪚K 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

uql9Xqa0CcXc6ttBwE16ZZv0rjlQ3 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 z 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%0Ahx#t?-3254(Kp36*sqjdGt$-<`0ew% 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 cXXt(WYybv>u$ZRCw7DB>b1x`zOqepF diff --git a/public/sounds/mistake.mp3 b/public/sounds/mistake.mp3 deleted file mode 100644 index ad3c9505fad3007b7e562a5f540bd4968d7c3cda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7853 zcmeI$_dnHd+z0TF!!Zwzy^mug+d0Ulb8JFZI%J%4;vjpATiW29p`>(ivxUaw9y57(0^?F>73jqFq{hyuy`79F)0F+TU4JI2RNI9MxF{}tM zxAejtx`@EHf`^|9%>hxOk_jX=hcc$=4+O*w;C-UfbSut6d2vLjNIQxSE?D1J`S$yN zoWB{9DMcB`;A8;m&o9!-5P52gq5vRRJSCdiiF_1oB|Jz(05v*`Wb*5w>XAW(z_|o6 z`O6O4eb#hhmP{s}kM5M5-cLBKMz=&Jez5-!HcWpZgkmH|kIVJL$)bH#Y3OnC5bDJL z*GqCS47OiFzK9XqIJkU)rwX~q2nF@QL?oURm$(-dGjmZ@s32Hw#6{U8u7Kn_!Da>U zwXR?4V{ZBb&n%fO|D!HqVjQYuikC^7(Y;1B{$2Zqv3=2Swl_w=`2|WO|4$^2>cuJf~S zc9_*J*(Q=q{&SRNq>_n9$ubaf_Foe7XE9P`KjbtJ;_Mj+VyKlMj*4R=a4spa*ZN5+J=X1c{;}!pzy!l3%@Ro^Iy9xo zbp^$;khwqcUfI{3NcyLbjok4TYYL}qAHrLYHpeiZ&S39JH955F%RCae^#QaiG>?Ep za2zE!F{!$Lrn!VggY^~lZN)ktNAoxQ6M{gFWu<3gLh)E-%29--h#M1yA2uispq)u^ z2bE<4?Ia=JT-M`IxQ?>+FHz&ONER*3emloS&2@Fi9b_C5qcXxCAdq@}gxUpA%%>2N zP;9Vp8W6lY?fe6Meo(?*{^ISd-RjV1fy`x%Ks} z1{CcUwp&%pb%MlYrYNkI8PqnbDr$t?Cx;M#)}dH2j3r7ednVph&E8UqR_FCzT$tGb00GV_o_;WAiMD%PJhvy&8bYPUNzbIDZQhEoR;4mD zk867BM@Iy}#44(lLOM8HH|n1dFF;iyH_z#{0vJ^HV8=B8#=X1vh*$vFPOH~x%cNLE zBAFDW**%1wjV4^iHm>QWbEm)Aj&G{0Uyi-bk^cSd#$e5uhO*@%%Lm~*nj67OhM7l= zVdNgmf|nmcV-o^~o;Fp`$&25aAHO7oDtwhA31Q@R&yA;0F8zXL6GchF3iO$z{H`VR zH1oC5*$phb=GHtG*@uS*`i%mAe0+N@H+F4FqOZ3ONiZX6HHZp%c*D(PnOEM9D7yT zlArU~%J03CwvU6&>%Qk+C@gaHa@ZMpb046I_<$0-=a&=UEhA%nBYd)-MBT>Wwn`@% zKRRq8RFqE;hVy|lc2cC{9t z^g7oVS9fCn=7(9%XUd!1PrKa^A3o?G0#=-h{##!0260Gz#Tips{t>exr&{AD-ran` zr1tqt*v@(VsmwawCa4MpaJlpcB%myc&D)@cwz0?AEhrMpSnJdpIzMUUg}m7%$>$oz zi}@)xYOXP{bq{EL=X6wAxV8wwgK07GG@zOKqP(5@N4C3Ja^j1;d{@i&{_gi=_19wm zcOIn+MG3_ZmZhrer)*!ZHU>h^DYg&}w9TMoY+r8IiE3%F#ygd58E39nf6Qo841hIXfFK}GDs;Y%A?Dzf zUHfwoYLX#$V9M_VZXc?F2({5`71xZKYb4=3(*NW1rdyg^-AYh@a2#T#AS4@Xq#_2F zfHV^#ev*GzTAz1%)d>F+l1N>@>u|FK!e*&jxntoHuWyRHh4&QoNZer9j>;`6%F7ae zD?VJ%z45^H9$!zbzW7=^1WlW13Hq#V0%D+-8x}Xxkif1pyx@1ZycV01#BZ-ABM0*( zR!8ad4Kji+>0^b2>I7U5y*q4NAKtlF)4UU~-1QZ>u^jZ(pS#6!-oGw%>nuSK;XnZhXD2oA$;TgxPAtcmw9C^Z zQNp|1Yqe=itHO(07kEdE`NVb9TpR8vg8hosR7>tgzC9uL0N^Xn`i8s-Ju509%M&p46JHb+87{s8Dib5^6V zrqoG_mPz%Q+%dEpemdsJCz4CUiN1joS+-Q`N{ub!WVWlRZxuMM{7PK~S-B*D3oC#e z2$Knd1BrRS*RLnfQbh~Uq;k^8{))G?Lj``C`PEs=01%>diL{{j-R`t`f1st|G8<`mn*eElPWZIOKq_ z_7MrtP`>7au14M;|AYh}gVDBgf}SfNRpH;}Ut0i1c#6L1Gmj_=`W?o2Na&tb=6jDI zz2LT6$~qZ#P6RUfchB4I^mN=~XRGTPvuQeqDeil!fj=T|AgW4k=XT?*sBWJuYQN%^ z=F2UJu%|^yf%}~vPxKKctuA&fHNHOXOrw7785V7P%l2fc>Km`p-r-kummP6nQK1Y@ejR82g?u4rILBGhp;}?&{Y~9N`l@eB zFP@Zp>6tVuC`!b6&`|3NbJW(MzJGJLTk_P8MK)xdeXCz0g66GkTt0HZZ7>n23e zK)^!~X%DOd{NS$;N+O(pcU@8bS|A(PG?)NxGVGx&K)Uk|&zEVG%g?qU4+fR`Z0Y(?} z_VwznY!>_HvjC-R-y9@ex_X!aVwmf807aBm_qm`gH?9n|ohW6FJMzV<$yM*thPgJn zi=xD48UskS)ig`6=*DXbZo6W8kEBADH-gylJ-4hImB>E7y#fqBt~yVJUmWhC(_ixM z{$LppvN3Om2PLyWkbU;<4bANIwEnKQQqnk0wXs>|IH4cuF=jCHQP;muMA>nY^&_$B zb8ae=QuHWU>tBY+%hq_`V=&Q6>psZgV5>=l?Ue>!Kogg*+MRoXmZMj6P>3mM58RMEhAW>C9by=9~ ze@#o^aNXNUXj3ypHj+&C3D5W6D3EOBMvT5!Gbm0?v2whs`5UhG{d}I}mS3nbdW1?Y zf>+MmhBD-~05L6KKBh`-5DUUF^xVL$NYlu&m|ATH?6X0_2=J8tmK+dvM*)k>9Mku9 zvH?&jaV?;|gEqAXsvuFQD~?Elrm#Ijak|W))w&+pKzf&_`Xa7BJr*W4xo$6}DV}Bi zX|6bg+SUq+E~8{+5;=Un`h-06|2p9N^>)vXg{~Gjb1gVjMb-t=JAIabFNV)rLp8+v z9?TB}S$gpp53tJO<%Oaa7>F6hM)A~?fGF&Ct;{FR&ubn`N ztSBR1JTm-RH9(n9`2d`zuOLq$K2ovq_hJ1CUJFEz>*3-4!eU{zF9RVO#&$+@vAsPL>oI`6{m)1DY{*jP-(Q-iYg{PZ{Fqc*Z&d=QuD<+z2zvQmJ=2 zE-tU#6OA-vdu)UaV@bMk(|SLKTWw+MwZTqM&%R@^Wx>tP#TO;D1NvGWUrx5n`h;(c ze)XP6Gv&{(pdR$klXgNZA;gH&4V@ir{w;~K=>x~tx>GjQX32uO3h~8x^XR9O3(|T3 z;0(O)6p`hh=wdcNf60Qm!%D}fx>L67tKdq)QA6-CKE31a>>Jr}q+R%Eu%238W^Y?x z>Rp$7>G7wx>{p{*k4$!EMktk%N|s(uM3&-b7s~U0FAcT&gd|_4_+TdXvH0Gj1EfZrv4)DF z{EzO0HSjA_;-wdOy-y^DZ+hh9M_RbEJtO~6urv{r+iz2szED@=EMX0bra3*91WCA z)NUHq?6RiveeADwk)N~Ts3h<8ldz`5Y9eQ*`JPL&%uPHlQ)~=TaUYlPUYS}+dE~xQ z^Jj-w4_Id}Y0~z0C(CHvPn)S_vR<0h)Z8aD^|BRCFSpX@#YXidLRXGj=rcWCKbjA@ z%c}HdVBjoPm?xcmV2Z3KT$r+o>2U`7gu`mem{B=Pi~T3s&|yNl&t9SFE^Lpw^Mvv@%a@m>RaNjvL(BjN;U?tu0wD-+nd$o|L~% zkJ0j}l>E#PZSAYINpP3!rjkG_9kI4-`5R+gqrx>CI$4s{&gu<`_NQUp66yYZY`QCD zf)b3SAD>(F9@UpTOq8G3Z(C(((r|xyh*h|6y9^WrQK-|_jk4b@Xt;H80su;Rmk

znGT$=7H>aFMN75tT5 zbXGo;89LaQT@e#cL>#u^qNkkLYXu*+GQ6Iy&Qx!_3QpjQ zS$@QkEy~FUx2?44*mzRUvD<CKzeidW%M;CuH~2bGz+e-pJSVQ{|m=Y-}FF8~4VW6%`f4RLWoZ9A8Ff zSGU}K!+))8HTW~Y1swOHb3)x|XbhxSQMKY2T3_cj+2m!PcR2%-?v5^VZ*T6d^sn5+ zumLpzY;&NcDz|w}?V0ih1AUsel|A9)BGok|%cASjDaz`p*`!6gV{*pBr`BWvatmDm zz{>6pau4B}XLE6v#L2j%>gI9x2Y|Gx3|^HqD_+@-8~c6bT@#~g#XGiF{l0o{bc;M& zKFMjA>$qR?`6+gc_xYC1Fz8j3a@O@;4e$hn1A-Mlw}w$s##|~q7z$J$b$rrv@z@2N z{3PhQ#7M4=lxjz+u=F!96Ai_?c4LAeNy9LDPT%NJ81{K5oZz_;FgGZ8{3Wm`&rg)% zt|PnBzYNVl1__sq|7IvqEw({KGlrkRtwh!oF_Wb7VAC|7(HFMc^6E+d2qW`+;*Xbg zKnKS&bdVR5TU?_q#^``aWrX5V_}F)GFPKv9z|ND-X?u3ipBIIa(K}Owa-cxWnY<#Rg2Sl+KxD_kjOBFMM>NapN0wISYAq(1rd{;e@Q)m3OU z%DMwmlmoz_ZbmwKCP;*oMdlZv{tIkOaD#w@+Jv84T!f*Y7*Nt{CFRtXrq>Sdh7Ho{5RaO$n>zl6b?_cliNHbI^%c z_-)JYv5mYJMn5QQPHfAgkujEI%yPVA_zLqUxC-dEOLp6Vt8{l4KXs>|6qSrb=qRdn z4}CK{RVtCH6_&AUuSw!_`UHZ*vVCI2-X4zQZ_~uF7|?JQ;wmWg!PGF>23{eAc~Q-2 zXN6QAF}TrmbvTD}x(_2%G(mqftEc`-lQT&%6=SJX2QAp!pq>|IF%3eze#jl}6s+2< z$M;WYD|L`sc8tx0e*hbw< zya1G_APn3i@J)6`aSTsp9CtENi-gMdB4fYZUgx>@S-IrY+wQI1641}OVMwj6_~ zjg!TuRn=#35!q(M`n}8!X${6y6hE)kr~1nA+g6wZ__*gM($judA@pLb#uejDQ5k!A zl3$YLNnh*k9*4i2OYpGw)l|p?=3XFN3!CbzMMtH>q1~s|cM*HPSI{$S>}xM)NZZNs zWL~Rwe87Kg5&{d%46z&|yxx|r_u~L=K&9W(w0#_zhI}PV3&mSxqTk49H^}}l-kEIX z?v;uVwCQ}DLB;t=LZ<|l_9RH?0LdSP-I_a{hOo&<2);FCX#Gu?EPD9b6?S(s*kKa? zPv}ePAf5bZ&UI~n`?w{47m@sHAF{-xtLLIZIcmW9Ojyq^yyNFcsoWH?@CyuhKD=gy z62;W7-49~pp^r1BH_wcHM9u0TCiy(A)|U2W%Vde8)PwTPZM>|c3*WI2FFNOou#n~5 z&0@@V6Oab}{s_-#vz~{Ys@?6%qm^zEG2G%S3OF>EE8`W)vYQ)4^geGEUVq^ z7j5TJS|aCVXq{3lZgs${DWNfObJ$phKbLT$*hM`l_=d8OwBemEH{LJMve1}dBJ1G) zn<1uk0a2qI6`a4rr4dWq7dOV!M)-$whVrPzflOC8wvccdbbsXioV+==tcXMU1b{jFD$6MQ#&fm#q7b0o7F5|d3*4~ Ti~Q}nLpgsIrq%;O!wrtGqZVTCu(=4)oQg$pqwQn6p#>Lgb4yeN|3Gne@= za}KkMNi(yUi<#}r1-RacW16X98)nXDw&4CYq^FtXNZ-L+$efL|^HGHPcPR^*3jXyn z-FUjpRPjvf|4_q4DDQlfe5Tiiey`4MJYB={Ri+I!{J(1-3Lw}FW)H(LOXtpAx#+IG zt#2}{HXo_+c_VY?GVSDFaovjRuiInN(K4G`?^f}v@JqM+C-}2hfO=g7I^Rj^cinEz=+;+ z^w|$?4DuX6`X2AtJ=qQeM|<*I#68hF2W3sQgq)`v(XyR{3 z57OsO*wUbBZ~Uj~^X?t=XqTU?qdA_<=TxLg?9lP{(K_X+cg8vGqvpFav>lUWA7H|h z?V@Ran#=<_o*4I@$1~pjp~)jN@mG3}=JW0!nmB~xiS4H8&AwZRJZI47CjRQR1DuK- zVjM63LG*vKFEp>t{ge3)PL_pha*)=AWAmBjyWpBWhrT(?F2(V-$^3hmI4u`j1a+mc zv0ww<{G#c|f7H8o0Oxxq{)BUv9GdvA6Z^zFZua7ojXuOZ zb_m-fEj8~?mazxvdni9@UgPNXtJkIj$J1z=MDz=NC(tA4ZV31G9Ordjo9Q#ntvSx9 z$Kz|q%i1%^=V*P%i|1)w^xX8y!@HB`nsrXKv00wicFKS07>s$(PW9pc;^;kh0K5tt zJEvKX_ujM&%qOqEnd9}x-gk{W$V+L3(x~Y5IogeL8);KxM)NesvzgyTxu*9H^w~XA zb)tXvFai3G@1-@*y}Uz=%MLLt%EIQ&C)dl%O#EfCEN|}7G?YWz@E$$TIra%}j?lI! zxEJ!~9eu|=;A=yS_g+ZnytWQ`vH&_A`05^1$k6fd_zFFz??u~@n5qNkbQ~ma!+kG( zoDmuDtjR6Tcyg3Kf)sidXLz1+vobCurhHI3XdjFoOdectaQVSC2d_DJ!=bAV-E!#m zL-!r}@u6QFwhz}2Pd~i=@R^6tIegyXU576}eC6S54(~mD=i$2#-*@;^hre|A9}Yiv z_?5%|cKG*)|9JT5C^xE%nxoEWd9*P)IJ$83)X^=Y9~wPp^xVeC*d_e;E6(v9Yl~jg21>ju=PWBbg()BfUo!9a(;4&5?CSethINNB(p) zc=Yz8pE~;4qhC1s#L=%EeeUQ>M_)PmFGmj_J$CfZ?^y2y-kJH%@H?M;=dpKQe&>~U zUO&bk>piyd*hi0Dd+f$zdynlqcF(b|9Q*3AXOF#f?1#r*JNAoXzj}AZyTk8(>fHz5 zefixVy!(rH--5Qsysytz9lZG9^@pzX`s|)VKbY*Z0rc69!yh_)?%@j#f8_8U^x2JG zpWTZ-+kg1U!_OXm`SAA-|L*XA9zHb6j>@CPsBg3vebzsEGWu-u=xL)n(Px*A?isyi z^p4THMn6CL#nH#nXD^Qac=Qdg&(0XzJ$A*|)#$UE$M%igGxo``2gV*8dlG&2?Xj1} zUPhn&0Dbn0vEPpU5q)+Pea0S@eHSHnY`Cm~k^^+GfC%Ou^($V!UI#W&GCojd7N-$=GOYFxDGu zjn&2~W2Ld&SY|9WmKckSlZ}&%1;$)sjxpO9Hf9+^#tdV+G0mtMRioD^89hd~VH=jA z8LED(ex<%gKUW*m-qL=p9n}6!`?2;t?IrDb?OWQXw0pEqXjf}jYOAyr>YvoN)PGT5 zRllX)t?pEZ)q|Xr`+WP!&<&wKIBT6pog;3GdtEpkUK)Ne zd?YeA@@#Zk^z`UW(WheFv8Uo}d^mnv{MAHfaxnQ!YH4bJnoXacel=6htjsRW-kyCk zcW+Ce<*oc}g;HU4;ZudTi))LowKA<2wZ7h#Zo9vuyW{Pyx4VDPv!~~erE^L@=)Ix$ zsq$+Twlcr+nQFW?t#(oExAnpLll3AR;NoROVz z)8K=HZ_j*T=J?Rsp)brjW7d&jd3gTt*5OxY56}M2h&s|a^7x$eocVKJm>ZqDdhYJI zUz$5QZ}Ggl=O^diwSZZ0=}GEIcb{B7dDlYU!mW#TFM4KiVDZaK0!toU^2;TEUHa!` zXD@qe`9&+374;RDuH3)MTy@c^L#w~P`mbxwUi0eOch;S??q}6nLTs$nY+*Y!CB&2=bXLr?7Maj?mU0zo97Ikv*DZ@&KW)L;q%|Up!0&)cSU#Y z+V$HDhc7<3`>fqhf5iF7%a;T$8M)-0kKXjrw=VTv`uL@9U$*|Ti!R%L+0QQLFTeMS z!WA3#j9fYG%15u7d)2n9p13-B%_Y~oc5TbG*IoO{btBikc)fc44c9+?L+OUwZ+QBK zH*Z{e+bZ z&hKCIuk63x`QvFnUjLKMpDg~#RX=&_ru=8g%`3lc{q45j$-jH%_m}+s#kY6;2m2qp|Kqto zME~&If42VTz5n^zAJ6~eK`3zg#COP_sgA~6%P_X!NMgt&xolwusn<%iI{nvZ{u?B{ zqFZl>p`h#FUy}7Qnyf{V#7l81&LAli>u#Y~$Q6p&Vsk6xN`+qRa@m|DN}+5nTddZq zD0GlC`beb|qNSlc;?@eqB)OVw3d_X0Vg2@72_Y=2gjEPRB5_(g#S0wAnNrdru4t!x z8cW!;%d$Lg`8xZ|Xet%=#gN7k!r{QO9LM_ogyV=CB!p)Ynl%kgA$*USGEK|!(d@EMBfKjcIxj@pb+n9#Z1>DzpJ2!Q zxS$F2ClG=MX>A(Gj5tK#IhGfLTUtp=L299lU18D_2iYRo4_#Md0!$H5H+N=RBGDFCmMpMM)Bfi^?)goo?Krl%>FZk7-I+}$(~Yac@pL>aBu^VWwU$<| z`pEbZqMv)wM|K_~Clkp>VhLKrTYLBZDBL~r;Dcc&6kc)noKqabRozogImQ3ORQFIl zzH=8jFurQX_U+rp&mu|`v_&RtBqO$ge0pgP|lZxoX2%L zMslSdk}EbUGl@ki4bCm*gho*!qse6L9}@$&-8PW;$2WF9vgQ+?Sn~+!Zf{>RS}uR9 zQoiMua%Jb)`^JA*xc!pbZ@=BMuqNNd1Z8ys&P@_Z64^oxFIDQzXZ)GGUkcFyz=Xtn zoSe>;a-$L=X-AKRg3(xT{68_4L*(LVx?x%*>V!01(?d?I)knUeJ9;P{bAr*|j1Qem zWPPzN2!`Q0@wn?4W-Ltm6j~43aloVT7?WcXmX5<9@*&mc7z+{V`VCut8;`0hgSou`$1EUdgh1Og9i^)eU6V!@SHr5(u}L zmwnW136DRAUEonp<{qYLAEkXSfTw?hd>#Cyh3R6ZF`Jpwp?%ttpU$UZy|-NP=L4YX zNzRU*L{hmIgCNz57#C@f6hZ&Z+XbmgvpOl#=w>C8xJk|pnxxP}q$FkqDRy%xs8Dps zHI2)YIhH@oY0V$gW;nxZcQL5Ct z*V0oe1tEoOK$TinpgKhNc;n6lqN)eJ%;5C61HQp^)Y0 z`^ZJm1FUe!G!u~!=)vN+$u zZ>+TqQ&naqo*vr0fl((WCJsR0Jx^@XM~2~LT1U2$v&bFfljI=`r#JmWF5Vn$;C)B* zZYhUJ=R)K`Cekx{(nGQy!+8IZ@(UiAKeDnNT?K&oaR}u zc^0BaWT!6=;odyD63VB9MM=AZNR@JeK;LOTNy~0l=2R)+1f6oBS|~Qpij>ydUSpX^_f8+QNu~{Ju zZY)MP6^x4~5hxWBp$8%p)+M|E&5sQcL4k6|`k|1rGHc=tS_V-B5<-3=aIEX)XCnlf z1d9U?NuhEP5oL!3)+P?eIqy}*LXkPKHo|$evE2Vy8_)jtwM8Y&@As;@GOfrVkq*_#Dl}^P_6j8c;ARZ8Ae%;}0 zLUJ+*1V>t9ydd)7!H$0l29re<%$k^YD~2rN`DaDNEA6Ed`(>)7JVC!eWyf~BXQcP_ zONjr#AV?<;0Z+Ae_rz|2PWL|!!U>fTE<#Y*iCy!-QPAF38y)bex>$Z{z;BgQHK6c0 z63wx7R#hY|7x3q_HuRODsD?~%ctKTTk=+)@DSTW<&nm5cRoTsj6;2XW#m6Ze*VLhlcV+Jb(j*jwTL3k2=6)!9_5sq0CXG8I&BFoEY_*zY^^A zY%&O*17vn%)AgG+jcE9#Iy+}{u4UEEWd4uECq_2m23gM`tusY75>2f_8=BI8o4T5Y zT0ALMnL)WTRRZ@u_~6g}{O8Lqd+D;vyu6STSCY*LeON~Mn(I(Dm7!d}3N4_?_?yxx z80y8uH$m=M2@MQ6l|0q;DdW#lel-YPsN4Xrfs~+%pvPRu-L|5N9O1`T!HR1X+19ul z@Ga@Bt8?vl=l0dk}IUXq$I<43;eM-Un*+UF7r188Y6{itdiMJYEeX}dn zI?ILb>#TW1dwg@g* z^2Qt3{gs8mMmMyTFcIkR49+JLEegZuAJ?^p%tm4@88d?)of4<~2>#WQpYzpa&)o=z%Dl z#?EI@C2AsTrY*m3p&{|2YUu_m zszF`l6wwUmju@6g0gfy5&5&{tLl85`k-<>#%ArIaCZ8~A&pv=y8b2)BUW6xs$}sgX z^J>MZ6Pofq`^pb7nt_t$4?2}hxz0^xB)#EyEYiQ#+mm3XRLYG1lJ3dIe02Pm(R@Bi zlF|HS-u=(ULT+s9{V~UhZQXJ_V=_}3-$ye|K0sQ%3=H&b4H&_b7{@F#hgrm|Vm3ne z+=<|=%P1bz0KHL;k?WB2T) z251UR>(iOM7ALPgjyicxEz~J1bt?bEgzVxt;5d#TY-0ruhm$X4iO#WiDlFl*voJ5V z3o6HgD-ltWWtKmQiv$fLXrw6SVr;Sv!!|d2=P56Jp_g*Mmx5c!^KWEiHL4&O=nDX6 zu&fSx>6k-Vlj`I4DN%vzC#0Vhqbl2R9VIB9y{?0hPSj!qIj@j;D95mQZ$H=Dm%Y1l zyt@P5p7J2y#P`V|@;EG+8G(wGTn5a-lk&2F|C>=W99z_3Q zSg68kkx#y~ckfGk&x$F6xkKsN_d;rQsj(@!fGCpGD#;lMZxmT~nRwWQw)8l1?tN+e z(Dy*nK~@W-GS&5e_mKk{_^RqKDXFA`c$`zL7jbk7IEY9q0tADEJz?yB zQ=YM9&YX>dWt|AVq#$XM>{I+{Rt^Lsnh(m6tH{BQAg@J4Rrc%AU_e_TK_AlLe&Y?6 z14Ds!3+0HDfSW^QhE@%}=BZm0On)EMjPLQUX^d>#IMP_-XH}AhmaLezAaTT(uq=rc z%%tP^gArB{@M9&F4Tl0DCus_-5;EI}Is}*;P(CYyVG7VUU>|Y7%aH=B%BxsHD)cfs z22=+|BpBkKdT`^WIyr&JcS-AlU31qi(98w>xtaVEk0Hc;7+vMHd>rR*k>vr#Zb>D#vH=BaqaPHWr_Td6#pGK!+`n#JW)kxabR0fFfb&+C3TjEKbpY7AN&tCxr$e zLzA_uAayFYGpHG|o%eIF#i+50BqI0InPod`$sCgX}#QD2-fXVjXy#wVn;9)6Z3fN7t^$h6wKs@-4BU)lQ z&{(i$*2&(Bz>k9WD0ksK0(cKiio`4#T`L#@-V!(0dE6h2o}5b|_!bwhw03C5vNeP8 zX&NUHj2P!;I)13Htj0>wC7| zDa%=4K z`tqgcp1btqS~c0-J9lnxxi0J^=S-V6toNf zxGNl&gYB}J>y5Pcv=<`1SxYVswPy8BAd4MFwl&t%lPo1mJ<)CS;owb$mX<;bKhR>w zdLZJ^@hAK! z0kz%Xbl8`1j@@&^2_Fc);i!~wQd67xaKsVA=jx# zgF4oVp|VpiQ<>7ui&EJcg#5u-j?@=P;=JMBh|#O8RXNFT=!A0<0=LqjX_e`m_$sm0 zkZJ3hs092mD@ur~M|_THNLt9w_|oKif#tPMeq{FS5m+>`{8OJqs)Q_AOcpH>@~!hEhL`K%jOaeLt{kD(nDVQx=Us zP-Bw7o5~g-yEL_EpVis<<@O>RKtvOwf#zi(I{r_$-bzO7;jp{DBd^h$THc=RhSzlj zqTt9h?_0}(c`@w7IBeFdFfKlLg_wrt%E-b(g1!docHzu*z;9u(!fQ-ZoX^gc)mfsz z>YtaTX9p<+f8eK^)ek4J0+cO;U$N;g6TZLIcDt0}Ra5_6I z$UTs{!yjOI2bEP^LlOD<8eV*j7nQGayK!|fn$~O^>qB2qZyDwffhW7zC^q~JmjXiA zyAqMF`QVQ%T(xo~{#K6PxoXwQ7jZ(bs2_?2Z@(Pnz2|?6VOC1TnFQscYC@$>93|9>m=a@+37q@ z11{7~a_ub5C7CRA`8Z2w5rSm;Nu{5qy29g|XV2a|d*xtf=V0d>mgDm|8$i~xXT+|G z&6rJNRWh08c^8y%mgn@89La^+Shm&4MdXy;Of{0tRIccyk_yV4z4@akvvcq>v{0*l zyue|U7oVQVQZ;M#_^{oC~D#?__LX(n~+pS0_@~+*&?g%fIOD zKR`;qz>TTDDf(&ZS}d?2KlPO7S|+<4Ml?)%_uW}nkv{0n`(Ew3^Uh@U3t1B0dLF&p zH~zJs|GZiSFAy;w|BSKpVkztl*cxy=&I=tVasZg?lnp!NhlDjoSabXtxP(U(l8<~c zPWE#KVaGQan3VUkL{i3IyWJi=S4D10gd5RyVRRU7gb6G)C zYCvTMEJf;iEz>@jGOm#f#t zLOLK1X{hOXK0&v^ioOrx0t9mz7A16kAQnI^s<6Yc(Wwq$9~^)UnPd@LaDrh-OcrDb z#X96vMX@S&B033pa#FSnvGxy)&Yi5(3Lu%44gU&coiD1>{G8Mwt~cZH0Q#9jKVOEq znP4C{^FbHno~4RaPNb6n{S4_36$f-L7GX%xo^r^{8G3)(?DqS6G^aNqn{&FYX(Bfi z>Wf#VrSp73F9f)+&P)`Rl0X2=%4sjooD1^=#zyj-0@s>0gQS(}Ad@=Z6klNc4QNrA z|CA4KIDh)-r-MiEBU__SJbc&V>=QpCpUS?5u>}8QhS(u;oj2Fp5fd}1C-IXj`Dy)jdR#8N7MRxas1>iU;qfC60e2qL09We^K?lR_M%!AAea7N|HOmLoTu8@QK z6`UrQr9NDomf>sYEs3@hqAZ1uF!+pE04K`PbBb4^i#_!n?B9!~!zKPzfmnkGk}T{$ zj|l-}pxB-3Z~zV5LcId)I*T+so0Whs)k~0E)TYGKpe^J95jnId0Nxl_^h%biBjAIQ zp2y$&RGtP*}7C0gyc51jFPBoJ6YC5d`EQTbYQPhT9JLRXt<)4T^wBJpa7F z%J>sSIHcgPvs5`zGDRKUT>_h!@FD>ek9a}Yp`sdPVXj!b#hVBef{(G44XU~-vkE5~ z8jxK$UwJOlqKC_=z8Ibod|WOr2x6WW~pq8|nsURVnWKAD#UXi6Mwap6RFC7u`3k*w>p z6S=Hu#(idMu02l&LGb%XpQcfV2Z3u8E?L(nv}wQ#9AW?kHtp$x42OsY4+jwpU1Wg; znd4v3YJ3Ygl)@T77fD$`w70UHSf zv1!1bIFriBR4QF@NK4rE1#~lDsfx}kJ|T@kk~Ef&2tF;&6RmvwMMn>!u#dah}qbRh?PI&3yKbeZl%u}!q2nkg&aWcpjF(I5rK?=Iyb|Cg5 zjfuet0;S*&m7IG#QAO?-?f#siEI66WD{iZFv}7CpUvv$sr*3cgTqCx%A*pAdv%Gs( z#YoRs_uED0%sC12v?udYu!YQ^qxw!-o{Q*xv-N`A8d}^-VzaJWKD}qD6$qqR?n`;o zU9=*2a(8uoP=k3!^<&B}p9imwQG0FD7A<1hP#cZ1YG}pSsEy`X%T$G-Zj&j9fP$Dw zi3a&nXv4E6A?L^!-Qn&6z`a$w`QvB*)HVAQL308|doW^arhjD3EKZRFN<>Wwe%%P$ zfw*C-rZw-*WTAW5<#(?NxyR4W?`rD{VJP7fcYOYU8BVpfX#qs@h!T(ifzuS-GX19C zh*49`d#|U+F7i#rp`!x%Xc3c}lzw4^f4@-XOCR zh(Ur!-*2n{v0iBe0}+HH5LxJVL`DP`^5qg&9~80&?^esvr&L zgfzwlo8j8BL=j$ck#Kaxm!UWCegNG*)hsUp@)+Isjh7*ucX*P0-F3!4RQ;h#o#q!;oKF zKHPuN+P0a~x@KzH%&x{2!^_uRMBL@qrsu5RaFaD2OvaaWcQ1=4*QT#s{z>bm4eQ~r zpO|!ccoeu_R7S#xhZxZK>u$SZBbS2}`hZzzjrz zH>_|pXOm=mn`uf0XA}yJ>kCCzm8HS?;em?8D_un&>qM=VPDH62p?t&;<0(VW+ML$X zQjZ$#9j&@SA`&b5EuSpqA=_BCEzj$&=IQ-BGL(A<$o++wz2@;)f!sg7jod#gRv6z_i0yI`#r5lpiLO zqAz46BrC*AD#Df|-i~+88hXENao0@3Empwe)9uOA;{fyhMpU#dJr~bJ%*x8;Xe0H1 zG}|bn2NJNn%9w-mm=)-Yrb&qCMCw%mw}i{SW;fOM1OYrn0d9I(5oiW_Rit?+lZA{! zC`0oK9wU#{;4*h=ybzDuJ>gg}9xujvf-#)**hspF?y%C}f4Y6`|1SIZ_uf|;#W|+* z3;|xP20m)DfFg7;j~NQ^s~0`yfw(BZIZg$TNAo(HqhPKJ8v*heYzM=TZy;C0CULPjgea=FxsZp}dG!V0(>6xBIVTF}*jwmNiZLFBV5-Su6+%gN% zeJ#o4v1os3mT$Tz39h^O#hY(_k+|b;M|TW%W&PE8zhMd1yRbx2*i&x#op;e$o#tA; zY_k8Wgw+*N?rHRIjX&GkqH9e%{XtsB%@|@&ejfrI-sWjnz;!(4Ph*^^&(sgts0iJu z=C}>w8(DMV&J~S0i~2{ta&dqu zwnW^Yd${X&$Xn0>;cb9_5AzR%g{WtQ1kq-~@y-IphqC$BwI71-ZpW56v(xJqHP#R1 z8Yw@w_YeFtXWf9#n>8z?WfqlZZE#^*u67WYGd1Qi=6D|{8zWw)(u6i}3iWWaQUO5M zR9LH2EFicAmQ)sEhB{@y`KYsxV7^kZ12YJc2tvnm2GW-P2m#UPPsbZRgebR^{nL?B&z=yo=2p{oJuv(sWxNn zMjeyX(@4?1PV)_#myHtxlQOVO|8F~m#9%`$58zz{bG|98@oAc6X`1hp=E3*)iNO!s zOs!bA2Yi6dG@m>yu)%skvjQQ>$FA%jSi$*G=<*DGLXA@_VvjHCGc#; zA5ddt75W&!o`nJ1$i}p2MwXTK>C@X)1j0b+nv#{&w#Z}7AJ@Vh%FAda|;#VbLR6VH<#EVf|VS*e3mhMATC*nVwmAMVCvKbUfpKKVFL^NOq zZD?{<@LqCkc;j%kiub?%~w{A!na5yZY4J?%`S8bA7&fw7cf_r>H)(yFg%S&08sg z%jK18m#4`5Yp-3te0l17IHXedR`Dajg@1YMx{VvJJJJ?!+qkjqF&uGuVI}ey<(04@ zCgq_5ZkB^aIs}@bGp1Oh{D=xQ@BD+aMQ8@JOSw>^mLyFF97H>=z&*Yf0M;pK#ylD% zqlwaYPMi_re@b<9qyqk~iup)KQshO$Nmg}VieyqQKf<5sc!$rNK=AIA!opX9hn(J^$>olKKCserFDD`Vom3i< zRu6LvTsfcyhZqK=GP1QzjV%fp_|)L|SIW8#P{1ZUHR{SSSVG5Zp5wtJZo%v(4}stnCxrKp=pC z3Vb>u;EQ$VBVSWEeU6z5PM4g3j_^RsM_z_t|7XRg`-!b|0pAAV1cuL{c6EN}tQ|B3-?N)TfT zhbSu9isd^I(vubil}$o7hO5F88B)T9$hnIb?}`@UN~>XWX-n%c5a_|#Ef#g^ z30!DtLAH4g=B%Ouq7%{j$Z9(#x#|o}&31&sIefLluC%r*YKbfooX$DL>QdOK>`z;2{EpQqtDQjso_#N(^R#Tx=th8BDq9J zZ|^km=(f&s)@RqFd}h-s6BW>MQ0=s?E?SJb{%kJeMsl%`Y&w(qPf=P4E!Yf2QDug2 zbRsJjFRVp01-J*kHpLE+3*lW1K#NGgCP4T@gZ4%T@nf}75S*4qd(x0kEFsH-6T3kv z=P61J8YxNp0o^B6D2co^YkuwFht7L>Nl#7fpSFrTbK7mx$k#K;Kt>phZ-D zw6Cgrl$FZHClz{NXtW1YL;Uy2-~%pZSr_c55v<|Zj9A<~%pJ_9nJ+U>Vb-}49B8#- z0{~H@96Ek=;<<-I8RE>xLH#ZXZ+~#VQpEsRw4w;rThU_%`9pIf5|fOd%{>H6jQjR|%WBT#myQ z;rjYmZdxDKCyWep-0+CN%^ktV73QAL3Qr7SVPX0!P`a@kQuu5?JU_HW!w=Bd;;?wj zY*Czj%e0my-(t$z4T=7B<;M3SeyG1!vxDj z%$aNv!DjGaLNa>T0d-kW02@4rI&mrWlRdFWWJ$ZD#X9mW>Pf~xM^`G}(dcO{siF$q zM3&>Gf8q00vAQ8_h~a^gFF$$b&9%-!dvR}hR@?M`hp?&h05A+Yefk`LAWSNSW-dWQPsC~{cUfr zZd&w{hZk*nu5taQMK3H~ym*^;LDHgrzb@V{%lA*64D6U$x!{7z%-Tu$=W!Z^+3QG6dG%zdP8iLXZuUjFTlQ|<{o3WA4-RtWZ;EUmiEibT= zNmI1K84C)is0&6XRuXZUd@?$0*+os2<1Kz7v%sITz2Wc=?U9y3FVxlUY;Ly83%x+@ z_%=5z4E8Fm@~2zY1_iBUumFJ4X$3}s_1xj2CELTP>9F@MaMJ;4q3Xu;Ow7i*8wGw> z#r8{5AyNondCPo7S8W>gjlg9BCuol?!&;{NOohBW3Vdi0j5Hed3-qPe0af)SxAX^m!i+Z3qRYO-)_%*z z#fLq?cd^8D=9wB z>ei3=Ev4Mo*9)jkil>r^i^I*EhUvos1FXj=^|3Y=*1IQQcTAr5yg3Dyq5(INHRbbr z6%!_6G8LD6zAw(^xYTu>3^i$&854P(wEBuw>W)f=ulX zd<~c(x56XX3!P&jvzoaG_8LBDKo^Q;Daj`V&k{q&Hcc*SyQToU6k(e|8r1=JP}9S4 zLG6QBs)9QW3ZhlqP?1_E)QCZ-4u)g~TP@H~TuhS+{K1HC!V+Nm4UnJYtwdXkVaQfI znz#Hyf9-U~Z#lU{TbBja7Sy$BtrD^Aa5!XtN&wpwTwx_E1ZM{9d_MRunM_NH*A*!i z<_q`|$_yfEkpRi&INOP3RW5{%WCaZQ7 zxt4ACY5*%<3WSAWMu1{#oPUSobxT8-M6%* z50HZu%%CcZ_!j`s9I_KINim<&k-IAx2pR!=`MMGP@GT(gIFV1 zp~2lI61$X+EIaihyEiS1q)Q-LSfW^CULpHEKVmER^(?wl64*&|JtVXdN~lyhMYI)G zMPP=5?&uC=8gvlU?|L{|({A>rzjvd_3A|Hs<($o!dUo%Kz4-iP^Ul+i^YuVL*9_xM zI1n@qp?r^ySoPMx+|8Tk4xje#xPg!E=;v#2NZ?4q>xY6tIHU#|i{@-zj8%k&W8kmU zJlsE{T1u!ZE?luPqsz!{>5~W05SjP~Lcd*3eLQohcYZ1|XUfatdVWl}8KC@37i$Pm zj3f5IyTp`4TpcDN-V9y^px2a6G89GzZ>xZCTzbF$nMzAbrR7Pw&;L83qjK|-^=xYa zAyF(Tv}Wt6`8i34ZjBEBkR5g`5^%@A9rmY&+k!TLb~bIdkB~x!8iE;b-zQrd+@95p zrnY15nf`z+bFv)>Sg;A(G%Zuzk?4To#F3XmiA3ln>syha9ev&hF)XWcjAgL^fZBs$ z*ircZ2r#Q8%z-e*6j(EjY&{^ z;0;L5A$S3rE@m5b7(-p8a10)3e!;YRxpvOv5q#Oqgh@;Txkb$7`iF@^Kh|=x zAS1j;ljHLvEmlBn4+c^@<&@@^j5^!iQTH%z(G-N%e6p6(5fBM(U6O@H{EXCe z4nX(L%l+r8m!=~Alf)ow!gV^CwQ5nh_q4LFr8Glac+okFcxCOQFFZ)hb51{N%T3pw zG@l25r}FV2JA`(~phxN#nMZM4d>16wC{c~~13Cr*u_YQu3iSaCVsOy_j8VhX!`HC z*2t4tm$;V63%+2YKw3gbw*}E3PZjs3)0Qy39g9tbpBAJJL1s_X7{(9Zca}2`i&;eN z`upxHNBuxHfW~K}gcHHnqcAz~;hRMCKI3CH@6vDffyWM!Ph&1B9`aX(k7GGJoZFba z%zeyb%mLnU;T)?&^O9xT zAM?vtcX_;#w<{T37$VcLKXo=a(-8nEg4@l;}X2g=R-h)Y_L&DQVdg( zMc9QhJyV1{A{+A|#EQALti+IT1#D4r=~PR-i55Ol`&T1^4<5WXqBk#xO~eI78IoYR>EQeDD_1u9 zQF=5V)FR<tW%L2lUkJ3~RD4_IBh00+)#j2@$yww=auK}I+ANQM3T~%GV zzTfBjgwOMNwy7zN=fu;Vvj+hcgcI>lc?rU_!E5Q;_-zpg^x*}&KiFtN(cG%sH8Kg&v2~`i^1ql00w!6STAwb^zTvyoMFo)y| znZ?~85`;lS{u{FcIX7jD>Ep!ja5pr?=O8BN1w9YEl)XTDmL@`L3O<-Ijh)8@&m&fd zM4jcRgp%j_fCjrH)N@)yCIX4DOt();2pgnnFQgN4XJWTupvg08OWPA;jcqchNYbP? zpGQ_6B%X?yxh_ppd>7p`S3!((E60&1AZUTjM}pm)<#Dh=?TG;F)oe2v?-a|fo5ld4 z<653pPo;OQBFO#Z=sSuB*mQ9%a zs02+QbTCyWcQk20VVx`_qwsg@2}29FR&-z23M7SK_q!_1(vJrF>1mQ>L=CqFyIBc8 z4_r4K>@~q=apu%|TrTBnju&&hx*3kb)|)J9kGvw+OCK$nHyc(#({o-ZT%_&=6nwjZ1Ai)ci2y6Je(aTTbiG)NBolEz zQz#ariF7iRE$%5c)A2+|&)t*?Yt`ZK_Tk~-*QHBk2zRvFu=m+wE)SGF%T5LDk6rXT z&5y=>d%^zt#Y5*mU_e{8!_D}KBt0_H2&MN|s-b&gX%mKUuZ!a(={|IYcl|>TJyo{L z@2J-*4cGZ}XvFD;%<^!nRINMZP+DS>L$mnz4hTCn79loo&@n0|CX~d7(LhYGf_vg_ z-n{Q@#gPXHQrZrlr##0U>W_?Qx4gV-_e!=^fOE-4<%&#>Zr8(Lh6?8HZIf5qTlH~2 z1026(<}suJqCB{FPh9sH%fVJgs-x{{s_i(3jBsM_>fXz2H=Af>a?wz;Q?=X>ik_*( zw@r4|?Pfd{jZ3x`>SxG{`r$*p8D}I~4f~cg-5s4vgz(u1xVo1&G2hoS!oQ|fD7m-L zEceE(4UDf;cCJEWkN}W#(VYd(puuAyJ2F1nNmc>rrJ!#q__l~nB%u}_e>GNo1v?{Ye>ZQLj|+a;PwOG)2s zE#=jV=F8T`cV$uK3Rf>4xzAE#vb6E%!P3S{FUgWj6iKboZ2ru9kM6!Ob8Rli{Pfa& zN6ydOK1eCW#-76O^E5u17JknjI`qRskJ0bXBY-v?&%DLWc1l{B5tw$g@X-5fV_PM4ZLLiwz{&O zx%JrIYr3Io66X7Cw$*2xPR6qZFO@x5Z%n)KuTNQJas`37MYqRHYvxmhv=xq@1Qx6A zF*E{vz8@mirHzoiNESh5Hf_c6cbd90Mc~9utWNIip=&thzX;8=PuzQYX(^fsC7b5s zR)X=l8hVW2MqnDRa5)*n3XsbGGeEnD;?Ytiekc8X4xQW-^YT$V8pbn%ZsGTOt&1PijV+)|Kvl%Ca<~k!Q(bw3 z(jU9^bo=ymM&vEYdry(G(9C31!Z@a;nrWqfTk|6NHSk2~r!>tXT1xI4vgf2u_Utt} z)SHakVdFZS1z5{e1VD?s*0FSBNG4SKZeD2r zNqb?TJ@Ina_ud8ASDmHmzx_esa(3JZ~}DX*f738SAmY7o?m+i%*?UN~cAbGV7CY zCludKv~{gy3we@Wm+-b4UO-U#3DYx{LfWPii?=ld#XnD z%MFj;&)3g34mGydKi}AR!9CO%b{o&`@AdXCyfatM)k`7AFZs26IbSI{DpZCN_)V_5 z&fWMCcT7G1NB>cMa^t}lzPa&W4mar8hSK@?#rk)czPo;?abLDE-Dra~Ho{&Q^hY$! z{4U6p@*d3OEH6jHo;nkL&CkY5jmB?th-}0ClZE{Y3;VaAkei`cjAZ%*fhgUMd1T@5 zZZy8#Xk1pWtC@}4uD{+_(T%^kLY>{Z^^tmgyk39Abw3%*JOG@q(RhTdIVNR>+(yI2 zE~T{~ZIsmP%@c1L4Ro@LU4Jxxd`AJJrzQc(O*fx>sy~$ck&R`U-sZ-hi@E>O?=V*V zb1)^d)7|)4Nh?wNWjZqA#x{PBuh0K+D$Y;UX7v_Y;~=kViXl``P8dI%K63P*U{pnb zR4idw4Rpc5Dq4+fJ{Wa32Uhve&hDNo_eMtRqn(BF*pe6BHom$Q;s%iK$=Z>Tl~X5n z*0)VeG&-%Re5vg?t%6f0-`!fB%Z+5&DAZorb^!8|sa}5MyCD#$%7A80di)5f%ojtcCM*S;n)!wJw2s;dgiUl-phQF^`FgzGkKv9pX38CpIlScFFKFA&(^ zU-*mlep8*L*iiKFnUg2a9F9_WXywq=ht3vz>Y2M|YBMjr#N_VhpWk?C<-#$a=a{<0 z{{&fqh9_n^Pbn=qFQg!~`#}r^)p2vF-47}|z2V5#-N#F~oXpy(nyC&rt6J{<95Dx7 zuovo>`TqBt4a0gIh@WY;E&JY|`l%-kEeEhfH-5sn=$;1WC%&q<03X-9MS$--uH!E+ zuTR-lWU{%x5*+sVb3;#G8TE=+zK2x696V~j2HDuNLq9n5p`jmT9QfIxuMNF8^czEe zF!Y~?{$35KrkYi|)IoKFx(iSBBkDc)sy{>BEXg9w(5gG~8{iV#Jygs#|G53}Twt3A zusovqxG%aA`kY^o@Mpi5$qJ$dM{TeN>J0mJXfxT&7Kq+~-pDLB0Zxs$K?V)F7qqOP zcQ2P1oZ6Dx;@2GPA=5Pe92{|EP|2QLtgMt<;*xEgLL_HRNwFcAtp=GTA|Obj z?hC@E!-D|v!V13Rxb1LH-3>$)<>s6c2s!`GRS7z(?l zB^FKZ(nHgijzOu%)uf;=)b(^Zrn%8s3m-MU-FR7}2hI+fu_$)&}ozNqmK48!Gt;W+Hi_CKl3CBwd%k4J}prnsvf**3rN+i*wdS;tGY z{xnxJ!>vSqruw(M4@N@&Rfv+kcU+15P&pB;Pp|-VUMga zT;XP@-VOl?hN5M}Of?3-u##Wc)o7&#yuNi+7QPF#EBz5tbNWfQdQk*XJTm;KTyn9 z5{i0V*RzK^QQZApO}v43K3UACQ{=Bfv%y2rx_SI_ZA-5akymN;jxzjivD(Jd)SI%C zngIh*<%`XHJe)|F;Zh-8`X3fG45Ma*^tnq^CE`Hal(e1fxJvR0G}cFhG4Xe_$JA#6 zeAA5J1xN=5E6@=46LV^=0^EV_wk$oTDgUgsfzwlPh*h zk#0McJ#lEub-R|Tm58s8-SqK7yAW*8RE>f@Nw|eLW5z^2v)Jln!RUk{*<>YAGbctR z?Sd;gkN>q9r)Q|;xPQQlavph=B|jO(?R-k`<~-vzK&uNDsTek$pr8rcu|Sg6=90)1 zw5ZJ+JWnk6%oiSd=nD_MK{-jMGu3et&eFl9zjE#GOZ1w__X$cHj`^+x-zU?R%}n(l z`q~pueC-MK0rq_83n60F#d5t~md}x;rN4c*q&>fz1H9*P!+6|WNQXhKSK%s6rc9w?oWK})wq^8cP+?Iux2Fs%p&w`G7gdVO`?Ubjh2vjCh4Pi*HJ685U9?Jpcb#1 zfawW6^D=?HfB_B2;yV(Q2GsC{w&y95%(an`TV55>xPJ)=fZATHcnvq4EGA*jQa_XL zck7MbLN+(s00#eTIuVVh68GtEPUe2Krh8E>MN~mGpS{Qp^_W{D;P0BTQjqTvj}#Lv z%hsHFp-4xxN}j<`X;5Q4f$RaHrp_ki-J2I;5B6q$8(7B+@15I$wP86xw4!fb{499e zn=fA!&Cfi2=FHP)-d-CSxw|?tQXM2v4MOdO18;icp(_vFea``P_nG(Ja?5+a_)=tP z@1o}|?p=yJaIt@FcJy_3UUB)|f`1yKR-SOBr?4Uxh;WftjC!6zl@}KEF`UjkJF1J-6KcuJ@j~>3tj9?>%wF z{SO{G67Y^{JktpAhBpLxOp>qEk$)R^+F-uVyxsZInKNJd(qzFudv^Y^FI||+pZVUI zn_0@s`u(zgFjqK(+;G6?{{zp%#)nhA=p79X*yjXtlJh6BKN5h=|K0j!xrO3om$~`o z!!JMluq+iWzue7jCVTzarI+Uy3VfX#e)ug9KYU?Hba!>`E5N&+515kh)6kee1V+VK zh*TnHWVSDQ5h@EDViF!f@yb{&>FGRuGzfuj&_WvESL;jZ{Se5a257-(?4tGIT z8J{~O&A2xWID1(=OJ(s~?XO?eSie2JdwP2JTa9=$(~4JYi-3(4PNZ63Tua`}aD?f! z*GwnE8}(eSo_+G7zIuc`S7&}291=`=0DePm%q2*qroHlD&*)6V@Z12?NIwuh74esi zqk|7nmt5_@DJU1mL&n9yh#~JI2yDnbWaiX|6WK?vy6Vwf;@5JGQSAJ1yixT1QVp8r zWNfUFb8_lzF7d=wk3M?U6N%i$-)3tah^ms2I-JSHDl=|kq?V;d8F-Wbj$Qp!5Mu!_ zsR8p4{joiZD#+6VP0>vUYAmt>X^0E&!U~kfHmH*Y;>x1(p|Sd0C8>U$#Ji%=!}fYI zmc1?;o=JWpS+nfhJZGC_ZVkDU?}o#~sW@fd_6vz^qh>p`RbSB_r2dk6S453$e5L+E z^xZ8gh<$G{GebTX)g0jH{QFYko=Er!s60Jl=&4ou>>{h!XX1?P;A59H zwEoRCZ8Vt_Bc)_%(qAOLh>a$01zDuMNh^~9lz_R!Zsa>ms*4W81{^uqU2I?c>jIHv zStQ@Wd~kfZfD7AU4T>W}R^Ub9EFGNutzZj*#hky8zMK;Isr)e6xT_1`VYZSgtht4mMKXme73vvCEn__gA;9)PHSa zJvqB~c6P4{*S8&9S-(54m%f#{Y;4D=2S)zv{)w4-YnQ7{-6LDfe)h=cr&`8mhfmZW z>5PURqL9csULb@3Naq2E*bEP^@;*jrCFGQ7#CZQaQ!~_q+Z18jbb8``k=KLI=ruJ>q zIrHB8P6E|lT}rjAiIJs7(e|U)Z%O;f*#u0mK9U>ignDAMaBMgkZIq}oC{^srM4gol zQ!JtG04a&@ghOxs0eWKgl~Z9dY<>m#>iNwKl}ekuE!xdX>c2CVW{G(WDTD#fi|)*O zYfAG79zIEoqB91HBig6hvi;gIuf6^F!qnbtLdFeGzW0o}dhg^+bzfzy-y5&)s~%84 zvVQx+GnZC&m`~k!=Dj;A8~6HpI)44b*T;dptp84+LwyaC?%xNV*lFQafK;U(D5^o} z41{=adRzq98^|MuZn&^Tk!|<8Q-qrO_zHsYMg4Kx-S~y+9lQNE<=(iyx7Ju*s1}k- z2Nw4DPaeAN?v1r{IN6#luATh0T0Xq}7gi@@)ofAKcCQV0=a^Dr&Q= z9raF7L~M)3%pVOHzaSdPq#G`!4n|X{=$3Tc6GKWpD@*gs=975evT$F8ZTNwQd!;8+ zwWp@Xc8VAEEzy*pPt7!!i+`DlZafxEZ9h|4c0+!oyJvc9)-BqRez{whzGn2uaEjoTlE=mc>m@C{+3iFY+fBY5*heX`420c~3(%m} zS?1o*phUIhw%UGA95OPN@>XzYbtDknL)PGok>Yr4*kL=S?K7r^;_pS((#Xh`19RjU zO-zn00*W`a&Z$~k(1Yz5*;;@T{w5T9S+#DuVry&ZlHJ>8RCQtN?gEU!D$|--ZCcJB zTPbt)ZFe1YR4f*`?AqJknM#8F(3r-JWWscz(4sy7=>+C2VX((mC4P0QzceztHKv#J zVjDm{4$@epn3?u-ky`uC4$KII;f>H&!j;EI{Klb$u{|9xI;+OWVY4cEGm%IqpaxEE zJZGh>*+To89Uw-`ZHG(w_v@TBno63G4J0g&^Ma4%`Ac5JS7;w&M4&Dmx|TM*O`9^B zV?s*W5t~2?g-LB2^r1ofUe>b5 z7{=v|9o8CgPR zqN!51e&kLy_rv#nKs|rUv5gIhk(~Qy^$l{$J3P6~21JE-8Ycy&nE?=(J2DKijebaR?^{-bS2}=M{-GVnLwTG{9@XA?&l}hL=H%!M1!h96WDcsJ z{U3_Q7L=OIrhVPILpXs%77p}tU2t#W({6R>m-wzo6I9z)R-Rp1IW4~AH$Qvq*s~`G zeeRWg#^C)R*G={#0s{P?jm8TY86%swe%=WV`7W4+dE%}}HJs!Pw5aW*v}Pq|DTZ0o z2|&tNV%a`Sgq-;+PDpi|l}@|F=$(Fa!cdFuZd8RMXX5%n8%~Dwp^Smi5-%DV$F5cF zz}?+-q81s4t#nJj3kOso54up(Ufe*t&$?0!Cas181L@LEheCa?{bFPbI6In2L*%PIMOYx0A$i?I2Ld0{w zW%HB19TnfUi*G8%bHNvX+n$9WWyi^rJ3QB$>BL9$cQ{%mldZE)>LI;Deiw5b@O82u z`K7jPl@7}q*N7y|n%-aa-w3@^NHr&6`VXgohjt!_irZm2csISV7vynXZDVRHFI2iYd}qmS%|Ogw|N#JxaKD4tGw|5kdMa3U5fz20L^v=EkN#{RzMpMUbp&m%!|a`!tvUD zmkKfEzPp>xePsLif#u`JmoJ&zTD_`&<=ftN<(BLJu27t~?t^;|p-Zpryz_O{OO7uu zAK%fOUU=KjzilfrG(@|T+_Lkr{h=1A%KpGrvEcXZrn<59_|G2sNcN$BoErPbhrau* z+;@+voj-7G`>8FJ?{56#mj8!6lVDZ;oBCyHCf-E82lp{Z2?%3S%3N z>-PCCjGw6)=89pgn1d;uIFP5>BkD1UIS(UScyBVoH>D8P=It}mOF}&ZyCfuBc*)dU zGGWEb&Y=aI@7rJ&F|6yTgjJki#aW064}IG9pu#XHvQHa@cO3@2Z*ga$s!!&r7=BUl;vJZN})6@D#5Gxr76p<0g_Ocou_v9`qaB zsUja}9*9K}`^B{o^o3z@&*Ehgdj=U#O5DYZ>c{J2i=B=CIK8VsmN?Pdd2KXP_2I%Z zatYH~pKtAmRWjjF+9es6r>J>C>mIQyl~5$(G>VoB?O!ToN7Bxp*xg2Fadm2Zht=J2 z;w1>wik9brhV@dZ`SsCyDC58&ksjST`oI&5PR5gc|5J?kq!vrmmA@Sx2fzVmQwd$YcB@S1{FZeT!+mD|5u?yenFUNsWCB-d%F zv1ZP((|*s-nyJ*D{kaLX7QLj@nb}F8C=|*hi{%lG1}(gO&%@jIJXdaFvovYuc87vb zJv3Em#_%5qE05jpq{W*VjG-+`6_33o-(8uUT3sra`@1J6c6A>+@X&hks;i3Y+FMn6 z{Mg1n92@_!58c$uWO_G$;MT=lZs})EJn_Y~!qumXYd?9GwwC9-9nS0G?Mm>ZctSiQ zJf{oUW~Z$6zj6Kh^<&jL?x-GndVX&1t7ks&E5~Yg+)+FBt&h#!c-xKG3)Fr3=X1Y- zru8{+W^xo?+N__MrRv+9-uHQwUA=_@?RQAd?9JV}^w^>vy}A5H-*-Rt)vJfj8SeN0 zXmRneB`vBx^ZC!)8-H_R<9p9KH*I|HTb}c*YJZ+}@_bUr&%Z=|3Rmc;a24?4f}Y(W zk4k35A`ym4lu@fiG7}1DTOtM>=GC`TMxo!V^kZhqjP)zce!)m(&HDCJH=o*GH#a^o zHugJ{H;uOMxbxGWei*!3wUEmds?&?P_R)2zSiF98a?5+N@g#LRlDFZR6^)O&J-jh8 z=4+eGMS{=j1^Bv)o%R5p^#l@B9xLfgG^P5S0ICE51+3G2W!7g_J)ci*e2}^cNp)K? z-?E&$wr{@+Xo|Yc+W4onwY8(Y`&IHA-}uJeEBC+S26f*no8~tjW1CCo<}S51K8Pya zYOS~1@0+-9^K;|x#Z!B+E|`_duL+5K0X%JXkbz>=8QHc)F;hq$vKhgN@H=H3d;H)+ z8eQN}VXraW=q?lr&0MswJF8J&f+X#IYi;FucB>bSl=CCN&L@_)@0p!35AUPe0~M{O zb}rok(I)vo6Q%l-)wm)p(<+`Rq$lz?)d%DAKtGM)J-&gqgJcP03!PTfk=J^WEev*K z64hET-p>?2QLP%MqA2u*v=8ChU} zlQB+@9qx@xm9vp)Ve0ftnYg7nyVhEVd%ZqnEqa1_F<{$l>(i8#p5 zRqNYS*x>;Y)6YZ}PtJDoJ)bys*h{%7O^sD1jeWZ&PwbpoDBX-s=NKUpEwqD$gyRw% zy=*^P|Uy~^49;9Bn-N7iA+(BNYux81fVK4F!MDffnQ|`HgMhb45 zoAlX?(Vu1#0BBKdmo-Jr)@o}wlk)U@vHS7vNW6V=YrX7h*}|5~cg!|tDb4BMX>2`k zeO{Yxmx`C>Efw3HF~h5_u9p`V$L_v*mz&NwN4Lyh=0wTf@8lx= zr6+eQb$BaORKqu{6-LKBQeQ)Us*;LCk$lM1^R`nA)EGH1L=avT$MZ3K2PQ|S%RO`i zS%NQ5oDz2691;UWK$L;gN{kE4YAx_sV=7DMcvYF|zsQ5Dh}yF)ZMUoC{BqoiRx_2( z>@W~R+fTI?b8fD7!?s*m#Y2Tk$O~mh9V1Q_$*b#6f3kQ(GC{vYT3pmil1(FS9XlKn5RB5cB?=`yoJ%COfsn8Pgur-f zf`ds$JqQJ(6xacmp+|iMbu+bv!9@VSmUzyu;iUYc)I=n%hXX8-aH3>c9r36!$1@sVsY5<2Z)(Yyptm+BuP=qB5bux?|*A zKb0J5RK}7iKU@He$5Yy^9Dg|Ny$OT~(o?-?Th?8%aA~;R&5Ta3 z-7;y1w?6pO4?cLTH@$1)&raXi8d8=&=XcuGrsr z_rJS3dCS^i8LO-zgR$zX$nK#4kA4KZl+has(5#IU`=Wo^Xa^B~(Lek)cKhOgntYba zzUV&-W*h~$L$+jJ&!+9p!te!fpZA?e*vO>)@?^rB+a9)75Xh@5mb-l}lAJ6jvKhn8 zxUxZIG7;@=kC?K-?iDk#z3U|>D}FkohyUz%fA@F)eemQmhl78XAKX6A-L}FmtO5M#{4BZ!h3) zLklCZKhY#xKQZ6wXRdtC{!)2-d^AGHQMs%BNUlIiZJ0!Z%1phg07m)CbAR^Tu=WeH z_IC9Kf3*Cq@~FSi`nA^l;@qt(d)g!EG%=m3)-|Vc$}R5OIs36EH@+J+RdylJ4fmIUW#c(ZuqL0WTt*(P}Hc-bf zUKrBE(K{QA=g9IvhS4%`tjWUgd1$~qBYA}}Ukouc7^_XW>%r5($;QL@f1lq}lq89I zaB#A=+)0S+B=s`pYj?l^G;331}`f=VE`oSGA=vCfMZs*OS{R!a?6dr{j- z4pRXUP25uH{Ju^&l*nfjD3j(SeDz+eu<_MoNh19r-%d?dY9%9WhUWXDkpxcM$n53^ zMe?Y>JjXwCm*uXQ+k48Yv>HxAP8;IPu*HXJ?MQcYvN3wts-&|aeNtVM@Ti~oj@kh~ zIT@;XNmA|k@uR6MK*?gr@7Lp%^xVQQgveZ7qt+&65C(aP0FUg36#Nb4Y_^-;mr;e2qvEV0Oy!@%L9m7vTf0FVMJ{S8d z8dB}z0+uN#X(5@5VTg?9%SrtB)20i&#n38?)WD zHC1RfNoF429FhNc_ks$C$0?e)GUeB6Yd5dmCIosI%dhyjMd$k0V~8|JIkS+Hoo_(f z1JyD^OG^*F6%W}=pk%yie5k<(I3A0&O$VlesAINcvGKUP0oFpl=qVOsuTiG{{F!*% zLxZJDJ};AxNK3iaj2ksWR3+@>FtYRK>0i6RL9D~Nd>9tQQ^?OVLw60`i;j?ib=k%% zvH6jerDl6pOfpGF45q*jkhWcvfGqlO7V_ghPOQGriV*;2O<%`T(8*3KpZ_ZSsVv!; z_8r+AxQ*04$DP2YJ0MY|mTFk_;R4XqD)l}zbrgMQCwN&cStO;8vRFl>zgbhKi0n3o z0mCT`u$iPK)c4+rA}BYKqHm5 z9_pI^QC*^;fko?D55N7CwyLP3dAX+Cyt+}-yt4MVraBW!dyKTgJhUQ8QQ}+;E7jJ_ zrR%n<#PkpJjitwDH~wn-)mwgj`|fRzF7Mqwk=;#Q>=+c3tJAyJS4S$?CynBc;hj8h z4PL>Q)pOYEQXhb)D_)h3wW)235=A@c^~G+IW)hf<{VBGwg%lN^KH%1o($?0NH(dI_ zYl{H;$pYE=tsk`l5krU$Lpiponj-q93Q!H=M{I~b?llg!+vB-YsbOM zFF$zTax`5J)^*}VRBjW_V8Y9|>yfYeG}Rug6x$I`piztLMej!Eme20obvxCnM<^>+p4Te1 ztcSn#VRaS!H>6j)(JGn3g=ol)Mx;zC`0r36uE!xRGVDYN4$pK2bge3Z(++*HR?+4u z=0+CFs@fUnke<4B$CWp&u&Zed@4Rkyy;TsNq>Z0e>f;(!Q>)7(orq!2!?dBSNIFin zqfnxdGm8@xF3=3G*;*=B7n=pk%1jS~;reWAefIj5Va!b@vU1avJ177_ip0e>M)?&< zLwQQ!0H*Du5yv#ZL0Al%M#4Rs8~Q6xx!d0jCy;h88M2jS_$vlFem>&rSAm1@PeSmm z-Lk_fPlaXt(=OnZBops~L^H|7*epIea>dVGv3Zc6`{5s!KK)9aQ^Bef1B}x$85p!S zeAGc-?Ga=^q30qc&?l}6TvjZS7{xFeB?Cb)B;*EcdU|~CY=_y@pTWB5QGA#ncn7fpBrIx@JJBJw!|_nn zGHMd)tx$*}Heo6O2)Q|bRi-j`c|05|)CHJ?`GZ8zoY6Y?c|(sGj|I>8fXdTzeUk#{ zVeF@Zp_Q|ViXD+krCB4CF!d<0a(L8@40${xg*&CNlb85SG8C*)j375Q59YTI;l0*`TWHJ;DEkwev8!NS_#le6R*!*XaFDH24cq>rdLfCJx zL39=jqB~QeK^k#?ZaG8=ja0S?%)C7SdE=c@XT#P{ebY<`-4CUgiE@GCha4yrr@qZC zAqIN76=~98>q%QbA@?s7@Ip@y>k3hDA*{F4tCJ@qzDG4>Gp66T?Gno>1WqWrp>4i& z4^@W^Ojuz-%ZDNl8!2Gko{hO?{TLRsB#?Naq;+Xz&7_hD&@QFDvtwwUnRAA1Y2O4; zk)Z>hr_@ty;O|_e-Wr*?uk~=)LUh6qkB%>&q+t;(M!=FWL|JOYDoDId50z}#NBDwc zPnU4!Ea2tFwH1(oNIL1oLve9`{poSizsAdvbY#Q~PcbxUi6|Vzxo4CRw^l34iDd4h zSs3aH@We3cdU%`4BIJ-cnxKYPU$$B#$H65%BIx9Wyrs?mnW&W6ILXMrG?=24i} zv|>GKj(Oa-2FJil>VK#&5O2Dkx68!|y&j$GRlg0Cc;VlZ5&FWfr0Po_yZCnoVhC_t zuL6Cg>;+LyyvlorJ_7ABo`h;7 zl{O4K9*Jx^2Vb#;V*tWCHj?HQkELC=P)jDY(rP?a`});M;c_1Mx?TwU;JtsVB_sn0 zHajoDW#mmZC>sLIX-2FXBoXUG1R>}^Tq6AfhL{u>IB>vtq;Y+KZJR@nXESvj^i`dW z4VivrEW_A9B9Bk8lW@w*O{aJupvDqL+=(U`Wrp8kLv3NU4vnT;;PXw{(oaVQ8Y#DxKLuz76 zKanwN&`%OXD~RI+X85MGSo%UB(9?*Z<)r=CB)!!oYfGxq7R<2y{_+C4L$n>P+_ z+j`2-WvZSzzI<%&O^%tdj&DECjFtV9ttSlqz}9WY4gGS->_7MXrky5zvKmy&ky

  • -hVoDd!sPW~|*- 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

    I7ktX3;2a z!Q^2NTULA9Xz4VDl090rjrrg9_cz6c)i(8Qv$2EpcW+zpBe`vRw-4}#wqtgI7XBRo z0dRq?Z3QnV7k6}^m!f@_lG?6XccZP>k>QQDrNe3=INQ@_xy8DhLaW_JjEc6M@DQzD zy$hJA!92~Dr8CqO57yO4EN2GcsrB=197<;t2WdRKKiyI7G6S%PB2Q+r6hq{#p7~C=|TC>~U;tsIGqH1r& zq7%ejCauZ$T8*MgVNQTV*sWaHZQw zyH<4{39K0K7Q#)po2Id2G<}@VrO|+n2LPd4FIhEqXlo>Ty;C#UVT4*dU5NvDO0#B$ z4wzAq%FyNr%a*%?@M!yK$rg;3M|O4SgVwKjI=0k26*Dw%W?%k?7zb6=+yN%uO zfrK2;j(cybUbZ9Wu~o4rVgZo=My<AGKpb5{L4TKxcy#Y}%x#4sdxknqF{8HSHj)0V=o2_4IDb>g6rPKK zga2t%)z~%E2HEN9Z;H0N-ECUSzP>0_AVr~>$M-ynhqla4UE8r*JlhN3swtvyy(&U7K*xTKmbjZJ7+gC*Q(0GEcgNjnIQyH^+YMH;`k; ze)2PVurv9jtI+c-msv8pIvl2C*rrFJO{=0tViESP1P~CVKd?3tdtL>idt*mwq6gx= z&s{sUGGH;fX)wwdJw--trv#J_pFaY_<{rLn@Kqld_)+vKF#Q&SFJRRe`-5vD2TVi3 z3Y40L%SK*Bpfgw!EC&dtC_cVt)ixk4Ms>+(BZoNV*|J)VQniLwjLuu$0-VD9HjO6Y zKx{^YA6C|G4P@0L|EkgfT>5H4NPdflEZgh(XdK6^XW&ANel4>JILQ$TH@s;+HtCCv_}s+rwDi%WZ`1=9p0 zw!-MtY~XfzOQ~J0GC0UIU3Le+&?&()JI+qic6Dq%x<;#^8P!&)QSzxi<|TpMUQ~Z- z(=wV`sk5;G3h1MBh59@wF|D3NRZ13+%Lj5ZZ6tjeR<7CwzFskvTE^B|z4l%enTx8_Lc!_V tQ}Nuu$|~3i@UJG&)t*y)k*%w@#|J;^49B;>vGG;4{mxNbf%{wUKLeisFF61J diff --git a/public/webfonts/fa-brands-400.svg b/public/webfonts/fa-brands-400.svg deleted file mode 100644 index b9881a43b..000000000 --- a/public/webfonts/fa-brands-400.svg +++ /dev/null @@ -1,3717 +0,0 @@ - - - - -Created by FontForge 20201107 at Wed Aug 4 12:25:29 2021 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/webfonts/fa-brands-400.ttf b/public/webfonts/fa-brands-400.ttf deleted file mode 100644 index 8d75deddae520da95d3cf111f4ccbf3361074292..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 133988 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 z zZ|j6;K?A!Z?(qSWNP^8s?GEo?AOlzzZep&61|{Vp?ZI%7CmAh(D8f+_e%BtLNTEKA z{w}vSxBWk_GEV)%MY+~(lLK;|$ca0&+27ngZe0ImDvSHC@RN3YmOLWoODD|lvj0z- z*Bw22+%-;pcdhr%{#vfM`o`RishIslwVJll)e(9BroFNbDA_6%a0F@I$g-+c{)7nV z#L7ywT8-N>fAiFc{cF~mIqo7c)mP-hp2 zmTkQ@v@dP`*Pf2v9(?_HxQ%Jct_JrC4?ULcXAon$hcJoc`s+Knu+qa@E zE}mZ>MF&f+RrRSl^4`@4a=Fdz=)BQ=Lxa4`Wc)L$tE1JM7FU;+R^L+U&&_s=#s1t} z_ov8ah-R8WI+|=GJFSRw{MRRoc|VEmR!AIMOrkJk)W!8$ZBU&lH)}uJpWCx%rdeHH zs#FF8nMb*>M(=Oa{~i@)f)HgrIWdpVcQ2J}k4zjVN8wEqZ-rg?eG~7W_^F8xO?-sR zV!_%=K7vqo4^2ps@n`e^F^6zbQ6ll|;0qahM1IgS;8y68#SMvuNE)YjPQW|hM-Ith zS*Sy28*lL^)2BJqywCy7c*SuAE@L3DXkf8D@#E6U4>}4Tz_}|Yh9p?weBSW!N7N%X zd?+9n;w?H5IbDqs+s}~xCIDkJ0Xi~2}kO^|!E zcb{P$(f&OVizS-Qbl`!Agd}--b1E5&B^$cc2#DXH#8`8e9dI3&Qgs$>D+-^tO?POT zEz?{ujka#i8G2t|8rq7YTN;h3r{3@zkJNJ?K2!9(Rq9PP3~M<1CNv=7)+j-jg8tbC zdu`B!5fGvbY4rOJ*e3_iFsPta%-i~G#xT$9GxekHOupdGSD-G0^n*H=8#Al+^^&FR(Ifo_R zDl@LX#8ANB8A^Blalg3BfR4MMW-KAFTpSFuayy3-HNgEf-pb9?<<(v)PY_9v&*H27 zpX#qpPp|g1PHC;*+cVA2e?Cy^c8lsjy;IV7#nYpz*zFa|UHPDNpg#VmZtkzH_6L>n za(}gOO@B}L`o&cL=?pv94?8?R1@^gD+EV-Z|M0^+O|ahX%3qPaHR8JSV9YOOk$OSI z0}4+uQ*gHa;Qyv1ssbqMihC48XKeJTHwTSIu>BEv{_|?}((w0^YE>PyjfB1Mu_c^S z*4mfW+49uxe(GebMUG!X;*pxP3{iMPFxu8i*v3rOw871Bo<3Z18D-uUE%u^WIcG zpW1%6JU?10p~HL8C%cI>@f#olcCp%wx)(+2*;p~%IPTU1;F3*ubuR8(&09V(}fxhuG3ET^VW;^T7(V)!$Hu6b1L^%fD|~$A7x}p|2b}cI?Ym z(z*nt_vb7BgIBWkHEb1qbs|C>80R(FI0T;3BQtq|2e5O{QkddHQlZ13SXfVGpxsU4Z+fAF-zZp|o?T3;Y`@p*`{~8$nZ=As zPPWTs-7A!9$w+!-r7)STB;vUYx&=9L9=gHypI4flqAGS8)mNu+{pht~D!Gu$B-4aN zRBb;-OI^)oQfYsl+Rb}SeP&jW3`+~MSE)j+fDlNjt7aFLFkbpWvC}CAeP-}B{r8{9 zAv`~EAJ~OYPkeBkkhEno(BwAG$KSTH! z;^XFzXT0}N#nBM~6PkY`L8}vIAX8gpC%XjXALQ^~j306g;gA@HeQQZ5ieyBcKfW8L zdW+|RqlKV@@V~`tT>A1fBn4q*)U~_pN?IqOyzrI0Bbd>(!vPO&Ut@RibY4hSt=53182kcy;2XLbCw_pNh1_jI)gD>u8303BZPZ%S)ZFyTaY0*#F9b~CK4C5vW?jcVn%(*6LbX zr*~s8KOM`@PpMj@P_-LbScmYOiuYXrRT`=^q8$H-I3)l!C?M>lPvGsvV7Ku5+Jal) z5cID)r-F!=CXf)LwgP@ zu2!dw5Kw{pgRs`cJNpy?x{I z_;ufW?3(K=wf#rh!*5pYcbx^6deFZdO)g&G!Ylyl!B00rAG|AjRw~t;&EEBYYCH2d$FAzm>t0FkY2TNUYEzX;rLL}4RabQ_c6Ynow%cGE zY_}H{V}t!9y;7-5(v$V1s#0TigNZ{3Ygob-))1DEfdoSWSk0+na6$-?)|H3gT zUm(h4Sh5{2RcpsGvx=gtT(OlYHU$QTF{$}O#dKgL`6U;9f#=X06;k>leLkvRn)b%x;88Ry|{Be7Ig*V zvqz_WVsku$K@`*6+N5tX5+$!$dPN4jk+H}aHov9diK&f^r0?*&Hyj`H5nGI1mC2Go z(BlbbMJhO#NJTX7(V1i80a86=Z{#^5>rXDt#WEtCn#aYIR1?aGs*_*q4iQZG zPU07(Dg{?jlzR9vcZVnzcw1BQAz%Q8dN#k$F3#6mihOc`Qr&XY=p(hJF>N(Xi^cJ34JX4I$^ z{H_80Y@>;z*y3*o5g!9AZXkTYykNW~ZGC2)P(g@%%9%dp8q6m^@aVXkDix3Nq0@9E zeL4xjk;O&XV%E6<$7?g5seEQ86uSF{uiieL#;thnz7BM+qNPSatg%@XDlsz;tor=X$(-R9XC*Y_!do1s{_HMwTcPt)D zt2w^VWFk6=ivp2dW6|kEbSgwjFv7DGrXF|XWI^16R;4Mqp?t2H=u>77 z9K}4bbs|)Plkp>wpDF?1eQq?510j>m@4WEvI{=??@_%B8NliR1SISL}XR1hMY^Mt2 zDdz*A9XdH)k)xd<(w*O2QqeLBvK&h*IH%&3RVAJJET{^gNOC5=XU1t`l!YWtrPVz* zfM^5ILo?t7GJFo4iqv2rJO!oDM+Wy+cNY)FE}wMb)Dpw)#uPqkI_7;m5G1)KnI;J8 z%aYI+Hxu!`G-(={NFYg7IH;n^KtW!D;Sii!%s);F>$?VH>}os+Qq%DKTuj|T3wwbr zJ}*mo#3hL)JxuC`ng5YwYw{(QvmSkRA`$iZYwkRAqRc#d3AUP4OMvML}=|BKdih`w!e3MNg%CQk}Ot<@!Ov6UdYQ->ZL&1sA5CK?+wg zUIqxi1N(?`-q12PJ>v7Sl*nvSPE1{0^?G6_RyHrcd~@YQ%zrL6HysXTV)KWdwp1uZ zLW%g?+`fM`bHn@Mth@k12`d=*t+B!hV$3f)vT!0YyL3(A`gg|Ta`o(~Q)gFmUoXtb z_~f&}s*zTThl&G3L_wefK{b!Z4(+fx zkCKt5z^D4%fFza1z{e@csq8^t;QY-Ld)!N1L`j7Yr-%X0qpNTc_ax{2(9q zs&2+HE<%1IVN>xXR7~iZ)0E@$5c(5GHlG%MdU!mLdH!^vI0oor_9nOH(jzR%iCFi} zr(Dmgdd@J2V|4m_qj@;?9ZG?3t@HpFdPwicW_`U^?!Z zb44aSp|qI0PWL38EMPSm0*h;lbMv44US=&)a4%-BBRb4Au?!vyWhOnYaAbP^#Oe{^ z^Sp8%i`UrkEI2kbe##v?lhZ?UE78M&;5kV)kGwV zj0^!Tedte9j(iueU0_6H&o1jfmw0V+0c7Q`X&u^&nVKSPn9L3 z2<^`(HC!LyO%_H`&}%TMt|r^p#ZAW+SC4!$TqrQ`L~iWDLt|vk3tZe6*QxzSi$|u& z+F8t%pN;DFv%*tTE3OIGO7X;4K0kJ%INUjz&gau7|9Xz3dO-cD_;~Ke&Elb(krx4E z>L+;TBxr#ZObZx;n21pMgqU#{WEf&w#76&wnG0p!vEJi#W%Vc72eB*@|FJ@wk7f>qUUVDqIzr1w?+{$!`oGs&F$DiPMn<3^ekpK|t(U@D4g?%1L0 zoGvf%CDM6F5SS^1g}BB!oFxDPi3^WRtcVOgPP!)$9FK>HB0z?ak6ce4LH~>=!i!6h zApYXY^+<~!O5>G;!ktCJ5Yyui`!@EjO3$Yvx-8M=lBau9{CHze zJ>r>6rzf4+fOtC|ZWHo<;sl!1is0!rRKy}qIx2r< zQCpITno}w}8Xbl3Vy%0EPS!VQVih~1^4+7iVQy4mJo79@xk5+bR?2!VE9unAQKil? z6gf$y@kk9qUU2`xTS!HKVr9oJHdM)n4mj&Xn$rES&-J4!q=?6;=ixb(K9(*r)ux_^ z+IR7pYb&!!QWz0uv^ZQSlYmzRJ+Rwz{KUXIILi}`;z6$>w)y4W>A^#XkHj#iyq8Ar zLjxeH3BSctIK9lkB!HAPC727sJeTLjDn52|V`cQ()Jp9>{&0p9 z@3b(>mWZF2r+xDq)1gyWoe1WSPkSd2cvF|nUQw7`&aH*>*IqXJi+_lcT|!rrgx*|C zB_C&idBVwDNV)cb|1>QX)`OT%{d2gV@pqZ-4^dY=5@clK&&mkk}f$ld;>jN5QQ<3{gRpkLsP~7-WdfR<8YY0`t}e$B;ov= zIF#2jE0-USPMuoKt>kmrh%bNT84~JX79>+VWUhq>0RabNj1oFaymF97dsWs;{4O|S z7G{)$CE`CqI`~jXg#>YulLUNZgjtOZZpK@3s@vzeYPiKcR~` z(8Y4-;fyk6mr#>~gBd49@x#*a=*Bz{4tW990RcAFRM^>o7k;C&aN@+m%Q1~d9!Mkx zJ0E71+2}_}G<@Bn=PDHuwHprq+RW0@%$rZ#dLliWJ~Z4U68n?K0-k+OAi5M?o_W{I zGVgSd8&P+hf6Ova0Gng~JuFCqa&!rwFJ5GEY1KjntAV5d%`-ZD&D`9kI&GYP2y#ba?j984$1gApxG zDhRYgq2fw$l4Y3(S1dEN;&S3$Mm~ODBu3x)V$lGRV5D^LN4yb{jumI3X+;9$R!|F& zxg(Rf7VBy?3lctz5~V@mo*=OvtRI%gKuZ=zZ}4!2$Kg-L+#%~HU|ANSnU%FQfG8ez*lzr!6(KBTvp9zs9t+3tb0sC4&y@=0$*71&d4MG)RE&!R z14?*q-l0}fRjVKmW@vzZ7_knkARZh5X*6&Ed~^T-=xHaSh?yFLf?rZmaeofL;Nsg>YlBC{GkbH_aw{wcgMvvk|3!i^{Vho^GnKaF}*V&Ws%OJ;&Z z67ji!i~HYNwwwM(G=g`0s$Vf_|yqZVN;!B%9xD9A3t{GhI%{|k?V2B!xIX?C(RHAU`(JzNXq`0~8w}0)Gj|Ch{KPx*45`#(}8H0*q7g3E0m-s47ZRq8!fa)ZiZT| z%%3`~p|V!NtkTB@Uluj-?M(c>d%E}E-@WI)L?+&P=DA+WEY2;hua&~r-w-LStuM_L zZ`rP5Wtp3dqq4p(Gl{^+vOeo#d@&wB9Pc0?-IE#hu^-78`tp5;5rTu~3 z+wQ!-+r9tJ+pfE@cdpiTt;`kImY3GVbarfpVB_Vr;@pb!>EHIu6=t>Z`MEQ9xyB1j z8BNTkUNzy#=RFg6&drVEt~EFDnsN6ec0TRQ+rtKmqIKII-~t z#JgVh>6g9i(?1j^-G?WpkKn+TpTF?khYE$~#OAal{br=d# z45m{|9wQwLVOn{8W8ll9=abRp!waS0k}F%9a32wQS1^}Ji1@^W&v$fLvTT}f%pdqM z$)X1fS}(0hJw~={aZ)YuhkZ}RQotRN+ueQm#v9L`eOUTq#q9W@m`^J-KPRbi8vAySm4#KV{d$AeSM-u?xqxvAiIhy-Iw>GG%v z+?1cp6PJAR6fDIVG}+LJ<5|Ly5N=Pu?Z`wX`OImr@1`rK&Mqcn#j9_*3KOe`E)jEn z_d}1}^>Gi%s(e>Ne0T5$KXZ9XTo;A^yvD+T5Pvd5^e zI92I3Q*u*<7QW?CmIZz-X>q9xwpf?Qp?s5N%bUt%$@JlL0bP6qlray?H=LzR1tNRJ zrfc(xta#{kkG}SGkM=!bPbw9P0j<$maRyB!lM7&b(#$903&~_6ILIUq?j~I|SzSDy zg`=1QQfk0CCd4!GfE@m0v2G2^0!jWGqq3yle>^HX9djZ$DkJwrYgBeS=Eb*1WzCT# zPG)!?pW}%9@Tlx}q@CVTIpRn#~jC<&mWbOob&ZjIpqks(#&94$g@87k2;cV8;ep7FSUwe zf$dZHt~EGvm1ECgGNWqY|6)2E*I4a-r(WCc<_=XBa!;{Z-P~1srfoI(?pIxu!$~rS zyHJO4;+CVz&2qpmlZW;k4UToV^pYFp)^k^Pj8@go-D=i)4TG=$+1VG5|Gz)6#5*k^ z+SMl=^?!YSX?)|oe38}!4ke%3Bc*P#*XgSR=(*_T#j z!QnV|Y-MSEbuD-3akbhtJ9gb_9nTGG!+8gfczhSHKdz@r|Ge$hJx4vhKJ2E_#qPKr zDl&N=MKNXMh3e=gZ)p!GsZFVUvCDJ3e`QdE9N(LRGlt}mqipfpfdAJiFF72yZ=1P2 zvs|=uP0Q})+E!c9MXp}4S~=V7=Af2b*RmQr^)6>CQtt97n_;)i@7CZkT`28RQeGN7 zzRBs`ZreV-yu3xtj3F&ARV=4@w`~72*N$O62B);@8Ei#BYdKi{B*2(Q8@3`}N`t z;*H|B$ffXRybIqdep|dvyj{FQyi>eO{Em3Hc#n9mc%OK`_<;Cb@j>x>;zRh(e1!EY zeqVe{`~mqFKF++!AF?{)C&feJQ{vO&kHw!b^Z8kor4Y^U&&bd4Iq`Y%1@Y(NFT@wc zm&BLFUy4V?Ux|Msz9Rmu_^SAK;%nmT;v4v=d{cZ&d|P~n_|fl*?}_h=ABeveKSWdh zkK#Xx|15q)%)x&V|CQN}|1SQA__6p~d`bUK{7+&{|6cq={8ao5%<*&azr{a_e-ghC z7sM~6A^^}3mJ}SWlphu$@Z_*1L@-Dcuku5Q%9xB30hg31!rjJXhRCXMIUy$rc%GIy zIU{FfUe3vaoR>v;NG_1)XpvMnCAq}Z-%+_DSLHFeCfDVL+?1Ee%USX8gghy)AX?>0 zd0Jj2ua?)yGxA#b6!}y#G+r-na7@dm$*0R3s}jFj{-%75e64()e7$^w ze53p=`6l^h`4;(B`P=es^6l~+@}2Ts@^|FB<$L6N<@@CON$`8ullOK{FmLHKH zmA@}PCjUVGEBSHx3HgWekK`xiL-JGd(tnZs0-QKp^^{u{EHO=#8$8T0^W~pJ;j7r~Ev#eUfEVYfk ztJAZ)TDQK_wRSwcmfda`_O`FmuzJ-k__(Rpdi6%tth%=AyQZh!YFW0m)uq5A8&)-B zRl0C|yHqvX4Xdy7p5=bm^!8enZKKmQtMNUfTiI?Jty0tC#jQ@gRdelG)n2H(SMPQ& zRx7+^RLrtv?TqT+Y1CUgX0_f5*6Q8uUU_s_A6?}+I9(fl?QPdv+|0DQk&C6#C9#9b zHh9fp8wM{uRP!G^s#I_JMo%*uU9D;CSE?=dcDLDBcU5frn6uoi=zXi#?UhZ}J{9q+ zkApX>cC2>U+7Hn%m7S7>Oy6o)d)|wkqir?nm7R{$sJ6W2dbixG?3lECuUT$%g2TgEM|`<`m2UM`m#rrX}>w@u%c)$0taqjfuGqhWaA&QVx5~3Cb*pE)s`XkecnL+f+o4PL zRGU{jy|&TN&RJHoVd!O(*Q}VK(e~ntc1@!hu+`^5i`i|4!dBl`HFxUm%8f}3mP?Bo^$qAy;`LiJ7(EzXlAp`5LEp>x|frpf19R&He2SN9qgLCL#taF zG^xDzUX21B@3R{WRvR)mHAR(`ZCBrHv^6?_Djbm5p3!KSUA!<98|e(?Qes)ys9G`jk?|Sm3w`g zqEWR&ps$u$>Czl#!)kk~4C)G0>)En85S(f14by0~du2^sVD|&|cD>WJ>#cIHvE#Sv z&32<-X5e={(3;WZ!T!gFRok<-ZFFs;-PURBidAoEO-5?38R(Y)XXSd!V4Mf7cDFtl z^{ToaMNHJW8!-3VoD65M?S@7b&sEAHHjH#YaH-Phl_Qmo2_f&A>TYliTh?mcT5UvH zL95!bc6cMZRo|&69=A7Y8eh%qmfDsYU;aH#QV%prdzx)*8J)UT+3uhd=_AhT=FK`r zbih7ZT*v!0td7~yXu_^xxz~>#ZSQMF(>QOz3#{Jx^VC|6nq6;M4VY55Zh93yRfyLP zj7EmhsGBX9V!0mZu5Lo+uBKJp56}VK(g?~rdv-a{?!XKy-O_fi)%C!*2NbK>6$X2i zC-h9fb9dW+u~aH6me(-KzP)zYtih2Xp1n5rZQ7dI+O2mCcTG{IKKLxvG&-qjTEXFF z)I=V;Wwc=e-OxdyR8!OxzPLMNExyObr=JG1yCv8IVybUJlHTsN*);1mTxRH**Nu4PFWbR7E!$HH%%kTOAEDv^xC*YV<LEtgPc3(AnV515z4{tI}4T z?tN_Ns0!(doXU2Uyvg02j%lxe# z$qB3%^~8?!6h zjrzXdhH@&~5JkNe02}Yt8O;h@={C}?Q{P7dD+aucaMSI!Y3vwnA1Cx_G@#=FKlgwv^m1EoXS3{lsts z2BoWzptL2501q4D^Z+vQ;*N8x(RZodbXs;D9Ai|v0iy>HN0(60-(@2n-Mc;WoWhU; zL!G9WQP*;tNdHdsahwvIUsE!L=OW<7wE-01zPdwJDq@HkIt5Pz1PkQ)V`$_Iled4ipHT{IUw&9|U?h|j@ zF&TnSyoGIKo^T5rp$E0rrher&kYmq&;xh)YGx>z8(DN*pSvLAQ9Hwm8rbnSotExs~ z3HGi85D;ZJuxf}suL9A7v7fj zLmcyLTb)+9-asox=dEl5PGNpEqm4MwT6Lv9IXmXAN2$AefN}p2x!Byr)CXd{VgcFB za-;9utM9wO5|(es-Z5qGXgfv?U6_XI?4WeDTrCrsetx^Y-P3qJWFFuqRHRz!o85ZG zs6eZ{Ja1+*SSuEIgqJs~K_DB=gzW5C@RN~Ed(iCj4Y##_{3p|N3*z2xMb6%V3;?GL4=erK!{^ zA#ZD@6U^!6t|-0sULBc>s?H8Z5+``rq@Q-1DfQqYwfNuBK^kh(5nwyw`fXZ0@i}|-N2p6H?V29;D_>blM^Q8Yo z0l@|SX=&#Q{KxwL6_Er20?ptk4s~MdVEQjkUj1L*jQ<4J;wNcm0(1lcRl5AQj`*KQ zz&OB#?M>{=K|q!NeG3@Mzx<(DiQUVN4lb@BpsEBQAc+5RV;WEmI`{)~OpT3=%|L=S zxfkjJ{&YdPAt+~|gUJ7dLi$(Uzuo?;2J4@|{yA{a$$vA%|J=XfzqKYlLp*_jseys> z;jFQNfdhoH{^kj0mL|r=KaI@+{cvCl`f50ML=E4-0pQ*@D3stRI|8Y&iDUvnG3KEv zNojvS42Ul?k{mGSYEjP?!yE(pcD-pWH#4tUT7oR7nsq030g73LPM2JU5u#EvB_RE^ zKv-6G30UgBi6(3pfW;s;L`H*XDB7Y)+1QGAE}2bNqRm=1s19Glo#8S&?W5Mcm%e~= zCu8(D;_s|@uhbi!Y;=zs#yZNwn;aXlBe}H5$hJ^C&x}Z}S|L}b^5=-}ZIAWIuWl_?)jH5^PMwR+$!S8Qe zT+*EBn%NP1M?(ERJ`ihn=;Z;;a57bYg!6-;{uHC_R?+^{AZNe+6@UpAKUES{asA64rxkR^R-Mbh7-Ln46akLT; zX@r*YN}8zPO0^^Ni<*l9?{MZW4Cw1)?qlx*@BxD(8u~H4C$$#a)d9VGHAyF4G^QvnAA&7pRS&*{xveYA=YYo)AoL)bQSYixNTm3m9lNNx*^lG)w_+?+O$G+ z!CpPJVX2`#vJ$$2VUxr;pJg1&?#~sU{nO8Zhy{9jZpIf-t7B?tmWTh&i8b8R=%Dzu z{{1kA#yj&0&XiQFHETrtF$Mhz{YJ>nKt}lQeEw^o|NcQ>z?_sG*meRbH|CvkugaY+ z3$JY7i_EjG*<60Ti)$?{t!O)$6>G*c1|35)eA|9}DBA=?KqD&&2}F>LJQlwIS92I8 zEEft1EXq#4Pq%G=(Y=!B{qb>dNJv7r{-@H@bgKEl)XSI4^7it-ZlF;<`<7cOOR)b4 zZxrkwZow1hopDGoU!G(ks%^tGP_TQAyk;cup%b3iabFRob~&hJi*T>CofT z+)^^Q0GD$JpdKBj$8O7Q;e@>+N~cnfwNhl*myUrmC(?HHHgr$&hr1Z98%xpLtYHWE zQPu_qLMkiC$pKDNk2x7M{?IKW_X8qHt(6n31Pz4@CQXpj;$-qTD0fVNC}0enR6t3U zcFz+=A0+TrM3KN|5AtISKiqbL6|SMD#atnf+@t51iUHaz^0t#plO0h3)MS()yUcw3 z(tUkJ7{*Ktd-yUW&Ik(4s9#Wq7G5+P4YYsp?Gd=sja1xrS^d zbmcxL z_85q!3Pv8KOGBFZ1Vy;(LjJMk2$jVdy(G|vu=KQLZvi<`wR!gP7quJngQJz!6@D$V zgxBw&F#N~E?``poR8UN|E(*3AnGgmB6f)0Mv+5B96cLB}6JO%?n_e+~B~(MQi=YNY ziadEc=mcXQ0*{8=C+Pil__=}R*IYWS*b+l9AEKnUi0t_$c65}xck-f#o=itniGzuq(B$WF zzUe)D`$#e92z-IeZcse{1!w4D)0mLNpEL2hd4uucBFxqzOY%RmWSLBH{Gc<(iRDwL z)Q;ojk`6ZGH+!2hfva9!Jv(2RdaHI%k+px%XnKAg7(@D2F7 z4M7^(VfN5Iqk@Si-t;Vg+-28pJaWm8MA)T|Bi#=|WS!4I49gOI=vas;j&g8IT*L)I zJ^ZB;-jK`L-5vt9GM}bLZw{C~Q4LRohVKnWHVnn{zC2$UT77e~Pg)(zJFEnL zi=$Z+cXk4~+uM`Y+CoUxFpU3D4Hiy#sAtz$E_QT1v@?IzMwJ~LO%)vPAVUV=w1Of8 zH03b!1s$YCv_jEysu0ap2HhH{tYmVKxX3fyy3Dv5dT>;sYhaWEmm$~;Jf<|y`(S-E z923ABX7xg~4y22)$1L*b$q}V^6cg#C445BpX%1F;%uH9SF8S?>i4C1bq}=zf%jhNhvL(AnXUv#=Fu$5c8@`UJIf>txV9n+%I;k(b@c+O<#V}+G+5Tt9-?Fckn9U z`KnE`=&*pQ&#i8iO|FA7v$#33!Gh|U2OevV^R7+C7E|yIZx8pxcvJd`VP~?NUy`4b zLl5j+x2k+8AMG@^%#(=EvPKMb8bM&l0d1DVw*n4Z&>y{HW)>}fBSQJgQ`SJiCZw*H z(JTq17)iS?1Z+o^0~h`*u*7wWytLv=cd)2j<7EL{r&u%@)@6%GKs1|3NKHu;tk#LDBozY2=iV{w?0miz$C*@3 zt{hls-tZ4pVl(Ljp&)mf_xcHH2JCsDEcUp7(A~&+gV*w5ec=l?7I7&PVg#5Fj;Up5ap$AE&Nq)jB3q$-<qu1#D=}Vm~~bn1`sj< zsJYHuCb>p&TqxLRI|DAp#_`m1@BNy4%uazj9>J9j+j$UUwJ>p~vH z#uWzW-R!N5=E<$OUp#8ug-2EofJ0s)CJyC=qIPvS2DFRXSQ-=_4odE~UVoI`3Xb_` zL#gAdYm$X5#Dx3f9>r3S38*&52Q@Qr4}~>&OMCL3j*SVl35JfQ0eoB-3`JbrZx~!f z#8FBWMmkbnUY>LkL;?c&20D>i#93$pj11NtB>}d|M{}@=oy@8K)bsl-9Tm908KATy z`TBit2ln>nFr`AUJJ)wU5JhIhxG7M`VL8Mx*C&-mFUO*MMKHc!f|CZqBwq(aeO z?8N!oPmydgaN~uP;ExI->YzL+`a=WaBx!4UfW&=spVcemvZU4j0BMmrE-%(krU|p~yxja5+(|37v)fn~^4UW#v6p`WIepe-BEfe_3pqB&%ObCY4k262 zNJ1aVYU*XLmu_XjSq~%d1L^27wFl#wjAn?yJ<%dQ)zj7iG;zUUh_^04>cd)BfkVwq zW2R!VK&5uX)L{SNU{s3q=CLXGqfr`cATzQ)(AcLcIWNS8g?AmFqtn{GijSwgsk#QD zW#liX!x7H&DU%QJ@$fsUPD%$+Tb$jRQ(`E&zx4~?kf^B_X5)|ShEqT2sB30tB1GF}ITe8K(3rO#n zyU`cfk3c;vB^aNVuJ-p`2u!Zl{Wh?J#M3?6S44X(B>@kL@SpKy*Q2w#V5}myzEniY z78k_`CZbg^h+(Jul}ireQDl7|)HhZ4Xx|aP?%b?pVU|?;@>-bzqRbODJ_9OBk%P}| z_Pjw10AeNLPoC$(Pe?7n(u#}plY6!y`iR-vO}{7({$VS96mCoT;yRq$dGAgZ&zGLl zzOOcXp`N$P?NpCx-_P(+f9dV!yYgM(*vEV(AEYnD-hr!;G-gzFLDUYi>r@nNP8H?- zd|3H8%RpLXHRF3Los5yZ_^dk;>qJx}(IVl6=Y1Px7J#@J#&(m6w8cmg(OMcQd{&t? z8)f%TomN^HX=AwhJMPPm z&B+}*Wf4`zZIYz(bGmwIfbL5v!qbAlZjN4&$qGuTwUzI>3$Z>Um+~>X6yM%EZ>C(K zB>&V5JKa#>*d@$7pvbAiFIALQTOuLN`P06IeUIBAWL=&#KUXOEa-g<#b2+xp%$sL@ z-0s7?Zhl;Zb`?m{@nz%5Jf80={IzW;tJb2*0B zmb*Dbc%n%5+@0GM?j5jR{IjGDf^3GZBsjP<;cm zXaOi327o$B6}ws_&Xp^6v3l@v-I90M`6dnM@-*CwWRBuSOB@^0ctQE?d9Mfwxu5{C z9GxwmNT6ff3XVNa2xvn3h7H7h9azhAwd+*mwjXW<~yUDUFQqLZY7>pL?&w!3K{SP}Ullbk8h>MFute z0v2oADsn%RHo$~>d66PuThkvq>w0U>9Y=-gYu0PXN=z|)Izz_TuGGGDWG&eCzUF!V z0KRO$?C@hUEnUEG7PHPpR-hk0RG9hSp0M&qjM^)DcU)c*mO5S}jy`ZvQ@Jp%d^xUb zV?bA2HpWi87G#p_P)+&?C6Fz(XQYKL8xw*$bPYGL9%jTHPSo9COqxC#8_})jf-)6k4$kkPJB>GW*ds zi;a>dOGN$*@5~RsYMYQra`0O0ZuPCvA6IhM(%ebuc6dDTxXH3&ip=*)EYQ&a3C|EwUakKR&jh|R97V1Gv=kQK=aDRIOmG7xM#P0`LwQfwnT za~(i-%$8+aG>i)|Ul1uU8xMs_^hrfTF&%Bq@UYPPeKEW6wAq%gfBlDKgEG-ty){)m zbli%&dL1SXOv0VoKx9g`DrdpPUYB1Q_45zd7s4R9n276??P*Fwen17@n=V*e2J0uJ zdb1Og8-<~er?cRXBr-a66iWIsa#frWM=pCPrKD%P1^<{0a{gb&uuqay z>8O>llJAxv1H^-Cm>xGgUZDU(R^n$34b~a2&AD0a`6=8G29iEvhtvt2JU z7VyPG^S$FYC=vCh(uCaHQ#W7d4%tOSB^J7gmvQVb6xLyjcCtwgts)@{ngZ1+aoCSd z-!Ud!sIhDQ@0o|W9N^N&?Fi|u$-_Ll#azC{len?V{^oePQ1I65u!>_?S8P|ZH{}b7 zGcR|Fas^@uXfU11)`0J#36VIWSmADvIb0TU{=umdC#U9LSqWEEyz6cOM75K`xwfBq+cCk<^ccLwuR+{Jn*cS zE0iwC_nwd1z?a+fowuxj@X%xDVlk3GbIb=Rn z9@&qcCA%3VEkYxbPBT44Bq6eBQ^0BB!*0l*?9-OU%OHE=%}&?7Kh92y$CPL!D6rPa zdZ%T4)o;Ia=c?1xrYfyO1TFx2Z(28k-hS%gq`RUy@UTN)o^HaaSEtK?cUdJ!Wf5Hp zt%ed_er0=K)yz7ol!Qi3W)~VV5+x->xmGn&co&89wmV6URuW<*@*M~9cK3)t+C$PO zkZM;~H>>Z&<3X(0@&Lh6=;euJMzv=8j$c?OsYxUl4Cxw^(c4kir#UTz$F1Y+g5u?~x!VFEj z<~28M&}vQk&2X`XLzBl?NDKth*K}-|b%)pXON7nSew#QgmiK;qK0;hVO>l`i2}8r4 ztD)hq*~pjPXL2tF%QvaI&HV1u?obC;j$>(zdI!KMqmr87w%*6cba%dyT<&WEjMjO^ z%k*EME7|Liz$L$Pf>lJd?Gu{Sk*B)6f)Qs)s;b zKuK{T)>?QV#0JU|aIGe37glaBl@n%%5v3t<`jO8VF4y7sn=g2cDILY19U?+Y>hDmjnCS`{Ed;^#=q1(@5wUnLRP; zVa6)9d>Eg@zeEn3&C#pO1p3{6PI%Xbb=(Md`F0kEHv<-%XJ==nsAc@&uCyeQ$Eq56 zk|^OKtD>{`X@y;2CJ?8CFFnYr2l0>PC11iBiS_{(Gv$!LpNH3m$dAvP=?cvq{Q2LZ z>+?8%Ooc8;4Gryb!I%}Jm&kc~pI^<_pl{c!{P_6|Ch@IzcNkdrt-kMyu}TUC{ssOZ zw84G12;E3lW=VQPx;Dxv$|c>p8J7o0E^A|-Sig1GUQETu!A~ZP<66cqUW1X6X&FU| z2>o?rRY?(^m90)UY*#Wg;;IA`aQSXURF05w8S1;n<#r*Yp5&!gDJXL%^>_n>=Qet+ zlxJM3+#!1tfBeozTWPb?lZus0`(7&h4c_j>eA%IE8#6=9H!~bmlLdG0gaL9(91{r> zWnCRsTxcw9t4VduC!MSz`N;rAssi2UH+?^Y@hYxh@tuGtM>2|PRdl(qG~QuneIAUS zI5|74?*+`7wexwZbqIB{U_>X#x3#=1T5)WJ%rs3t%i-yMfrfr&zw|Q!cUuW>FtQDc zI2=w@v0)qA30McRBm4TTE#TmWXaZljL0o$#s=ND+IXtyF8+a~xxp(x z&!YG`{Oly_b#ANpv-)wW*8UBG+YLys(;vN|i)1{pcr}>5e0i0vM4)l(LX;ewR>FII z^%~|#zwbnETBE{Lu_qm8o@>g0#`IuU@6N1=yho7TZkf{FahWvvp-ExlD4x(uETSk9dDPQVh3at#i5f5G%Ua6J@`wBIL&e2{kV1r&k_~y=bFg7>vAoTWzvt@r z{nFC?k_Y2%>nFPO%4XU<@^N}=qjsvbTSlHFiz0*}^VIrpd}_04~+H1EeVNH2&`Gw*G){iO8Pw zcln!p!?5hv43){wY+8QG{p0EL=Edu^B;@dJw~;wn8?JoAq-g9gIX?E4Xb^ui%=eWG z#WXhqKR?+`TdN?^*9bIk50M1F9DyJu9=%|4)MMu^PMk{lDEDEUe=JcOC(bgFa*J7H zP-CrvX>iQ)%N+IUX_(~bM^1QI23Wn@1G-@A1S8ZT($<2Ue z6|dDR-AmY)=jIHo*HfcsiwWi1wi-O{m`coI+Hrm$UPQ2)o8X-0avRbZt##8gP^F(j zIt`{qu#Z|s&`K86Bu$ob-Rgvz=_1zyjgP8eg%ZCq7HsR^qzFfU)puC(!x; z{%R*tH^Gq)GSM@7ffKN_PegEviIgXVezOOFco1CBxi z<^50C^PT)-gm{wQU&D8vINn}~hnQD;*-KdMQ)>nvI6a{%qDe}fKuMJ1FH@b= z+K0)&psmzyfW5*rXX@Y!IWkPKe)lM-N9O15cDnhcrK>L(uG1%KSlw(YtZse0Yy^jB zjSRhgm;g`(w7YJi=&w!P6^{pR`pfrph9h{FE{OJa1_dX4QE51gA$gS;ZNmg5C8nYE zP4m_Y$mcg~^_+d;8!@u93&<-Yb7;)00QHLU3z56-I0~!sXgyePL}9PW-}sUz^Cls= zb8_ttNS&0I+|&3C##vby2pcpV8H$lg)GJVCdIm!K&ah)cnw59KCkc;eSYxjEONe*9 z{lt&!wL>%_)0apYNO-IY$xSrEA16l-$w_3%V-|Pp;gl4+RH1&RIP1aY&D5EVxt*7M z)tLL)uU5@ND3qF01w%vt)vqp@enK_|BDRVlRw0yxzJ>vm;FP1&hYIj>#Hv)vbF-haIb z6jxXc?xjeP1mPE*Zj$rDq%87^I_)6Jj86MNIMZPbHw`K{X56fP_Uy2JiwmzZwY;fA zwW67DNR<@4AevK1Yn~{IsIwXo#vr3inc%@`=Bgwpa5hhaThtI~#g*T{0c8<32|86h zxD*Uf%=K2fNZkad)F=-TV~ry>110p_^+?*Pfh?>020<(4JstkWm5tXfj(1Uoa$a?Q z&%jFOBt~i4K+HnRJj*FCOjO#t;RE&&&9>ht^y|9D{38v3EXYj)cxt>FWmfZ{oHEvM z=n~8td%A`ZCk2Rk=-~TdS1bvgGI8{Nu=(GVWN0H>uL`xyONnB%!+R1c1xcA{1UhKq z6G{BE6L&{J*D{cL+y?Z=A-Gj@0O^j6^9r(FboppcOV8wq2Ir22r@}m^%U||oj2iQh zBUtonGV0`_n!j4Wb`cQb6NKf?P~cugi>kx{Ju;9Q$LRdcG53FV(eUNrq^wi354@)R zn<5eMrnRvfP_)En{2Ju73sQ)=EAbmZBW&YGhK&c!6o;suc9SFMfcnlSViFw(bZfZV zYq>S62TR{MhMM(Vm0uT5U-I(H*t<>zZXT-@42195f+v&DWIj}hp5{xT@~c``^vEGP zzV_KWuA|*O%?Z1I|AI%ezt^~SioSlYumlT2Z&&N~9M-kogtEqZmet3)&Rbd{?z%1_ zwfJROP9Y4kv`v&nlBlfEz~7rAQ*BU8^1cc;tXQY{eBb&Gjx1@Fw;9k|`)y=6Hpca z3L2?2N+L&`5^d8dnQZqnGhV)U*I~Q+J}dMz#EdL&i4ZYdv8S*Yk^>!JQ~e+;rV0%4 zZ{bifP`31)zZ4)S%?ZSR`u=uYTxVEll>g45JHpZUMNb1KE359MKhJT@B?I}IEBlm> zxn3sl4WA|t{tB!DQZ92?9S((bYhz&!NZA+DhZWJ1CG*3Mnz7mZ3H1)-xM@^62aue6Bp4b|L`7k$yY*CFwDgN#-#4L3T$g7yRneA# zmQ`9>Qx@Eq0BL75q>&X}`0qmQytyse&jMOZlA;pl#)*t=HTdQpJylQPWtz|jW6zNA33n-+U{5|Nd7&{fMk4(Xe`W>91 z4zjtSkuPf3YKg!Z#317d|ESm z`O7M;K7aA)`KxF#BeN;18eM0ks9SeZ%LS?OMN|pJbAUm&2%SsiPgIbJxpN_SX{6I) z36ujz_8oo}s)~7MH+}Q((jYd%_2D_4Ct8q!TRa^xx)j8olUv=MALF>_$)=s4IBQxH)~pNA*QS zqf>;?Lv+$j;8~Vz+z-aCz<7V1)g7(6-#cM%Md3-h(vh(C1k-SnC<{`{*y;Q$x_w|k z-+!Z#Fs4N%%Z=S6u87M!(fHV)Jd?F81}*s&zt26n1}3r8&Bp`f;S8PmO|AlqO=28C zhzptboJ;D%3}(I+)99g@TuAMYM&^!e+N7=FH-4Q)LX7V;l{iv^3vD+ar@o3$=!PwG&Mcj7|AYf55?J2yM;L|ny!>l_(vCoa++M+Z{OpS zskxHkv?T%8C3u4-BaKZ1{wSwkogZtagnQeI!P20Ve;~SQw6D&6q_$9yJHg?|0wX~RTW-ic4+vgwQ`_T$v z;DngP;*WJWO@LO(gDDk}`_~U8xX14(ZxeZR^JTi;|FR`^VeQ%8QtJp@@9+DvjJaG7 zuhPD<+u@d}1h)e9aZ_;jxcJ{Ur=QV3-N|Rq?qwM600QBQrTZ?_#9d6CSC45@8!ftB z?%#~m4uRLB;ql*(wZBH4NnXFkzaPLavqcYfyDWzsMD8=UXW1f$egw@B3w#tcpU&m? z_`SDIHVWR|EweC{zm9#0yl&Y!3|l*F>#kIJ^XVx;ROlHVnkqRwcf^?bAkpG``y_-= z??Kz^3I^fXNjf#)u0;KW)-8iw;H0PqOtPhp9RLwneR*BDu}Di)!RSHb5Q$qPouq9A@FS&4<_yx; z3Z!-~$+aSx6cx^1@5mxSs5>hF$UC(G}Ek2vbcQu>P)}M$>g0 z%?z2?nohMbezYc2)E`$h)yOk2DORZ0o#dsbA_Q}0xSe`>le0CFC@Q}sv@weC)L^nT z6l^2+m{6IMlH?93m%b)eLbg;M(=Mx?u1}}V&6rkU8PCF0XlR^QS}SMe(DZ0)YIt^P zXn^T5Uxmg#QeISlinI=s z7t%8=+_^Gpmp5)J6cXbJ{4xhh&h_0S#%hB&Pn^hpdw3a#=+6(I0)#Fizt{di@MDL) zLjA8&qObn#Phw)<&xz#v?>nH$$I--H`~j-c%^5%_YpsGgju^5-CnCZX(~4^T@NWz; zrD{wmGcn_VWlD(Q7W2Sw&@q7Fs4qpOLkVK6>de{%nK4JC-xVZQe|{P@_0-vt>*~j? znG71m5c8937s9eLNez74--&%K=E8U88QWZy-b(k8w-ziOfH2V*hYh#aezO_UT}(*3 zW2WBWk5S*KcZL^Cu0E!7iu^e;bN~w6g927n<1{%Sx`(Ok2=he1LX$iU&x*awFkgx$ z2$%6kit-iHgv70tS`-%PHR9h$m6|l1j4*@{iBER?wX!su0jgxqJVET31?5@Z7u;=@WZl|Q5E_-3#4V7#?u=IOk}W}WOQh`CiklNSP@ zzUMW@7i51pKEL~evgA4dJf&rVrJ4tsyfTNhsEMP(#q@0Il_iR$mW^cSX9`)usodm@ zQFwbm)nTsrh>iTjX{RBs2-9d0zFaP#1`Da(sb1`5Ifmh`ecHCfdAXai-Bg&6@W9u5 zRBd^`N|`w!N1#;LT()!nLl}bJ4<(IQJW<(4$er1oaj<>Qd9t8Ceqc{Kv8x;A^tEfW z^BW$^WDjkqnNo=Q>KHPt%Gqe8-Gf@RLARdTK(*DcC+(1~O} zTW!d!XFWr+=&GI*>{r~)U*lm79{5lq#u?m0Om^TFUeKj?aqSUW-oU5h#~da>PWXp} zxK45@USVAAi|+*=rRG%rQR<)xi(lS ztV8?sX@Gt1{y zl~A}DJ(g2`LMz@_PP>TXa%iwP2w;=SdbpNo0Wnc(3EBe5K(DY{A3RnM_=iio*^3yI z#xZ%OS>b666+nJ$QhzuFi4c-9xCe_YEBlLMr{qz;@+sO z_TytDEjn?JQl9) z#EU0Tq$@_;ufa|0TTH3v61gt#tolI@Y3Ow4xs~yD-I~?_Gaa59%fpaWsPD?fBXZmJ zc=OC)n`q1NSJ%tl(c12u>)Z#;M=On%wbgqTJvR39CH-(|;f(QcF{L=KBAZ;p)Ri%w z9GeC|p2Ge$1k9E{)N;XefgsllZ{|B!L^y!E%ptS}`h9!nbaJs4ZAKvu(@1Mii{vRf z-T9*Yuf#=5V9ae6;M20|)}VO|+XxY_{v6d%fA5&m;{FgysXjIYiJU|A`q(qsQG@|qxh6P>L~~i8g=|>Nym>pPli1^4;j|W-3(#B z<`|tedF>xlDW!j~-R7P??NGjUw3rT)>1$>xkvub&F2>n$=ced$eA}sE{-9b>DGsj2 z|GdWBX7u|@WNVdPiMpRB$+bCtHG@r&tJ<;t%PebwvfaM<-Eaj))^vpNx6M-5Hs2++ zUq4XQ0{U)V*SK&-rTm|GiDbWdwiqPc4H*zL9_^ynMijoju zJf-4=IEAjM?5d-2vDGcJg1+8r%Izou+OTCs4wtT;=?h?l7dY9cOrelP|4>$vpf9Dp zXcnx9U&LrLtcV%19cDdYrzc-cC9`R6!k%}Ht1ZE>M|B4p5y4=&*g!1Bqpc;4tvUDG zX4gTUmRf1ShKQRwmECcOddX#y^!SJx(|u3-7xXIi=^ot?IA~mx)l&XSN6(hkHPJP} z3(~b#JF;{Bs0y|+_Lu_3OG6Xk&B1!9Rpn3+1xp8*jCBS-@GH)Qtz&@LoXo+TtIs~X zDQ7=L8=?#ded1nnI)y5gOqFp=N_77?N_i8<P%w=CMp%noFF5E@ddNpvc9k!XGHXSh855z8t+P@Nj zS;%5UZ45%(Qm>3I-J|vOcOM&_<2T09^q1|)5yVER$Fj$!{RG0|A1$7f%|JQ7t51ej z9`YW~YzjN?hqO%D7RX5@+IoG~g6R9~*2()7?m{Y0Km5|oS+&3)qiFH5mZ#pgC<3zz zY%}6;3TQ1M71=nSXq%ueItbwU(8U@4#IwiE>($ggj6&-b)mnJ)$&1IMW&-Rhnv|H0 zkqp?=OV<|OsyE`J32g9MJ%6hKt)%0{z$n(uE~fW{s{HgSngYW>Qj0?uN3sZ zFBB~I6g1lE^0K)EolKGRD^fAdS>e7PC)IsLmV=5QRF`rRn@yWn ziDhBQj-q!;;6|PYR|Ye}3zElw*Mzb;U=XtReO>gKDKWvQP+g2`X_qF^>SR`hf~YS@ zm`=Vt__U}@*6JvhR5};tfn|9siOkwi3y29d`=<2re2;wJl- z`YmC${B>STwDp(opK}bBH=CH%^+bK@=?ap~y4DQtHk{!%MhT5jcT!E^YM%Lp-QI;^ z;HJIg0f&L2;WV099`+$G(fh$Xp8ishq$xK@z}HMQS73e6D@-#QRBMW5GqZKV3ZHVj zRvIu9W(ML0Jau;7Er`#GU?#QkXd+4DGEHA zaGt-VRMDgqn<$b-X~gwa2)4DFh6Em_d2BZUZD`E8f?K*P;i5=R+JL<$a6ctn&8$-} zJ2nS;s(6dEQT26Nsk>;!urO7|?Q*j@6~N9uF8>}~IS|bR;!KhfTKVVT7f^IY@sH>3 z6OD{ohETF9x+=9D(NG| zbW~LlrOtsH&(N)RA6%@0$L79* z#qT-}bc|oNE0jQWiEjNvs&@Jg^sP?>R%FgFlL#g;kKoZ`$0daT*Vr&d=`uycwn8Bs z6^I3w1#%H}y`TiE{r(uyrHj$TOq@E4Ow5p4;r_IWTfS|@M3yxtbj~I~3tm${j2`#i zxtz$Q5#;NGMXlF5GKWixZv8Jzic4!9SS0+P$fY<@{4T2sOTTD2E-1umQb;Nl_jV?h z)MZl5bvk#)cFEI?w@tNA_yYB*>OHHNGQ21cq50xX<4Jxl@6(_OG-+a4#5@?xmp0S{tt?qVBZ8 z^^*AmFIz0Kgp0tK>U*-I|5NKHK>PyAaQGK`*|7A4cnjr%O_Q=d)~|Es0BkQitlyjw z-z6rPV;5nhdZGG5vXKv;*`F!L;Ujle@H*MmHa&ja7d)9UryorbwrU8&V&bk^3yQ`2 zUn(uS%4n@EOfuZRcGY>iV{y6hd>UH?`~)MKBFXj^ctnZr zYNm>c)RuXKKf~6Yi&$n-W`+U@F_c1SJ;S(9hSKL29;ndkMb->ZjMWq!(}E!6CLqd;5OzX{zsb5%We zgv%oU14~Zdy7K^lawtHTI$j?A?cWR1KEDCO7B_9|Y5!Q6p zS(lyac9X){v6sW-&IT7Xmne%l(9k`8Azt$jsQ;--17a%NI3^dxG#T--#4#An0FzZk zkH&K)>;COl-+r_7Wm|g1lEwM&-l@tHdL*$E!rPN3ml_(^sTdg^?%A*iJKl_}aA#LL zTv)^<$Ht@AtE)9F74sKRk_n2-sX$h9sZXapUk?g%JRid0M5;lyxQV|)$4dC zCyv|=kFVF*2BLo)Hb>Qkhjm!x0V>;=mH~FkhG@6N`&keCZ*Cv(@0RfJv2?GiBCp%L z;P9%>m^Qt?e*!olu>IhfjI-H^=iEN$skju4fyV|W@i zK1qkfV(W!+9`wzW%cquf+7!#0j#ex|M*kql>lzA@+dp;Mcu84{HOC1Z6v|S0Wgx4fi_Y>Qsqzi zvx?$m+J>|8Rb7)~$)ObULaoS*(&-(W$p@P#(CBb-i>Zb(TGhnxaLt4&R)ksyE3EeR zH;2sh0U9+>?Y3lIfhR(Tq-_|D88nn}#W|H`2+Y#iM61=lbQzZ0px1!OhW77a=XD<# zO`2U#-%EsO}V!Ge%CQj0`RbLorK9XA|h=d=!U?FDB>;VcU4+( z4V&aB-eA`F2ndByZ|?S?vwMJLlpfa-N`8gXX9_RQaq)o=nHgq}@I`6(Jq4PL7E4R5 zLmU34n(^@N``L7DHZ@+m*@S~_Ps@BC^tyIcuee>gV>DYT=>pey!!uvOo`3LL?A@{EPCpofFhg^c>MR(%zutmPi2N{1RVOyX>>?Xp2VHhh3 z5^Xd&;(CyUq0hntSZ)x6S>(Y_ks#@Ur%q9bmYlFa>5V#FMhE60E5YWRw!S(d zS;9nkSYKBB`uwL=l|xiT($*oABg+%oMS-5WShNqxlR*Ml8DvVq{aBEc=xBtA=?wgp zKC+t4Q6xGKRWws{tcX#o%}v7ZO(_%7&WsbM)VB=Lv;-4u-Fwt1Sl&}XY3ovrp0MaH z?-sZaP!dQ%TMwKj(HVnxaxjl4UC+>q@e1aCY+qq_cidYxDs*s&hNzTz)Kzisr^$#S zOR8BE)=~ZN-4w7@3}}fu88i~lB~~1z50%2nj5;De)G^Eo_a_8yNbpNBS}-Kil$ld> zWKr>pg3>Gdq2p?fSPwaEnQlLsD^{y@AV9v5=9=Skkk$lfKgk>tc!V0G=MHh!$F>K1 z7<6_I5g9tkSpN?@K*Ya~U%u3%$cQB9G6k^3Pp}Eq7gsx-gn+S)&8a8Rkl6Z6uNee1 zA>2e;>2DGxLtJ#U94afv6Z`wNX+u93UWA0PM4YQ?%V+!`o9p*dL^Fr47AxKpwf+nsq#*h(35+$ti# zJ;;g-cfUW-Kp%7tPZFb1#TEYZBNhIbu3z^Mq7{%#7JR}LgNH1}+&K%R*P(fog zYf8g3n>OMjL}Dbp*+8s4qCvOa=^|+|+eO#Iui!m*0fsG*EJ`wL5>y}qY!1wT?Yd^8 zHXx?$C|}ex4;2s`xRODtuD7-gxwn%Zuh(zOuBfV~MNQNvW4Pi=nrcf*3Wm?P@3Avh zhE2^$mojX%*8DpI9lkAP5M~NPI?ofq5%@j`MaGfdh@5Iob66%X^r%S2$3$t8mnwYT z(Z>ifNGfX77WK0tB^tC)FB~JbIOMT{6e@}@>v-vEB8XHnR99B5d143>A>`lzNu?6s zhCqvCl5MUA64VvT6{J)JDzaglnd%Y6w2cg^{dmY#R934kTjmf%0h{sZ+cwd7)P2CF znM@&9Ol8V!?ut|0tp_hnr66PC2s4O=@5^p4$<#XTOH6k~&bryN1V4AutX15pd{(S@dS=RQowV9c*!sme}$Js#rx}+$QMijO}hL+V#<`1VR ziFC@Ja1_;(4P=N33)ItmhWAii6PkLm-0Vjgn)en<0grp z2_|9l%MjczuvH&>gL#t7VNc-2$f+bTa%L9;wwPOTcQP5(YqRLz8fog_H8V{$Ca^ZDrxX#f&Y^ii2Bvqm1JpCHLWH<9$_uzXfsVEWW@}b+#t$MLEi}v?b)^N4RUbQ`{YFZ4kDOSJ)fS zQTQB-2f9q02EA6YBvvq5gq0Zr*;0auiyWmY9d<#V%__uu81U-$2Q0EF#%sYy>u1?< z-)DXNe7E)48{r>hwXWTv>bi=<{MVnCqgUqhdOBr0elDLkQ(oE)^4s&FXQyO7bCau* za$Q}j*Xwt9g`#Weq*6CNoX=!Sm3)8?<;Y)Yi0_DFS`O{gFI*}*e~JpUL>2rBCzZw# zsn=w0cd0DjX?bFS=Z!93rD^Sf!^W8h9(Ze!79XirN;OS+QXW&fvRJG)3gxO&l)Yr` z|19C)YPLFc%odgznnp@uFr1%&2t*vMkP&Q>Yfw^H12&@;-E4@#3jC#Dc8kkv>#IQ{ z7nn9h$fZw@FY&6JR&(O|;`DWNfuD4I(;)>hi;A=%3a{L;-R2FH<)-E8guLm~t4EB~3ylfuoSG$s~ zV1-%q9_n$vFq17bH0IT01BrK9d_fAtl%Vo_dun1z6@`jd&Q(f+pi@WAiKW`bxy9sM zvQE!JGB0Hq8NN9WB^(BWZ`#6WFRCUsuWGXD88${WiLk3CVFHcr^Fik17S%M>zIguZ z?)!_|E=d=qjqk8i$mFiua_}ArCfM1=50bNuZ+(lMu^m)Q*9@{}&mUU9m^huuV0vcm zo`ai-7Y(PB;;3(3cn01M&!B$I(A*mLYVLK3w{J66YI8!@=->y-LZaqb5F}1f>ISGv zH<@kYVzg{CSWXrNVgXa0%M3Qb&@0F^KvYMYQ7ugT1aV2YX!uKpMC7xXZE$VXzxA3O zr@C@EE$cHu(1=k^r|lqTxWT?^ZAP=d(2|O^z?O+*E?J_~{*9a`srIodDn@q-#3Hie z$WB%xV}z(e;FM-P$)+k5d8MUTwwBs9y>)%`og>C5R*e7G1CpA>;~ z8#B3rS9Y$G9!lFn@jijSX`4v?yCtRe%Yt>{bA^5LvWY63I0hcdTgixAxvEgIU>3Ie zil?Y3BGi?7b$sTduBcP#1#2yk1U<9)=$HFRGzj@_96UF94QQ8=?bcK0PF?%b42(bV zx0l)#o`Q*u?_7H=#|4~m;g3icK8Cb-C3iJjN^t9+lGA~ZSGKbHfM1^qJr*#ZNn*kL9z!HqDp<_ z;y9w&f3ZM=c7Qo-nNvK7eHz3TbfY+;%s3Opc#a|=V&!fYSfD(l;nw*+lv$5 z4`s##B)Ts9UnmAB!^2>(Lc+Mm{NC(;`iR(4&!nmAG3Y%|Br3c}G{xHxCMwX=A=dQ) zC|lT)fx>MUFKm%yvhn29)HFMh-OUM1-F4cL0G<5w>(*6S}y9SGst8-Rb^ttXl%t{3$C({~*wkP3YtQo=RR z3O(Teh!{G5iYGdM9O0TGh@z+WZn1haL6pxjo4>v_B;;SaCzrxl48HvY$Mx!WeN za-v6$vo+>VlgD=<`Vgv%S7qSlZBIR`2M3NGI*}^ueD40EHz9bv5%|TWR}2Os>~sgP z|No=yJ-{qGs&mm(wR6sUpPWPIKKXRdbecTflSh-4<0zsmB!R@BfGhzL2m}}mmce9` zZ6idIOt79m8)SpA2{ssPu6?hs@x|EY8r*Z&s(pHZK6;<>mqOy1|WT3*7gcAu^tzJoi8jd3Bt51Bd;~B3tJdYf^cJ*+r*{uCdW3c)H>AlSKeB%kj zV}3?|0d@ZE3@_%8=YCC~n4|`36dDx=p@+i-j8tuKW}7HJcu_KS(QC-bTi;ts2Bml3 zlBv4(pgS_TQ%=Gk3 zWtJTH-vpF?LcQ_P)sNY!(b1GmF0SNqmDLwXGN{*s)xXtsC6}|ApSB42>|PdYi+L_~ z0XAEc&ZY7%@FEAk@LxoI{#WZXJK9@ruRhRTZWOszrI0i)+_3G+)%Pi4K~fZ{AS%aU z5fvAhoH)Dns3PWYD@?woZW-_F+}WwlS(;vM2Qxdb+~)u8%~{z_C; zm?(%XppKmW0@Rt${?a@N{lRGqq%gf)Rk;}E!U)RRSbKn0<@~+i;pa0V8 zy-7{ePL&A_)=HC0|U)QqW`0W5D;O(kWhh79WqrfQIr>Un>w zFP6)H4u^=!+F$o}_j9MK&+s^m@&D*3$lc(15<`=WI$&S=#DvV^|5Eb2@huyPs!&06ix-S6ojA6sG&eC(Zq%kyS-|(Tv{Dj8Rov1}jzmyMt}o5)Bd$BO z`K7JRVP$f9q8dyrEKH0M6}n2<&Xx}@E*%ca6VnsrFgH4y!!#60vAxOlB3iMh5ieysM-$Ub5~fn|gEmBSj3xUq+t2Xt{NC&U5q$BX1-_i*ijRv+Y{ND>iK- z)Ln0v=cS76R@mLJcP^SBa%Q2q|Efgdd|s>lF}LUX@o5fPDsq&>u0(?r!tI^8iym0L?xx)#SE}wq2 zK*d0I2pSP|&{IM4Eu>SSsEmrdVo))iHhIC4M2_TC+-e>?dHndvgXY#|bLqe(2To=B~zNk8@C?KBBy8ZL{T97lr1!QR}z_`(D#BI5#IPl zq0Ea9OJZFR>Y{Y}2S4~IPm_WWas2!Fb@vpX9~tW40=xmHc$+3`ejR_mw=^Y*`ebEy zo*nkN(}^c9G>y!Kue&6XNSv8pj*I@MF*ObAHZ&55TcU- zP8!UmlKD$9r-S%#pgBeDLBB4rp1;oAD4-Ie;?2FMULvFU0Rgc1e1A*wd0n%4LqQ{q z%*k|bC=*HMBsi@a$S$&zc4c1Y1W`~{_sbzCixM2xm3)=Ya9o?W1ywgB`7ahHCl@D1 z#4@AH;Y3aAQZ>y;v}mt4J|H5Ikw*yH;|1HM{Sc1i&dxmXF$xcX=QVg>gy#mW z!MkYNRXrhPo7InJHkb6d?&_awy1+$>;n)5qSrpWolWG=z0`Ehlq9$47V@Az4l!)hb zO^S#m8X&eZ5+99X4iqs7dQv0`lEg`p1ENqPBB%JiT{d|YQyvmzWrjb;-C^;dkO3MY zA{BV5%S6ap9xrI+9Z?N))lij6l3P}pv=KJlCMA@W}4-_%5Zf!MdHJoIiN?L5y&v=*>sic?}EPsw}J^-QMzuQ*AQ z1C5O%d0BMzMl#dM>YchD9Xqh$^3Ah_yzX&hSHB}&PqXqcC8Jap9W8A`pD~dNXKIbe zAVk(9H}4dMiBSx@U_Pa5aVSN&rn$uKm>1cjj4v>D6{)#f22u2#nEe+{8T^&65+KD{O$(% zVWj%6e(sS+KKBTD>OS0A&L~PIS1RRjJ~BJ|lgBaa`EfYF>mKI$hlO6C78IqR24)Zt zgL(h1e&P{4!nuAeHrzFOeqO}#zqJtdm5~xstn}KyFj`XaLku4+H$a?sqV@vcML~^L zHFTy&LijGSzjsl8#)0ZM@{4@5ZWu%`O*%4i?KvYFcg>;`fvINlMp;wcjH`>3d^|Pi zl*;{Hlx&sDt>iO-W7@uRC-*Wp`G-Z$FsaYwD5*TN&J6XCR&*jw6Ra$Y_ptShQxhes zl+qb!h+@{@p{wMLdBXwOqnQpl<(`Q_X3s$y#<#WB=O{Jd#X@I<9<9G%UG5KGJ_616 zK6&!wlP73#RS^uUD&Zn!>7?7+FP(EUWRcB51=WT1O;~DVQ(P95f zNp(x5P{{`qYM8bsvs3RXDIRVqgyUM6vL`cB_gweH8?U91! zBN8v8u}o@w;^fIsd}1=~ojTRs^NBM{sgvJ2c@3FKSn;B7Ok*?CDcZC+Fz zzh*RztkJCMFr79kfupXLlF3r^=(@Ig0qnV@`CBqCy9zJBOtUnD*U7Yzi}y5}I&Wy! zIzAP&myI)R`V3D48U^vQ%UEMiywUO;zyk1>QAC#!_{SSc~)1~Ll$7_TP zzBLmz34jxW_@?Ku&vXXc=?Kl*Nh`tdy7Adjf*{y7b+?Oz$we1TqE1n<& zaG?tV)d4Y4joU2SvTPU6+0i3{#TMA?G8qp7lV#i9!VUpHoO_U~?)c~(WXCysDKZ}T zLpyHz8zz|YXZC{2x!`Oun0f39r1<_>*MQgN%{_j%YviG zf`*5Xt#$Ws&QWgu=p7&3!FCxwlAVlW4`s_2K~MM;W5SN@=qy?qnj7*s4dqOYX|70I z06Nlu5g-j7+Eck^uw}zi>5nE>+}8G1YdcX(bNiQ8Zb@<3ulRe$7Ejzg^256(nx)!i zjZWQ$C0ZZ4;3HEt{zKJcr3V_L@_pv3FIriM;}Ynw{=OgWT*J}ME+}`g0?8-^VwIFF zqHqXU7ItWij0uuRm>ar@y8J9%K$k2=Y~E~lnqeo5&i*&ygXy@Cpf!m6s0mxzxaQ+; zuq^~Q6#2;B8sVm(83GP!1tBc7pg71PzlDiI1E2i#^(QZS%p!79&KVKsOx7;)#^x1H zL4MML4$xgBsp@4JH`J}lZv6&< zepOITzW&bRRHR#G{hBy2GF#3_o_WQFz;j!UV^9wyH>o5`&S?5b)iujmg;305ms3H} zkPI9!5}2nXx8{C|ALNsv>&^`-!|*3Are4JlPy9e2OoqQm{ulJ65$25`F@ps<)=1 zzqu>FC_Hi1$=5IDS8w;Yz`o-CE9`*tR=&yV>*qkF`vYc)okm^-5=tcoR9WhbVN@5| zG%zJJ8a{j>jtZ?0I#Yn{24NHfDDs24w))BG#jV~w$(OEdFP0a2g|s`nueZ&6^uXn} ztS$$tTWe*OkAIac9Gw5;mPxA+Wk_-BaFX}#RJ8Y+rxc_ZxF^)vyH!> zSSo0`HTJpDe*X>Wm3lszkGX;XKVfW38`@W%MwKVQ0wa`XFcxhs9y7==Q%e}+X}^JE zz%0{AKeXb4jUi3)Nr3|JH%u}+GO}S`TUO+W$*~y^xmy}1ip+bkZiz%RP4&mJ{5?{; z`tZiu?9Q!oO;YG>+?p0`o`kjLmWrtSK=g&yD{nre5X;i{Tz12&eV5mH3R9bR9YGVQ z&x;Dj3&ORk!gFHYzN9vo9cgW}xGa~c=jtRB6-&>A(_T_9)^BRm>z1N&y!>f3|L}-c zKF}N6)-cQ#854Cu%%=p$2^?y0?&`CmFSgS4OBV%rJ#z=MsW)v6d(dbK6MuVf|jk!o@~LRCL|36d@m|BSAh?x_Z(@lS!7+AZL{>-bU)1ZhPsn zSD74bIzdLRxk;4>yWebEJwhgODSSj>$#<+XO@;OLR<37 zo;h;lnd6Cbct1FJ>+=}vhWmN#P-2k-aZ0W>i`0*e;N5wQJ?Rc`NF;(SbQB1KAmTEzbyICSMo#N3;;MN+YNmZ48oI;ic@|L_v#T!M!{kNo=&8o?g>^O3K zU&v?pWAaX>;cj6JsBJj?8TA#QG#dbCyaWAC%m-HQVN7aYSQoArG5(}-9P8g6tkZeP zGnu@Sz>l zCu1ks4?n55bj@5metgl;jAp|g;T}_H7)B-7$G?wjSl8j+aKM+~enEh|71@Kh1$R%d zJT<@1bk|H`iSg~mdClS!=4)VDjC0;|&AbrBp=m56tIcpo%xD02S6dsyi^=f^YrH2| zl6*fbW-`T@q#|0Ssom2T_}(MOmtXcH9xJaco9gmC`K%y}PS1~3t3F3L$@h!SsGXYh zNXV1omBk9QoZg6;lCIfaEomxZkPVZg#lCGjXdLytW~Dt*Iehb$)tf1LkqCtB-88ph zyjXF)iXec9qN=LC+TFbo4m&|bX!XwE#A2ci79v7QhD~R^W*y#jviguIk~ZL@!-N&zVgBi zSNwB2GjaKwwjTg2y}aqBmlSp$U067}Sefp<@^@dk5$c^hj4eBpYoQhe4KFrT^t^#o zAy?HN{_P9i8r}DAQ)B;j-#5OJ{Kg@&=~b82pV*N9#_GRq_-4$l`q z4&NgC-hzY)K2ikp{G815^|c@Pj~97iiRYJucsk@S`8j=nJcMJ;)i{MR_V5)<##}jrM^Q2p z{fQ_)wqYzEAxf4pZ*AG)i0v093%*sFGb*4C_0Q$Bv~Xmz8s`RWuW>qr!?+u0e%nK9 z`*!OErocI!>fP<_)qgI8D>MDZe&s`<&D~-)GR{wH(^I*(5*57oQ85TT?@_f<}#FcJs(aMLa z9B4rfpCUDo)?JtKd07vYaz@k)9Ylkq2g+YdopNJl%hdRy*jYUGVm`DpqG9N~ZTNn7 zWwa!RN+im`=*H2zADK}?ZSuxREuh-+da%4L5_HU4dtSUlp+$7YrQTDY{pT~vE(Jo<++~YcWRlLEu%YfrN|>*xPy#1T9D}; zyy>7dUnwo^zcfvAWl$i-a`i9dI?MZsQP8cO$wrNgRg#JnczrJte1F^SSK&Qo|WUeU_PXigsE05h4v5L1V2Ct&I~h_VQF` zX>w}IY%Vw0Iyteq^U%KgRx%e~oLQl-Ai?;N)&G5D{5Rixbw3RI*F1IIOfot9@nesC zY&m_&MVaLfoPxTRg7Y@uyjlXSG|YO+l1)XusM5&M!56Q1BX^{5%$3=9mQilo?)vp=(o9n2amGD)c`bIUzu3r7tR}AGDQvV2S z!{_6J{QP^CCkHjFNmhqrUyLzQiK!})9bkcCl9pPhR{;Q8#}R`DER(PLe0osH4=lkK ztUb6r3`8mY#~Drs6|o{&MsmC zF7jN;U44_AO1b2EH&qjro9E|mRzT6cUR?dz^78Vb{$0fV;upVo%hFwsT}kddTQs%$ z5R}=~ZtoIT-vm&)R$Hmp-!O6KaK8F4wexZ$N0|B5!I3W#z?&{J#b=6->SBS!6y@kA zfs%09fEadOJT|?)1{>O43Ykji{=GRm6~b~WghV`|SeI?HH5>a{u}#fh_8P!+3NLd4rTf#4qRFDOXCya4cy1B%d90rPJozl# zuaIaWyC4bb4Q9e-Vi{Spn6*zl6DP930d|f3w&5ndJnQ{YdqM|V%uh&(pPDMvs-bUi zsZ8e`oe{f!d}Ar6(I~xP?_#UciWK7A#BbbpMT$<>vzc8fkyx8+x^Fbc$QW7K;~)D? z-Y9HY$<54+-EzrhEeMrE8@hWGbJO8Hjif%9eRL}!2RG(Pp?c+VdUV{7N^RNm^S*AT z&a2Oh7z6W5a#}ZHm7~!?B#xdXA*Oyu0vEEDYq>t8T}9;r)N0Bs*6<#gQ_T8!k-tl+ zx?xJK8r`bVq?fZrvk>MRt*Q+>d46pssU?e7&LwlimeYCJkfTwBx2Np$0z}@G*_oC& z+pl%V_~B}qqz6W8O0rVZ{SifM7OOqcG^Eju+tZcbomz-IOOXuE(1e1|%bE1~`B1J} z$E(;!cDYKdS_~-`C3s~xa?&if$|g&yj1UgB4UBIKC6kA{4OuFzm~c28Fml?Ec@+-& zL%zj#aZ+lFW0wqzD$_d{d?v{GwZtA(9DZFO;Yw zxn4F1GoCBkRAL(&!;!k*Qz$hJSK?$e>qx4MPB4VdIq58?CLL#v@|+}Uk|1z`%t@|k zP$HQUKszdOrbmcpaw3%lio_hQm`fz)aj7?&SdIBX z69n)S$DFR1>9P)6IgEP+APaDQNKzRShQO2aTX~-G#>sP-k4xc`n&-PC<@}iIdup1) zJg#&rQM_v4iA3Z;dYdH2(Fhv3$aAt@8%gU58Qtp295!oJv(i*Z@f;NPf~qG716Luk z#w&uN=O~xgVS&?S0)R^)To9x+p1{+PIG*UXqVgh5E_>;!C5(-D%Snpdi-xHYBCC={ zcr6_S963Lx>c(G|pYQ*E{W&Mx>csy~wdmaD^`-xdwJGi=zDNF)d=StFdi*kPF}Uj? zYkz;Rmbm~$3F!|*(Z~yuDBf(T*=oHLj!rLMJ1MCf@BOWN?>*9=-n{z5i>|7TEMH6B zPfdR1NPjWsRC3X^%Ujx(F@NaDy-+SQSbhBWFLAG3#?&H!_IT*4J_E8l!TixL0KN1| z^fqrt5>m*W{X!jWAe-FTZ@Do*>bbLDWVk~Xy0hQNVF0)x1<`?i=R_f|g&G#fPda92UKg;y)+Iro?-=f6-U~ub{o%L2 z{p~+sf1mlxX9#)hiYp$w;@c0cE9Jsr6vI%4CxYW>;-+nD$I-pXydQ+T0X-w29G{(O zLBlf@PA5*CB8gLv-FfH#xa%&m|E6!hi81cojaXh)bz5-qc1 zQ|s-Iu71N5NHpcRej>r*W1pi5#tC&n;=;sDiF*Zm>)W*Q~eiJTTyIM_Z(+$nm8c1l%gURVVhy63+1$B75!?V zZ%T?=jT{k>xJZK3j)p2bsmK8^DNO2;YFO#j&$w9}>6bmppUfAtd?3i(!Km)&@M#Ta z8qTz~jt+eKp5)>7_7h^hR#qH5t*qc_9l2Q7JEN23(Su?>h-7Y(TRtR#m%cDHd)>}ds=Y9EByK9dB0R{b(WV&x>Bja->^si!PXv8)$wF*YH7+V z6_>ABz8(qmD(Chgi@&4~>8*9Py|Wc%o3eTqf9jkw>Tmda??#^LL?rZBA@Y ztR(g(4knHO;=dB`!Oe-=nK!myk;G{-oJBaR)LW>MVK^e2P7|26I}wf(TO?|Oq(suE zm^2rMtOgu9Cq>i5gKvSrVK9C$>NT)pcfKmYPTPV#^atL~| zP1GXP%>;zY)JcYLFQeoH6=}IjNrliPAsZ>#PX6#}^FsX||N3W_z5TOG#`f_I>cZZA z{Nal-c@ie+rTk`cOk5(Pl+YEqBm5=VNulpFOSDgEa*A#tWE(^FNFMZ)a%Qo*iS?Hf)Q@LbUIb(o4AH965Qk`+i6PVz8~ycx_?V$lYOuJsXNX{r zWH;6}+_CHKz58Ce5~XdM+~)LVW;&f2&s41}a$9A_OQZ8jrW`HUBUf!Ye9O&8F5OV| z^bNJe{d@QB-?vwABcD%38T0|fftdwz3a%%e$imyd0r~;NEa4gxcB4DA#Rt9@b$Wx4 zrG5jn1KL9qv~_NPJh&!u1|yFOCTLsWrb(U?eJB^KGxjq+Fc#Qx`6jZ(;5os{b!omB z83M=OPcG(UO(jXqEZCBoHf7D!F-FgMT()@O)l0Cez*je2-dd@pktb>Ow+ML$)m>Ux7-{Id)OA347WKeR7G&8; zCxy&JCXgv_RBE%i!b~MCis5v%5;z~Kt+cLKs)D+y=u1~$xM+)TxN~%j+Pbi(L~S$q}=a>Oc;Y$5#d*X1)ehR6bt|&ld`PJvL*p8ReT|4N-w|^m-gDK zl`b)_BA&5mDWfG`gX&@N53&2-M^eyoJwfMq6_9V5r@6?)`;ZGm#2X4sJdkhmf7yp|(?vxzLpB9A#Erz7uTbINr`8h;O0rzom|*Qnqa z5L}mr*9L!{UiI?@5kwNER*^VY9|(db@I>Wf?%%&qP3l{4c3l@Wjx%#njib{)j2D~Wbq1)?t0>=qjAZb3_FL4w#Yh+8Z2J*|)9mjQLQ|{^NOUAOb zN-B!SB;r`4V@$uR19X!?f9p=kjUGRcDpPXov^{7q$b*jPN0ljf`>~Vvf;b?jRPlpf z5*(!a;W%WNryP<4$+keaYh~!@Ipi8sRPj-XJBIg<6!1t-&(So27sJbn6l|G1u6u?l z>Vm~xHMdg~)6573%}^3vyp5Lxo=3|MM^dtWKkqB9Vo0E_iNDFk0VIa(io5h>A&^0p zfLncagQtd&RH~T9b6qY$i{(=+Cpw8Oi31p!dwzc_MU17cBGno131gf?+NVYrloJ%I z<0KkdsIcGwq-`VcgDgS+Z?t(uj0^B3W|PR)&arw$)Jdej_qR9940a%EN16m{&BtX|Y2 z9PE6E=LMQ6nZlUCWPx$|Me9jHnGdi^^xS!sFM^1eYxjo5*fN{1fQD^7t!C}_y!Mb!p$X}du> z;|DwsI;s-|NniCu*;F;qRqZo8uyOhWPZSya0(q-TWz1YNRZ1LGc%3VRh)i$ zEQG#+@Vpneis~E5X&)Yl0MdrH71M=YruuTp3}vBqU^J-l_!E@$0zEFs3l_BHr`L`~ zXk%ve*Ow0j9MpPFt80RwPmdZpTZNm?IA{!kN2}_ACi~&evnAED1tpE1Yh!n?k0KHa z^McQSISVd;))C8ayh-xh(M6HJblcc_#*Quu{AJskAMWz|=Qf_;Ih>NGk1ib9ezhWm z;?en|upmZ%z3~{&?b|qal;`%cn2I$$4clZKD~`El;{IbT+P4--jRHZox!6~I=p#=P zZ~FS1-t_gm9(?ve_^dvEw086j(^k7>c0zHhnBA7&dJ_&bjbWiTolacz5ElGi?P%@b zC0AW_gKFlim*=be3(j!6I9>Bz7lmOnfBqsYIe5iuFM> z?D{C{3ZcWoI)U9_RjlJsA{%sM{$+V0Fc){spjz(_*k1j5pP7cI2>YP{)u|~4A{(hd zLkDxe?1vaD$DEED*h8YIWv)gnF*6^qZZUQ{lF%&z`av`cb=vV-l&2VyIJ}O_d7oF` zrpvOfDbixZQB9D^V-}~n76=Q{vRq!^ZATEkteG;198)4U2`Zo*kxeH#RRc_YNVgPD z6m6OF?Lw-cY|n7{Z2@4GI-d+G4gZ?4&LO^FTtY_(9VdL=kfG^A>o!Gb>v;(dqR3Lx zw-q{yuDdh~@YxqhGJ^9cv_?786#T&CWe@rW+tc`ofDhFT5=8mZ@&?a9bZHk{}K*d;>s0RpsXt&ak1^FkV{P6}Y4tB%u`;xgagH z7sJYm*sd0xO+9t#GF8-^D~ud8Gk^iQ&PhBbq|cVK8S2BO+WZYv6?je9&S_ATHm3k| z3cF-0CjqEXE)d~5;F>M}7siA3{Ss}?i5aG0W7sLR``mgZt_5An(9(BC=?l*0t<8Z* zIY+y@MUhsYMiq@*CMn-QRc?~$d&JOn^Q@w`PW?Mi)w>0E93F%eY#TH`)bdcEFq0JG#*qA)XJcSBVY_$d4O}!PU=J61+I;0 z9+-=Bgq9NWK7$r=L>;xq8onI13XNQ&GPQfWV-+Q1tL?o;7qku6o8IJDK0lWKVLsic zo>=LWMr@j#e(vp_;d=MdVs&A%5zVzL=|)jLlA1D%f;n$$2jrr@MI`=f^0@EnDQ6@n zR(3UeV~ym@R6a4x$NnSwFnN6<1#SHR+z+TGEtVPy-iOG>V_5$20;q1-K--KyCt3?S zuMdX85kNt`T0wv6CianS&|eoxo|4T|6BkS5HsVrp*FC(b^IYd??(abI10>wA!P6z@ z^Oj6+NreTRL5m&zs)k=Q@Cn(a!S6DfRL&yw?Pc-Kn8qBRL1T`hT_5A?nS=NO5xVs;c=J$QP0-wsH@(ybsqOW;(imm7 zL5z2kL=;Se_-ODLdksv{Q;)Nt3(b0YO6V~&IkFK0y8XVZ>+b4CSJR!ROi-vdT0ua$ z)|RXc(18dQN#I2Eu;yi5S(;Zu*_3Qi=9B^H@SH-v{AHqW&{gsZA+IJ1D1;_~bzjW3 zOTLg&#Nz0rPbgq5&E@2-QmZIYQt#O&S^b*GNgR@uk$~SOa0J>6T*=ElO>@bs9Q`vY z2q0O~8eyTtxgvDqAb9}an&q@5H;k<2D15pgq%$JmRZdD;{}bK5V&^X+kQ2}#ByWce zJ-XIzm!>=nsO2S*QgdTuTVAJ~rjfUE5($5KLF5Ag9v%oWl42ZY^z={R{lPu4c%_63 z4{?Ep!WxCy0?YZ=|MH&06G`}z68ZI8=Wnd{j--t0;B7>{p=)y+Eh1t>Z_F7!qb0P+n1~)D~GK(E0!he`lJB0fO3i~OPD}YSY5&W5!cvoe;rSIS4HHMlXBCAP$IVOTOOzY zN?xgh-k{`Mo|EK)DGpYD^9Ih>sHlJ@su97k)kH!&{qKn;Jw^7iyi+?^%w9Ii$kQONt&R1e}W6+?d zxC*Vnx6Cc+)5T=LDBf*kjn!{^e3lEU=@O;2oNORVEYCG+0=J2)!n?1*e?M7NLT(qA zR5$P=@&_`CWBPKevY=+_VvZM;yenIpjB(tJv?dgd zs4y<%1e=!AH9RdMa9J~zQIk*A#qU@sVtLWIMK&Z+mdYG-1QN7VO#}4(d{X|CEa!4L z>eDoT>+SR|y8nVxchkGwYMtcDloT_`5sK8ef>@wjCc|f`OEa9$1$WY$kI<};r5AA) zwHGMBeP1^`LR?Z;c#@^k(wwX4^^rXe^`KX{=32VAnU3m4fIjB%`1m46b)Ak+(Y=#& za=dCbXstnuL>6Qbqs1l~Unkv(1V#$@ zV}j$*M?fwl5_v#o3yGD)C5h{yUw?qs4Phf zbxqIy3Ef1VVwP64x`>)ijVI+!b}%|NUsQusY3hQIj=DiYS>>>EQDedrpXJqh&G>T|2lk)`|l@82p= zlDqKw3v;VKsm#q)rpn#I8_i(!;XQjE9&M4=D4L<$oJRp)hl%|LIXJ_(5bh&ohD0>(vQ|= z8%T)5CJl53hMB;CO*e{Jf*Ml-^UjIY7xbV93N9Hp8V)rmJs=|nb2bEz;$!9$?JNWk%#&?QI0Bu9PE0>#<+l*+ZC4~ zEm6(UXsJdu55pw7RC8e!wkA%OlCt8dl~4iPE%2)7iINqPM!~G2+1?Cv(RT8@FBrTc zaK7bgo!4$5^!Pg$1yOwR1LXeORp{B50RVd4x*&xqUZVeOqzF$HYUnwVE@aD8wEZ-v zQsRMX@Aw&xyV}!aPau|SxFONr&4aE$wAa2Cz_|9r6B;7!_vwV_k3I_Brq0Wn!pRh{ zSR^saHK+x0+6l|h#qz3z=#$em^dThhBt$M%s9akgUEILS+#%BBO*>ME#OsPED0>2q z_1?+7GVN) ztDUE<5MW)WSq447(Zp?N_mo)bMVKJ&>q{mFkh+41bHElGuqcwBvc8#<=CzC_n=sME zngrUbU=jnvV_iURf?!mjrfC^n)nHQYXx2z>mWYaKsHS8#L97~iWEg_u#(dj<}z4`fsk;t(4t9_tFz{6oN24ysOc#Pd5WF|Nw z4n$_n!*EPYOn26Z6AZfj^`s2YY*DLNpnG?`uq+C;y!hb-$>FcN?1~h13>qFjr1HkA z6`fP}9dtR8las?MtyzpsS^o;z{!B~_Ny9nJCa<_=scwMvd%Y!7*U5(eD!?Fkv= zeaz2@IS&(_ALqRY2gB^f14cFi%4+RNL-8l1(pf z4}O|W@7NCbH<#MJ-5p`md$MV=9k%WAbH`Julm)W~Q&#IZe)~9ij4dVY*6|qMeV05> zej{N50-6K#lNg)@D(HrbIo?9iqUD%@M2H<2*9JP!&;v5G7{w_u8!@IuXT8||3jh9^ z351zm-^r^{mX{@NmXrA{qZThxNojVWwM(2sB$^H6cfVVL>MKehFNF-qnPstL3G@g+ z@C+}fyih2&opGl5*8=G-B^THtB|iuKmZjz))Hy~u!RC=<%n>;HU{Daynjk5vF1`0X zl9v_;k@8yc(RY4KM(01?ETmn~S0sB%+_1w8i#XcJhjt`g!0cS{^Qk7f=%>sW2c)5k za?F>_!OFi?P%qA)klys;SDf`FX9pQNycNCW~&PJ0sO{ zBhVrzICb?oMb^d1NkKQnsjBm=#wlWQ%jUGGQff$t_WYIbD!rBhIZvfd&6?bOiCXCy z60|)nKMGX_RqM8rHhL3dtL^9n?RXK8G)ktTmjR7IMdy|dB(8nyZQJQx=bjc+TT|(*{mH1<( zDm#&^5o7ZW2D|rBr?2*JZiQ5yeNIVX* zqq5e+vH@=NJgCG2E^$IY6GL_|;~C>;L>m`0r#{P&9Y#|u_jr_-S|I*Gun$;Tfwc}B z#eQSRcgA4YU|q6zOuI^}8Z>?=S@E782E;k{` zu1ZqLf;^}$bPxJwzv*U*6cC9jn7k?Ip&{27Mz;Qs5hQCKkrd@VRn3Cp^MDd6@VQ0e z)0pNxrN2%RM&OLLCI_CSO!N$yx5f+3@z(fkjh4BEiCR_^M|1VE?@wu!rDEu+4Y%RSj<{v|Jvk1#tEW`bN7(~9Z2&IBtT3pZ6R?O~j9 z@6yVq(CFvJ78b^G{l^bh>f?aw#_N@XQDNcGLIK-uc>2?HjC=uft9ue3gva|@0(Hl$ z4;Z0p)Q_nNB$oe+S#jg>Q!(-bF>2;e^@{33Gpb856&B1b;v+#-yW3);ID;~?RyL(3 zx}>*6RIktlpC7ba9R>@AMs7F_<2yx{i78}qZN`oij9LeN!3&c55$ibP*z*=+F2-HT zm2lv*zPR3KqEs(8kmCjQ{V5uIVK6i@K>nOz|J=kFcNQKHqEkv3fKVz+u*UFaqhCY* z+B!?{4F!?sr%{i`Rt9S>8$;xSTUv3z96+2ffK|9sJO(~15c4?L-0pW+PCXEMcq5IW zF@n+L4(b?ZD@xr35=Y!gGXgbYNon#9Dv57s3!#4oNMb3~wuwZU!gR(r07+V{C zi;{_*(~K1`e=r#+D)qN_V_4~#3s(gIhKDNz-Gj0W{+ps9XQv;p3TfO-h0@ua8& zR7b^8v}{XLB3<_~V%y}sVxC3uISL?tr6I^Kr-mYvi}S7_k0v$I^ko1estY8;tLS#Y_I;ZQP}gMDQxfcz3fz>*4w zT(s~siK0ADxFd#S8xh7Bs=7jgLaQfg<71pqFtWBChH|M;&15t;4;WUcmdr3mIEGMp+TG&JD|WMrxJD|4LGw0 zxcV?)CglESPC_jF6R82$Xbl_x@lUmN{s5r-*Bj^C0`Bj2%}L8oM!q>^Ex9?*%X$xV zyT+91r=kFUTJq3;-y>Vk>iM3LuGiD6r*KMU{xNVwS*fR@h9V3022Aw<&E~0JzWKAB zaXP_{1JhzXeLBGo0@DP~VjjN-daw`Dd<*7Hj!-A!nYz^R*$C30GU#9q1&Ix-G1n;! z0|&4(fL=9E1HTQ$k$Fdo07!nN5SgkCYK*Cf`&6Y{)}DT;^U=(Sm6B+Mx`eJvTy&vc zs;7u`kZ^`?`u3rEKczmrBqgg$bs`FmZOfFmRjHE-#s%`eLg9PwALy2^a4J7-YV-ATo&8xyo6HX`p1Z7n;ay8J7kul|OUo-OkIetKrMoWdUp9B~ zAU~-&*#qsl>o=zY_kz{Gx-`c7+Uf7nJ3%koG4w4B7v+EB)?l<@}?;m})QhY$x|_MwkxBG+oael|lpE>YuJcguc49D{(oM~l@P zo+3!1Ou{5F976VQA@)hKVF9F`R4SKC5;Vhw4Xb|wH(Z#^7rq%53KN9_x%XFphmcl^ z0P<4|bo3)zw}NP<#87BDBB>T3pLiQdYs9ie(YA<|Uj3&^Dp{$tD#O`2A2Sulu)TOR zsTk3ZhL>}#L>tm|0rrI+4Him&;t`+tunxI=XH zBWUYVPyg)ncj>8f^vKCX3)=N;VjdZidY#@d+I=m>hk)H|kW*Vm-+$LjW-^gTK9LP99)Mg;@Wxkl`2BU8_03=3`1<+z z*Kgdoa}NHl0wF&|uDot@=|n?IZU{{^Q{{MnBm09AXU4a_2lZ$&(M-(X_*PBom6)vp z{Z_C|uH^Qb_ zXNz1Zh{|KBRMfS25camj*;5O-g=3or{mc(M!|;qRUa_^*v8*c9lim5v=lJf52_q3C zN=!dp#QPjDhX-+h2(BMW@mf6&r)~l?LNv&-pd<9DOZD@k@#*l+Pe>!VbZX1H!*-M{ zEj^{rHUhc0Yg={Et!!PE8j~YRTWWNZc{1G;KXXp(JyTS_Q|Or031?Tq9j{6D-zJ;p zpk*o#s@?oZ7y6sPOi9Lu^3+vfvDVZ?^Z&5+9&nOe<-KT~Q&s0w&Y^Rh?w&l|)6?Cv zyR$R1QPL)9S9v9^R$&!TK#45SA_P~$AcPRM!5B&8;2?vI5nzxF_61*KWBh=7xnN}b z8RKg%;TrpaZD#NHo$A?L$voTd{@zS?b>*(EI_FF0`~LrToJ|MzPr@aZq|H3+Cj=*i z&Sc^abnf^=sDFkB{J9K|B7> z^-L)*=a`5Clkgg zW=khAo(q?G)EbImBsNYgLYhj9tvZwo>>n)06PN(0@Vv*SC*h+aT7r@pQ+nQDftI7nf_g>|FTxw&%*Ey}h_N-P1Rs_bDH zl(;qbT7Qelat%YI?@&rpFc27_UNw#<<~UKPX;E1R^FdaJ(~U`)aFvoo#e7{yx3KPZ zb9UV3Tn((Nz6hX2s+YLs4b%P2iEq7EwhkP@%VG z$$R4U_G0NKu#XKET41QC48Gm;Fx7<-)1a2a99`q4>Np0sEJ`pooWH7EatjFuGYh=? zh$}@4JxSBybW+zMlPZ>`z?EyZqRtbn)+Mq9rguyCLj^9Hajx;lV_a{$=W{Um`1}lw zbTpUENY$-N5OpGq-Fa?+Cwab*NB;n_^yfr>#h5Hs2SsVqSHa~2|De_DV?AgGowGrG zX)wHW$D!-Vp5`3+klZK6MTcvJ<2x=rXE%9~OkRKfov*%-Jf_dLCKmLKTN(@9n+bWr z<(_sm^q1t+Gh`a%ev)C>2|{q;SXPmKnUzVOr2DTwe0b-`n=p*YQvUKMo_+SJy(>Fp zO3p9GD`(frG*Qk68B0#5Jxv`ogYj4)?Ip~38)St#tRaAOVJ2B4!^I3L_ZR-`;p1;P zK+&vJRYP;RqG(pe+J1;UtUB8H;LLBu{Oj{nm!DoBAAjhki(WAo6OpJ5jnmY)uVE{Y z$=+LU>S{LICd;$jzejV}ikREPw|fTsbi}=hrJ}&w!E!|W_3)20GTD48W=K62Tgl1w zlh3^GE#>u-nYdSU%2c^|CY#5VaRe9s%DNokw352S4$ zt7T(W6{K1i7(?MSu0rEuXuSxuA{lR9AfrAW0MA3-#OCRb`e{GC>niskVcR}h*0oA| zFqmH0W7xh9Cb;*;l4;cS+u@cZ70Z~OF|3N(j_tUuKHGmM+$VSfO_><)ko)Du`RcS5 z+J0O^Y}JjQf~%=Nrpfx`m!Q{R{^OQ8BT0<8meFoI(ph6%rj(Kk$!}wJ#3tO<+0a`{ zeJuMH|J|s^;VsHLaDioO;)s_>+=#ji*2Ak8f#ueSM35ljUBss3@MbZ{%Mzmf!yX_c zVPg*#z>D{y}mkl|zu%sbvA z+XpsZzj78XFxt2OuYdEHX@%XFbD2)}bA^1qKci?YaAgJ;THbtvr>uHvqEK5=0#!4$ zf@YM@+QEsclW{UTo5YjV=+BfTSzc1I=__}fFD^h7YGhX`V;q5Qt~`DHxMjQ@g9vnt zcMQT^8G{XvmoM?Ni}x=auh{%hG;-#s@fXlBPI0z;=Ie+!Asb&)hH^~DD8~vpe}@9H zP(e%xMoGm4$yk+{>V@*gKc1^7g-76B5bugCvz{ztu@~m;$VWFfD38GV+%&BEx_Y3L zrlkX_?pHPxPc)sBCTo(pmq>z-e-iBmZM*H>h?i&7CZSD;tMW4B+Vj*E3-mw6eZ{iDTx zg@Et@Lr0t;&Zt1zB94}wGFykM*q8#3jfY+6FnIEA%ewo?Ks61;f8bivI(+km0h{&_v_TLANq}`PsP_Xq)WA=Z1qe+1N0SYCq3O;7fyM+}IrajKwf; zchv1;LhxqXcSaO{K?9prg2bD)3n`Un%Z<+4>gmQ)X}zA33)1?F%ivs3mfA}OBP&bu zDH^NwVxh@sk#PU~`Yu8$BhRGV9@M7irgxn=vi{@7Z%>k-G(8dLwysb*OO=9>4{PxC zMveGZIW&B@eT;DWLdbqk#=!3Z}E`k|;t>a@eg8Os}f z>kKW}vN7mPF9xOvHjWyHt^`Z_{B6_Q!QPuCSnLh;nRXVuGXaT6_>YaqxG4hOtx?Q=0kZ7vOOdZ?b*>g9GbG9UOd0Nei}0_ z6shX4fN(L%QV%fMtUs#IEEQoh9b3EpF~;Y1kVMa9~5&90>o58=9T5`7!K+ zx7m#Z>>B1M^ixrpBCHb-o+(FwRIs$tC>GGbBEubnXu`Y35k!#&HYdyx^cusJpL}+A3SUn#lvhs3bS9a zKnhPPzOG6(SBo@@r9mPp;sr-w#?-=-J{IF6gI_k1(?Dv$jwrnleVsMgCm zq^c400a@eP??RR3`%A{=s91tP}|F8P|HDN-LBiA-=g64_AF=x>U)z;2{T`7>23iZLq*pAp{(0rW#IIXoIC!RsGsUHD9g83Tqca z2azwqizk{)`PhKeT$jbcBsYmFZ+BEOumi^Q8&*@5vGB#ArKWERY(r;zHKIBzGD4C~ zy@7$fXgiMW^x*d@2Hav@!9WDuOmO~ZbOpSBlY5wh=_-n%Q%$vFW)Rt6ET`I5P&73o zoDmd!DjcKp*j6=|ml-H_%KfDvuxvH=EK>{vj)&_$AselD(w$SHNSSjd;|b0SL9uB# zn1iGRre|V1U}YMGqLf<^WoLNqK+&o+C=-K&2($c119MW3IH6Td(c$({S!WbsHmJfZ zEz&71qy1=kMyPQ`c8q2*06(dKZOejNEmJk-+=X9G)VxY;qMIUmQDCx4XI#!HfgcIR z@CS`tzR#e2cbysV4CZJ2ImQX==W_<$Et{Df0~pjT=K}_h&P6Ilrd1SHcbM+FI&9ix4M#50V zIvt0C#>(Uc%CcxYWG#q+Q3j3k`Lp`t{^B^iJc`)ZOcQ0FJjTSKr4En4o5s=O#Vdo&69H85(HnX_!!!YDUJcMoFhAdn(c-}M*ZXB19jYq`9(5q0Hf=KBa zv>6q;Ij596n(RQIw75nPQb9o>29H)z6~&;cV=LS?Nis7xoj_MqbxT&TCE$JtyrHN> zRdfR$yR0alZo>JYtpx@=`~P;&J-fl0vA08;m$w+Z_fnD&rL-k~0oKG`khh6p6YD_} zS|al}(l;Q1r2-ujev!|VfTkRJ2RfPRIwqKcJ~yd~=%pYxq2KDN%aAVyX-i!P4kOSh zHMu?}i@|oz6%7Olm!WT{AzT@it*fcVxkfLb7FA-ne5$bsLr&;Fxm-77ZaZZLB0=Yd z!fO8OFrEqWptlY>j*^ur#TB9(Z|8IJkdfE$XP{w>nh5PFcLay;*?<;bKitfvCIx|# zhOG;#&qJ#i{?gn+iK&U-ZM^M9VcTc5{;u?D_!dx`J|dTc5#d8yB8%-NrdAH&d8Kj$9EzK}2D`GRHs>(2LLEtu!Q$ITO zCGz`le<#G<#ZI}313AJyL1+qrLV!iuV1_E>@zgP`^S2i5TC-Now@Wc}CHsYq{PgNX zoq4*US1vmD5UEUFJvV8>*xtqXhgEMJ_=DTetu2yHNw0yr2C8Z)fr-iix z!`bE{wXB9|r9S72g%ew*rnXFe_ikR9sa8QpBv*q;u-&R+1FTxB{Jie)Kw(kkzG+!z zBc+Rt+2vPG;z7^l2;pMv{@~fQu7f4sz=4i z_Qn&}KYZe!iB9g46#>u5Dw=6{0`Y}krhtZr6&mQXI9ZrkS(*7?IGK9<&O0B!liYFQ zjVDgL@#mCx8zjbZgV&(2)QpOZi6-n$PCV%KVZH%Pmum^v!FF33p8+Nt*WZb!fj05u zQ(q^qChw9~M1>I(9xfFFfcQvUK|>lRyc?hPe{t)OW^ko_{Hf6S$gA}E>C~sbR`BzP zPDK2tHLyQe&TiYaFwu&*TBL_C{%tv+E0du9+*wxxbAo~#T#2u_ttr!;KMB)ss}*K3&4E2UBj?dfmH=LC-Q3#1QApO*em`m*#5>F+>q{yqHo zI%$(taxS@;+(OzN`7&9j_tE@+~# z${MSchDlcRKU&0lgt=Quyf_rKGaZrloDN0u6edk&6|SfO+rtzqZ17x(Aj(e1a2+fW zY1Qa|FySC{#26N|esGhpS{wYMsF6A8f=>v<7KX4A-tT7V=h;cqrQWCyOQRLID$KBg zPhrM`vlESre9{raDj{TAOw#_+V!VyO8ApiYBr+IaJ*sB-(Tw+pa#l*#=Ix9;e6fd# zdFY(L4pBvp#t;XK$x!5)iTq=@G`JODE{ySMS?tg(g}m8tXo<%20tJlY(4vZQxK#>1 zUlOx+nzb;I($=q#A&3h@V;e_7v~o;kk7Q%MNWrG&B=49SCJV{5CL57iQOt7d#){kI zAqBe~WGu0K8fY8eDJ|(r01^}Y4o#LFvZif1&l|(V1<0&)=k4Gvo5nTX1 z3fZn|a$r?ajp%V+M#qg4+jJN;CkR3$Fej*cjDAiA&kXbmm^@%h5V-}LrsFyxMv%Nd zU8bdlOTZy$xnP;VLxJOKt(4abF}hc3=rP^YDKCQOW?0m9mDrz42nQ#MuDNF5X&xNx zXsOw9ChzUWCUp#EIn*`D{aPa?g6z(8WyL_`FZr0aWJQU>RGqqeZTZ=We55#>70OXA zDsi`?6xNO{+Ky#dwn7~hOgjoMN|yl@I`F$d@DK?;Tr;a&}_CkNCd z$dy1wF$=etSTz;lh>`c*%1GP&ZO}_2!?lUG;1{r^#e&9MurKMBLNwcByn;5R5+*h@ zFnD?jh$_ZngVhpQ(F~#_OtoaQT?-W*hxo}+E5>nIZHg|yd5T7pf%&CZ9a2;UjF}4G z3@sH-zmuXbqS^$ZJ2c`sRYx%?$#t--O!cX5XjBcb_*+H1YN8f3%z{g_m`zL)S0Nm3 z3^;qlsp#-T8T@GYU54u*7ssjZo%$HG3>@QXfHuBDSOWs02t$71aD_`EdWE`e3yGR~BYmHEQpjo07p)4GrIAe*G^EgUo-ZQB@<;wGb2od~_3B z1kGO~ZyCaQZ~gqG%c~o|XgO%tDh8ObmSP5ZGJG9TR$tVZpW46Yf~dVv15-@r)ZozQ zqT)~nhajb9EZZEsP?<5f*9}Va`g1uNWUz6UGBz-*=ZJ(M0yiP6f?gn-0QO^`S0t0z zG*^)H>qpbzxio^ga^t#cr&u;z)6{*V9!`}Sg}h;znrG)dXzfpItY7!0Hx=TjQ>#@= z`4Y$n#QzK8My*>mR78u=twJ>OJiMTiwEi^g?{cPJy1gt+J0DwJM6&PG(&;MftyiB zkm@C^jNzywQnNdl%q@6uxM+-7a|SiX`vUb8blYfsrFUqdBm1PcZC9U6$h@43i&Ny~ z6-pLzuBioiaE<2Hz=zxT@1{KOY8^^qu-)i<0k#r9Pwom!rO;H@@H{b|Wr~(Q#r19H zI>%mgF6W08QZ@IPwMH)2*xK2)6MC#1)(D+x%4w$*6-qzqjdl<6)YS97Zo1vYebbY# zG~J$Q%x;@)C%xs}gCcCq1!SV!DbbZ3l%(+J=uC%eVfkjFUG<{#+^e1iX1xU#Wf`j2f;0O2g4|(@#_yv zPcO7cu{l4Dmxk?xi5mtH6R9FEsy+>n4A&8E$a58q!*PD*JaQh_VBezdSY`o?e>zWAN9H|WF>-Au90}q>9u=s z!Y+KS(B5Ey_-nM|5_JIEN3a-Yz{4Y0CO;<1RmwBt0n^{bqC0X8=$hj=a{*3{Qx(<_ zvhmWxhcD0?^Yw)*arHvu=@)Ig;L3}LHusvR<{JxBu=TRTjRkUmklPh`aI;EZNXQG} z;C4-9PL1mbqZG4*1}%iW z7x$oz*+y{_z1mKe5tCvLF+yOV=Y<0!XzatTeN2BoRJ3MudGc-7oLMOpI-Pu6S($Dv zYlYm#*AGrN+mpGtCR4rY%^l2<7yju?V8~w7X&COom6?N{<9O*o-iZR`qcbb>&4UNY zM<$1yTYC*ECsfs$we-N9rYU3xJ@py#Y3N5fj)@VU`yC#3{88v<90h(#KE3|V$I1Gi z*VeASdaXv*Pe~KfsVlEMCAElja^p(r)Wj)iG$Imw1moPJbvPeZ0`EX3KQs}*84w&_ zGC25E0zQz)+C%2l47ji}jUqd3HUyI`AAgPnUE2u9&qRC3Dp4}nB{BFjTpSP5aQ0%% zLr_GF9wzv76h+N}HNjm&$8vqJKEM(~svg3Pf}y_gLC<^8o4m~PF7pBsxRh&J#kKd} zwBL3s2oL23gsZyix$dzCj=8SqY3i4$sc8za0#%!@*-4mWi4+*a^BHlA+73NrjtKL8 zu~u8J)gCrg5buuHD^5oJbIK?N4`hO?MP*5R=Plv0(4PN53MsK z^+_@~A7n6+GvLY$zZtBK&8~d9L!TB5r}L)ny8XgZ<;F)Jy|Gfd@RM8HW#5@EzUG|^ zwl@FZlcQI+8_u+26>Hb_2)%hy)8N$=Q!zt|AA8W@Z@%%yH*;s>>8aOk^TM5vA1mz) z;{}~mrmWuFT{G>Ao6|L$zVPhhvYF@dy^i@QMWrvi`S|l|U;Eq57kT1qU;A1BZ=uJ2 z2*;h2if9u;N6k9&X~@NFwml%v%+5~F$d!r8)QmDaOHTe#F-=OjVj30hiPJRx^MyGrQMgjBgLlJ8@QADQo5aa5Tp8$6N1bvq^?|^ntXI*5414D2mA}?Qo7@ z?iBdYol(01Q|z#-bUR__Lx@3B92OOGF;aFqGKOB3MmQ>)^t+MBsO&8Ep>+uoVTSM| zU3l22y@*qd>UR+^z{AR z*=tz}ZfL$aVwLjLHM{1yuQp3{!Z?Ur|U!vz;$9 z_T^&LDw*k567v=lUddNd^Oi4G>bq4X^2-|HpybM>>bfi|p<6I?#&U=kB(2kRQo~9Y z!K~QIDk)lVT_o$K0@@_*kHqI_yo7I3tJO$tJaH1lEKJs)eFkP=c76SsTJ4$I z$>YaQK2bXfAIFbpoG;RLdXnsd?+W32c7rGK66wv-+XOnEG=h_kGP+sl3D_mlI2F2C z)B}TsD9Vio*|v{~{*PV-34_G-i=!JWHz4@lW^9E+3<&UBKuT-PFb zooUmKVK~MS&oDeQcl19Uo03q5gG_sbd_o!sBud0Oyk5E$yvuu~2c$Phk4f*4-Xr}Q zVv!6h3SU}Pk`x3Ca*{XNI9{c6DoPs7%Oo3S7>vQsluc0S=Y1ysKds+POkE<%xLUxG08#Y-mh zgc|AM#&^yibi0G|ckb-mn@ODRgmh^ zoIoQyB1!2ufs^zH%~9HCd=z)mj5&G)I?c}*QsZX!PBGWGfh^ESyfx?yMx6{iNmM@J zC>VIiKSm{2t!(_mAHVgj$6q+LcS}q(?-l8*cVG129tYDbb{FD!581ujag+(iKG7ks9+n+S=QK;<_r6 z=Z~GgOha8Kvs+>>$CwZHH76g_Oz^2Mp(&I)k`#!kkYkr)kh{b_%&%_S{u*W zfv%<%ShvyM$`;Pkk_Fr)O!rJ5xw~*@Wj)}pJVo)pwApXw57%lvPZS`tVpS6m3 zw5^mkIVg=LU!ch5Jit1M0krs0l`h69W@sW@_HdL$Sq4|!M#~D?%3?ZDU;~ODvMbgq zdX(5vgrHY?EEZ!&EE@4Fi3&am3t#vBV`TZA%Tvp~zgB~pjlaD7RY#A$>he#-kzUhs zIjyb}8pSp&mbEz6%Q}f-KOexcKH1}Sy{pOch)gfT+*%#xzHm8#qc0b{!ha1q_?JOq z@0V_t?w8&!{g(7c(*G^}7&I`;V3hgjF0{9Vhafz)+qQ%_W@n8`~1uIIA zVSWrK%~1G;12J4C#<5tIeMT_hn^KzB%c)${twb*6%SUpA1Z%})vh6q%;HvV_2b1-kMxKBc`2Fhc zoTE14R?`eE5-EnS?G3qHw4MIDrt^uR5*1q9_Fc$uTWEA1F+;DBD};v4xo7+JF!c38 z)NqKS*t)|f3&hXcN}z*lJM}%e9_wDn+!*sr2(_GvR*77xB)*D>U;#;UXh%~;SK0qX z%0XBrIak%eRFGBmCNf%zmYm(9<|_(hxv{X&QgN7wFps8iPkS7bC$2z!WXis1?lA4b3i{hF-8MoZ?DI2vJ!Api_s z4=0Nfj>GYas%o-^jFj@JpPc$Duv4D^Pp6IeW-W)fbRf_)Tm;9$G$Bib^Ht_ zjHx0By+#pV2Jx_BMz^VO)nEJtTQ>656Rpmk?Q!z$Kr@dG4{Tf5zVY6g?`s8Jf88~( zL!mloctDfx8zGHc=i*5O|t^m4&6j<4@a$G;)+P+u&t&excYE7>Vy?Sh6{v~ zU+^vK@0wauy;-(R;yH=t7{BuyK2bW#7AJjckjvV9d<;WXDJmATz}j$gk!4xP z!}jSFW66m}VU9%hIR;*Sx1!$*Ua#yZ`iiH^RI>-RCRebCh>{%iq>il8GLcPL(@Qqu zh%^;OwQ7jin0YR%^H(rhl{ICDuK42!h684P6{HE@DK@9q_E~uJE@;X1O@svX`CwXE;$u_TdFXjnvkb*Hl2Z@I5PoM!@xTd75T&+{ z&h>Dqy$L-e=E(XpgZ=vlIQdPTo9F;3r3q>qTz{#m>sH{A3lUh0YldOen$?EqxZ0#i z`owXrv<$=)#$)oxWRKBU$v~r zG+%02p=pY?_j#}p&@P@pREiewjD}#~B}_&vMG-tuN5Zj0lzo&`t3+-ONd6B>pPDSF zrt0my@lo>V4STkxq}nyUan(e=X&FwpG56=u>t50R(3We5CigpDK-5QXxZ%;}1ueT# zEE;Kjs@2sLd;P*&HeR#3H^T`hr+%HTkw1mD=YphJlP-~@iRYD#567RQF{!hF|7Dru zb@p_ORFTN1p&k&|3-jUE5L1*mr6?*zN5%99Wt@JpP)Mha+%++gln$J)E6aMnuP-b5 zp@XHQ*}Ch<)DMn7^w2X84V((0#eD3=GjrdH#NlVX4ouC@PfZkuEOt93#bC;|*};w- zgV}8gGn7)tjoGlc@pqTK^0LcThtspm3Zb^?A-KfZ{jPYX-x1XOg5AI+eT-|fDjD~l zx1QLSM9w>nhVj_Zi!a)r1oj={KCtmWUqZ;F|FmuY>UGymxd?9u9*=bDyU@;G`tRZw zN(kw%js*FUfcM}0ao|!4Z(Ko+4fh>7CCxWRM{9nosh6GQ zoL(#|DO--^q#VU7C6b@1s>S-0)|`pwoqNy8jpk-b{bc*#0)N-e%SmItGpm*wm2y%_ zw>5+2&L9``N^YrUH=yCS^+aDVU3Q&FcCU*gjR9$K=8~{k5ajEY_-=`Ud$Ouq!;oO&_%pQzEOC!Hl(bI2tFjR47NRMa-K>R%K+P$4V^_ z`|B&lYd`W5eV0*Bh%)=~hd$IPFHDUHsn`oelDDh=WMh$ewOmNb_Ch{zSYdhRM_-~p z@R1cgLe$YvogvCpfsmQ@M95ta16sN1Iw88-NZWztIF4awap6<{eCpHWQ{;2dn-N0> zscIdvK-*cscMZ+305?F$zxJ406K?yMXcMLwCPofia0SKADYRBx<}~(; zoL{B!i`GWYVvKbddoLXJF0P6`drFLxaH%BCBkQ+xM&>v9 zI1)NYhp9+1q5{=_lP#~4oULlf8z%bAX8*EX3%AL=dS#w%y(TT6Lv}7Kd}N}?cp>!M zcBsTGXchl{wpN{+tJcUQ9x>q_+jV|@wp~td={WSn!p@CPF14tkwcDOXblUiDYqPU! zb8yd3o%#&jA$Z;Y1apxqN+c8#@~Ev4fvPv35zmUm&sY#R#TlnV7v+mXghPtyDJx{N zC~YuAn{;WoG=K)zX`J>>(XYhAGB-3qwI>oCkpNfhB5HP_{k0ndBu|>pvTqX+=UF9L zuIXYTcoz~Z*61Q!N`e>xDCT6k$P~n;Mm%cUR8+7eY+1&lD55v?ypFki8Y5iB73k71 zq?;*&?}NNE1aaHwW_7nR(<(=0muQ|${D^X_TOgQ~rV6e%^thNP6=#^1o)v&AssNpY z{lYN6I$)(Ok)ETpGHZEd&ju}-RfMZx3UZ{cQe`aTmVbQ63UtIiR%}ivc!?SFsi@3Q zskva*JQ=!;-icU_YSZ9#Qr!@h?1DSgLIhxqQZ_-Hii{N+Tr(q8M#@Au46AG=X0&9E z8>0Nn)({qg;ujM`&$?|^CsL?mFi6P z^DuQxooX2t55|wa2kmGP7Sh;dVD67x87Xim$i-17&Hfh%Ohz3f3FDxL$+JqnvwQ*g z!U2tTUA3I>m`9gyabD^E>XG-|eZxVOTR9CYBRdHD8Pd{71D~*&5@*b6WTlSmg4wQmaLHBG&C?2Hl~t7fwm zag&~W(~0JUWu17qKR@{CQ!sh>M5|#NCmvqWb<>U_&qB~2+jecw)?E3;ZcqumiH6p( zJkN5mTy6`@yRPNph{G0Hb#)q2S8r)++LGqTVW}jCvZHmhZCFIaM1c|3-F%|ATirL9 zX?i_2+wgj&?jDtpeOH-=!F0zo91R(zb(J&AFdZ%*963SYj7Gj+J=gJaUS-C0&OJT1 zww*fKDxBWd^)*McRhrnbSiG2k)&gGwyG2&Naq85+fbM<(tpFkm5{D*;ezfa5nJZ5| zvs)|QxM-N#XTXEt-@U=75qYBOP81S?p=y- zE031PzUnie_nyhd;)%~YgEUStsEH6Trkk8R`PI1>?ce_k zTLe&MZs`9jdw7em8}~@(N#{!!N|#DkN!LlYNH4^Z*G|8KyfVYX8_@7_g{p#m#yfJdZ z*E8i>ncYt54L6?sM{FkFOyp}fm;Xy-la>?YVRqyFibXEejC(@+9!@WTHMQE43i!tM zUQf5|re&Je9^d~}Rc%tbN7HU{-93iUw5%twp5+sU(Xy<4ns$(G+~|T4Lhe@8$6WUl zYU2Vq7*vwVarjwpwr#PBGMVoEFnQ=Q3cEZttJ#BeC;6Uz0#1Oaa-Oi`48 zS*j+Jhi7(|B8}Ap_e(xi+QrcSlB>Hi^K+wyhO`ZaVTuYmYRDA2uZGxP9+jhn(p-d* z!;Odiu+&m092j_x43gDrMDTr<%-}n9S%dC5=2sfmuIbV%rPoStl-?%2SNaXmHQ>>V zuz?{8W27>kcK+47$>Emq(a(GjmY_?(O;3O38&u6=I^xOCdV{h-vnFMm!;fQo4C^&h z5%(Cd&RO$kuTMdyvt}atdpj!=$KN2T_F- zLA#qH_!Dx1A!TWjrJ;`INwfXSs)iC(A+s`{nbWLB%kuJNFY>}D;bsDlAxTW0M4=n{ zrMzcPG%RgyMw9z}I$EY|>o%s3w&_-HXgQPfj#q4eWDk8`QGD>seYa6`-T6t!8ul!G z+p^A9wlccBO#1t7eDtQ99zFl^`!2uyzIzoc5|xdq@n<;x{33{AT@An*&=tyv&Q&y& zWTIoCg02t}P$q+KLOj_r8O2f!6YC%higlQ)ifO60u$;{e%d#-7KX|vxMa5}8iqbIh ztHr==)LgSQVX9yzw%Y_mh-x%jrc-UWexVXXVH!ocV}mUs%VFplzF*0E&TPjvmX{1| z#~N48*(;NC&sFxGqwuvInz6KOTAf+P%UArs^ukbjt zO>Eh)IQN*X)lUQo-)X^mnG2c!W1(vX$RZc6%2{^U@N{_`w4)evA+Yjno^003iOm8O zHfRX+Y=jX+9~AlLzzlwt$a>;lSF=K^-nbsbrxQ3t_UsdlI;^T)=f%1lPH4TJ9t5$z zYnL7efljU=eZ!871f+9D%5-Tq^-+9wE7wmnmoBd5jI_in=glhq+*H?{ z0(0B&C(A@rc30OcjQDoq2iAi&tt#;RXA&|A!ckVHF$A-~_?{)kwjMBzlH;!>xzIy5 zm#DGt7#8D*q=5A^wqFf()iAsgNbHBpue>sC7ZcmSgiWrbhHS#sK{I3Ol2&cc%}#r_ zb&CZOhyQr!wj2_`%RF=^uL!0VrkdLMoXhI@RK+4~!%v$xa8`pKMwja#Q84Wtx-y(i zR<`Z3_rJ@JRkh)Vxj65anCg@aZo1rXd_$+0GOFisEXlHU&o`czQ@{AHZ6g({@rI{0 z9@fO9o7}$`M<#Cx3x#mw?KmX|YXj#7A;$G}lV4_%o&*i8zzjK{DaeD6? z!opkE2|c9iiC>#`+?$paqWN}#Mef3C-dVcj$e5d0I!^D0<~S`~ES&^B11>h5r-GuO zA`95T;Tm041Y-epC|>%%y+da)snQ6&a;&A61&kqcY2?U){eXhwd9#};s>mp3td4Fb zI9kl*CZ!VufjHzk4(TlumUGEXu&=WGGxjAdf;IKoz9S0z{(iPAC0wlw=m*kLJq{YZ zr6RgV-PkaPFWlW|f+Pb1$=!1k8dM1Qr7>aRhEZ7j>86^DB0Q!*v?Q_=C4~CJ&7!s_LRG zM_*RpCM4*k>oSW{Q#k10W+M!M41Tx+{grXuFl<%{Sa9M@EzeUsk9FrCxGGLAYLY#j z$-(eoAlqHF)*ijkLH059iVX!+T%ZyIqW~t@LPT|gC`L32a9g-+!&*{#PO;BxuG+EQ zo4;zuOzTJzj_Z-WCGO3FG?MOvp5bCFIiq)NE?|5aZTJN;SQ8Ypf&sP<0y~Q{SIK6~ zvP$Cr$dM^h%Zh#D%|sW>X%XjKqaE^0nZ{v0UG0=L3Q;}s*4!F!9$lBPwW1*^?C zHR9iB zmhfBc2|{-7s;*s-`eBlIgsxw@<+_T-R&=6U&XH~-rz$FI#bSAPf2Bw^2lH;+achZ8 z%xwJ7CiIiPr;~HOzi${+YE9qEk!`~TnFP?+fB5LtvaZWwVdJZn8@6v*G_BT*=zr|g zXUR{Q z3r>xBO71$xXWNRsrEUG#zC1Tn_(e|~t$G{@BkCH+heo1nrrq$#A<#^=WrKz=i^pe@ z#8FDkjVJVQ?PZHb)GJ4XRMt;IH!tZ`YJ){-*`_qE%XmiG0H4A_%<>U(ehR<} zzd7*5c;zT>h9WTr zF2M*hqI~xUFrU%oB2{iVyPvQQGo^8?JH?08jKz~7^W|LbV3ay$@RuOjYeKR=3zB`^ zGIX%mjn$C&hR-9g@737QxM>@TV>v2VZEwg&{kR<6lLUT3LfMZB?a)c}=!PUm^2r2M z98lHV^TC!JjgCTR2P^Nto>fiwdSaS3+`~)t*f$*{4j@4|>4uC2`ut?>!G+lMecSOh zA{TAX_pUp9_&VlzV8aW%zR!?b!N&Ha3BjKsK|3sA?0plZelg610&kAFYQ(Uy?+D}t zEK|WW+dpU!l6XNvNDX}chpL8gb)OCdE}fL5)NI++0RJj2@2Y?F9N-> zDkh@#z{25J>(ft@!KgbJan^p`4|)Evu$GS&dzhKqbxF609A{nRvUuKpG01{WOjf|< zl_>&=K%?0zURwx3FfUGh2F@khn!V^uxk@!c!MDp7iU`L;spoRru|{0yTvtO!l=Pzn z5dgIte-E~$taxCpda?p7SlKQHKUQUOzPufl#LER2q$SVCgos!2W!RB|T!Pmsr7b1f z6Q+kPf+Vo;|5E~s^6K~u`^f=>e;25@8i7Yoc8X~>gYahXu`>Yggzw}^U4a+hiNH=f zMT`qEq)lr)PuNTO8OM_L^$rVm)wu(*t+jSC_9XQ+4Y$MXoH@x`uB}=T$bBr^>diDlN>Pci9y$wT)Mb zx`eM&iM$WwzQB3d9SKlzwGSzu*mAUjwJ zGKK>l2~?(G1{JwU?wxc_V^NCRnZ@Q1P*UJarl-Bo?D(wUfTc`Oaxt+IcIY7Im|&|P z!&dg@i7fU&Lp!Tt$&Nn*a5TaZqYR}cyL5qdJ4PcGSFzY0kNc8$O)rh+n2ZVAQM9eD zsbC$gMPMUSs_HX?#c521)vYL6Zzu|9^=(m%!Cmb>#?q+XKy6{$-8V7KU`Y=A1f6MI z%P|vFN}A>p$3XgDn4eE!_QN{Qr5t=1gT*!X(X$=Tb=|ipoX~nROf|K*V7XCiVYUe;bzDP!WxH5x7m4dm zrRl7LnYlANO*1OO4^zvrX|BQ#BCWhPblX=*t#&0TX`p+{7fFUgw1 zIzx>|-?!XctWx8_B(Xr@%KMTq0(&G5qbLbOkTp>l@AV8>O=EXQlBlw68`p>w`$N1x zVW?a#<#{3A_(7vut;@1ib3!w)>Gg_QC{b|eCVHG}RyPO;iRK$MuyN#4L6g&vTld)w z*RETX)+@EDU&LRwnR^2|>?w(fjQRjtX$a;7*q;4PQ{|l$lg!BKdk*Gbnm_cii>4^q z@y_T28!OQ}4f51R?eO8(-~Su>vga-9A{PVwl@XlpJZTbapjS!nl70)?ztoD-I13sx zK@T7>E5KYAC}4ueWFTmQ8P8nq&SEW2 z$#lI~Dk3@@ViGF)#xH@RZa6beb_BFbJVKmh{|{yw)p~LBxJIE|hyynu3gX?ot=6Ph zv!?HRg-Ual%xx*=|2iQhk{ER>ALXGl>2;%~Qnh9@xTEKyyj7oe8c8{+`i>4V+^ZzT zsAOAU)*_P5N<$g`yl!g^9S&kuqoLVGE-ILHJ%y9%4LIpX>}~(X33kuaxz#j^!EC!V zzcYKTr+#|s59$5nX_)`-(%(t{LKwmNi_zlPOG5_@V9Pj?HZ(4Uo?;@27UbQkjN>Lf z=F&vC+X#a@|C1>&8Ihh?b=69%SyV~Ru%OYHS^;wz+(@weMTV(6rV28$#1yJnXq0#^ z$Uw_9HAHt-prIM6ZV}2ILsN9w^H(aiV|oX{uHbuSRXMG1Ikxj9rm=0Ib*|xtm|dYp zbLU>_nK$i!g+i)Z;ekdzJZ|`2VF{@i^wC7}2HHa)im+HcwTZyQ(k77dAaw|W{SvFV zaaUDfB5AZ((+#chmRJQd%t6)}MDbPm9iRk3B3)6SHq-Z9I$QK3fs|`hClm!di6r1i zB3MqL-`RGpOcc+`y9{Z?6jhhOgOSxD2)4;u#X~zREZBDF={i@nmRImWw#vj_%@y;S z(yC0f(GKs<7yMCW(8v|0r!GMb1=UBQL>4Rh(MCaTtnN%YGl&`tHx~q~Y2v88ti?4$ zHQ-(&1czo{VkkC6DtA@)xVzOi^zj1A*>7$j-JwdnYFkyU7xdvVXkOka2k`i0?BsqEc z<+Y7xN$n5*K*ZC$Q_qs8$P;Kiq4lta`64p!U?>i$P(IOax4%?->Zuwza{v9^;_Zds zyz za%QPBKB5z!IZFrKT$nEPu|z^#9y+dAcr)S={Ev92KT7+ZTf(K#UtL`dJ6Emxb8~*^ z{$4P3{p*Y%SIPy3p{t%%vOHB+O%R7htm}@ZIl3Mjx^t<);r{|7Zi>&UJ!2ax?TUnJA85!~7#f$ge?|9$e z_ecKb*54|Ei4pzu;^LD}zG!Ll$-D1<{PD*hee_Y=?x{M2X{}^v3aw$-y(Dxw{J3FK z%r~wi7Undzh%hu7MO0%YS4ubK8s=lSp^r&w^Az`$Sbl^d_Ray_gEP*cgXrTeYk;QTtL<+=mJF zgA~5P5GuJAW0=&ALbBmIvI|ZW6%c-2ALmFnJi9?J7T6>swN3Fjn0@hlEzex*lAe&r zEswN|$Z|#1T%N{w`ZQ6jMt$5tAIBkYmN7E{jR?0aL5-U4Mx4bPb|SQ5VkjyeM}iqe zJp_kQYP$|h5X2_}!S+56EU+56z$GLq+-hv;Wj`={P%nuE(=Wp~DRj<*wc$y0QQ<^W zBe=kZ%yOCuHX0~xLX$xvtu@yva(JX*yvhcTkwwsP33ZAI^f-95J|;^sa3$81VB~RE zc$}Nx`@C?WQrxjg;KOymfGLtxql!gXH<1ty>_O8$>zl}eY#{sQuX8XtsnE5=FgF;^ zYH$O%?&1jH+E8(Bnx1SqcBN!m#MY~}Q|fqVmcsaea=BeZHayN1ATM-U#EUy{)1Vq- zQjiHQ9vHl28gz~ci&(y5OJBiUk<1z=l0aK*>`OA!Fs0sH*>ZJz-gDs;E=VtBnjM9p zyWwh)1$}=Y;gUVw=XhlbHhz;JPb2rKDML6ad1&hEm31>&W^eZ+^a;RK3@_no%PfIB zau~BB!)JRqDO{iUDTRm++>6y7=zLon%)D=F19VR+FXd>st&wN+mYJBo zqc_hs<3=Nff8|D_4F4KeTbP3o|E#MUutRrzIJ8CHz55AfBg-6V=?C?e@!LkLN_W40 zDcPz6A{4rd&TP=L{b!#cFDK7j;v<=MgHam58~(whd3ZRyZ}frD`-g8Jhp*kemV65T z{{HVV59PCkYhj*WfOCymECBNC?RGIGvv!>r)n1tkKNfV<|_mEQw z<3`o8csnk}(5XvJ1IC&hE+hy0Fr&MU#aZjS_0=My#pI8YY%oMoP(mAS;4vpFU7<<^$&OrQuZHKXVb&7R3o_Yl zk3hhsAA0|-+uOI2)gv!^-TpI6RdPmir%@H7z@(B|Hlu^nR73Y zhD?iT*_!t(Y3gy1R`o?|$&eMJZO>=j)z#4TywOr`e%>CMU$@-Xg4|B^!$$kZe;n67 ztY<$@9)mV-BL@V`xRB3L}%G5O_{ zt17napRUf`dccV8+wZ>i?X{kh4jM&rzVn6$=;rlr_{e(drgPFNu4iZWAHTDcxQav3 zEf9L1{7_-JaJKMp;gf~07QRQOFw$qB4;7=All14=JJhSQL^|Dmcg8uAg~O4`!nS95 zWpOmXC4&BVZ5AyDW=}L)!*W1uuSEllyBg&djaucN4smi+*NfL+RAcvn_FT_8v4XSr z^F;UQP^E-Z{vfpqx0e=f1iIxWQ3>8=E96o{dk#J=Ub|TF{!|^G<=2I&3JxB0#+YjE zQck%fTT9n=ki#854-kPkyEA0+*d*x&Sbuq10+Q!Z8TT=Q5$c|}OxD=e;Z|#YUg)Lz zjo0Jy63DuxD|-EE1*JI%x=MK=yWtT3kf3JMLD#BR?^KZw-!e6(sZ0?X4dNvdhHZmu zdbyO4uj?F>l!F|?BxyP(X%Clun39Bn38{Tz+Cij+Qn(DHA??D~aA8At(C{*)5fw2h z8j(eTOtVmk4I)nJ60{a95>*Yq} zaN^K~#YMOyhc@gmbW0SJvw5h28H4un5~K~G2EpV4N#GQbuo+4YHPb;wMT2HhnG|5> zk6eo`3gY@WlFDI~y*)A653;PS_3%^|WuTbgzFmkZZ&$s}hyw9Y{vj9_G zg3D<&hU;#<=W`O~xqR{^Cr}<>?uhnANuGKL6Eb#WBw{!KDF*V;Qk4q`od$y>R`IlI z%;n_Wn3QLrdTxx#!e(PAiEA`oi!p(BjxRx}tz*Vq%Z}^dgEy<`=iY?x&C@mjA99Lx#s!g zC!w=}7G`r79+cN`AjQ*|+JGkYl5G&(DnmcgS>R!BQ`Qyv zVWu5Ap2YL>)yfq^TEd(QlPg=@I2NE9&?Id&_s!vTSHXri13yC66X>Z$3n7f47^_So zI`sr0e>Vq1)CT*(3OutyE$T2|2b~@|BBtI&63QRGY^`&dQRCK9I#;AiFa}i$ z6dYcj?WH*Ch|aKhsY!vN#OD94GrfpniVs&O3>=`@|jJ{`N(F;?I*0 zffkz->OUX`UM)(G!;uQXCT}cvyQSS|<;bZ;T~zF2 zjr#doMR5L{4}UkA{K;iK=ZgpGvqPL^-`mWOmt}j|e+~{2V;vMTH5s=9OyrA<)8yRl z-`;u~5=s;2R@~h4yX*8xaskem6kZ4#;~NTZL9Ou;VXI6ova_zOuYxRhS-u1ASFJ#U zT-8ur=&E!+s3hb|N;#k(r7$j2tED9jI?rZo%zKwWXTXf|YA*o%f3gJzzjxPYO_-Q6 zRo5NeiFD}H_ytX?bL8)J&6p!@QcgWa%k3su>f@FsYH`0-rM$faQdMk}|6z6K;7Yp; zr={JNTbY|%pPPFNm=>jgbbgDm-(pVGeH%06Ybp9)(0UgZrEi0w3tjyN>+vw^T3pxs z4s`p_b@Y;3Z>F9}SHN1A%w%Qjft}lT!dn^pOnoUaKHeMk;P)Q_yu^&xx{!iy{s3$t zLTRILPvNn`+hFgErebc`AfWnnFw0Cj*(9rck#yw--PYt3wGik*TBi%!ayS_(z8IIw zZk||?E%L7H_9pAA+Uz8L@g&|6a2omF{^ut@`9D9o`P-_19_58s{yi&@uf;W3wD zY6A2aTxcI_xvn085Y|IcVaAX43|=%`0|OnD^Mf;u^_V7AE;p`02Q+!uD?E$e?3EU2 z9_SMyFU$zJDIHN1DKp5~+h240?XP+1%E>!UuHf54njsah;xVMqm)3nh@B5RYCUMvD z|Gt7!8h$v;dq(J0EidhOn&Lm+xp*Qj#@Y9(^DqNCCCfO!*w222{2lpBp`2&3))X=6 zr5NabHkrkaPUAg}0kUDTPM47T-u14pzAK`J{(@tN@4WLc>7Cx#IK5FdsR{evb#AoE z?4vKe^G$aW^3e_aYQ|aq-$8G9FKG44pf_9zcGn$+7pwK!J)k-Ue_leUBYqYcEcfyo z=do))cw77ba18ZvX1MLdHnQT_+b3?rkMEe$FqO^?B~c+iW{q6*eqOE zxF6<_CkpRDc2A}0qt9Rs7wC6Lo-~GKjoca6pi`IT!LFDHlBwAuL|sL(-sL%ORi2SO zP{2^-QH=7$qWkh}|v0Ql;Fs zxU?EnTlo5_tFu4SXuLjgy{nGSQS#MhA7+yeoLP_NpK=WQ)@$ZqmICo>n1|mQ8=4IY zgkd{StR>3XwB*m%hIOjf>MLp3X{3#xE!LydaTtwTrb)PM2l}z0z55T{ZiMQtM(t8s zOB}Lk`!&II85A)J$zar{Fs9jxfEzP)tr6&saT~e^>#1O-PTp_uiYFaIM_{u?WMRg^ z{(r(8_49>eDxOQlF;h?!`>v2vWJ5$n5<|TDIT160iBxnp%tx4xbh=IH<7p?dbmmH} zJm=J-ek}`^?Xg&LH_D@Ivr8o(YsSJ058qU44}97`G(IE3ro#&PIj>$0 z0-_yu%qTLZFtd1~BwIYtN(Lrm0oCwsof_Oi|0bp0xwAxCm0-~BtS60mw{RI>u3L!I zm@M>E*7|Oqz6(sjb+EHL)0y)ET^i|(&ISD!I0;H=McfinBL8Hq&qN>sy5k z^Dl&#U1)~8+h)ieh~Ae@Hf@k8hIDr%rp>|il~kg?8ib^>rN29?!Ohl%Gu`c8+hBK! ztW;m%?l`~Gcn?#!!_O3x7s zD4slhpY=n z$aQZMkKOd(_6<_@H##+|l(a+6Yez5L((YDV?s_`eKeSf;@mj+$NTc5Dp1JV(aI;*y z>9ITKEMw7LGZW2l8qd9-cA?d1O~3oRj%j@AQ=iH&`kW5=?xm$ui;IQl z^h*itFusHV4~Mv-9_>9(6{433n3mT@7aJgdj6&v&G~eBSSB3mutrB~4?bWECmH72x zI6C`3h)|j>OTz zOjo|Fo{NP6wB;55{PSue6-3TkpN*&M<7pa8=xf<5<-=IU&wr99HSGXn8&?_!cMcr+ zKujKaM4#_vx32tvr~4n+Q4lAk8n4z4mdMfi_V&4Dy1KG&!}o*nQn*xj-h7lkzeO*v zsoZ8Tm*;L^sO~7=y~<$lj%4=yTK)#F&eh!RxN0BSPL?ulo2@5v7HH?%P!tD)A5ht! zuP+v5b!jPCKjp2?t;YhR!O(v_Vm=#acPQB196rMkCyx?DIIm{g~Yo?BR z8)Fk)`uLp2!AvI5%4#c4)f(&UqZ}e111{5aI}rk;Kofm_%OwX!MFyIPz~rVHNi;1X z8Yk*4CKi*UC6@)1=X<7`eNRR4z?8o@_1DfvZ>9WrB^o(U4+_0?UH(1t* zqn35ul4YI{DYTPe-)5RuoU*JVH(J&UR!m}DHL}b*4PE3#;+ygjTxz#9kO`arLd#vA zk1=P9o2s=fq&b+5kR&)BJ--cnq3hzUnm!S_{gkG^b-|uK_=yJ}`~f%gq%J;<2TyeE zS1jl{lULkl)A=L+sxb3qT&icLYWmOCQ^4SzK{K{V+EGI!>!)CDqaX136+FMOm>5Q~ znEyMrOeeaf^|lP(S^fc)+^j6^o9tVv&=DNU^lgrX7A{&9{2f-A}L7 zYAe-$D*5}0LAknnFIiX;ZN5}pTv%L~Tk4RN6;9|ECXbr#Vb3zGN+r~IsZln}#QNx| z()^sEw<&KYOO@y$ud&u>tQ8p{CCjVC)sh{g=|X#MvESF@N)lJ|=jT5AE%Hk$C+Kz1 zDsL~m_Y%)t&TFS2YGdT79H7|(s&)=|!x;bZy2_rZU>7;t_)rD$i*%Aho^f3RzVCY& zBcJBc|ERU7NK9=&~A( zM>!WEa)r+}$S2{B4lSHJNvK~>qhjB*NNqHjM9suA1$Dwu$ki|HV|o&}*HBG};+4n} zdSy|7T-jGD*?hH{i_SguvA_D* z?#fmBf0NrSxcPF$Fp4JQM<2cW_BZ|)z3haB$*HDUhG)k*siltf?bBDBzsBI(+xCOU zZa9DbuI!Ni+r$jIgC4?iK*+N0@xw>1yOV%uv2>xUgq4pcEPPJBmQ59lru?2gmC;mz z&2hClhWf%3sb=%Y<=uEVf{CZIs;zGkT&ZkeNIlym1DMYvLGMlmTmQkayQ%(sHT zzt=Q{-%|UT1FwXtr{@hO<;&jLa}fd^*H#FaDN=)~N0#l2`oNT=(Bw#P|I9bL(Wd zd{r+1`yIv*YZVQ4@BAIt9ncl0n3dKEOEh9MV9t=s8}EJyDVAB&AX??a>(UWC1y$h& zO4|1Y5=M2>nJ+rJ+iv&tQab0_O^z!;tg+m1`Kwn&F=5O$I(7RM)1-H7)oCte7OAFj zrDQT~u%vOR4~okV5ZfYrah+4=r*x{@ZlKfFVx{*SxtsHoHVZGhWH0UIV}AbUo{(93 z@ed4pOlx!LZ+)7=Tz%Q!5M#M7|BGan+gCsO<)@$i^3y;1=_j7}^b=2h;&re4#OugA zg?sguA5Z7fa!qq84zE_yxk=)0U+s#Mrzt&i-K7Jn91hTG<#cWu`&(B#^13sGoZd6W zKKeA4PIjKa;&=b!>#+Q6?^st{cV<@DcJR+%?es}Jz)vRKq>^8)pyKbIA(>hOvQ(=T zLgWpNWg71zJ2dskxJlq`diPwnd*i}FG#cI8otwKc9*yD!Qd?eLdiC$Dto+WGH#Ry; zOUn#5zWh5YxQ21rvxvbuKB9PrUhd3+N#;(?mfwCgnYX_6nYVs* z|4Om9x_`A-T)DF4ItP*i20MM48L;8F&)Z>ppXumjS=;?Wu_ntpZWcS_O=Z1PtnGdW zN?G~K;>!Nj{p$IU>)whNIFDb-vO08WuiU1$S1#9|bD{MWtO-`=v;Yg}eRkWC~4- z!O@_H;XmZ+NoN0s@DI~S;Bwb6r|W^@p#5Rm%!v=doa|uK(HK*@f$Ep+WjNWR`kj52 z2-O%tfur1W1m|E0F%*HTFZ9-c9~(t(z2XDFWN*p z*L#j6c2N%;%aXBWFxRd*dY>%VTkZM&j^~Ev#(aBV*d^N-wC6X>(Diou^X;t^Q*xH3 zwJ@xO%ZcwN{yQ-l7twv4RMMmxnN)L~=A!UhzF2HJu0~Dr;i`AxR3!;?ouk6Tbv;Nb zr!IJ}ZfD^F7&;LMVp()-OD48pOxTIEYzKB>aERawVZR&&&4a{sDU?zoNVO8Aqy(i< z*Chv=!L=oL982zuZQ)rp;wD;Y?^^(#MRUQvDPW=>_kmn zZ$>*ir3Ru;mdmunovImBncXNQ(oeWmc5P+^EWMf<^DyP~yGxCPkhq;?ZPTn~LUd0| zS8b;>Ug#P|_saEs?Xu9K$=Z@*w>MX^O2~O|<=mQKOjpS&f?%20{higRVXU?IP0U&x zoLbTR!OAqEdbK$iUD;(0^w+ZHudiPX@}TA%89uzANahU~c=2gKOY+qCzrg_kSy1}>! z?UKg%X&yyHOEk#W$RjLOs?w382yZABX@u)((xuaW9?G1ve^|=9A|Q0aYbudqC!Am-PP(7gQ962*wlG5kx5_Twq)Xn`L7qD#BRT z5x7l||7FI6nq!VEei4I2F@}T?rMgMewW&G;R39ix6QCy|+ML5NfzYWUEuad+K}}r{ zg;++GRVj7kBUR8bO@jJMED0Jo)j53TT!mW+6`x>Q8rnMQSi&R`2Fo2e5c3YJSb=B2?W)1N z$mdA$W?Ph5cBUI#=n>3L1ZiIpgikdcAG%DjDTOUSnHF%TKG>7$*&E2G!#%=n2eaIB zMPDm1T_3Jg=lr$|xhJ^A2#atTT60CtVoP#BntH;9P{f1y=qPb`-Un2>A%!7T$pBTT z33Bohux}LcEthrzc7TpJVNK`~lbj$n--L(k>OxSCC?v!n1)3dW>hnMtE;7gYabGa0 zhaqzPt`HY2NhgKfs^>`Gw)LoG@ScWUM=)Q5qe@UoAq-tQO;pOcMC--6!mTdD)X&QC z#h*-nvY#nC9DZ)7ic{JX|DQ1Ys(LugOy!V=c zJujabXRi%1!jO~c4|u_)2A8h82ori=PNwcf$ZVQVR{8B98V`Pxy^SbFu3BYz^k}31VRL-}TFG>|*kXYz-^&eRq^!Ho_E0YO1|h$|T=pvmG^f z7%KQRi7vInaF9&b_bxP=T*3$|V6t4>tKzH%d*WqQ?QX98GJSc&h-}xrFc*7{}F4hh+Z%6SOG-?L2x1$n}z-xW5O$PWRN9$vi z)Dn#T^#MHJNitmKCh{U*i>S#H|zG#dCs6w!$a(@Kexlp8hkx}(tlyvASxV` zAI0K)vsq*qC(QOH6=9c_Dosl_sAUM<<~j^$n70J6Kt5Pr;AqtM$En81Y$l?bNtj)1 zEIB49vL^CU>eRJ8(~iTy)wwR^lFMdso2 zoWLx@i7MGX%RvERy)rBlNkQFKXdDYBe~Y3U5GvvOrZnIMlO%Fz6PVAZ7Li0ZB-KH&=aVMe# z9z}U=qtKC-6B>r8n{a3Na8Lq(1!I|>%`vl;%&SN~O>(c3QEWCd=vqb?LW>H+M+~|k zHm{Y3-NaE`DRfUqLii{~<(T&JA;%Rqg3NVd6`^-Z6qt4;0S%}U_sf!-N?j-^#Hlnv z0>YiIY&wqZ*@o`ewA&1W)W;ZHl+=08x5~woV^0!A=(xp{8jVUC)#Ijxbr?E2v^eNo zX!6M@^c0tpv&s3w2$%hEl+&=`aO#$KX92H;DZex^H8DRoWeB3ef5* z#ElvfIc&IQV=`lnOQcd%!vscx6AK45TtWOiu^iL~1Fkitq#z?QbgCHct&ANP`Cl0H zO&x4j_yW~pDsV8E0&|CRe6YEqyf?}008~J$zx6ikIOsONCuFwrzAjVtzC9rLIy`kCA%}d3|5;(iyInX6^ zp7Vc68^)v?TJX#*=}1RmV!s53eYS8PvQ>wZbdvUSbQzd8d-8mb0cA4ILG+1oitdTC z=kUvbKu>aHe=e72qCn0v&5yT8EgL$hW7`yTZBw(Lzj9M|Su0IRM*TX~(_Xt-HZ5VTEKW$E z&6SH~TrXes-1)hEn_;xRa-v()JH74YL38~DYp8ge7xCNZYwr{m5&ImZ8K&r-N_-_U z&tf1l^$~3~WS)MWzvori4*5~>v(*@f%^UCMSo#kF@YiH;hDnig$D|c6y98T*Z2E! zdtDVE*By7W(Rd^g3k081l{-3{!aXTp$libSC|=yWxV3k4+H=oVboiq_RUDr2rR#w1 zwnO$#&i%gc|H9kf{)M-bx4iep8`mD|{p1%;oO#c`es}b`>Y4u~@w~*QX-Wgbk;HY0 zZvQJ|wka=oUTl#hq4r;V?#*xh+?&ba8{hlo56-`G{l>eFzC8ZmS9X5+6R#gYnFn97 zY`iB8yx8F-m~ww5_Xq#N+wrQO@9z8JmsU^TvA=Yx^yM$#X%ve_RMJF=n!@ow65HZ~ zFq_#fZc;ibM=?}H8}`%}4|7lBGdLXwmt9@+Nmu-kH5{SleprJnZ;d-3wcU_9C+vX-1`3V{TWg#ZWX{pszP#Hb3t)OD(^_L_IEf zv?1+>Eg^#C$mUFk>D=|UgUq1bwY+3g^M^$sL>7P^X1k&1I#)M5VrDxnliDR*lOWH2 zo;*u_z0fQifw}+9$O#zdE@!X+atA14kkPL-+8~1%nN86LiYa{<(ps%WakoG1%<=@I zy`~|W97&HoWFJu3e^k79x)(mp2D8|P{@5kRp8$ocv^-$a3`WVdBxxmy&uF5R>W-tA z^m4uE9bX~*m2=Il<*shrZWTS-6f_$oWI`yWMK770tbwveOEPUZt`XWsT5|nkOGJP9 zagsKZx8+B%*7nLAlwijmD8BQfkY2~IfM(L%Ug;Xf9d0GC_yX)Fua7xi-|$_{Ek(9} zbeWP@9fP{XU{pNCT05(AMqZH4vLQ*yfO0QMywdJp!}Ve*69H9mbf`+H6-Gfbp3fJ5M`21650 zCfX3$;V@Tama|Oc{Bnx4Cl#3ryc@2m5LKiHnp|=`5SM+ zy;h0LInkhC*qe>xXtYhi#9SXh0fR9H{(;dvm`ulAL9XGH>*&Q{RNHUpnuEHJDFjDm zLQG46?i%z+>01;NDe1xlZBeq{1&t7fIBLdRs$@lIK$SqJ1|?`HOv68P{dGrz(gM@G zFqj@a65E!uywgF(rj`-lPwiVWwWYY~t?X=u^qraV;Q; zasXEDyW%i6sPSxyr1yq>+T2P)C*oD zKUA;lk*B+`Wj4#b4D;Q-4rz>0vz$ z!&f$oc9`I&Vk2F&48y7)F%83P{g+aW)XME@b+Q-)ivjtJX+Fq{#_z@EL#Fu> z$GOJb+mX4}kG<@yY! zm|~{_F|kc%s3{LbM?Zg8K=v6NZ=|eG^3ndltfyY($X2<$b);g%wj;rQC!=0tMK|WE zju}-_p|9@O&JWgT+;0~t~ znAxG@#fgkS`yCC({qZPDViALqLw+ZY|K0NElH_yCUC~3jDXKXUC#P_$C;G4l1ht4k zUY{arW<1mX&;5rU!nfTQ&Viy*s@{B9(|Y?JK*#JH>|u}IBSdut#QYjqKPu+T1^s*o zb%vG!^=pYB7(KoeRvZUY6)x!!vPOw^L}(8;^^p$0PlAb~G4_s$WZKh>2X&qNrLOOO z<1NCVZ&fn(pXd|h%}T;`zCZq(jJr~Wm#0$@uZnh#o(`+k@O$6FcQUuNy|ul)z4fI# zwzsy(o2%jOH`V^tu)6!rZ=O8)%?l?_USJPC@Bm`P_Uu}xkbuUES>*~^H;ZLAK;pdV zC?LUf9FC@45+bFY3<;7pu_g&5`S4v2-u%M(hhBTjJ-bJa{~tH4U$^_k^;6fMXpza8 zQ{Q>V@#6=7@{S$yhmB7>va>rRAKx8~|6%uQ8TrC)df>kE-yV}c-#tn`yZakt>))Ou z$ymj4FTs7^09#JsFwC|$6`of39=u%)bRU6%teEu$g3gdJEyE!#KpE~rOHal~WFfbq z|C15ez&Q(lBzw59li;vL<49&m*S19BB$uAhb4N%;u9CKn#!7q7FjtI{VlXSlYa7~R z`0>{a7WMR=6Sh{njqxoiJiUf%$`er(B5oX%hq=8@>x5es-7%Rjg|3~|_=u9<4jTfY z$)%6;K#FjIYW|-R^EITDfTAb0z!8@Fpb0AITf+W=?y$zA%S$P;WN!LWcY-L20*5UB z=SrpYv5ystF93ToifyAUA}J3gP~FfNT$`pNW{1pg*G&sl5ld)Um%8%?CM;^Td1{#M z1}N#*mQExkDWO9W+dO7#;$G0hxo>cdLygg;e;>Il27ev%^s3bIFj1npp`1h5-;^V0 zgb1e&S|iB3pQM*{QPx)osBVY6roK?Rw`@k7pMB_|(Zdhl@k?fK!|KKgXRB)~Co9+7 zvrymtT76-mo|5iL=bGux5%Q6R`k(7_MzrEM&2Tgdn?a*d-q)?~Tbb)j@KCZ|U)cS2 zvE4d>`kD9a_n;4d5Bl(n3hyg?h_uN|p-2B2`A1CGMZ0u`-azlCZ^Nt_NYj@IIkRs; z%*pLqgzKJ=Tgr7 zY#641_A~i2jS2?loqXe+cN{)MA~{d$j1)a-`H0gXeiKGwMIjh;txoNgF>`cV)`A-J1*jA)K-AUuhRqU63Ca#M4tPDzA#pawNbV1X%Xfqly? zlkVSOW}`airfbpxD`dapa}k2z)r!S>E$gRVYI!uUicuq6CI}pP!f;I6ajT@{2Ss67E4O!1Qk-j?!>*Due(A>8~&t>OUX*r zV~*6RE@g!4t|l%Z3JqC>aIMXhh8LK`cZ+HAb_brkZF1AG2-9uL^!3OJbdyLail)TH zSt5AQvdtJ1no&cx+KXB>uq3A-V&Dc~ip50MtYQ^Kf^0_$dj$0^Hj4rm=aQ1^)@xu^ zVVgk{aLr<5Z@HPrB&3>j>_pdPeGYdEq)hG;Q?sGZ?taQHMtYT-9?@`h*Gf}nhGtNM zZWH>ODLfBZZ>A!q%yO9soY1u3qKQ}LdZ`=*3q`nw`E94o0x!iyMkgxz*q5utYXYB< zk_V;_(GriBe6n0&hJoRaF4qWB^br^d+TNO=E~~Y~U)m?23aH7)h}c3mmw+)|tGb-~ zOxm^9nsFSO#u27Q99$h`j(AKndY0|^Ya>fHowCn)Y)cTvMDt9-q>Dsh&>4n|x`GX; z#tdIVyW2L>u39Q@xgL}POGDIMDk8y%Za~MkOq;vodO)$p^im=XlXjM^wS zWdu{G$bE(i1_ZiO+8qx^!)$FvtQ99Ul}`b*uRf9~(`laIA?tvZuL#jW$H*#$0Z?QkK&pF zJb2+HNHgx-E7&g^CWl!JhMCW_qOz>4d6;)2-ZX?+hh8RvmnIsarYH$kixjb}f8w|m zIW8#S0xD>mm4In5DaML7&Nt&Ln=6|M5pk85h04??$_FzNK|OzqQ3XRJ3#PSc6H{10?Lb;v7ePQSm#ukpJ zGln)avxoUU3}TgCM36zDQo*Pe$tutKiv*E*sC)L1w6U*dlGhiCw+^ZA zF#Qk?lnxB0@dTBrxJCz}gHHt2Ww_z-B;AXj1SL|gVM2x&S_YL7uuPE9vX3Vtfgn`Vwi-EU%x6lZcQ5 zQ}p2}iCO*>Q06+3a!=u5Na=?S3}f~We^=w&xc>pl{u9jj)>nmb&pl0@*$!#7h)z}$ zLVpc0(6*tw6az33b=u%L8Ulep+8QV^F!RzFgg*ugBJAU{^9!Kl5SDfQ+Z1&9MeFar z#iadt&KEP7(w6RGIc&zP!VJkZn3CPFCb(lNFo_7woYReJZNwZ$5=k8i2|pt}Jyc*4 zPJ45;iftk&41bnZ!6LXXL=6d)k_03q=m>_+K~jP2*Ki_MuxKq-jQ?Dv=SIpQf<6Mi zBxiyPV?q%a2Jh%DrxjEIC}#O1Dv=Xtfr&a2XE2zl@5ldb#o){pI?T(FUp82a3Vfw? z$PC>KjU|ho#*0!-eKvkNV2>=BD`NlP@c+9 z>+|IQM?b3d9L&Q+=&z7l@4K1l9@9X|S@b0Y`jf>x2C5rqH8yBQoZUo)fT91ph>LsX zwa`$ne**~1jZN}8xZgjlGAbTr(f+9o6Snpz`=?O+_0Tj1aZGs1Re?`baU42FY_+&7 ztFT|vGISJ6Vah9n96bgNd1UKPv@SM();RNi!VVo1;_Aa!PgP?og_n@mASlkUODK~f zcx=Z9m}Hb2hQf{~I@JuzK=lDxJwZK(XAr7qWSdbnYJ=)!B76{2N$|8DcTQuUw=e<* z3U?*rHj=~fp2(keU!w|fjR0?^K_51$Lf9#kAOi7Kdorml;Kn}euJg#&2`v^$pwvK=- zg4qM%BDDyvwE+sY=DSLx!`9@xU)JsiV?jq876HAtNoiBlh}oxPwDDO??PZm*TUWdi zNKZb;w7vk{r7FJc>GD1pzmgzD1WkQm7GjW}x6#K*ztAaUg|nc&ysq#>;RA&~DE!yL zKav>w_Z8$Axq+Moed2NQZjh^A0UIB=o%&JQpJt%H?2se^i*SloAuI! z(GNUcm%R)L3{V2}2a2jK#Vd$(l8yR!d1On9l33-OfDM_%!)YQXeL05mQ!$E1q6<@U zS7ltDg4}`YU@X~W(3jg(rM;T2P2)b;{#`VE$7>Tb{8Lctl=8qAX%{Lpf?uqfiW}a> zWeTwEJ92|xcx{7jQz)|o(rqMhU2N}MxZ!bzv@B?HXT#ZrW4N_Z4g*KsGa(xqVqz_U z09Bkeb(ns2ri0NSh%pEIzPB<%yKM4!TM$nZe7zg z$I$+&+CEUK>3UV)w{Q0!Iha)eCV&SifRnEp5r_`7pwDtt{|QE_U{nJ}Oqd9Y1*4c@ z1PxPYLk`7)1qTO0hvgNx{vu)#amiSLV9}4i`$r~N5t?R5*G>)Hvh@9KdIj7h7*D#? z8HU0LrYK55my}>l-M8u9Ams^nz2|;xsV+rhxh~`|biJo@(M--EuPScY?mdR zEO52l^13jkgc?(#NP%JhIFkuorZSVVNf+38FQB~WNh3s~MTLQYWhxXyp-$yDI1&^S z(?|53Lir;908$(mEg0~{GTtMmihzkrA*VI4E)?VvsS$|6MpB7)0zwFv4W#RV(AJSc z1{t22rkD7rnhdCZoldV6?Ur`0WV)eHf7}$vS&6xa^U8B7IRXJu8Z(ZhiBLAe0}xBf zwOpN0sBET0pr*p%pk^qkNl`s|(AHscdk}L7+d|14qvh2N%k?q)AJ6NxsisfL6%N~v zjgxxp#hY=x9>d=iHTW??!w3({eqY`s44z^I1elp%tHEiR#yjZ9K{-vXH*S*4pyU|W z8a&e%A0WQ1X^wB`0f+B#hG!T|53LOa?Qx^zK2`%PgKlA5*e(<%Gpd&HsP}!0uX!fa zA)+qwV1DFoQQp{eitcgHD$m=t2m&%+tW=7-AHesA)6&|$a(ds6VcEv^xYhb#zg=&% z`g;9u4gIL0zxZG89Z6nXtn7a8SkU;H`b!$N@p@3#>itIl74JEJ{yibf>-!8XsQBo1 z;Yi^au7zSg>k&FGVU(-9R(WVXXv!+~1bt=`gr|XRi6AwMMA+k$&hqqm$%Gjcz51l^ zo$q{S8b=#kl!X_Y6-w=vqc4;(#IjaC)VTV0uWZYz)Rt`t!n=mB!%$ijUjc84Ru0ehXiy{RmE9ZwX=o`s}0xx(f`uGlH+DPRjCw4>^>!gQV>!78pWmHh4a}duM zWx*`CQQ`_A7;$SkpPb4Nj*;Bz9d}?>8ASf2lKd=>=iRTzNYW(Dl9rNNUPxZ{%P+Hx zS8>ZHyhv}j`RUV|d;Jw=QTyvwsB^s<5$o)5j>lHSU%_L#_U(swd}Qlt9<*cr2oL!N zX0kC>t?hkU5LpOVUKP#3{%pUMm_BQ|)-olbEUpaaMxk_g>@mamZCP>V+G)u#m$g@D z%Z(LLa;*A_c#j_~^EZg)My3@#7dvzXv9a18;#sm#7>Q||3Qfup_D=& z?Skrt+S^HAmA4qOnJc?PFa95h28@E7M7i(MDg( z6tf8o#wwTSbc}9kp>lvHu~dr>Q)Nd^F+-3?ru}Z761b1JQMlxw)6ePzp8qfzjPW#( z<1yR?<`h>Z=KzaPDdzehIXnVld^$#FIzE+7e+u?xgXG*9Lp(A=jl`qMeV**7#6+kr zVJf_FCSfzrL71YQKOKSM(Z~E&YE>d#19Kc_1Y9n_)fk!LcT#Qj612eZZX-W)gJEXFajx~K2d$wmL(Nb! z$|xFcENbm zxU_4@i7T#p-7=}2pAfx#sVmX z2;qX%SBasi$QhMUJ;L~0kl-$=Z7%w|EnP?-j0l~UVvN0@7;(y#5oV#yjA0}R!Nf}_ z5mCR=SY1bQSoCgU8YhZW?iP0>XcA!NddIKr9XS+tFr0~lf#}236k`bazTTE>&JKQz~%8Rb`U=)GJPdxrNYbY5Fyj*iqTn&3M6(URsO7T61vh ziZc(m)F==8josh({LsO*UG`30tJcM_x8~jp9nCM$kubI@xBVC~FzQO$7>?V5NAIZ! z8xvIx+MpzXwvJWo3`Y|c6IF4vu~{CuB^2S00eYT}ilE{sQsHw%h)Ck8C=tDq#l?wny8rM4#l6#Z~$p4xdteMc>g|Xj}$I| zcoto!i_idI16YP(fJP5D;$m0xKqg{D9Mp2(;VS8E9g3lo*0(?}hf!xQLHG5YIb%_) zn~ay7rg^|FRvjT_SQ6MMW}{u!U$EqyJ!)L39l&0yOEh7pn%-Lyyh>wo%J9S3$Ovif z>%I_bFy9Ysi@vTo+&WP8nK^&z;JgbD(fye3GtVp$80a=Ax{gzV7Pk;>GsD5q1%+>P zz?!v4RwdJsRwZ5NN&oR1Uu*rvH>R!Kqd)c+?XNzTk$ZMOLdg4fKXlhi2;F@tx%1x_ zlq{ytex4ozYZx;T692? zUPi``MEWXGJ94(pzLwDv?d;@S{V<8=2X`->utd^JSHWHMIaj&#-6-8l?e))GIVT^F zr+XE>>^xPX9OYpxqaA|wFv<#~2?l)uB(_Stm#V~q(^ZDxPn8@%MFE5GLQZg`>_V?W z57SBVK^`#;<~tbA2(>2@mHZXMGZ2KJ(kOvqNLO=}IRB=vlp5TXXAa5_mrBzCS>{qy zvOzv#>NAxJdpJ!lCcz%&*PG%wV-WAz=(#ONko6Lt+TMLCY82esp?R}*w&?l+iVpb! zJo7;}&d*Ytv+e+={SyU~nZYO(8#rqtx?9}CNB}=zo!K_Oj`IX2d(K^pbE^hYk#Swc z@4JC5z>+_O$YM@yl^+KK_PK3nHz9`>=Vi|2cPUNPZwO-Q|E=pwfF!xfGwbE`^1hGC zI+UK9Anl>48nt8~{|99z};O~ci;sd=0j z#mkW-R;DdGZ&+q&P}w`n=k03!#!+Y4P(j+|^PNZ(+eb#4>2Cc;oTx<+y4;D1q8R$c zLg06=Dd$Nm2%G(=B#Mz&EcpKFHAxg)6@nbo8;RqJR04G+OVzgPfTQl)g?y#zIuZy` z%WYRthKni{$2)}`JF$Kjv*x8n!?BSX#qr~Owc^^KZ+OdTG$aMYF{QU*P{P9SE~n#9 z6!8X>UR{$}Q-!D)JT?Vu>-_eI|C~E7wfbWE&z`OGb+edeG_jd-j=5>F{NOvU`t)daW3WVz~;o?}<%b3mbki zv|xurhXiGFFWzdlLza{-Ul%Tb&+9q1sZvd-C!!daII_8CYnol9im$!@L_QNFxK$bz zJRd7G!w^h`GAGhnp5qG@XeYi@6qEoOP;@-2v$|^gJ|STs3xzW1#H0wjmErD`W7sRe z(7byC8Jp&qkfEjx$r~3x$0YL=BIQ}T>}K{CYn_`H_kh(Qa+h8PqwU|ZJbD{EpjuR` zXHCo%M6~J@jKKBvt-r0BL@pM8@^v`EFF?-oseRaBp)3nWkLB5vLd`&ux^rU;m3j^e^wEdEU4&;5`TO8 zTkEnZ|BFn%C(Bz2thz^+504IQ9ASBm+qs#$K_i)@f-`hMl!B3OVF(bxeWpFcRzxJS z@O7);w*}Ixt5m9h!FK_=n|g!lNr~jWT@;}m-|lE)&{t*A8iO&z6%$pHIxSJs+C}K; zGlLW5oh~nG&bhOqV5B|_`*)mEP0Y_T1o6#OH1>m$tX4PRZpshA*warPmUSB4tAdK& zEQ%^;Y*0g_rx)|F+gOWpbUdsf=;TJZS#Et1*Ek8@>kTZrF#+!d^N`YjW)q8~upW%C z;sHKNv)O3UoPanC(ySR|;B3q?9`k`GvkD1PcpXKv0R5bFLh%jFMpz1wMHsUg1*Bd% zc6W6(ovp2EnyXz{Tf1=jSasjp1y#MU^@UT_Gxljxs-Ct^S8iUd9)Dl$*o!AB@UB`t z)~l>ORlDu{+H6JB-c>zSJ8hHeE5~oGRBpX{jeY$O)KxgDdc3+C%vM!xHGiUV#yZnI zQH5n?>#Nmchv0}epRAs?&zym^^VM5>)zuHrW*0Q=!gBQ_oSNApmwtBXE1X2W1TAzK z`slMwaX#yBkvR)qS1`d&>yZ{=Y%OyD3C54fbR{CHiSR#m{zTA8hXo3u}*t*Eyh2 zY`1a~~n(#EE=F zZt0VEk=^f}b2H%_PBk~qtwGzqA$LXYdXOR!a)M6MIRgU*Gfm7}m@xuvmR*We$!wNo zG~WOtMjUks;`rg5$|Cm3Sg=L`k|0#bD4Rz@>4-Zz-4li1CVH5!b62O@vax5v4FT~&+n2C^k(+O;OF_$@(5aI?VmTG7PIOcOuQx z6jddi&1Ks#>>4lCB2nhSh>^M{_bIZ%+p<)|>L##fL}e<9sVMeEoL}q;e48ImDOm!0 z1rAD{dE^TZSHnNLuApgagvvERnyx;EuoK|j79~VsfUj{S0S+NVlF(o<`fZu|k~}p` zqN{7*-d0yVLA-8{$RANw^0Cq@S47^@6_aqgR@U;d#P^~aM@&^U)y>fw0pl2a$kMAv zFN;Dd@?d6eA6*tFy16Xtio916<~$ACb#6(n4PytT(#c%ToAWOrcy_=*-Lq{l^;rx4 zqd7Cwa4?$scsr*!Yfz_)t#C7WTw5m-@YC35=34vN_FAX2*5;bUo$YK#2Oht>uh?o8 z$i8Z`$iW&c9w&uXRw%XbgW|sG{DqutudTI5<G>Gc>A7WmpHe#NzqTMMQf@LFM%VAyvy?Sdf=0&0Ys1VtSk zu}q{HsEs*Kl*uO?dD_Y6ovkNv`FODiHoT_)PRnvNT_Gl=g`}=42h^%>6x`Zzr5cfX z*OFHYN*o}}jLeHXb;&c^M0UWH%=x3Z^{of-mFET z9aqma1m0J5zF@|dE$0?od>6@&p$|GZ7vIEsh}|6Kv18m9tj$?Q$=^xR5ioF{D}6_j zj{jck(=Q%7cI=yyEgc1;_qp<4z#4A7AGV^snp3%l$gk!0Vcl6XWL3r)%g$neWI-rp zQfoGuzYp;(;vXO_7N&A1JmVf3h8WhKimciar583e_YRvg-F}nfXf)`o4s7DLv#cGs zgU-spBlc3GRN}R`RIyceGKrV`vSo%I*cOwCtATB>_5E_aSs;aGExFHK*Im9+aO{5Q z+3pI6mC9alTB>B#bKIcEk@~vGudHGw!?3@4HHj;+jg5WvYJUjMOFJqwn}w(iBX|qi z?_=aYoPg>z@DNIS@l)%Gy@KsVI|#yve@H zO1&|1!J-EBY#u$3*mFEzS>`6Q7Hwh5`xrsR*cv`366QhRT|f%$>AaSJhU{3V!!fMU z%8e-)WwKf`2{GbY;r#mHjM;c` zc8~$B`PAre8ckc=;64Uw92I|iEC!pN4<`b?=6#x8Yi^or^Ucv zNM$Ucic>MoKqk=ipyU;a5Lw0Y2_91x5cZ0vn706@Km^iTNu+8nu{9$!=Fnf9ZI$YN zjV7@Q4TZB8uru6XMh4AcZh;6Zs*I$&cm|6jG9^S%IYHuMsnsys&Cd&d6P$Hzsd1~k zY&#YeGcPjU7a5&y-st>YK|$mKQ<38bNLZGYKCsN_NHpcl8y4U`63RT@F;GB1d|m zvGdwT#aUeae|cx*SG?Xn-`@$BJL(s2j4>)AG)POlJgIL;L)eP_+x0@kMFxi+4>oI z^zL`Kt-kq`v-SONTnX(FfE;~}d>MST1UVig8f-J#yvp*B5XO{Hm&hNT+?1uWlDv8H zo+ehLrBY|q`N{tC{w>HvaQ%WBzn6i$c+ zoUYh;J0Ede*H}-c(?DHrDVCR4ZAl~oA`I$=EL#N<8Vx_RC8b{*6?IWD3r8CVMk8nU zk&SM{jfoYuG>cf8hCx&sMdVsygz5$-D3teuJSoG7uvvWt2NEz}6$*wUmlCx%7L|jo zz?F1wl_Ls+SHn@L@qFaPmP|CWAAr&o+K$&qH~V3c!V&W00`fKms;v^uvIDzpGn?@B zd<7VfndSDsR&Csn@a}fWHnR@S3b&BvjfVb05n%mw22Bj=Vh)RfSZ3gzgo~QwkwK%_ zq|w$dimqF9H;hg`%kmw={zgR2IHE?gDRhM^I#F-PH?6MF^qc&!7j?FY2X==AJc99p zSMa<-I4rk|*B9I6q1k!bz^@m=gN-ri_57vC48j}9xFLsu87&2UwB-dDJNJ{{g;9eH ze*3}tK|Tv`Y)#gQN1(l?pnSpR1sks`O==Tlsbo+On*y6pz$Eh^ogowt&S`_=1s+@g z9ZpI(jKxgg0I>aBO-|CdW|}t~{ni;t_Klje;umVAVOTX(a!YpIRo6DHV6d{hv@!_I zZ)w#=p%>R`ylAL0RdgzxTn~(tIDrj)&T0_bb3Sz6Z;EoEaPDoU#{Zx#tbEr2HDm{x zujr-`JLO_Cd`S|lPVF}e?NTLl-LO(xdQ_6ctUgNRgEwIERo-(WoZ~agzEd`dr5I}7 zsrXJ}a*kptA==c%T$O8*vvB{1=no+C>SU8|r7g^%zg1UF-l`7=F&7mrhVZE0KrU$NKF{BR}g=T4Ic5P)`8qcmjn31n0&0Fe4Lh^&H zACUiiVC!1)q)0?nB$D8j$>on7{w4$RR4K*8ScZityAx5hjk335{}dH~@@6N+6n8=Y zE18?y@Vj8mLLcps1%b(QPAiL(NnLIMTRV#SECr^* zek4!pta?Kl3`0gP5Kb2;Bwm-WqUUKg7eRC}4YiH=Cn3i{#5iWR0RrLZdz`3=qM!+CM5#t41PiqctSSR5NHH-FkCR-j=<57+Y=rFN zEG?{n%&)q51j$msY*7_MEP0%)QrT(}OOQp*%=@a}E6B87@*Pz>C#Et^Efa;R9Hz=~ zy2|SsGS>=}+9hpLb)0`5Fun-_(mX*cFcdXKb8reMvQlCvd%L zp=hs~8dzA^6)c?#;1_y9zCN&9zUWXx1*=Nb9TRl#oM(`~^fLJp%g1A^q4UyQ2CcNo ztZ_cABgh(Jv9UZEti3?cr*PI!7PaIk!RI$`mlWj#&up+Y}b^ws2zisaZj*mYC^ z+lQQ2%pM$cMOBuT{CdEZl6^NTvb#Q*?btCJth?HIw^6ao*lo4n&~2{TrtdWx_iDrV z=4X5fF%H`hgBw6?OeZr8i}n@2wBdED^S=H{tk^)H$Y&MO~$Bm5%F-J{@# zpr?I2_r}~)x!=w`oBK@ev&{Q|E{In590(ZI)NGrp7+H*2R?u_~a~y309*4}CV72y` zzsop`LFUfFd0ro#&MR)17VUEBa63Ve`Yc&X3RrK?76)S(mLSL{SRd_mIppe`qMu1I z$r}4_IGnX2^5x~C9^Y4KW_iq_FP5ugf!gEL0ky8QLg7uZU^dsd&^9fS*K98=)y7L5 zL>w*D%8GJptqB%(kNbihIz>&jzER-#il#YF3>ro5z*{)qUtgnvL3kst7}|Jmzg8E` zis>Vkhwt&Fuzp~#q-ti?bt)FoE1ujC?6Pb|q;Z(a0Urch*#ui$>NHw^`n`B;H^olj zun&E@)B}6WjLWiM*_GD%a76`slxuZC9qufE9UB%mMfX6JnvH?8tQ+S!U6;${&f5z- zZ@IqyDPp^x=gMEIPL{IFl?^cTeJfJL&~fY{&-*8D;H0A`%N5S7+C160v*TvR1=+T` zE$E+wQ&f+bQdlCS-Hb$?a3kV36_{bTb`I1oz0AY0F;X?{JMzuAb=G}`#V8E$o0RW-O2`#1pEJIl>UUW=Q$`ueV3d?Y9i2h&=Z-Ugp0>X-}ZS9u@eTIPNB1h}Z_$e1_-G5HepvTL)lW z{x5QYMS?N@wH-ae26t)!@;r}t*pyCiF>a+1JSQMx$VE4f-H&`^>p~}XJH_Un&1*Wl zS1#Uh>c%I?h1lKtjvIH#@}50zymhfz{K%U&S9brKQ+IU8dv9Fep1w>TB%cDC{jS^t zSZ$atOC|;fXMqHIs3dwVR*7bE3r@zt7)t;V4gkjE0!3~*+bK+kb2z{;OKt^2c9=*E z1i=*1CZDkaw;|}WUnh~`=?29*9&qt)14(>RtCtN4yksR%y<%8UWJA-DH6n22%GCE1 zm4Jg8nYN-}zCQ=PTO3ryy~Lu9V#+f3?x1*VKMEX4blZn{L8)K~OPg%q%=f>=^fD2= zcSHBLzET>HSOObB){UZPHbl&!qpC@R6>vE{-|BR2!=2YtNnSg$Bn=$y0%#afP&7-O z-m^Dqg*N5AzhPwn)E4Jsv46cf54bb%_z;Uw z7`CTWWlA53h=$T!xg^{OS~i?vEM*gXkFJC)X6W&CaZW7@{H#8hAh*%s>PO#x(jHWt zASo}ElVI`UR0bb*YJ{YYz28w98Yigc`qsZ}m@3a}4aM1S1+Jy)qQ-&IVrp8Fo;Z;v znq~@!(Jkt#f2pL+@jQNh z>3iG-=4+a{d~Q8=eeO4M@5}vR?pq{BbkZXG$o1f@KV>B4A}aNJcq8OQ}x%fj@}CL7x@- zglKCcj7P{yUb+P9DKX>>R_SE6aLMNhIFoFQ^I9N2Cw(j_LY8OD1jo6oDC*=K{$;VVcC0g};|9OXA5- z-nvI^O)KW+-gP70Sy4)$@RGIqu4cVg9b4(awfZmq$uWt8SQSL6x(Ggwxv5rAHPJl; z`%jWWiyARcv<3k?NF{4jGZDbTHn6lqmTl-Gd_8mx8R?a9ldfovse<(f>!*k=5=;>x zmWs7M949Lrhd}d~kE$rLz$rR6l{_ExzT^dhux!!!i3AbUKj~qSi(_N5BbJHipus7b zif$lq$@~ha(1;*&J2}X|-Uh z>Fxu%DDj9q4&9wfrVjQqLPf}g?qZcfaB2lnFBK6JH|rpb0uYIW45>m*6H9_%C*C2v z2%6Pp2NMzW9h?&RbpiJs2;v9ZNo33gCJ2WCFJm50hZRbyG$pd@>N-5j44rvn4& zUB6;Isz>eli|^lC9_jMEvhH-9UhSD$?{(OSH21F8A_GmZ&BraeOiY3HyF3FXNJK_e z#n_ry!aU9oO~D30e~OFdk#>yaz{j*MMLxrBL%%D;@HO?BMZ77Funrq^;m76?ga=fW z#6Bm1DeijIG9tfd*pkO9JeI6sU8%qcmM;iyoD2j$Z&-#FsJ09DPIVn!<&dFBvlW{} zZdP+5>;maBK>{+-E(^R&kl>K;U9k}-F*3YeJt$XEW^Z2^9oK)>q-%&zR`b^C{a26#cODtHPueioTDjL$Y^_a z-ehG!8T+8mxaw!*-!Gq%ymRD@L5VVCC=1?FD%>_f2L_&GE*Ju?CZp*U5OUR2fn_sMSdt zNUUM!m2(KriIs9V@A{L_^TI#h+VgY}+o}&;umDD8kQj;|S4cgIN)MC@RGf>I)46lGJD@#ZPXUgxF@oU-iNja+a^IkX`pC#T{=!jA*Yv|4q5G@SAEmJy!&8djn+c zDa1I8W+Rpp1^>-q!bd*bcDDyeF}$5C5X^P2u6LGu`)b$85B*97^nffRo-FK})M<_@Xe zefP!V$4{NQ$Z2j?D7IadTzra?)WGly99JSzxmDDqi-Kf=H)gw7S0M_w5nj`Ejku^w zwso}%UT&C6w!K{Z0c5hxG!elFoo+|;7nAt`$(B0QN%Ot4OzZxBf?RL5_Q}Dn9h1w6 zuu{|V1^eTF!ZhSdFTVH#9Y6ku&E=)tFD`HkQFd)0PYPj7TmF1*Jw-s3bdjMr88d91 zSD78LB5pKX$L=xDZ$ydCq8*F*Y?guF$}pHkSkQ`1<{%smBZwS^<2h{n1a1uY3CtbZrhHqp=K=V};~rVC)**2v z^w$}d7Jlr7IpjCiE@1&TsUlS*FDqeO;02Wzz;sb%EJ+fo3?PM7loxi*gxRhFdH%^4 zKJ?@Zzb;#H7@97{A~=C4h93k=VB1TQ5O_;L;2R7{9_((%3PYL7@=`w}B<$z@AJjxl z(RiF=U}Rum^lnr6kh<$YJipCX25u$>5V&hl2ctRvzx%(Qsg>~okjudUmIeTcvJIaA z0C=2ZU}RumJn;Vj0|QgT|9AgCGPN=QMUVmGX#ly~2sHoz0C=30RK1cDF$|WSu-Uu( zaF-(_nG297;2E}|qu?24vHOyRyQQ|Jd=S$93;wsnBNY;qgf8qTzxr_ZDOngK0Qb(%s z($rV0p1B|08t3Z#|3#-ex8}a?pIA4Flm3fhd7fhmo`Dw7BkWJ%bC8wObMRMk?lB>< zy?Cv4OrfvTuCQ0>T|?jH?6kziD0^*}TXAJM!+$v2_uM~dZ{m3W71wL=%veGO?z^lR z$9c{J?X;FH?g5YDU-+M$@CF#C{^$FYD~$cH$Ey^7u6XA9YSU|8Py2k-+&fxjp2K&q zKJYjm3on9VBysV;dxbY`Z0(GwKVkP4_Io}T4^qdi+J zN9jzR`H||1zx+GskEOR8#bXV7EAv8WzEXYhx9mx5yobF+?LRFhnIk;c#^+t5{oI=W zY>n(Xe59VlsXRM#Sqp~okNd*d#fQ_m@yxfp{$}3^!FuR!flaC*XY4=8jHLHyM+@<#dWXn+tsEKXJIkD%z+PkyOMj{K zmFsw6xWIiFiR3rrNYy`coh*j{0000000000006oHE&+l8x&i0{ECQ?o>H~-a#smZe zqz2Fj0tXlehzGa`4hUQbcnInVKneN^R0_Nc5(`8NkPK1`nheGbiVf@zS`MZT+z%2D zf)Az-&JXSoTo9TN(h(pLND+Jz3=%*RViJrJ$P*S5R1=sxSt`_tcBo~Gl zAQ(^>#2EA$Mj5;sI2vXeiW;^XE*rKS0vtjdkQ~k(EFL}{x*t#L*wy)+hidb|~B_ASs+HE-UIS>MqhRfG_SaZZOg@ zh%vS@)-wV#NHd}|95pyKb~WZUJ~p&A-ZvgMJ~_xb5;}}K5IbBuxI5%L96WeD;5{Ne ziaw@3>OaCi{yCob5>KvA;!pfg`cZ6A=u&o508=(o zd{m%S4pnwl@K(@P{8wyO&RF(YYFXM^v|A2aR$HcAv|RjMj9vy_T3+g3SYN(h{9s66 zc40PQ{$eU(RAiK85M_8}wq`PBT4sD^+Gk2cxlpV25N3>RBV=QK5cSs z-fli_TyBVN@^P$k_;W0C+;kdrbadu*LU-VJYIwML7J15g5_&{>_IrwZ=zI`-uzc!$ zT77nXpnckY1b&)-CVyOjM1eSgu7XB_WP-AT9)x;?)`cX6hK0@gP@kfoz@VC-w4qd?grT^jRHX8y2&FisYNfcP z(x!r^IHz!@@~G6QIH`E4*s4IPlB&e36su^fz^oFiuB}R~o~`n(ey>ok&ag7Dys=ub z^s*qb#IwG%Vz%(RLc8d^IK2eEKEC+Ch``9f0>L)HhQa*8YQoyXfWx%J4#YadaKyI7 zdd1SoV#*-RzR)btgwX=gl+`%ZTG%YuPS}Fj=-GbR>e^!3pxYwcJlv?=3f+F)rrsXl zI^cxh!r=1Zvf?7*wBsJ*^yFgYa^=kCDCT762Mq?wJQA-}G%*efItW`>} z`#zEg=-wxxA;nKS=aV6Y|H{5u|9{0cW%%gY zXa52HcHnmdzrRR>yEMAgN`M}m)UlD&hBQh|&Jsh}Xo;haz3%BPfWOs>OD=aifcXU_acrT?_^&h0}7X<!pzJJvaPb!Wn2D|TvhHcGcz+YGt=&7G0U4}W@bvR z3Ob$rfBW>Yplrd@efM4+<72D8AO7Ij>0{^kqwo30F(#%Cb*V=Ih19134QWJUn$QmI z(jFbB6LgYJ(Rp+}T|if&({yFJ3SE`1Mixb?JI^eYyeNkZwdb zrigArH>Hc|W^@T%N|({)bn|21r(4h!bW6Gw-I{Jgx24?nC#b`_cXB0rWt65IvY4LJy^f(ZlHx^hkOXJ(?avkEO@a!E9jNhhCWN5qtDY9=!^6v`Z9fm zzDi%CuhTc^oAfREHhqV_OFu;b^s;V$<$;E?+~;31EA%oE<>UEbs4e1cE% zDL#+S=L`5se44M!SK+Jj)%Zfbh_BAq;A`@=_}Y9OzAj&nug^E&8}g0##vJiY_@;a@ z-;6KeOZhUsoNvyz;4Ao+d@H^+--d6?x8vLM9r%uXC%!Y^h40FDPV;Awg z`96GKz8~M8AHWaf2l0dXA^cE&7(bjJ!H?ue@uT@M{8)Y*Kc1h!f@4lNWyw>{IA_KC zJmZ2(u2^%;XV|c1#|_VU!AoxWfS<@u;wSS{_^JFfemXycpUKbSXY+ITx%@nSKEHrp z$S>j-^Go=p{4#zyzk*-Mui{tpYxuSNI(|LBf#1k);y3eK_^tdlemlQ|-^uUdck_Gr zz5G6YKYxHf$RFYl^GEoj{4xGGe}X^BpW;vRXZW-HIsQC$vStl|h%IzzqT1pezT-R#a2C0+>(u`!9$*7Q-NZMhhbymoz7H!uw)&)+@ zoSyZY%GQOj`7kMTlTHha6=sbpQkiyhHJ5!=Rod#Q>#wFPbh@Jxr|ZT>sjLg#hFE9Z zIyq>nBp1fX^yEUgBrio3l^P4zMpapNq0?r^EtGSI+uEIqM8;arHtl|)s+mkxHOZ9A zn|RY5ZocYoUk}zl4{BARTUxhwSlfJZV!PP_%UpL&j&^0E?NpJfhMU<$;et{uleFsP zt}HI^Ce~isiCq%5x^Yb`yGv|jsbS*1P z-ilo7U>z|Gn5N22*2Ol!cC~uh)VhiiWs*XUj&u!D%$+FR*lwz_Y;pwAb-i<>teBuI1y*hnVbT#=sj`X3iho0taydY`9>LeF zGYCz9oOIK2vM#n;R(hFh>jwTHi$Ym9jGNY?DpI?X=&F*5LpWri>wb!)PJr6}R2v+O zlwl!7RX1_qKd|lC=E^v$s<jP`TVdBw`)2i+-a^b9~>kz?Cw5oy< z>C=?sHcE6Et4bixC%SfOmGyqReGew=*^TA0#>-#^Yl{F|+)v`2RU9g5Y?KsDyq6dW zAkU>A&415XHpsFKv?e;O^b9Mqm71wjKhfHRW|&E=Qv3WGEzs>J6wxBEVk(RZlHBN0 zh8yPXVP!@fU+u2KcUWJcjWhv5=!EWFe(}ZiG7zOW(BJ~y92|t}teFpDpD>YAaxlfa z3Ln_dCs;(yOgR z4H9rW+e(yqH0>TXH=+D-evS|@oIdCQGSzBeao}=UN@bDnM+kN7gR$LW0NO#`_0BZf zh@GjC{!p>1M3i;kNyrhHu^)rzd`}mxc~?5yc2$|iAzHF9ZQp}5!Gt5*U?H_$04mu2 z;Zc=Rx~AScI11tX+tmfnKXk<8O3{X1E6YCn>+q#lP#>pXa+-Iy;vcXN9xfmg!S^+?|Rkl7VXr9B{aNpIt0}MaJIju z+^FoKV%*v>dTe*VAwj7QU=st7r!+c5N_!3teI`cxwo}z*r?OX!ss?cN4pJ?9-XdHE z?JA}+4Ql~M0p-R%{lV9AROcc#D)GdAyv{X@!7`d6btUY=Y~-Vewfmt0n8948LEX9> zBY+MgA8$`l-c%Sk2xv=+AFM8*%h}MZh}v^b=&PQ_Y?2phIkG@bk^>Z~8p9jU6|&j; zm(VINjLYH5u|zq<4F*7pnW%?&pA&9+sBlo?B1fGQnJQ+FNlTd$i{3n|}+-A8|2 zM7HPJT3qvjCtJo$fpK@_)V_f^UH=je-MbI$Jl`Wz#qXZO|V1?TAV zEOhp;Mj{2z9>R*#=ja0rkOUY0zrU;`_3SxTw)4ERx^d6bT^Wlu1jEF_%D#7-I`x?t zf!@6U!J@1aD}(F}e2%PgXZMJ_ui3RJ5}3u~nLw5yd$2DUMp*gX!yXVe#u)B{iq;>F zaM4Ra`Ub)`)4p^GWNgshH*gQlRbpKDXa zs=%!ncitpN=79V%Q9}-bO8I+J$H;l#Uw0RX%4m%;i&1c0^s=64Saul~ZD*mDU4K;? zuIb%~Y8K2y1|>kC%nX;Vs#{5D`a!PpCcykY^)N`}iL8}QofZkOYFD&rk*ttMECf+V zCy6IhC~{;p_+%roQ7l_sr5!l&Q&WF4u`Lo#WjPEN=+lnji>o%mc_0#}7U}?LVIw__ z{G^F@StFN&&mwdA13Xs^m6f8e);^;|1=kkL(A|fxMA$)5g>1(LpRQaBveVxQ zk)45EvADl>nFKya%C2o-7@8QI*>sxPb{mUFD@+v#W#TFx`ZLBNVY>(L0M64+9mLIa z3Ky_;>E8AAafvZ2MfH~~Sgs+Qo3v2+1XS+h0>q}$>q1+C+1l00000000000000000000 z0000#Mn+Uk92y=5U;vAH5eN#0yF7*MA^|o6Bm<5t3x^m01Rw>A1qZ5KTg9Jors^W> zdL+!PZXyEw`&w0FV$1!^M$~Qxl=^2eUT|FZI1q_jJ^TOv|F6?v?8}Z~Z1e{&+aUZchANmSwTECo~`bH=POoN$A&m z_CEeUlod8*xR+>jv}f}}eeL~ypV_@By}U18p$I-YELmub%R*r!(ozVE``<1-9~ax# zT@_G5QK5uc5@(QYMuuYGSpZ=lzBB{zAM9pbYU= zoZ>V{Y6p>M&!nbQdUHdjcAD!P@-%U15}i-0<1VO63?tN1MNHY?DN_N2oU&( z7T%>~G?4KR%*~MV)C@>mQsBXCcz$lZzh+}&-3)p~P)(fOEy5MKxo}{MY8=7;P>h3-!rRH_wDNHR!KY>tp=Ci z?PK_BLQIK5CxJsd$S~#1xsNl4fB~NJ@$iH5LY{n3{ew4q=%dI2N;HP|j}_$mjwK|_ zEKbW{-1OS^@l{aOnjAZ6e9tFA5yQd^7`j+K0drkDWnURk_xL~%dr08!PS}u)2LM!3;=aVzW_?^bT&k{B*AJyqLqJjq8fIc>G7JQvVG;at z%}L2#g`l{^3s^n%!H}7=7jvE*PlFErMar|ep{ONF! zu1{C#$%z~H$MFz!e;^DjaP!dr+}kuV-?j^^$Y?;@^6_iz9h-0G1r`A08ej0g->>rD zjJt0-s$m&~ibiZ$P`k5GKEnKC&#@0?e(!R_0)PsX9|)*a%4fb>T7AUMEn<%WMUo7a znFWRhp`nQeD52fDw7a%fiyse{%xmWRw~UM;NroyG7(`$kxosJhuyC7s_y7OrNNn3GHzoOXy!`9J{_1cv?p$!7cBEZ>{u1wnO}K~T_;j;p%5Ibd_bRi%L?{%=l8 zx=L5a4pUQ(n-Znz07?3t$x`ciZ;6iRT;n|s!xDjD5!9ySIAtA_pw_x4h#LH9LVy7r zjNC3WQ#++jbH&JCEBWahQH*!h>f+X`ENE=KZ~^Q3xH$EljMcAnY2<(NI@*bhg%|3QlXe}EDIQWgNxBY>30AZ3pLP_{tI9$DKo zo)m9)`s}2%MgY`WAf+9GkUc}mvunw-tvIzfo6_qsx7_xa>oLcA&he1*F-9&^S}L}o zJ7b3QrIl{(_xI(}b?4UPp2AwuF(=F{&=VBE&4XeH5==e+Hq})MDlC^#C3t&Y*G}2C z|4#;;9Q*0yIJFd#g0dXAGqfisCBiA~ly>@Oc)bhWH{RLldv|u=PA=RPr2^0dOv{pS zl*&qq!?*>;;xNo5pq-jT_#l#0=Xs!A6i~GDA?_wPwDAxI8q%y$@&vX63vCZ}R_Vyz z2n(x0Sa+8L&;IBt0Xxz)aGieE{|S5oVLJ)0S9J5Aj!`{q9W-QVITcY&=28O}VS5u0ywW%dhxG3A(h0k!vZ{(*;| zeR6qo|M|1!PJ8Zqz`@6!b>Vg2`PYB{ThAm;)bJBzqy#2Mrc^t5%4+07X4h1y>2RHiH|K!rHT~i`Sq*SV0%2c-QYSizC$Luo%oZTrE;FdHVF|M8}s z@B;`1{NV-=lr)8|hiVBiptGB1aDB($`5xMku>`;ME2Zn5qx`=l5I z?k~pfuTH=F*Tb$pN&EBCk3Sa8_y4$QuN_&<6jA}#{W@Knd-7T?tyi6wl$a17U19lU zms(PZ#TQp-!FlAG*1NiDs=k`4OD`o`@dX!DU;!!O#f=jsa_GK&di=}DQPM*5%`?X= zy=LgqrBjFLrkY~1HZ7VpYEZAvc;k#xt45{ah8ZeFKXK=Gv?I?0UV(vzm=F^Q7MfuE z@WBF&v$*&7%$_xRzPmfa3_V8U#?s@uy4vHFSWe?>-4j`ajogII&J@| zxARA<+L*E*e){}LRkP~9>JQ6@!<%mtze~&9{pl$i7J^U-Ww{Zx1cC=gnNPKhV`+%c zw?u&$#m-z4l`IOHB(!uZDjcLqLNkLy+5dP;O{9#H1P;z#Uf571tOk?e%`%A>uYg2o zvd8r6^deLENJy9>NFC;)TaVd+7a+CC{vb-qMlpHC{blyOEAz-9AGlaHthe4^JdLt9 zN~*8vW@Dj<0x@O0#fAI`EgBL7CG|=V7R?&w6~4-LnyPeTJYA&4se=>;#ZX|j7%rrL zgN52m=$*2@K)Vou*(L{1@6arLiJ&mj7Y)Odd>n$=2r(RFFWgbSDe5W7idFg)ck2$H}`c{Z2qaGEY)3U_2^ZSPI1(}nb!!hLI{=bwv4h{#*YtY$xTea2;W~Z-lPSvf~ z0J1|HBV6VR(!-6WX!UstW39pY2y0#qX!iMQv=@sdOmWbmN^?Cf%Tn&+S*7%QeW zy{SL~ThXEJkIrpY&q91{nRmnUu?IWrVTSANrcgzB8K)eD;n=-45V7t!VG7J4V4`pU zZI^(II8}-sU@Y=+HyTQ?3CtSQ$}xS0C``5Jrqiw$YpKpVhmw?w9ME0fQ`EAEFpKA+ z_N9nFE{GCB^ws9ZtAhrN0b~aribb*5^C)>i6V10TYUb!YaHA!1C^XA-alqb0r-4RK zhRiamn1fj5!CPU^SV*v%i$&}lflFJt#L6VMRr0fLC4RC>h}mh~_R%M>^*SgMW#yan z4dW=gaB|NiYA=&C*d$FTb!1JNq!uPgIcChPnYHzx+e|)#xqPTJwbz7t;LVRTKW`J- zA^DMK7eiBm7hcCamSQD(@xT&O6$RY7RYGBm#@U7^5ZPmfVv}uOCMXK1$ZAIvLIniO zKkeETjH}0&7y0bSbv5skrM)sclqkc_;6h6R-rGgq3~;&1vSB+(3XvqS1uJPr0%^WT z0ts~;VxB#3%u%i9HQx16eb>@WUNW7@<;>%i2m{#am|-S*{*=~m1e!|Q+PX1C0}`MF zKKVACP|P**n}hD{P!5hfQHnLrP-5NUDXX3zLi={9SK5^{P_Py`u9XE{1W@w8)k*Ja zh`PR>`&ufQaeD?ME>3iL77p%IstMsnS;fAV^v)ri>A2b{%x(b(s0@UN=HOI=WK(+R zZk<;B*()yI{mpI8p0aA49;QUI^pe?!q=@3~C9Kl~nY9oDMH>b9ZkF3*PPEP8Ii`H~P2~ zaF(RodD?Mkc3aq7+F_f*Mx#jAG_8b*&gX9+Q=8OK+DkrY#fzT#0yy8v)MZ0~0nRrv zE6=0e=7rolCcp@3F+Zaoa5pkAe&!^H3JIh1hj%=cTC7mxxgDq7tnI2MFAHOAI>P{k z!mDX`EJMwbkJH|7QEdYfvIBA<;tA!uixG9fli%um*(!?l#Wy2Dqf;S*F&u1x(B`WD%BXrVln3n=+mJnKT*JB*xmr5bEkn=1;U$@+dnNZmkio1&k?} z#?oe4(gD)sdv(!oVJQq(SvAEpie6^M zf;&uQz12%Yr3exFO2n{O98OClvKD!V$V(p76lPiKa0k9j@lt+-D@Vmp8oIsatiBl` z1fLN|8D`AB0c-SKw~yLvzVQDe3{~;)fCR1n8H2Qy{tanYGTGZ1VltfJUy5DmFS}$=d6VZ;dPW2haUCUOql4 zToHNGEI-%v-+Z~pi&s8e>@0sCN-MNZuXK{Om(|5<6ZKhXgkuM@`D62unXr+E7! z0D>f3pnVMhij!XLv8tYC%UX15%gy|{`04A5aj`CDB|@1gHr+lU1TaHDMD5kQks&e( z!E3FZOqs|Iplz3?QNnzB0hj+d=*QD^o3y0Q#1) z02Y7eLBxW9!^YuRm(bR_+&#YWUm@l-dap~odnEn)XR-Y8*XQ-VbohvFj*<~RCQ7k1 zwV}2c_4;y6>1hrCLj!nvxG>ZyUI%_$24cQQB|th=O0296TU75{4{c2H;Bu8AM{
      yTyYzYwa>~(xtra`nJhE8w3*^yh)_}@r;B&ny{A>;!e9hJf96F^B(gh zHLaKo=JT1#@Q>R&b9#{uT>`M9(uScYZ)>-D1`2&s$62x1s4QPZ)j?5VjS9weC1 z)~N1=WtISu1dsz^mzD7Gn|A4+BoBbI|4*^xfl)z=s+MxZ2&Pr^RJc7 zx#WmH-Lv3u`B;Ds!2s^O?>-Q4YtClegm3nj;l~{|H{El_;mEv%M_>T^w(R8w<1n^K zISSSs&C^sqE@%6^J&!p&Ix6r{7{I=R2kE>w0}@#QHQW*=`&d_jqAj9?$9qoDm&Eb$B z*o*X3o?3~U+}e2I$+Yd2E-;N^KqyX;V8J5;S!dtMerMQF$qg!M6eQS-?`IETfi0}* zm6Y&!wQ$K3ijy$_(Tug{N}>Ks1!tTDw)-q!u#a^xHs zIgdiqOC-da1G+FHZRq39kIF9&JSed_d>(8b1_HhU>P++`PS6$zjwWXIw0K}Dl(U*` z_X3Dc@(7K%qeK~A&`r~ceWzR^hV;igbmnvJ0q@P#QH^4o0{fQ3q;le6X&7c(gE+mZ z-rCiS&_V$jBHI`~QA0^{y|4SG?x>@QV|Dz#>g=9g%no3|9h$R6cL0&Sf>ZWQ%O765 zib(6R;>ebbYgp9c`6RH z??1lfI1`CyW#TJ9|K}hYD?5EHuH?|FsJcJ|hI|+khV`&o3HU`xw4T@;95&HH^f-skoi}%CXL6h(sNvpE^W;!WolH;zYX|^Ge7d z%URYo1a!S7%aYPmcU*NJH4w0s_)uU9k%)9h29`;ZCj$hEj9DZJKPP@(W@nq!}XkV%oh5Z$xIzKA6rfaQJ5g}uNwz-L|{vi-3`r)z`2nn#4 zyuBQ(%JZm@K>&D-Lan=di%meC^HEwur{gxLa~E6b4lqBvGj2i(T9X=e*mD)Eky~4D z&)f5g&lLz8c#FiM{a5#99wzvIKO~%7%RVaFMy#B3w~3q^kysBo9@Sgq0pXy|i$g$N zb4jVB8itdRF0i?QeySMu%O`d6E^3tmTlGes+dR&Wid1X)O|U<~U>0!bX&&f8m7W=} z(e1zWOhRI2jj^)~x`B19^v8EE`GtN&brQ3%%^OBO+Ca=yvdCufOBT9%G3g44 zqDKSV+cnzWURP&!DcwnSv+Lqq(z`u+;}SvcTURzFb6$n`SeShD)5-{IqJKnd++J!= zmj{~Th1=Rd+*6hh?wH^=k9JRkWjq0WOHy5XlUm#1%&@<@ck0k^Q<#~3kqHQiV~Y}bj~9e3G(F*f3KI}Jj$ z5ov1llF~8fU{Sh%28#y}ebk*d&a(mX%%^AcAk0#NBFNYZBMx42w4`*$ILigG2E^>S zwnk^C*&{X6&^yf`(dUUdAk-mP)(GaKpc9)$Z^)w5u3Qe6dF^vcXEUdM?c;;572A1J zZE#~Vd9yYO&QukdPE$?V-X39*g6divE7f*=K&gW5J6UE_54l%L-3jrpy0UdvS3`R3 zyki8{BE{GEN^98T1c3c;#})(r*g?pbV$$vKjPUtW3t*H%b9|Jf_*4YphkX0NZeL-F z^cl_#ayGG30V7g$z}Q1`$hU1Wa`_toVR5L(xi&j6s)%n}VP5TMZt2Op=Q2wWiK~C1 zdQrGJa7QdpE|qz=z4)tDjqpE$35BV>ozy_@mcJgjnWv~Hxb<7tf>P#3YSq={QuW8$oEZhFPKPKzN z=5WubKUiNQs5#mjS;@_fH2wh5FeFW?p(05047nFolh|Quh!%)Lu0MG>hmuShB*L*n z9bQDDzUN2wlcx#+AKxvSl7tSaAC*V==20z&V-Dg?l*H~$=9#6EqIkDLsJXCu=0;8$ z7Al!22F-j6lT`1+oGhmju?Q^FNC2gZWK<_@YjKrPaCI;xa)YE=UFKnVjkp$&C}L?H zRY?}#e1y${CHbi=1&KVJTlk6u4+ZieXzOquxOTGGxkV zY~ppz7b%gb#u!<{s}*bP40`K2!7zzc9S^{2&%TB>$NdngIX8Z{Q7N2qC+!$Cbr@;S2yo9 zF4wtHaJ7c8#Y?A%iHl=l#e$M-7VGZy#jAz+N(0W8A!Bz&X z$N}u$O8;@}cmwJ-mk$~c3}y5-4lVyYhrTaZO?xzgDd6|j5D1#2FaKt2v|hd;m1^Ja z-c~A}Y}BvE%TOOapM=HgUOj(NDDQTug-{!p!(zqOJ2=mZkh!u~A$*j5x09q6qt+dm zYHS+{skDwic!_y;LPEzV87 zI<~D+4ntXH&DNd$+w3)SERXiygu`I>v@3khfz)AG+&YZkS24uq<-HxwtBXXp-aC^e0@OAtpBjL}x@7Fsm57qMhPMWtycUiw+ao(Cago?2# z$Xb|6w+k47-8VV}bB1)`71HtXA&1;I!ql*xxCJS=zzn*e(eWUe$g41rG!xgCV#PrO z8I3ZzoTXY2s=0tU_`OLeh8&}MxeA@czMX)}17 z2XRV9VgMas)$li9A0F}v1cD&voE)vO+J%ba`z@&)gyUhE4EC6wa?De z=VSl=$-(c2VI&*Tah5cnJ z5}P}<;xE;U!doO9UD^ok>AQQl`&Cl#fCIp4`|8K%eY-mAjT0Al!ixj|rokQp4SoU~ zI)tn(v8rHsSM05_s3KNWzmjilUUofJ@Dy~>SZJ2Ym6mJWlH zH@d|lT;4W1ti`WRFy3{EEVY)8wo;P5$Ld(+x$Ys@nJ(?Os!YT2&t&)GW7$wVTc^&i z-R3Ty6|;o$fi2E258crGo4{C0wFxZ&Y{~!-M!ex@EtLt!A1}JCTK8}HMez!|uR1sv zlo_a`9I;R;gj|&>i&hp94#iy{`5`h4=*gV#!j3&p3gLu^RP=#o;T|M+Y?cfFC94^lAPuZ@R40M5Odu^@* ztLQ=aib{h&X)WvxwQ%s^_Nfosf92&0_;)#nAn}W%8xe#hL4ZFJ@i@^(fEJ^)D0rmN zC~<+>jQ2a=h~lj`gdx0IH(n)|k{YwCmv?3)UAuf=sbL&JV=bN}cK_!aqUIChe*`8==u)t*9c;&9Z<;y$8ZWJmv>-oa`?gj7> z0y+4$T#i%>*OFZt#@_EwPV46y|U<&%jL26`IS&bBMrqb`aN{~<}1}e zyOmtH$DV{Y`+ro4iM)}J{X`8o0h>AaDp;n@J%|ropUi^iBjJdD_0!FUFX;UK4?w)e z-ES)+BS)W1|8wH*Qn+k-?by*=(-QuRa(3N*(v#V_21I5)V1W)U$7`84-_5*{JMwz3 z{8mqk5*7{m>37~q{~_A=$8O>6u7$J|%GzCkrE9rneb}#uOf+jRbt~VPDhe8*0OP>` zoy&xJaeV<`^F}3uMkCZSi3;nZ!LmL0-UFa9!g~w7TWuH~3n;?&jb@GGHDKv>$`Q=6 zeaGn+=y9Nkas|;?*@jr%+oLdvsfqzR`1E6;S9dg*wjo|=8xeX3Y`RvoKO zW(76qrW&Hn@jG$={ghV|Nggb8Kl{!F6k~_rUe10a4conY5N}#o)i>+8H6?f(Pa^4j zep7`=8W6tor+;|yP1NKjnG)jV{Z_-v&S1Eh#$o97MF`=->7S(aE1G&X#f@)iI^ou)AZbQCb$2I-J{3zhGx*9Pwe~m}gw;g+WlUu&h!A0hT#9s!`_jw`#kp zSGbcCIIq=JK}l8)@WFvVrTR2wmzYT@9jAT{|FPeTXW1g@R$Iz;+YB41T?$HCscizP zDe0CXwRS>5VLL`wltd3`sSb4h#wwK z+>X>nM3YyI(c^lN;deZP&pHz1?7NV`>LKKN3f>$}Aw_i`7~UP4So3KXh;LSzz_*|DoWN0`5LA zdroX*5{73sWsID*hoOnr6daE2k7GxW;qU}!5XKQU3?$ZHa7ZIli_<0Va9t55Cd*3> zSn1U!2sn6t+>x28M#Ek`V!hUf@vK~)Kk689ZL~7-_-LS5AZr5(PGP}HV6WXHpCsHO z=*Le5u;yg9!jjF%xVWD~PJmFKpE$JpR;dh7Op< zwbW^`V=_zv@ov(76|kaNv$Rt%ZUEe`| zn6P*1wQl_8sp+^Nnra}_sp5c0R+1Kc0oeqUrVqLL^ZT$PpAdGG3G9lLeu-9U>&S$b zso;Z+KG9+mQS zC8e0x;cARx!V)T3x?C3I3sTHszj>8DJUgRM>WxS(+njLl+9Sf|xng{K&6F~)W<%nv zUi9-HQ*j2vr;TYOTcEd1|Z9JB)m5IkZ;qb~SBidV9g`824r}62Jv(M!}^|fEz}! z5R`>smoh-|dNJw5&wNf3jxVGvSFnVtp7^Iyu*bx+SJ>PMJq|oWW*f7f3fu1;v1w&2 zy~JR>Qe&WdZRPZ>_091HT9)%brADW7A^mh>8&Ut{TUb{QuC zgRqSZiz=C~i)~~&#i4?3;Sgvb+!Y`4CA&XGqwf&aJOjbNXZm7o$qUsri%J&20vM_Y z#loMR`BO(h|GJjQ&);%jwx4=@?&gP7zZWl#QeZ@VW&iLJHOWn-q`!o2AetW>QerOg%nfJ!{QQkyt5|Uf+#Z<7F~gfKxve2nX)+^K!e^N*DX}0SM8gPhyV7`H z+nJ)thIdEM%fd|#G9Z;Bi-ly|$OsTEOu>bsAQOX?^mGj(sX{er54Lv8G%qj(7Hyz* zb9Ipem%dVHWkmCQ;l;dmBo7dp=TKQH6DxA~Z95CZf!#DF6xowK1Q>*p&_*>Oy!aD^ zB3|@_oen#^xE--cxZYg>e(|LE2FZkvk~!kk(HHW-i0vSo8Tfd^PE>Ly*BLegu8t$P zO}g7n)A!rke*yx+@M0y`r_CZtaHZbZZr}#wS*r)HfEuVn{IGVH=dbe6tBAfSyVrX0 zM1J(?L9XFK4}uP0x2IO>xM}KkZY#Zx9Ln}URSo)Rn{F`;;%GdAeH1w>k^FB(cQH%R zYQx;rPhV^g+|=HiUihY*pS-;~KV>(x{_L?qmXGd53hGe|tA&@L0YP~+hx0_|<5WZK z8rBTn@KH8I{+_KV%Ef<*N&n9vk>W!tCSwGMN(-aU-aJ1u$Fj9Jw?dpj*SKyvzFUSc zOg4JeUp_cpG2Z;RWUfAW%{h9r5V}E&8z5u&YE2XCFpiY!odsrN@d95}Vp| z%}BZNk&@?e*NvHmR+U=i>t8&kV{^p4KLuf?l-eQ04|}mJ^PB1^RMvpW=`&uuZL&LQNJ9hp~D5 zy~_uCaj~ZVwN9Z|>Tzg0Tl@M}5xu`RUY`5+xMf0E7XdtqN3w&4bRfWLk1&eli9@lS zA={zQqIBW)i`>z0Kzi|}dRYi!6#69ZXzM81gb@Wm@0D;b;rdD>vP-w}=6ih*)&o<) z(7gr$ALQdhZq*A(+0G1o9K54lhbW?UtWV%EzQZjhv*`8WU%i%p0WLSte{ld;O0y{f+WrD zuWKB1%E?Fn7#rczp^~%}d?NriSdu7l^Kwa&hMDYBJ zL5c2lmu?LupSnF9e|jO#rLY)~`OGfHKU}2->0Q9V8)Xnu@%rk2Qaf%{Mal?SUI?U7@r)FB zx+r|j5~z+#bJF4aduqDY<`}tzYI`k^kXx{BEGbsNw6>J#WW6#WGN$x)xL$Rv9$}dw9dlpf|-pjE$kGUg#~%I zSd?;`PYU=s^R;X~7-7RFEx5B|gtXE#XUhxxz1!((6oN1m!;l%kcrxb_8t);;O(#QS zV#zKc#ZI`Bym*o}b6m!O^D0EM8Ak^!oPZG_H@w*(H^65Kr z6AGLp@m>MbQs$6WXK_vxb-dz2-%0n!y!FXbyAHD42`)w>VF|E2KII#Q9Zg~cW~aN^ ze2cl#niu}RkB0>AB*nYiCOF?-x+yz&eMh?{iq~#wx*Z(Y6wX>L%cjO*P22V2o`X=EVKnus8)9>>dV0uDnMZ=1yY z?|%Y_by;&Br$$L*|D5}6`_3|pkrvnI`O{A?E5X@Aub2uEZB*>uxX_8cZY0a!kX7Ny zne#6xWsS-YO1-Q+Y>wv6nSxx%AN{72PQJ?|`To00^eu}Gn3`5~(ZWjGsYx*njhbda zYt6PcuuJlukLOHo+1n9G&@%eJ+3()mLJ-xW%GJvg>#5r-t8t9LMJc&uBWZjYPa5Be zgmK;_snq|FL#}3~3}v9R4*HKJ2+)J%5w&OOxt%QfNPYWkbgrp($Yg7`-}Z>tUJknumHS59N<5kqpxKN0m~`s_ z3|%jg&~k0ufDbe>bBMrel1?W?Fu4L2N(ErnZ9IdJ(sfyp3S{Ws3 z!eHxw?wAdJd(W;!lCabaGH-=r8AO>kj}UKu?J-zyBEoqHf+;64ctqYP&BWh+R7S%0E7MH3i ziieX9>(Rk5j@`u~h&<-N)Vbo^6*+omPCY-|D-ArOXdtyKaZ)N(NoeXyq|va-g5d6 z5&;t3>84(P(9Qqq$Z9^)yuBt4uc^;{vX*)0_Dbdrl|%6GZZG@Chdu3=M+e$7W-=a7 zrYPDd&@!(<8GK!KP`3K4J<>)w?Y^lfd#6g7_Uw;CZ~s&?&#lGIxcRkJ=(&1#I$~=n zP~vvceRup9KOzXx{MP_f5@2zD%adBy`* zFH1U`b@)4phEWfeBA`)58#6bZ(riX^?Ni4|OJ<6~iUXqL;m8?5PFVZyt4^%0m8bmK z4Q&j(1fs8CH6;2-dP2=*o%&pp92;V7ua414{{b7~g{hDYVHvC8^m)x?lqiSNy}7n! zk?+@}Z@mU6B(Jkq?d6y>(t&o%2<|67$&bQ!HAj_h*1#yS13CeU~U5 zLm5tRu2#NX;m#u=4V6$&dS6T@k{lZ$wdj{%_>OyHmCI_BIq=* zCUsT@UO&gFQBoY;obb^h@SLt_K*YW%lu2sm;>yfc<+46y=G#31HUwBb-?RCD5Rg(@ z^xgNec+_nreOiwYV*P=hG7+GK3Ek&jQ}HDfO;w^Sg1<*0*+F|%HS-mzGsP8mZ6e^Bv~+0 z=7YS}ULw{c*Eqo`p3+h&i%7faG%a*9YgGnCsOSCLtRo?ayaRqG^e>|)DFC>IjJwoC zTF!6XZc&gXiDzozkt!`IM{mt?Jw<0Hl&Xco)DD)q+M25mzdZMvC_uI&ushUjD~R7Ptuie$WMl#Ka9$(_S@w~n`pOofvF z>rw?vENFbVW=Fmd{@bArQzMKC!$U!LNOISMTG2-cnB;dONZnI6w50lIrE0XRt*|P6 zb=qcRnd(u^h$M}_wyparUEb`_24_iRRYj3e{zbbiL3QWbtmb~3IVVm|0z_zmD1}#d zn+tZ++F(*SM7Ljl)lU47EOCs#P4-;GIJ`@ zW~SPRazQ8U~X3r-Kt5&+GE}t&qZme0;uo zWBZ^l_wFeTZm+3hn)JmvF%C=d4W0j&O+m=a?-eenYor813xXbw{eABzI{%eqn7Jgr ziCljfzN5vl?SSPTgP4M~H>_f*n!YqO6=fS-7iP3DWGU3V?0jzxdO5lJNXd^jWss&C zdID=z2^Z!{lgPrV>4>`N;;UHu%PT$n?DXRD!QP=yOi`3Z@TczD-P-3L=0}kA3f zd63aV72(E4|MST4)YNypFGP)+2`x${)WX4?`Rvkou~Tyr+jR7F*m`O+ecTF4EHW}` z4II_4SB?{NbR`cxsyHZT=YO99JmSgl)T7mWynCm){dg+B>BPj9oVHWSMVsU6+NGX0 za#7QFj*gAak3TV8U4Z14PufZTFuC4a;hE)0S3ND90~ zhsjk-Pv}+;urZW)-8jL=90q zp%UWf2-pIW2i}5@NZ+UZj{P)t(&q&GZxRB&PO)SLZ|kG6sE1WkK)*sFKpg$~w+kVe z)%-8%(96lk(Y4K+$%%f3V){;fH|l(%2fWc@%EqjRV*K=Fz1AZ2YUP ztdEjfb?`s^i0adqzl&%>;cKCy%fcTjn_6yu>h~)jc8^Q9RR5%harJoBRLlx->VASy z@XS)psJ~5KFFm+2(L_~4vH7DU?{?R72LY{rU9-ccR?Oa@q4I#TBjS{SRqSH*2>ehO z&QNYup&w!TK2(Olz=cjG`sekYQ>6p50s$ytHbAnOmUO6bgUKNiCVURfq0u@a2#d&$ z>5mknG`by{x~XOT8%c;5?&-ziUeX0Hf z08E5Rdau`@kN5tkSP-{lmG)m&tXlr5{|D&!7p5Ax>u{_%)@%=gp)^L29MeP!;{TvM zCmk#eMn5lat{)g}4qsapTiS(vzZa&ishaT1c)Y)}UqjFyTp^X&OFfk)Req3zQ^Ld+D{|^GXF2gOv|5GTad3I# zm?O>tY&h-5rFzr+8X6Y&P78|5iT>1PPGUjZax1C?q@ywHwNtm^B7+_I`DFa*wcr)> z`rO`IZ{2?Q=EhW`Ij6VnBuZ9hBB6b@rTMesZpd$%8<}z~iUizwBHTYvNf38_^6L2U z!IVa2sXqG~0|_|^Otq)%r60-Z?C|%VhyDW?27`G;B8}GtnrrRHZvFJbH`8}jx)qW) z;p`0!i@%iW!+QI|vD#8&?bb)lTe~Mbd;UVcxKl-^6`I@ag!~q2BMP>L6;23n*Ku=R*@l0vh=~K3wHU-I^*D zJww32w-Jb6!PT}yFY;{PFIP^af_wjnhx`0d9f#AifjlI;X-MeAB~{b6%6&O0kt2TZ z#WQFZm((uQJs5$zS2`PfS7BM78MEwpPtEO8z366Hr0vT<`&zBs%%nLm6C}?)Cdq3e z+H_JCY8;m;KA+z^-McQSY5)QfZ(Qg+>co)etP^;DwQg%sdUBztNR-^ZiVzhR4+y}U zp&>Ae0BkN+-7GtZV>Y;4Elckql7u98ws!Ry!1gy06Y5`qO24^WX)Se)+Wi%V)GMp$ z8g?!#eX}hrHrcx#)+9qtij~guIu}$9H!33aGnndWQ#xa|pxK`~o{_EFkR>G5)=?pv zsniWS^9|ykHW+Feciwx?ES`=(`k$OSj3abMD*K_Rh{Z!kv8wyETJ3@od`Z<_{PrVI*7 z0J%krMJwAl+^L(6+w3vK8+m|??SFZ4zVpZ4;C6Jbj;YQkp=`v^C*~I2g4R7LQSTDA z)VZrROhW$h1{2^|DM)xLqag5H<0Cw*M5{s~5FpB?Qqf>82t>FpB%1=mPOTb6+K;yz zbH=}R__b3Wv6Ud4ITjS!|{OE$rJt4o(W~ zUqiilXFO)YmtVf{1+(!Aocn7pID5rEw;g*6+PBy{n8ft@AI6YBF0Gs&L+6{-3!|qj zf9>`SGY~+dp|ijgMoND?(2H;-Ixh1{Pb(82a&=Nw~BKXEg-f;)c8PmV~d6F zp>17=9r4|k1qPui5OZIo8KvRXCXpj41xq{}IfGS}=KU1C72)pTZ_W(Yt6DhKz^sZU zro?x9@r`_Nw=p0Pg)nh=@#nJcnOp=vM=cndG+C|nryr6qayaRZ5yL9B{r)-Y*=x__ z0)2EbT4#fkEeAKeq2$;mPbU}Pqze2Wb~2NKaL>eYE(QR{0~M6}(P2O)#O1eh#8eQ( z`6X&^MXYR$@Wugf@-WZYk%|1)@E?bd@LmE7ivHGxO)w;!ht%thw$-)^G^;ACuj=P@ zGJ6qY|HwcIn*Cu{>C!YCyTI`wKEP$s&77mBsX9E`xbTJr(uOv|Y7OMVKfY}T5Misq zPOKO31gyYe;wuT_ahrv<5y7U5MeKw?nSc+0CN&8F0Ui#OXqCx51&F{0`b!baISs)V z8iNZk7~u-M0{K7stGIeyG;eRCC~6`3b%U z1w0E~w}ihkQ{FA`n3th4BYEd*3A28)B?Z{>h~MkO9*D+@C6zUSQ-SdNn+P!zgsV_v zb2R?Tv0UuVl#1zg4^#}aopOJUlSe}fHXdwrH{YAq`q!DSL)`&Ds%mv5a`^wQs4z3I z%S?3qkSUU7AmV2JAt&4J*`3Q~_?dYfpk;OM!8Jii<7tG#`ZlkWKoyNkY&5PDXL?s9 zs1jq#NiJ|KBt7Mj9A6OXw7zZ+2Bf1b8Sj4gA@XBW{96i-b?L(ve z9@7YH`TdTB4jIdKw0GN1z`M8Y(M8E6u5Bfij2G%gMj~IP-NO1dao?94JL#+$Y)J%J^ zz0fzh^L3oZH;sHwro{^9kZLqNt`wwX>t=7T1GxX^x9Z#)15bbwre((tOm z(o}PwN?casVC&OsM_ogRg?RLJ9E%@Yoh&**CKE%?`5|h6()%_HsBCAE@-SUFxCDqK zNVefQK^Tn~5FEMQ7r*UP$~F%>8Xtv)`=DS((XCfn7nd(;3Z#w4pXwlON<)I>mh=Ut zf=B_NKI9&sVM<}}G`;R}W%Q#em)JB5k+E}>UrDN5nvQRCLh84P*4U{nOsz#wMMAAd z66O{%&|v%oWlBejRqbCZE(9>rWP|J=&8os(0Y~Fd_=Xm}PL*D3G#Byb#H67Zlf<2x zSL+wC$+9M`N6$VHwt7B2hg8eedOp-1BUi!_4~eLj)rEpJBA7HA#j}p=wy9N3M`8Jx z%ZE%0f0CwL2e~s{nu%eSVj670XveG)_ZjwbnNjax4wt@ zONLa@UFF$xMv_E$=zETI#O?Gh47ZQ7YvoxfApH#FBH|4JO)r>4F2+PtxGsk$0OeZ7 z&yoN;K*YZRlNg=_7@>{8ahuecCEYA1I1vs~GzD9BL1=a)Bvi>k0u#kU4PcZXZNgEM zDocVGB8BVwkNN|^D>(2mLEashCJ56?ERO51N{{~5>vsSeKWtB!WG7C+LR2!)e7SyE z-lgUH%?FBml?Q8UKiCKlPP?*~ z5l~s9G8-EuTrm~NraaYZJE`;Q%ME`6@{>qcjCNo#$iRh@-VSouM)ztPl$nVoLUhEv@Q;otBp37Wf@<82-g6O0IZ@;EU9Lq zDXy%er*Ru*gU&%uDEjzFQhAVALM}Y|#>Q7`DBkGKRV~}7a}Zg;wKfmSHTL%Cq=NTp z22CXh;x8c4`d7sVVJ=N^+Ew7!&>=wXz?bR+wGO^_g?wfYqQ#~3``o1EUd`a^9R8A5 zz7|)@O@&ILn^!mYPV?h=kj-l_!&kgFEiz8fT}NZ97ZkA3TP zwhR`LY?-MQHx20JEDr~rdp*kfTv|a#fOdY37laNs9;Jv&$wb55Y8g$sJ;4F70T!QW z#5EB;4v|LMEKA|Tfhnu2kWkCly`)@ia??yjH`p2?7t2y(Ar#h|cSEvX#p}!C#Ft-d zr~Deh$BhUslYviBFyYkRyE#!(Y7OeYdDob$@B^qh=QIr~MPz_P+~n8dom0uiJYRiy z=8l>$X+b~iZ1+&0aa#Y{Dk21MXhK-I@l8ir`+fjrdnB-knU|NbcB3=^1}!fbnBYyH zT|wN6XUz!wa0b9x9?E@&hEDMN4>tJlGWN|MtJ9r&57Q;^_GP@=Uq3aTmwhM{?P@f+_!9JGqPU;cAv2IXD2X14{yCZi#kj_V!qc<*0Jb*8n-Yb}uXl1lexCD6+q zeoB9-MsgSCN{ZU=@r=GBG9n>ejQ|LTc@X161Y^n!M#-bldQK&-1=Jt}-39q2gmpW+xLeigtLVXf7JOIDbQ zZt0zo8Wu+ERXMCUlU^pw(jww@doVVbH2Yee1%JdF@W0K2^M(89{*}P-uqbo*pc_T) zQK2qLaGN9s#EV>S90RG=CRg}2MW|X-LBT;dL)6TaJMj`yR-B^!t!~F2w7j%A-yd#5 zp@ZfJzb70)f5J;QR}1wGW(-O5Htd=NW+nR-vKEH(K7Alj=KWg^S$MF+lD3wz;oJ8rDT%UVVoa%^*s?Uu3X6?TIH5>?k$ z90t2}=r^;oUom)C@C~>u0eqvgEmhTh%jz}60LwV^-FQ{8zaM<+qK#S>XzgAtISZBP zyq&K~@i*OPdy|HZzEtLGgYBh>bjN#+`M%pJl_oH1&G?4>tnobDb}pIScCLocHci5? ztFE1f9{>L0h*F7Br%MtcE!-A!e|-w0qgZTg&{rldXpTiW$K$=0{;}U(XmeYReavQn1g=GIKphqbb(l8}q{!it%i_Dms(->o1A-#(nd1 z2Td%G~RJ1Gy$Js+Q|0e_|xj{CyU~_z1w%Wr_E!;sy*c zi=#^Eo(>TNqfwx-f>rYj?Ov>E*2VLyGo#hMctgnAR=qjvNQlGsID;Yx!%Oh#sU}^k zgBDVoq#`AoR}d6slBYS{mKvqY^cbD=8rsPP>ba!m@qAUNx0UzU2r9v73Dw3&S&JPj z!^s`EiW7C*7)w(RTr6zW=cDOhR`yD}F}7M5_AiWfdM4EkSG-{SuZYWcVcL$R=(dTR8uyY zGwEX8u1GMJ$}<<=__l#caN703AUng9OlNP-m{8La6-O9oT&k`^PC40_REF~)4oZ@h zbP#;Ls&o&-DXl=0|5>gdBaV)eBv<(Wzc$r$%6{B6ol3syOz|iD?z_nG4b@7UyW(4* zf={J(7OXFoH4IeP;E(f$TuS}Qi8H!Bl!6ySF}eT3bso({ag$l3IxlibPvhe&U))%| zg#p2WN@e!{;if=Wdjf*8IG695fIEpw`}Ru7mQ~d2E{tU%HI0K@lI@35NuaJA+!FI% z5^=>4p2C#^EUeHgm~s5YJ;ERI$3=nU+3Q&jzMF}@OqMF9jnu^(T)Ks|>kWmjga#iW zK?__ZWL8`fw(3||bDOQ#>;(Pn0D1XIT-Ce8^f0kf!>k>~QpDMQmwsU6YwhYuQN5-k zww_zRi`u7lyKGcG7(KNxxiKDWnT@aTf9NXlw7dVz-dh3AEl)>LCp-CE1wrtilRshn zx&0?YYk2v1%%jHE_wMo+^V`Mul&`*MH9xbCv9a7gM_YKhTH`(6Sr$#%5`{|-MXPfc zzA=sZ^e4L;BW7b;;k}HO{$^rVKCgk%Z%21)Q)rHH{EaE=$P6@$xn<5B_~ytxt=d_4 zZz;SjU`l&i50HoB#!okH+rN)6`8yI~6udg=ZH@b9Hy>lXYnne%h0xvknNY8nzQi}2 zH0}+rEm{x$%ow2^SKN9U>$GOGdD?uNuYK~|ID}{%uj2Ew@Dzj&pVE8UGh20Px?5f{ z`)7;>jdDyUtuAb=DAM;C<5bdPqh>daQA#qHDXAcvq3&LMcXH!6-YVky7)6U!>D)d} zvMhJua)~x3fNn6VoR1_3K6|DhFbxmD@J@Ye`W;guIrk129G)oYy-1wj*rba&mO}qu zfP_5^ZAA4S9}^zbUPnDp;%CXGf(5&HjwL5`cm*qQpicC<{%3@gW-&goq6jc(84VCN z8H_#b5Y48lS%IotCln-W0F=ZdDx~NKRea!em4W z!7{X-g9m9vC`h@e3Pgq){A`e2xU|3q)(gqCy8Gq|w%@KoHo?P(!rjc7(l8R15H}w* zq-w5`mQ}}Z9)gwd!RFRTmDWa`1 zsT)M>zZ<^2L;QAQ%@0YaCJ3!c?vuI-oe6Us@5^OmvEc>e${e$`hb`inkB~63hJf}J zKp@u-g=t@Z-eVSV;Hi{-Z9Ycv3vP!m9YHJ(>=sqITph>95>oc?r~SmoeF^aX(CBITF5@dq5e~paqSbRYo@YmuwRc6J)HTV&yE3RL+o`5!$b_ zx2Lufw*!eN&zTV4{N@@v9P%FzFc$L6;o!=E+ueyWvk!!&_rqgVizlii=&hS9nq}j} z=cODjKA&S;xaVNKRu(xv6(+#~?-S?~o=yQ+mLbGTKc_b(Tk=IF$}_~hO{LvBAFKq5 zRgoz!LurB?D~Eue z-Rfn3ccVtVekbv_AB_C!%ARNqJNcZv=!#H_+=e*k>XkE0jtAg@TAV^bctuT#>MUuTq0jD~ECm>SD%#d$4 z2T31g0Fh`xo9U_)iS31&xa}{_va>Ma6zJJ3%VAl!!SRiX-GbfLf$~%L9gf5u-x&sY z3)=(tk9{U%O?_yT4ii5lLkQ8cZ$vC<=F`#0j&xi*Zk8T9VN=T#KzAQn+u7OK*pOzf zpV06V^{CP4Ds@hqYFBX`_iz<)iFxd+2iU}wO2YyY+x@KwmB8Z?@Gk+eHBp*X0xW|zNBRlWjRyHhhSu0 z9f!%~r@o;hg9$SiW?IEBsrg~VlH%8T!G&TK{&_Ujt?}sa1Y^dq!Z}PiK`0f`_v6_K zaij-?X;_$lf~$MHvWEA?p31voa3!V#5g@>-L5)1l*!`5DgO-a@biUqHYTRArdPY4h z*gl2-^%2N7=4ML8luFY15=o)$m4cdO`AnIqaH3kuDmSeBRR#o1dh&ms?e@#yji&TF$l+PeatEo2VPxt0^()jaKLm;7;*93nb zGs*jXfWWy+8)Z1oHyd|oXB7af{_7A%V$51;WW>G_*;t;`SQ2iJucvk;fpGwW=c7|` zpw(FYKW=$WdotiUs`Ey^V5&2@+Y3nOrskg!79RkA<#5nA&$`7;Xm!<$vzF${wDddg zpa=yb;n}I#(y^3X`@l#&xo1B=%&8qnfba2GR}ZoQ;Om#rA*QU}5XP7gsaw)Ru;Z(M zh!nDjmG#*Y2(R%W|L-G5)=ry0A}vo??}eNh5HKp(Xrz)4-khY8`D0;OW^{_22r}vH zE!Uba+6fE9=o-)tiZWP8ks)0Qh^WH%cI)vsQo@Eo|&* za5JF9OAxBO4fZg|Fmopaq4=0v+&xIrSgia=ijS%;L{ky`bS#4P`PH~FfLM0?N%3WW zQ80n;y%oq+d7KKi7zTDgtQX{QY!wU%YT%CnV<6wM@6E13y5&LJI5^~(frJ>O_&vr!;}Ewvoa?j9P< zd!>y@Ag!BJYW45)EFiMfXlIBG#ybV1-ECoO^q_&WE)#2|(zJ%yU(L;r$dDpoLv3|b zFCI2KK5rFc6_`&7(#q&1U;z*o$}K_z7pLzl&Cc+US7GT)|5XKZ0=uuu#nT;I4|wB6 zjkmusSY1T))V$FT2Y!Ol!$9%r$Of|~L+A5qtsDcpdS4;d6D;lMOA)pSmk!m+Ay#7l zGMw}fnB?5pxxA4_uoOypPc|=Sj96mPk*2UZO!>juTFA4dl}d;p4)oG6ze&iZ$!I}l zC16?trW=(D5s|b|Z(A&d;AIT@(2*E2WQ0^!X?q=rGdG+ghcpgLC|Fcl9Th>pj+t$vz}mN}yy^PKwNdG(=+{#8 z)(AIy*Xsy#Q&1g{mhv4&H3W?0G!d4bVn#A&H!InAqUKtpjEFY6sm%PF^Gy~v;~?#) z_tJjWswq`ukjNetHf&v_l1}DDfEE|`BBU+QkQ5JW#AC4RlV9oq+*ABUc&mhW;AbfYFD8!dX7HV&5kim!j>84p9)rrORLy>sl+DW$z~Bh^cR{RW zEX`PziALACl#cBl61ptH9mpOYhg?pE)_&1Q80UkdWw-CV`)V7b8lh=!(5w#bzW18I zlgBVNiL)YdllT|D6S2n5`$-!tjV-+MAiCRQTGG3`REAi1t~DG6Q}}OwGZ-)O3VDVy zx*_12oC?W4p~1=Nw7vyB?;XSp3u9mrB0_ptDyYDaQfucNkTz77miTFwAy}qX`E(U5 zaBfo?L4vR#Y!jJe3Vd9Q4g5%rYfrZ7yvKZ&EW>doQn=-uYoESVK2 zvlQ$gpp7kBYk+kiso{lpo4|ta}OPB{-3$y6@S`w9tuHId+4U=29b@>TSY1eK< zPg$FL7Oj+Ra|X~6|aJO#y4`MJ#cumWegICgmP%(fq{!>zha20WENO z>6I=H9Q$AE%S8J%Jtjo*se!kj*6X9q%2?c5eRG>^-;Ea&Ln{vE{-YQl9^I;;801?Q zI!lAWtk>C^4%`E*8pCG~1y7;A*4&}$EPERgrKutX5p6s5iy063rBc2|S+AK>fy!CifOw(>zxdTF~2v_+GU(a-aM59qX)o zarNlsr{hvUB0*pqA$ndt#~s_O#!!h}{%Y0doYmbw*ZQxoRuW+G<>a@Drt}oIC;gS@ z#JvY&#N9zyea=Jm;`PHI(KVy^24QyTV*hDOvdU6=*xPNJ46llhADq4|x-}E_ApZ1I zW?}zy%@Pjj1(tU5fNW1%$j$+lnz%KvB#IOhT5{qqJG==eI*0`zvlwP`#EKGmjaR0e zdA$U)-``}ydU%xen6Tp3ie=r9V@dV`buvh%ST@R@-|CG(UgxHw_aog666g4x^CPO_ zxnQmG316Dg)wceuc!;|EFy@dg~f#);9@AUF}&|4qb-s8^|#LkV2QbqYcRv3k%L z-orJtYTMG-D|5y(tn@Lg|5t;e+A}laCYe9Hk#6tRZM_au2QyTht+xdc|C>e;r}rh2 z14e`l!i?8jj^fj+Ds6Rwy4V%!Ev%XTLtW@h0A-yY@ z&jsX!EzJHL0(N%@%;h6Fx#DB_)XEMlKA)a8(W4wa>&T|Bv~Et)Nm!}*3!2u(&#O1?9F2+zRbf*bCpDEv4 z?ekU-$-Epl2sILGH;8D(+?B05+m5%UBXrsn>6@;~bvsLKmpRI`jC3EomLU$Y2FzIR zLM_)d_la$IkvXyW)xg{2^L6F1uo2G}CtwlZE#kUxggZ&Vf$hsxqr%d6&wodj_UO)8eLi%a&h{UI_y@3oSvCUW*{q4%WrCdo1HnT_1Hc z8Gywz_kHamGvZ%wYq+FHfUcZITI#>cn>{Srnm38w@XHBKE_?s%N|K7EyZQIEj7>_7 zil^Sqe4tV>a49nq&WyZgQsLw4Q6POKnC++EmF09G(Z;BC{^G|LoWBap%&c6xa79Nw zJ*Du{{~el({(o5vH<)>I=Jd|w>+o`V;n1)l(hurWk=j6kyjPnE#30PsyOXmyvyc;i zzcHP7{GHR*(+mGne?Mn^MYo@vKYdWKpI=#6{c-E|Y{bR5{_QW!ug%&JBi>^KR>$@y zKn+`Hj1xT0ZO$zJjK*2?LczI)ZN-l*q)>IGl<7f_OwzO>iY;AQ+R2oQc&+s}Lq@ld zJtt3|g~8=>q^VZzWz2N+GS&&U4GDVjEt^Q(ixCLwXr-&z(Gz=6yRv9P2z6w(jNr1_ znc1NTWw>&+=qiQ3wsmj+c+C20{MNzF279$-a#!Mui{e*rY~OlTcNgOK?ySE}EZtbU zHk*;#u+AO&>ekT%|BVe0?6s#3e|i1DyDUuIhqq1}EN^YTzP#NSzklVpo`kiDlca{u zaC55XFcvh5(4>(rK?8t#mvHbJcKE^AIgH1t2n41ULJ;1dRfLLRovVaa1Eop9%Zi%u zj8JDlT&RiZW>1m?x{;k(C3Qvofqab!lEgvTj3PTNWo7`+I!jpOxo1@mJPt>dW6YTj zsu}51YHJ8Cph;RdG!FOLo%lwJuQ6L(La1Q8d3F9u%t7!Y2m%WJ$X<{KSS zH5pk1r^+Hj&7j<9nw7X~2_ZVA(bP+ti$KbWJ+b=yg)E%sbevn5*Co%A2YVA!$W>9H zP(>%X)_BrCjjd$;;7W2D&0am@!&{H{zLBqV@6Xn0Nw;p_UDZal6#`_g=GId8F=<%R zYVx$vVWLR)RzCY$r?I}4j+zV)c+H1$0Lb478Ka*Vgz_?k4|QB;Nn2UFdUV{%qJt?J zh>)QdjUqQ9y4#M!Zged!rAyuB4sC4e%uL&QTUJL``z!tCyq-&5lO8VGI(y68mx)zQ z8aj0wEasO1Vl(%PiR$6eShXI+k(x{^#l;n6Eu~C;>ZRjBp=Zc5E>7{Qh4; zO&fc2l@!a5))j_Yv7XfMEE7%_cz`xQ381mV0LV1U!P!ljDNbYt2%=R>&4d(8(JZI| z6@o9O(#KIv5$D8eC@KE*(GGPE51C@S>)0R{Z~7aa1wNR zhkt#`nX~pR!e?k^3w7*L3a!o8*@llMHpa*EoJoNb19E42IhQDiWwkiNOwJ)ptEI;3 zR?5!h`|d6&3P3i`wSM(Lh&K9o?ToC$#WXI6uH7sdXmsVY0B_HY*&V&}yOGs1=2w!< zR9`m5uLf`E5t@f*+)hXrSx_xU zP^b=Hy+SiFl2%h$Rmo2F+XT(V|zGT^gB(QH4x?H1E`tq0H3BW}F+EH_UF02CZ^_ zinQ=reZt?;rZjeyzi*6=O{vzg^2Zv}<Sj(& zm6S1{E?$^VYos-DJkM>80cGdWtdy)u;>pE?5aX#i$?;5ry~wRl(y86_-o zAZ&QVvSZ*Ja=%9?im~ZEK@ruWmUv%b1S9!pj>m)*0K?(=GZ#_k>M9Moq(QXQV)OU{ru%x7o)-@dvE> zq5nZ5Nb~FN>Lk)KscA=j4XZE4B2!D0F@AvxlbLMJ@D_%0pu1VG0yVdvR0%YrPNk8{ z&epu2<-jAcPQF;1Sj2US5G*d;y{xGnm?1(-T%t){sVNg0k4M4zkA%|IpUoNg#K{_% zQtM^tlex;KzZZ`s5QW~y)9`}+DEUFEgO5}zM22|tDPU@aKjvLLE1oRd_ck_) z!u!ONzP0^oe)^Bj^px_$Na}nx6_8oE!v9;+;S;Q}n6J%G9kAVX@zHS_!qW76!)9=e zs8XX_uUg#jaxWa1t7$ibej6Qmm?;D@0Ul+xFp*V05XAm4L;X^v&xm|sK}&bVd`-bm zk$>%Qmm*^^ppUc+Pm%%zpRO!7^8XPXIIO3f-Lv4O+i{r?>Pga*h=|}7=(n(W&gU(! zf)H05N?>_Luz|q}^%4vOuDeOe?^pAd3Y|iE8G|o>-~XX-RvkiOED0AJfE%Js1{4*~ zu}0y-TLr;6enfB033|0@y~#x?%xhh|>kM>yJ0nenw>8X|zU$Ua`Qkj*&@sD9==9W| z!Rq1AgC0nd=fxouX?oq1J!U^PcWL}1&Vt;YiG6bOpC!4Y6ge#S4va2rXC_G}g`GW& z+4#W4s}uJnP1xw%%B!Htc^$dTE??VXEiUG(%u-!2Kw7sj{e=eKYPxTf3{JR{n;v^A zQ_UC?Rt~cj2T+GCHr-t16yV{|!Z&4r11u|p8P~b)uxzfpXzHeHkQb}@ihW}D`ykk` zq`4X0(07(0I7b2`bFovC)T9T5&gxD5YE)qa-lKD5QzA%pdnRkGCw~L1%%vAR@TI660gs(iB=*&*>05+HeL-=1rTm$Pi7#NH zJ7mJ>2$qkcFTDO^9c(%|rF4IMkB?feS~j}b*uC!TwQ24Po~_N67Pg-YExyKk z?)i)|UXZreZoTOmA!`?G%8$c7s%1R>?4nt{cZE}OHoiLRC+JB-ELzM00jtT;<0W#L zOI+*-Y+gA->nHBQbLglvl2=_Psk$UfwWjeX3+-LqdBW??P#3A1AfeB9(cNyFdPpk> zOq;zedT};EP!l`2Lc>a-x~)reHFyh$2T(ed#9%meMGFY<)o5q^f1AB$gPPKxlwk;#e(B zG;*hhvl1(bEHS;#%BGxjkZ^TENTGGXv9zoy#6|)no76dS%w{c1?{$S+x1f+#$8~5l zU|nmzbB#3Bq8jNKmF%aOt$)H)XVHX%ii{yS>N?V3@T?uuDhG#5SQjbnbMh{>*in!< ztkJvI4K?*t+c~Kb>J3h+Q&p{NY$=r|xVEcl{u+MwP&C*^uEeZZ1g<-~FKHM9w2B@_ zF9a1AW1}u-+1MnnK$8N?clVV(xpRY&N>%C>TZp$0YuRpODdsQ-s;r>bnAU(uDEhHq zBDO{g6cQR=x8S{KcBV8{O}T{=^o1(@OgnX^=i}wG>j)8r8*YeFJ4FXs6PXci>tQs3 zl}IBOC$24qn;_wiVis)*P280Nyv~g_J%mkBEKA5b+aO~t1>p<_CDoFc0^`DW1Cs3_ zrU0&T!m+UGP)Hy=LgPqhMW#wBIE5grnoYISho!aB&Ns@Q7Dh8H25lLK&cVop{J)N< z>Uo`+a^_f_(Nr17E_N#j72p<=bO3xoYELIhY^(JPrW8das=|7>3~^m1AfVt##YVv& z<7f%w*;|i1iVF+j1EyGv%AbfNZrXoFqc~t5mJRBeV}NzeDJ!_5ODEdyd(r##!UppYg&rm0mN)m z+LmMz@u7I%^Fvf?L1d5S*h^rl5Oe`VIj!3jGm%Mv9K>nBCNW;}6EU)G2Rb3JDTB$1 z_)o+K#H3P7_n?Qx#d@s##E{Dp+;Z)h<`;Fdrvm;E;Z_C^MkbezBHc=Y*&siG+1+CJ zYdtK|e9$#I+J_N4=bvB`<%~vEX|0+}ROc;$_9C&frYVpM!g+>x8`%!<`~hcu1Rlow zWJYo96g$#|^}MYrBO7$407@ptvj&pS+QJgan}>E{Ekj<4J1;t(wt-M!=s2kNYN!l% zmwt9|M!Z7H#2N4TdSA4McgyGd1IE1hCe0b{p z_WWy|x(ZB7+&ESCC+gL)6vE<|(DSJT&_2fcKLlrK+B~M9Ug6D+xrS!v#>JEEXy*PJ zqfxsO$y41Moi}?smHH1|?-)SNPl*}o8{d>D3LC|wD5^VhknPdFf=C0WrG zEXQp~okxhxg$w;=2hY4U)vI1u9E$RjMk(4wD{30SrW zV=<*L#EEd|p}JvpyWQkieJvJyYtgBDJ0$(!sYDJTE_FU(faEiyil;|v8!6xQ@o=4+o=JCck>^Y28T)y~}U+#JFd3wPO zvXeA&&+RR|zRzQCLXnd^g%;hi?OCR$J^uNL+wU0WjaXfDmwUl_)9d8T?Hg_|UE{=Z zC|y@iW3GahiBq*9{GLT@-rj_2(i9#N{wlwHk?@kq0&4JGeDptVj<@E77v{szypn1K zSIf9mW?%NJx)&<^RKTj;@Zc{A;8@Wo@1Ktn`c0Tj3(se>g(SX2mX=?!9vOC9mr5pO z{L$gDoV{Huwk#C|JD+9>J9iN)%@A>S!9F!#T`Ui?Si(Y(9U&erP(imnU4!7Nd}Dv@ zgXKv*L5#)5zD@Z%fUSL(6qt1J;W{TUM4(3=9A5L70ixf;o`xl5EBv0ow z%b%dyLK9dMo=>esqJ>T}wy!kd&ucdq$uiOK{c?e*eyOOeW{~BuIBZpFk^(|3&sLM& zqTaGns}S>@!V`zfgM6B{qb76Xi&HQA_4rCGj(9mWoakj3jdqKzndN@=^Oq?pjHa>- zb2H)<)82xz_1Gn=TNtGu%baJouad#ZU}z-Ru;xjJ2zQuHN;%sV$*>;EGMC;d1aGlV z2PE{C$XTuvGQ>YpYW_FsO#Gon>%*+Cd)Im=nRjL>lk!u>rbYM~^ zl*uMdiotW*8PzbzTA*g?8lgJ-d99~r7^`Jq>V#*6VUOhb@Zr(>AFI~g4hjq)_)O~d z#(P&b>~vZwTa^D_Aj#d^sdp;t_tt4GcZN{8`|2-OcNZ&?GYzA4hHHylBH_1@Ut>>z zKt+qcP$HCsyurSPM^MIDc1$kud)X~;6^F)3U2>VQUgJAl+m3n`;s^m$E4Nc}G*Va= z7cQPVQ71msD@DD~k`c;~AincGwP~5Uy((9;?7G;GpL~VE(~1HEKY&Oh{WJ>He(;N} zH}C?lY@7s*LbDp3TG#-O7-naHbe*qWquM08a`{n%1t}p=f9PAAlD!X>oD%fCr#}L5 zB?IzU0DI#;IWPI^SGT-6_q`l`V(EJX^Vgru5R0Z0kAjsdesxxMo7fc%6HtqVvJ$fx z45|bgpOV--O|Ws|o==%C2bfP`Y?w!|JZrl!MWE`1 z&W-zMV^CFq{;o-mG_V)-@U4A*`3825tD^bZciZE45`r4c(^OXYOWBN6b>H+*~=;ULNCt~2SQE&S>&IQF@Roe zDgCDILfPqJn}DxWGu$ZHS7L(QkERq>=`9s;1xgg$G9vqQn4kjab@1)RaRNU+ z&@tQ25Gk7L5ifAIfnZnD_0v~3cQ+1iZ$)~S-JPB30^@MkGip;yAT`BIgoDeNxsq|^jOtMhI+CW5C=7>@n&D{Au7q%a+E^V=ZkbzU2>lqF*|tvv#7kkLD@g!$Ox-<5iiTKZdwTh2+_*5T&RYbRcG z?3=Wsb1bG_ahwNIj2Dcg_yO}&v#4qU+KYmrT>Y4WA`E$`YdX${q9B*oa!@6jR$@T0 zTaYNFpKunrYhI&3Uv*riUFv6ffr(^v2IK-;aHsU{X=n~8p`~_8K~tl0y{<_14#wJ( zRiQJoXw>&>Q;`L8jkOylM+n2MhwkX}uaZlTsn)mGkLweVwSoE<68`B2!gw^d)((xt zY<8l&t-H8vE>BM?iV^Ew=sdA0d#70eTl@3RM^!$(rmvS)3DeigJzcla{q$z5i6Lpd zx3!F*k2V8mmlpLzDT!GFl`JbIsZzhcv^F{wl9$TOPR1Z1qnh)3kLYR=29T-ffEgxL z>Mu~EGhJJE2FUJ}vKt`!BfT<9^*PegBRe{=>XcS8J!v{ZZ$BVjI?a?1du76Sd8&0f zT^eYmer@#Xr1`b8?K9b`Y5C>KWxYCFG-#QFAz;U{1Wv(DOrbmHXQfAB;N~0V(JR74 zGD!KC{8|WyEN#7*S{#BmYiN%7eQh&7LcM!O1}-MHq0rGXVrQYXDOv)-hQ%LUFJzAk zy2gNjBl}b^w{;1@=dPSAHK_GowHjSwvLSk)gv=qy8d71XU4J?B0)M{XN+ij0b2^ek zd$!YqvsSBWMjNX&mVs|k+x{?@!16ck%$@Kq7FIcpFRn`(N$bYLUs1eFqc-bZFHc9g zUCY-nsixCx=7+EbV!xS_;G#wg#fC2?sU2bccR=bydvp$1RY4m~Ly0MQrp)NY0YYXS zrbdcVwIaelJ7#CzD7KFESINQi=Wg9v$!_0zpX$>CtY0Fd=*xF+lPbZ|Ce9^|IY#;r z7`y31#Ia$*`cSEjHS9zPPs4oiXI~{iQS+>9lR&*LeTbM-o}3N-<@j_3F$FZm z%JO9*aPn}@RKvne_o-L$QRb^aQ5PmyP}b@-8GfHfMkRVGMRwfH$P)U2f=o+{cnN>@ z)?(t@XJj;rXeSeRQ`EKhoLbE2-F8p6K#KCE@>IiZ?P$7Hk*P=P2+bwzpgTvDic4{V zhqK=-U&Qj#scC?MVn(e~m+v$V17Ne)QTZhL9VOreWP^W!`EVD;9TdVLsJYw>!(a+% zhn!0PO1LKN7gN}4CN&$haNv$KjFjsUGPs@nMsm3O1P z1?7OZUM#gHB*4KWaWnSoRuk=?M@An2{=2G(L z6nLoh`go{t7`>v7f)%HPg}Iign@Qbmy5l`+G#y?l1O7qW;q`X)l)1GTJ zGc>by!;E}1fGUWjX^~W;Ka;+yR+*8J&AI={Sg#Mr9|b$09~O(t@3!rqeE#%He0Xu_?|vdQu zMFIc;`uT{-SD&;Me23iXGrK_r+x-%J~$vXP?Zr&zVAyTaFiQVDmFX z!VEsUXVgLZVr8VbI$NYt}(b9`P*|Q=8Rv-U(6HN8n*K{a>du$w)3dgl&#koyv6*LpI-~m zmk~I2lH<;YLWCDN+yE%;^U*m=`Q~2mUS)k^k#7UKVG)GD{zG&eb6P_Ozot$mHgsPp z{<^BOa%sCQ@F6aKaJ5Hi(Edp}{RW>g)v!8;M_1-($CWfLS01dfTub1iOIKO)Hs2WQ zHWh*MU)UzaPTW2zX3|AXiYSqY6o@j8@@!P#kJ$f^0M<7b{rp3jFQij*c(*F&B?^kh z=uCxyaLc&h?n#5*9yfm5#EgQqQ98z)ag&Sn<)ODug34cuWsKDOgC0)u^KiXPy5YRg z3BbyWk3R|o!gs~)LieISX|!J%{vl1F52VQ=9(-RSeE5J2p#O|ic!yvuUc%xJCQy~a zvO}B$d`>MxYigpC)+HQ?@G($+l=vq|h9V${G1GZ0@6ryotHBilyF`|>U-FBtMdq9X z55ljPL~3dhBgbSivmT}QkYr(791mdigEj};WSTP&6$Np-X{yD=W-P&Ur&w3xOvcPk zF1Sg|d7iJ@&ufS%jN&^paI&$U=#0@4162i3HO=j#)C{_5Lw!jI@c_lPSqQ@Skgv}W z%ybydjjGYP`FKkVZ?Vqd-U?zLqY))9tTg!-z*{Eirs_lS?tE!J?j;@%d^YpS0(;sQEN;Jl7DRp(sN{vydE z{_HI{r=bxru2X&%1QToJNg`P@s2}wc6Hw_U{5091+N2wO_F!x>yW{% z&(u6cqfR-5rJ9k)V__e_S%7&WgbR+F_tL=n#Ws7^p8<2)-!Mp!+YL^i@O{?hxBAc; znoV!#1+Pel$q9L9vI{*&cd(dXJHt7O($?lP|86!0{BXCP1xuV%V;)g0Up9T{=6w0~ zH9?lPR>)hffU_lF_b(X9J0l6T`u=e22zim|p@Co!2>a-m=O29ICq`^tKfZ5v9Z5Fy z{Exq`M`?K?+zj;&AYZ_JqTHSlWmiM=tb&CRik~H>86%cfo>fM$#2kWjou z8d_Ai{Ca{&^tJK}6h9Pv^eN_zag>>5lM`LZUP)m0Z3r|N(=nw21JuYh5DkQ(IE0Hw zbBqjQoJXUXL>M^e%j@f=Nn}{MB!onXTzwh{8C^%Yu^uxq9KsX|wVu==JD%%uiKpNa z^m)dA$bZDY%+HUK$R(3B3gyYesp=Rh_^b;q@#D41%tOI)Gp2Y!m8vpe#f~#0b46oR zb7Yrhnh3!)Mj3aW!rv>a`yce2^RqNpO;Eny{TLp)v!uAbyoFt?z)T=B4T|wMe*akX@6M6Dy9D#8P4tOagy}fV_?GKTYhC zu`kuRCM~ZjLdF4C>@n^Q9t73hrkPMxYvZ7gbv4u}(OY~w)x{FK>=C9r_y}&{@ zc;9vv+1q2?Qm&x8$c0FrN0!|Fcn6Z)D7pU0#-zoD_!K+}iq4EyxI>Up>p8R}1{)n2 zCRYgJkJtr?LL(EC5?6QnOdPCUjaRbT(91>H6zI0;Hu+LsCa4g41&KaQesCW#yaqbx z@0muk{ueXF#qQ_ zlQfIA)9KIKmpH5XTQxx%A5Lgg`ZWPz*y60;dJS@Q9cP z%v^}4s3DTcB%&ruOd{3Ch6un#0FZOR1%Eg#z_lvL+oH8(C53Kc$F7hhZ5Z`oUl00Q zjpVjHi{k!TP#gWyj_G;XGxWQ_@Apt|KO6zc z4}n{7bX-qRSA!0Y8k&)~L644_QoVGf(w*0v%xoG!!3cp6Z*7Q|-M6P4e|+}N828fy zzv}OrWNE!Wo~;sb?+bbThA-92O#gOg2NyZ$38gn(BHjDQ5R(ImS#>GQ{9bp}$Z${= zNn9*G@eo%if)It4NLH)KVmNw~Qi({E1U|@fBU~vD?8y2j=OZO3DN7%p>D0geBnk<< z6Imrl7=@rpkvJ$$rw(21rMj-5=R?DX{D~w607Phx5tz>?1bnIf2 z^Va75J?95GC@E7H?{(-?yxs)v@$3>5Ohe;(VTgjR`-QCXQeIP#vz6>EqkSCP*ZwhA zfK~9Rrpu!UPV%4&S2DkG)dSdR(@WF*{b6e!6H%I^&RDAm@IKSrX? zN+pS^CCl@Iasr^fheGkiiunLRK)$~saIjM@0ivr$6g-UIyJgAyl147q&)V?Clmp$7 zk@}4tEXKDgzxJhJfmwY72^3rvo_Ye&s<|=DI)cNAIP@cBQMqnd6J1(;HIDWz8LtI>XMHu~n-VjS9)fWLal&Mc}-II!?ADm)lb> zC2)0iQ;pWNCKyy|q&#Vs_uQK9NHv^xIO1P>L#CWyUo*|O83lZ9%`2{_x3*p7tdi`z z%(jQUo6i2u*z}*DZDa)uK3sJ8z_vvP2DOL3-uc@yghcngf_rxJUl+X2|6CCANaW`x z-k*BIrkYIGWg^*oRo1XXC`3#!EsaV3^Ye0wcy@tvzc3uK|&4dYuBZ!MbQ@GT+p zv0ebJ75u{89GULk*%rSm$HVYHrud?P=w3yn#-;qE{ZadX$$SrK8T3ZJdi~C&oum<^ z=i=H38BatjiInyEADzrleK%u^2V>`HdgJLkm58(@g&Uf8Aj&Sv2U3tp(QvobknRjxbbMbx6n;65(w>;d8q1%j*UW=UAXpH==D-)G z+qV@rMJ)W7@Z_NB>h+=Wdp1!Ytuz*G3vD#91Ou~HGl1BF zd|M|eWaeD!2gXOXfeh96{qggLWD1kP91{3B^(|Lpu~--XRzv;yu;a`bX3bZty_Uw+ zKo}dUFp@zgH4AKgayhJu6N`DhY_{w2e+SHVIV@itEdianW zZ}a%^P!d;2SG)8Sjr1Q=h`I`6%~!qs<}#vzp-k;8Plzo0qbuHph)tS5x&h8wHm#oQ2`JJAX9rc+P3oN}Gwhs~wzRw`hYlzF zT-Qcy6wy)Ci+Q?8_ooQtL~%$1Z#YDE2tYN?{0Dz%R|UmH7yqlDdM9NbTF=JJ&%QW& zq1c9i<1;yV(Tyol+B%2J$Fsr|DV5u!SEd?;J%b~?T;t7VPZQcl37)9(M_dK+?CSRg zk&8P~8ZNAQHoYqEQ3ZH?vZjBawwbC#WwA+T(0$0nCdeb~Dt&gYPEzp%`gEoL831l9 zqCKM1AJ1b5XhV77^1Z@d8cZUl&c;3Ryeu9uI5n5`LUINvYKTL+7W^W@xFtqJMA{=1 z7|N?8S5J#tZ8CxcU^1)KSByZAiKB<}2*^r}CX$O}GHOf@V;-5)!#6w+?)O+e_Id7K49k!$s&{CpV9H5w%BdE8uon1)hNC^WDZbL0 zFu|#jQ{e61sEVs8`FdTbMUZI4{Vw{zNSYljzFGgq2SvdKPQG3J z?~EPJ`T2r;>-_nz=exVOE~jEV@QT}h#?AMZINqvJYgn57{@{{OK*uwR&80Y2R0Uq` zB*!yse-7)@fms$W#0jT;hJ6Au#`}u9`p&j!!(Kj0W4)SwZAG;D2q^$x&L2gfyD@z3 zIe5kV+#;Z?sN0dtz^YJEx73bzx|%M70mD# zyNrk<2v)+sxiZHEhECkLfuOzXdtd>dnvRLH;|qyIGHHjhf^0#!Q3)DzO~J2?gdG(H zl>Uqjr%32i|1^@kg902@iqogg;ZbR=`ppn5hP1@L5FEcJ9zlIhu)Emw^B+&4ae?}y zq2!cqe&TbNHNp2-(oZ<5|C)K~5vz~uF7tugPe#P%7r9Bk7(n)Ze)0l>h=l&OT&Bt2 z)liw1)>7w`v5eWr`rE$n7Qgv*VykbwSxpp$Nc|QWjmQ|KJ{_qxHHT@%1NZT?g(3;j z(_k>NGAN?$r3~zO23@>K$PBnye9t?eeN_+kBLKxxF_YozpEEv%w~yqnDK4E7K|hY3 z9;X{^E6DIXROJoGcnqeP#kgVne(CfxZU%pHX?FI*D(ej~OU$CNH3MDRssG$2UtIcS zc6Mh+G5Aq2)KRbT5e9v=8r9t!I{lctW{Y4=X6A12%Pl=ONo`eY_Z07F%2I!xXiEHCpj$^HS2$Ha2ZPw zq6y_!FxWvM3SOTVHzX0yS+sOznN2(9q*2lWd)x!oNx~Cyrt=(XR|X1W5@!m9RYKJV zr7O!vsVYOCUh10BS4K-KkXsAcyfrLaSYwC3e|c)TKNOnQ7}p<&9@W(+TT%ImmX{Z= zUb*{Uq=L)k~fC+HsWkfao9? z3r7Xdbv(f)S}91+1gTYPb23o3P+1!iyh{B3{W>4>7!~RHFqizNK^0GXNavhQ@tt6d zz%2R^I@+^yBPYoQ5=#nqRpzt}oC~sn!MwcO-4*F=EA9r`I*P!IPvjIC(aHs|D3KXP z%n*Vv=BjtddJs{EuMD{a zznJHQka8)_FFrpg`i4CEy5iL~+D}UTdRf!t&d67i7DZ*dSVmP;l`^zTeMw3-!g6Jk z3Kttu_Ied|3Ck$}S`BdtY#dw1{I^T{l5$t&ZQn5|O_p@|#Krkmhb~JZpvHg>>R9dMKAblZ2*3fp1O=nJbtJe_eb0_{f?f)>IZG*j4xIw>B?ul{n*{ zT()4^KJ=V4RSgeOI_;i{fd8+P`NqEa2mFO!#R1Lso-K64WgBLx%6$SFI@-|%m~8|I zjz-H?oup=NU5-O9e_NwR9&NHx)JUWe*hK#Ap`V`M=^t%1ZH16!s?ROpL%%GjnSF0!P95Nl=dyZzRhK|`2Licd4A`}J`6qE*xGviq%FhR zy~Z^?cfb?nF@Da|J*Z)s5Q06@E3z3*~Vtm6yTmKAnS?DREWm))A;ri1b{R?zXylj-@b zgBtd!k!jq;VW0SVsK!rjk2`R%so+@KbrhXKp%Tgo?7e$Az`_H41y7IsWW1*bcx^@6k(E%K%+=07JxeJ^YFBM z12WcqL?M6#rAm40duX8l#$qxT{Buw1 zSRgXDzo0=ROJ7|n0MWjL<1A`^;HHXGF9jkXR_xnV(>!1_7Z;n;W|cXJKU7_R zQki0mPEU`P-Ik!Dc#-SLs7g$li^U!ccG~HxyGB?mr#Js3nJxWgj7n1fF6%|$^hGSd zA3bxpvs?)G8@sVe=z!%}I$cg@2P|LisM^?|0&QY*VYZIy9>wsfCPiRsM5?)|&}nT< zJ=N2s@wFFmeHoWTSNg(m4@KWH7*_P&nE8^4^tO?YPBQ7K^Mk42vW;a2{BJP>ch_98Q2KX94Z~k8DG8XV9)zXccl0tutM*j>X@)o zE-8BIi~9b>m;>YOnkpP?!V*-$_$3)NleUw`>uKvJu--PWY_z0+?2Mg`$MU1m0Bj}` z+^AbsUDO$c^lXWu7jT0jze!pOWj1j* zw7@{SRHdG)Wh&Ezw5%w}Dz{{c#U`jL6UwU3s=QTEuQIQqstrOtM_a(SH&-^ifUXXa zF!BxC8&tuE7^X+@xK!6nQB8LYA|X?~O7-pB6;O?|XDj-mkZhct2c<`zVVU;*45x_iQVvKnj9hrGThtvL3_9 zP^&k)RAkRC;W@BZa+Nl!%En9ItX}#a2YLML4vC~AoBxQ#=l%b_$cH&|>4_5}`jt12 z5EP_k{KF6~8MQX2rHU!4AbB$dVr!0-wC>s6%q$!}SEN)l16TXCJiQ0V8 zGrFeg0aM>u;V$=5vkGrhTT|zf6HeZn_p-f)TkX|M6!|lofg^a8hZTtAtJ25wK6q}B~AI4U7*=+E^BaMb17O3UAu}@ba`^x?RM#9 zRE~}H!86-=RdXus&Y7Npia8F0i2VqyQW#Fg=@%}=5bi}23VF97CkML9S*875%2KlN zcONF>+i)$Y9?TSS-$X0?3rs#U$!V+nN$@ebSu@66}qCS zgiP}km1p{`5L+}wyp?Pyo?$v(N0s6!;}wQK9Q97C(4;te;&WUe9QBRT6#l+uYXNJQoxoTuuO<*}s#1tS`SjRWF6#P}cI~txWc$)@%#E>mOShn`cL3 z@d>WSL(3}6HWMEGKiU(z;nV)58A}}3wxUwMI-Zzg=~hr3N9?7|GH05j%j{#7g8POU zw}lu>F~J1}*Yz+ht}HREtN32iYutq}&P*P-4oeK2J{yrUn^_0e^SQ-2Tyrm+$XG7_ z`m^y=%UOWYDH!8i!eoV+rhU;S&}UqYqx&F%Lgrh9EbS9r$p#axe*EeGgioKSSGg1J zJI{vtVXyD7t}kqucGI5~Ld&ind0RFTirZ3S-%;rM_~s3p2+eCwh=u%{+l+1I6h24b z-|S~5Nv&LQGhSB$Qv@!R3A&^i>*9(toNEa|mt?U+1h0OGc0kTQC-6HY5l#dtKHH|k z;0|jgtULCC=XPR@!*{-n3Z6Q2U&(n1db81DwCfm_H5E6$mUg5v4xI^z7oa>AH`2xy z=#75i-p9NPP_Y}tL6ky2_7fHHsD43dROXdLKWxf)i%I*@?d*uN<8HkS@O7G}3vddk zj)B2*Q>KRpC#X@Lk^%pt+E;QFFq2d^ho(-5;U#fpljtr>uimAS(BOQR(EpKP;R65B zhOe&kaBe%i0*776zopvtYq*rTY~gx7`eggCrNnfqhsKCdXF8(!BzSBb|2H{9We zXJ8!PoNXu9Qq!|n{h#Hhpf;koTOGiNBWUrLw*@Z{VESo51nOik`iLqDKU`I(P(q|O}^n^FJA%~Fz)K_-mnY#iD zU`P|xWkU$jK~Qv6CW3ji32h({RF`?k7eNB^;biR$$b|r)Zw)Gfkl|cDf3VZNz-E|0$pQrersbsfdFP-4G&6gVnBtZi3ah+;pr*H zv1F$!Gu@q)3nu0mEuzPb6P~p#E~6k|NHN%jtq&WawN}WM1*B+vTR^l;1_5BPEll&A zjcvATm9e!@N}Bi@%+4>(0rGaFGN(WujAD)N1cGaA$<8GL=4eqS4D6^9do2t?)H_MM zH4?}{R7QE_ZdtngN0)TjPp%euP$V?$Je#0ob+J^e`79;CFU(@+_4ej5`*S42b1RA( zg%g{EP0gvohHVoK!YwMg;QtzR=Vjx>PJJG7bgFn>yEx;AWunUQbrqshpw~k96W93e zVrZ0s!-sZh;@5OMOLG=fX%hxJ60}ueV#q!BR;Kw zRTBK?yI{!@z`5S3F$AfTtg5-HDMc-<{ouh=LX}hN{p{!%b4iJrnt8MV-9b1FJ7p|k zaSCQ4rxP{^GSp#N$hAaZcVaCw9r={#Bpev>DX=(*1f}Zezd~AU--axbJq&r+LA5=M zU7(-!3qB(GHul~AzijX8M*J1>=Ya~WlFniZchD8|HkC{P*0-+Hb<~Mk+^PKX+abm-0XN`iwv?n&W6U0;gQ$@E^6BLU{|WNiysr zoWeb9deQgAj|$h22{YNpQcq0@TM)*W=Sprt)RcL&^N}?c=HV8cf^j_|5=EaXQC(Ne zWQwo9QNc)_{C>&=!`+Pkadwn*US-xWerLEHa+8PsD+#KA`Tx3TrUFx%l}5dam(e^z zm{V-x1rA?+I22^u1;%H<@N|so^p91f&}BOlb(tQ$=*Z3~ey#qll$$p=XlMJ-abgKU zWDtHvor>91f~_}>x8~^L($ZpmsRCZ8J|0{=X{*V#Df!-{SQWLoCuq0VqUujy{v@I{ z7>l4qn60vMNv~X!{7^WD$Ct+OuU0B68Kr0w`RhXh!jluj#Z{0xn-Ff-SjHEFIR!>z zc5eIB+z|R2N2mJh;ki>EM$OGxlu%Qh0Bi$kORB390xkveOp)B;&5LI*fa((0wzj^h z?Xpgi)DYrTiMPRo3>TpM(ON(B2Gorg)}!ylVl*}csH`@JY;a$F;n&hz}Qvd7{{ze$pkHa4IBt+*wY~deYKBJrkj(lqFw&I@$t<#B2TFA+%#Su}*wiFd3 z@mDFQ;syC&gH%55vdd-CF#gNm1)W(22eNo`{M}Ukm6iKpPk{ zpFSK6lddgMtY?AM3l7YgBRaOAp&gCJW7s(qg%I^Y93`X4lKG&b-;}mLAPEs1d?5G)HUcMQ#+J1Jy0`c($_Fojr!3jM3I5b^% zxJfD`ipt}SLF07xMdLVJB6R#6y4_>@3w&HRZ@ci595xBD6Q5bA05UsbrlFiW2QKoX z!zb>K!|UO-j|_qaG@)%1$W36w*s+R=r4^N9+rLb^N;T>&9~NO zZA|WLO!c(aJ9C{4bs1?*4e8+98_>rSWr^thk1bxhme%nzJ(6+A_SYGo9}C4zTN9g}Rg0fagLuMRQ8 zoRV^7*RQ|p232vMi;tw(foc8g&40|xok&&v&C&d~xDXR{O>a3T15;A!k{6*>BJ>)z zN8@30q}vVb|4-B+>=3mJpLNt#0vea;siR@~V4l1?^3@%un)skN_5PQj>HD~x8R1y6BIE|dNbakD~v>i>VS zcrKGMyjaja$OKd7`W8^;ns2aEj@862qC&%h(d6SyFIab1M@3cJ zUd+hq`W&)?z%K|t(5i>!Qz3~gknqog!GJBK3cXV$8!^u>R269{X7J@!Y2E56Tc?k5s$K!HP%9Y{8L(<+l z;-k}(|`;r6GDDofCOiqxuHXVf=Pg+<{_&_6xsr6 zd?$^HOU~3jj2u~r!HTesd1Zx&Pbfb!;i~V*GYfKabwu%8ZSnpeEy%N^*ss7dfy{`F ziIqP2U=oMTWEk61lPw>}tHzfqAw;_ft$j z)|u;=Th`P2#tf`r+fR8bB_OEONjmtv9b84oirc8_BHxD-lf8oMgQ7bcN zrExg)FiK@M?uV}NmPbZrJk5kz*KbgV==9GRK6Q8o6kS60h+-VtK&feg)z`!t^bJ71 zHLo}FR;ND4AdC6A5%RLmdty#gn?aj&hRj{C81d>EK9t}rr^%)FV-|Cu= z{#+2b4TipaVd41-#Y+{2launQv;UbHOD+~V(1Ftrstwef z?ON}$X@ETB(&(N^i2?k^>J@9I>noHQuod7FHB6t`l+&K*&rWjno<89>Ov7)+EpFA{ z&uY`);>9bSJ_rCfETS6Dzm(VJ!OXDkLl2vkcswHGM8M9=$#Mh_seD?mB8!vp{T(tI zS4KynV<`m&cAxaH1}07Gg14DrP(qhByIz#DUZOCF!X=ja`A$?dQDAiZoDstLm-a6w zB(taT`EVuJ|Alj_ZFV3bVtiIs=1?yY?=g4&Jr4cjtTdQor(|9M?WmYkA*vt|D~OpC z&YX1E%>RWI#CD?9MM@#D{a#IJbssdD7@H9FvK=@|I4q;T| zd<>HLj2{lWmf-6OQjt9iW@OuD8J>0nrLF(Sk=Knk=&`M-Jl&KndC8ObK&470rAo@9 zm47o2JeO5eur841P$3Zbxg=UBF^cS(B>%K;r`Hj}OWhW!@7e_b#=!coexDyA;=E+V z-W39Kt0ScYg@CmoNvu0rH(r0u!KRwTZdcy%``64A7oBH#m|DCyK{*Ro5~^I5#{&~XP=z84AfgPMU!y?Kue^T^?7 zY}8s3{e$=aBJS3FKHjo?Btn9mVa(Ek$gfUmeo=82zq9uHjc5?x#)zez@BQK|Qn2wk zs#odX;V%x;8$yF-tbk>VjL;Wm;(rnSB_am?WFHsX;+J5f-kTBQvR{Yqq%CPT?=bOr ztHV}1__oZlR^ga?M}J%WaQq(ULQzujjw~E&@95a1xay<2ArNq4qRy6|ZX=#f!j1H^ zS|?f!z&$EJ!u*UVQ6iP;7KAGwPB#lucEq~sKuXMC;npD3kEu~C9StQpE6z}29Tp#V z0B6A@$e?sxm(AX~idiOmlw)PTG>7 z&6B&q$*JOsWy@iw)6$+*3CcZ7OS{&yPD3iIDIe?HsC^;=xY89AxnYciU;PU%z+YE7N@i?`j4Vgk~{o6j|qb`<#{`7uMdQhqzWwtaREh%iX1@VTI zSe7jhVdx%6YmKl<{;HV;v44=3!6bem1kQb08mF!JYR{L0bHTqJ^oThv2p0K(#|jH* zL9tHw)rI~n5^;-~qA?)wwrS4o;h***0QrV_dTVOj5QvmH>zZsp z{8RCnM$r*TtYWKZbVw2!07c?k2-Ko?P8T#z5!2k_h$IS?#W4r{l9CyrxZ%pWB=&tq zg){D5X~zi^2eSUpMIba|jKvTl&2;J_A{w+@S~&|SsJ7SjH+ycPiS@}gl8~hS+~_&XjDm#a%;;65AgC4X!i&#;WtP^s%BZ*3qiiF{$nt zO@bZctg$# z#~zhjW&P2-;X(T)K7M@3;=U4NO+SCtV)4&I1IOJ~JT>X~@p8fne}Jb@>gwJVDGddv zS=0aMYjz+(r8{;O)P_a5vNiV#uWp3rTP=SRH6D6j{gg%VJOn(1LLpMD+QWLwnp#W! znkWK89ERcdh^9v{RLv?izS?CsN6s^kuC!~m^?gjMq?!=sH36t?iq*GP&0>JF%KZnX z-2nd#3olu9i!M_YT`LdfCogb=hE@5h(8?~?l+YjCx9$bYdzF#dIktQj0-IG8izba& z*K&N`&5kKobI>)>+T(p=f>T*6suBI>*>Z(@z~THQdZ|zcH}}&w zk!<1H6O8L#S9hBNOd5l}XnzO&d*y(dwaggA$h3o zo1uEt*C2FuVX?(rT(U4ovjt=~oGLlY+v#v^Fx@G>>Qvd)hMS>+Od|iPZkFk%?+X@; z43|Ipq{H4z0OKk<3riEHY6vf!C<1-Dkx_` zCfU^`Tr^3z^d#ax6P7uNR}TO9XK2uq^s8eXY4ZeX2B{#^3(|lA%#V`!V{4&Q><6*S zUnwy2S%#?`%X+`i*mAY&MJngXxPlRBV=&4PaNbdPcmzfyMAn5=E3VOoxKlJa?J)N= zjbn7&>f1we^0Mx>ZWEtyVp3|9%934`#LBukGE|zu?cov^&lAoKSdTT4)cub-5jW2x zG$4ec7__2p8cJHkpS1~;cHoItZ`49`X;YBJe?%s{s=qAftALYMos?A3)?`&nq!LOh zMZ2@^)k477^b2!8atDW#t0yQO><-E?nRA&wm&4gXvY#)w$qrz$cguIP0CecWfjy4^ ztpkBoBsqda&=|nw?sq`YW(1G{^d^!$36io+P*4mN+j607I+TxJuTfc^BqG`ok_<+a zIGh35&4FMo2_j|ipI9`N1mg>`<8WkaC}nc+=|SSO zw0L-AYFuv6g!vu? zF(!UVL;Y1q&PkmPD$w$5uPgDmQU@m{*+3pdR@Uua3QgXA|L1>-S1!8&YWr^-9hM3r z6C(W^eZEWoe!%H(QI`7dInqdHX%8Ps)r&pke#ddgR_?#Us1?%u2$#sLPm~Gs<$0_r(hIXu6r)}n=>F3O84_uQI$ z52(D>GSFSR9FN|DpTcE&q;{8~Sg9ri_6t^r5dxK}&@fQ)P0f?F)RftMk^msl&5BIB7bMuxSDN9dZT78p!(T)nfHs^bqm`(vK>D@*{g zQ}NktiniW1&#FwNXPZMBBUQ+lmf?o$KJRHwz6WOb13&Txy*dX?*M(wl8zKuAcrUi zI_-KlK5g?eDO3>nOo*d+EC88HT239^R6j788{2Rm-M=9|1QPt`N7@_9Jgv->KeS zTEgRWXQ_AKjHQI;`)P~AO9_6Xv-6GI0?zD)u*-N-b*$4Osi449qt+~4mVRRP?BhF! zYZD%nc(S+XTM?Z%ephVZ_a!Y>OZeV5lt`=cJ!*}<6uaW+vREy+|3^WK_nMYD-nC zOu4Z8+-1{;M(N*AQ$0L~RUrurCE)UPbW0ih!ngH6`?~JbZz;FTFI{|V< zMFgavbYk7(n8ky$V;Bhr}i|F zh;` zfoIM@ZE}B{`)k_Z9N32Wqcb7rDnv0TNasf%R+~>pd3(NMlsM02fu+p)-?x zrWMs4H2iT3LZq}GtdKE^HngTAN6Go@%2E~O5mAg4d<#|kI{i^=D z5mma}#I(q^6A=Z=Y~T}WoQ}Eoi%}BRO+`@?rd|G?sg%Sa{>iD3gUOu*0%tkqqYhEM ztG+g`R5GoF%d1F09hECZqDl|ZqyN9sW)ou#eArlAkdzpJHlX?dXAwp^oFNelUUdF% zMnjT`IvVrWmSnBw88I$G%0w$ok-^=pJMR)%5V9gZVtr2S6Ei<1Rk_7}?kcepu#5AnqnddsimKBe}Dx>{-&_Jev=saYx99X zt-Ier8#%faY;9WCMA^G!|P3$Oc~ zGcs*?MB*=B0_V+(cy%W+LZxd`V-9hrs1oWB_Y~FtACK4_r4vh3nfEV@n>9Ts9JpWQ zUJ7Oq$(S_4g_kib3myyhOK>GaVXwIiB9#)}fGwd?8kt-swK1zej)1wZN!rdsGR5gm zrr(<|D9Y7QOPh^njpxzk5SSA}(&UKhz;3HA^hx~H6RbFx5GncMkVx1e#3djoa~zXV zN_9cViggnC)FHbHhR~Xrj5Mkyuv~9fKHMtcUYFoPg%C4{(A`kY>_cJq!I2lNd6qjvP(aIoeq1o>|(5-lJF+ zMrI78eti@uI%!<#DiDBxDeoc@&l*a6lcoejZ4#J*OkDW)Z0+cXI?vF^!^7ndo&25p z#@vsbH$5gXaWr%u3uufz`jz}%8@Ra9L6Q)xOQ1RgYccm?Sz>292X^ITTZnLfKI2Q-OioDg`}#}D(p8}LKYRo-;Q?W{hjtx`0f50P9+j!)%IC`Zx&1QvJm-1v z@gb=&)NQou=k?pkJN)&*Zs32sK_QyZf??36xh{3d?Uz@G49aCGQdiM0SzKhH6Co&r zjG$9+@}`+-`OLua?`X8Ti`xE3anw^Dwp=~pW`vNefrQ^o8Lp-7NeD~=^}frx9@CRB zdP3(Jm0_x)+51%`lt&gwjQ}c-4joD7h2r(U>Q&I05R$GV(aNAal7uqSsG39?Bb50F ze0N#hlOybA<*9AkC!20RvVf`O{Yw_{fSih1}vj%t99YqJ-x5taP)+G*@OLrVWY4l332ABuBtEs8o=b zzhg}}s+HM6#1I`aGU^ol`#Y>vV+vF%hd1165evwxdZj4+85gXNqjKd|YL+B47nah7n+f=kgB8PuGrarf*r7 z#LY~?Uf!-4NNX}0mb%Be zyo*IPkN`k`o9rFv+QV7Z!5;Nif0hnXE~mDce1-wdk)alNeR5_=N+xf-g#pAZ~%$(m}oWqZ2jK+?YAxPXWM62{ z+l3o;f7Q}jx8zDK|n09U|mQng+wOgWS9;PEK+k%vP*aOSbDFvp?J2~WS)4x z=Ebz5pn*Dg>_V>jfN0c}TZgxNvN7z71L7ai`&o6zGQhNNe!hj;22Hld&Mp4dN1_V2YoU33j#0eZ!GhlZa(=$6X!@rO zsQJ9*w_+=J_dWk#1IQgG-3tNn`(kL+sdqH}tMR<*%`|O|xr6&_V=4yM)1gQhLd(JB zTt9P`@5

      `jhXDMjXN`3)OH5;K0xO120NDy|Zz{tiKBQ?$)vN9=i`NL>rg>l(7h& z(P7nS%oDC#qR3EzQKUm09Wg2oKsxaFX{(W}nd}Imk3Ma*5B8i`NgCGnXunv=`V=V0 zFhMRHgD3-9>Q@JjhjdQ`C6S|Eef)0wjH=zcJO^x&CmQ9S_hCYe2;32FdIp2b*->I_ zn@!?eX=?>>$CjRQ8g23%u8yzI7*fep=G~Q{Bb( zUgPO~2ekUv=iOmw1bfoyO;@j;9z)%z zg>b<#JQfqnE&%xIm7vxgIx1D(sXeN#@RNI@CsO7UuO}BAp(M$h*LQqgf8LW}EN4bf zcA+*oxkkLETG1b(WG7`9EOJ^rH*9yo-19en24{G%)Tg{cXJJzr$h%<-Gc1ia#4qVS z?6KL_I78eh+5vCrXOodGBXBF)S=BxxQoo6w?LEv*{w3sY*H_6V@zyUCLU9yOT!jC3 z1W%MpQgn{2JgZaewbmwxBHq3&@kJW@X$(kW$BK%&heos4PMzJ^&@O)F5bx`dkeREe zNBk~(!oR}%mj5kJexV_&SY~|ATQyJe;_WK6Z-4=0Js4AewQtx!+!j|rsyTxC5q>$o zq~6l_e;mdaQD4Jl5pr+!?#7=IFOL4%Io1EXiDB_GXKhi=txP0ufg&=-$NsS47As3o+9Jon3@$a2Wyc0-5Cc;n*&`b#wx5xt}m#b z%uPB|90i8byF8Gd7!9BrnrxGR_A-Q9=cVP=>g84;=lrP7XqdWJkOaI#`lPz4iPn+@ zW~%BkwngPbQ$gyAlusK9ef1UTzygfbSxqFB@&SEg{BX%|N?uA-xIrI8`t@`tyV=hb zhG&75`)@4js7M_(zSk>WKq7?64aUBPmql9akoWSSa49;9Av`iMvbv(nH~L{yQm7w2 zy?hZ<*dNmn{k}9Q?9%{KK7O)4SC6qb$TQiiWy=iiYSimx+3NLe!nFf+kMo`-u0l}M zvR=7cb)t>F0;M|YTpauo13Z5BFjM#|Sll+D9M(TdE{D35yZdPisJ+x#W3D0?B;_`T z;KNH7u3viirp+ai73q=)uHUkj^LoaEoYixF3A5nJ^Sm=tFhU9p{`(FK392w=q&$HDz7dN__;w;NEH=e8VsS+ID=6&DySQvpp#WDL$A#+zbih%kfV!a3%7{Dln>QXl&-g2)t18NCVT57nwcGHjYnKoE+Ve?Xl1kd-S+_%Xi614Q6gzE*qj`J$s9)2&g>_d?zoGF ze@4;?Es7y0FpP&&2e5i7OU9zsXJhdQ(6L*CnA?6&Yhyfi?y-GbyCP0`t1a}URINJ@_8*Xb)%!*@V}06Rd$zwT)Z2#6(W zTQ^NK!+Qr0j4g0!*s3KaZ(A1at1anUUhF$@;Bb|LT-BFhGBqvPo@`ql7hjQJPMz@} zckNAIfi&>h>jM~$V{!jE1a=(Rd_U@e*UvmyRNrfB^ByFQHOI$S#4V3a-oC`tWb{_` zQQXBNJ zEVZWmD6sagm91GLa~B0#x&q}ZtX<35CkmMFKKPc=pRyhNFjAEqk{r4H75d&zeMEV5 zexK?$1;&oyGEuf*BKIfARUN-^hAG7<33o~gGPK5u<&Oq;++^WgE3y;XQP<;_+4ju5 zF;W4rd-?uwyIIWR&SwoBhRB3Q8r2neB+`6zmp~U&GKhnz*20wgaTR<+3iSew80+s3 z{f>Op?x>@f4`XT?^4w3WT~qDDcGk#f|Dd`fwrBHDR52`MLZT{lA4gn(Q7@r$e z`~1_vAo~*6jwh%{G^EqSGA#)frcv&dD3|W>FP|L#yur<__<6$Qa{skuW$q~+D*B}g zh4IYU?{VbckO=%>WlR&zd9G_1L{JQ+a@B8o;m%gBJ@fvJW3&h+)B>z@lZSa)8fH;2 z4L8^%(rd?T+|6WFEP~^9!rr@DE{y~jF&ox6v5#U1+_;zAVBzmq831JA(3GwO$z~ocE#N0w#M`UK5IRS z$|m)3ZP7(~KFrwQy(j}xk`=f%q|lY1IDmwSg*I`%2xEQ*G%2uSDS`4_!bki&V@Gx% zDkVfw-|0gA-5K--il4T0!bH<>u0M8NT-vOF7cViPk!(l;B#0o?KyH7HXm{_6Hq)V_ z9_8!m*Obc|<4Sio?CS7LTtLwAo2@bOs8Y`R>rQvfGVn-fpp^#T5Ec7J+fl~R5iiBY zV5K;uhf*TZ{>2Il&?`^^NEUNgRm%1Eex*_JnAV#(o!}{)4O~pq1V%dh*wZD}ir3<; z7(uu9aSn*m&^XuNtN^PV%31l_eojj1S|QXf z@YnF#wp(s-v^+5k&_7*Me}*l5@AbO=^0 z(94}GpqPh-lv@ue%ea&QZjV=R$;6OZAsr#DAz;bau9A|j;*xpt8}{$n@U`?dSSLW! z3Jk$CInQ#lW%J6<$b-FJ_zFrv21ipz?MJGnarEUNSLP1e7P8}VrSag3s!grql$FSRPZkkvMED$P3PWs;TSLr$i*Rr5G+wo{+m@@{{dxdNr2o>5oQi={`fJZ^Q(Kc5FBJm+rrUtYG@ zFc?XHYm|RAqD?f~WJ;#KfcvSGgKXmJ@8N-u5p;MKq@iiYqBxxCR@X#@8{9MfsYv<2 zXxVV}Fht-#8(%vF59mu^33Zbo8ToO)QgTJ;9v~`b_5BJRISl4sY5ao)L8A}gLHi&T zu4gKqX(5BepDD!!<@Ovw+3rRTR25riFfD(>g|`i0zWxM=;u3B`?0hp8w>m{4SowKi zGM764!^=q<)r)o@0G5<|%#PnPSlAih>NfRfAFai284UuYL6oH$MID(4CR3pwmyZaD zG|5lCnT_%X9Su+xsBbgRm8*!LvK(CB`lWp7(yHnNoP+N(9NbdY8eSS(x&YwrV(0&-12e}m0({O^ z{d6|;r>f%y5|Hj%J0c=#Z25#dBnpTO6ye%m2SSMmKMoXQ`e{K00b6FsccESWjfHXE zTitx(Jk0E5Q$T*C(}`)ml6_qFMfCR>DE(*BN1*D$mWdua)S;`KY~{9{bZ|@ zly)rh`Q<4TOUrPcxW(t{Z!@bFj*W{QIX&2ne$ zf5eq!@5(;z#CsTwis0fnnZ4Z`1!fgmye&S-K9l(O&%=Mck(Z+b0^uw7&hM0<18Adh zAlW;|-=?7?bIwnT%iZxX_dYM@H@CEqA@5}Gj75tthxZ%cd}BpX1_Bt>*w;b~i$*b1 z`F}j%jEP!N)Z59r|IxyjH#87?7(+wOhWVcY?GnO?>7+uuY#*LBkj@i-P>%d(N>;@Y zYXb{X2MSXol&Q8I=R;KpG_8@I8COjQW0P)rZa#RZyU+h81@?DFV&!R|RO0%TM23H= zKiak3aC>*ql!vUVnVrg%*`dT;iWM&2*_N65muhbRn>QCV{fSLD2?P8b0lxfLpSvL! z{CJH=-A-G7ybJOJ?E+iZPoU`<^bj-DFnN5jlo67SKyxEiTF6j8ItmpJ)7wx@01=in zIufIIYnG+bU=(^!uVLiMAW=v}RCksN0Y7OGUGST96SPm`~sS{nuO@h}Qa)ouCu$lF{mo>85&j*nv9Pv6=xFexGCI($Pxb;U9ZZ{!r9v+58KB*S+=Z4b$@1 zb3*m!8z0ZY^gC4b;4Jp|x@C89scZf&7WhJL#S8_8s%h=r_J~#h`tQj5=C+i})t_9@ zcI=AXvAtX-XXV;}R@y!1d&2#818bt5*&W@-Zo5;sB zPJBar_W_KGs6*B2`x8P{w^deo3_3afviq4>TEOM_tNj}!4g|z{PJC{h|LOXwy}xA- zgh#g@`cCBj;&bnUwChLI4U;gn^be!wTXBe*~p1V!(NHc!i?t+7xu_E9os1AY10)6UB$*JJ)3kw-Qy|8tON*v@?C5R@3Lbf%NgaE43cFc zuOHMo=}Mzf32DqTk}dbhAQ$sG*#vB#-6J1{IOQC6`C_rzerfV@BIh~5Mr3R*Cg);Y zdJ7v5G<%C6S?*af79gYTd7i-OIlUV~f#h4BlK~sV&xFxkJ`MH(c(TWS7UqQ<8_Z4W~C9B8 zQX!4aGW&rt6VErXMY=1yi>5hq2uw!R20wDn(vzJ$1(}5dZU4!$cmyFEiOJ}Fc|ISa zyqtLCVKd?Pr;%xR*YL0NvST!9(nhv?!A!)yV5@+VL}Yqga1z0a;NDc+hii>f3eSvaDzFM5vbFxrZhw%d`qRp zz46!$m(BmI>&zOr`$55gM$_t@@dj=#3BYKdsA5SK&iAXZafJ{9y=N*)KVD@q6>=27yRn?fW~__L~81gB6b?*n3rNIGcOV z&jT|ono_Og0#MVUYcr0MRsDJgxG@Bvr}?tW0MtG+w6~fv#a1X1gvMK9OG>W91g;FY z;5pXuN0~CpUTQ+0iAo{;lsZR1ZQhH|$>VO!N?~q$8y)$1cGY_X+qAs5Z+>V*k(f9h zmi%!;n@#|WlEIO~zyGXs%#oy5<%V1w@bkKsUVEeaH2(mH!q?Fv)_*Db=}(cy+*0_- zjJX%BJeY+Sm(g2|$+`*le}DSdoND?ec9pw4y&$bnUP1pTs$zF?!WD{M$Q^=e?B)(_ z+C6U>E*8E8Q`h;W*|GO{09$q0w9^`aR~ zjmeOo{jhTm<8wM?T8r7x*Xzss1Z*NSPLOfQ6>Vc$nFd)i%mV)v!TBpK1Mo;HU|Lb|c&SOU} zke^ua_#OY!qX>#?e$A{o`4+%>!(I*Zvnkp#&-grTPQ|Yd4P9Nl(5Y>fYCUID2nZC~n5w>scKt3FpoBYwZH3ZDy zguR?x)@}p95_DtbiFM+2eS2~|CGL_GI`i7tc95^gx5-n3K4K5J_1WAv42wta@moRq zrh(1Vu`_bJ?V{W-L*z+EPY`DR^k>p`;o2G90fE+}hqor&ibBPsDuZ{gKP_{f_xGk_ z$#q7qUZ2GR595tD+q1N18~i4qFW#mr{eKUPSe1|y7nhdZv*Y0%Ftq5)5or+&@0yo= zi2gkIygD#-tLBHa99{V5zuvLPQKqwptEYg9l=2|s_325$?RDRDQ0#VO3G@7nhWO^f z{3ZDKf)S0|lwb*!E++)~Pji@?s@aV|bx3By^ z^8C%OtHM9z3I0!dj(oAw{!yvNd563w;wui>Ivvi-BlYW>SNM6aLueFvUB=a>LJj#N3G|2bF0KpsE)zKcM&zedRH1C!StENwcy)~}6AUOnk; zI^LAFxUaM{H@2zNmXh|i)TB=cVOO1>PN`GrGT8Z98VzA}X+oUB@ow&-+9G-Vxu_C?!4MX9(5%sL zP=!nV-@Ghz4Nr2CWSp0G6Q|^YFCRXD0NU?(YFJa09HzL!ssn}4@qrkK8k1_;c2Oh> zSCTZ)Y{!N3myqLQA-85XvK(=hMnmmf5FC4fkYx)Xnt)VkELWp2#fd72Vk_2`67wTh zRNdypZ_b?59eE2YzjX#|Txj@iDDN^MXT+`|CTt@e8JOw8ZUi+0R`l`%qs>bsK|l-; zaLiXJE@Cj424+0SuF3^FCFDd+Y%AxWc4uB;)aP!L-Zp6NY}Wtu2@x-B4uwtjp)x3uct zxi2Tg&MOkaTHA?n8>4shwvkRrxdBrCqTBp2{csWZX-!%wezT zGMB0a$LJ+sQEXW5?9RUv0-Ia+J^xqych`}u4=(-9%l9?^4gDA8npb8wV?Hk9O}&|K z^6?UJPiifdp2njPvlzpzmz+!eP4Z73v)5z~<3JHJB7J=|89LY6}sQaZX-f1a=QgLK!SwTw4 z+Xbagm>oEG=?fxQx%ytCbDfzK_xE+z{oEaqfEM<7%qYx0Iwq+8~X4<(``kJo@nup9{3|uKTJpS4mO~TUaPb7S zsGjWFxXaN~zjMhMT>9a!I&R$iF4Obhe!*FIW*9&_9N@my}!ty`Ez1YtxFl}%ma#75}3e>Pb&KnvbfaRZG#W?$~%C91Z z-!kT1b8!)bHozu=yRnks8f!^QdHZSnKKVpP6|sEyIAy<*b3g2Mo6b}W!ka2>^wuoS z0w?I!p0J;wl{uc+kpJIIB<;9$oQ|kg{fIA(d7LsoK6lVrV2s}~+Gr z2cEFq*px)tg*=BYP_PSvp)RJ~M0$?P>A%fhSvOHwB#b3YQJu zz3YVNsVqQ0G^>=tBAlLLE&n6REL1&ykq5=nZMEn9@ z(g>ftZvh+p`3PA)%T!>$SN?^A>Bw5_t+V$rq>ZFi6>`P|MFoxpkicw&0!`_7(Lj=% z%b{%G@eT|SN!A%q1+7!S4;qVP)p9Cuw>>b(h9p+8!c5w`Ki(V7v~61=d!6 z+NTaYF;5>xgde8Bw@v$(TU|tnw(t>JR&KuJaVag({QcE2ei`QcfC6`gG4KhAR_IN&!!?q?M{JjH0P}|o z=iZF8q(2qekUYmQ7m1ijgvh@$qD!=osKl-|sBPyB=SSZjML{z%I0j`D+7q5JASAXt zK6*2qsiuRvsTQwa_S&5U|Ml|JYfRtFg3iM5BDZtnvg|s(=f5JlRGb7+qIg;9BBOV( zY0E_hKG7B)CoK5l(6ktWLd9m8v#?`FUOoZ)*|)H}2XcGcN_C`bnU_NVy!hQhu)5uQ zK|ktdoVqgAJnj6>qSK1y+i6p38GSih+)s4mYn+S0A?JD9;y-Bc&cb4FVMi}?M#$V z-7+_|dy+Q6m%E)fi33Z`f&Iyp{VFWxGN5y190QBNJ;wL>tu7XWO7sZ6O`%%kBx#0} z^#aZE&Su~Zf*hEUOnWw7Q;4BGWbmHec}8Q>9y+jQq3^NIHSt~r4bZOzML{rx!1X=N zmHWTLOE{*~zsm0`SZ2X(*P4D)9hqw98RHFYBK^aIqEG?D zgizu9cH- zIxoEaM)L|8?>G{8#Bc8L9#NOX_DpM0F=$~dS{*eqX@r*mi~#0P&Vx-F?nfmT*=H9=Yy`v?C^md58+_{8LpAha zZ5XlgmTezH2o)m1+#CiuM6A33AadQ8%|}wu8s(!!>PNthA4SN~JXLR`Xg*IpL*k?0 zm}v5Xojm%CHU%#4;OVEesgJaL9>t8-Xzex#6A=d$g$XAB&IbBlC}N>SMzsMdq#&6` zNp7^JqE7=Dos9BylNZE82)zKku<`&k&#wWHgcFV))rxi=@=@U98*svAKu{jUA{LDs z%*K^6S2Ym}{W8;5Ifa)JWM-Br<%rIUAnY?YpFbZKSySx-Y}}Y_8KOW>QgK>1TYzF2 z0l+(q#e0<0>>9Y=f3BQg{yLV@$wJ}@txEXlEUK~^?;nHBpk?=Tl=!C5>L!8#_Nrh8 zqxNcwoQ|5!G=#=%rMH+(9^V(25zmZcGIhQCQs{Kn`f0rkOy`lD?D{opPZWtImYco1 z`@Y|h*PJN!DZJ6UXG`S#%KDPB>5~cl!ZdSz^~9|Ff{(Xu+_=@3xUZ;KiUpCDiZbxI z#+kGG@uSh+BG1)q-rsfAh2x4%=5d}}u~=d&I75ivI(<5RgUEU7`IxtMEG{Hso|P@e z4bP6frA$tE%3%$9))jU?dbRsQ(2XC1{)`* ziS1JB>Z1s!9KwGHrP+r-w0#9Oo42g1A1|fQy7axPjlw_?+HRCfLh|{0siS1d1##IJ zBH*xqxA}c~0FDl9g*q5w{59!@Ni~^(#p@Ow-sVv6X4Q%Jb|yB~k8h5*D}48}O7eSLRS)&z=fo^`}eT5x|ss|o^hz2DJK+_`Xb z`zyN)$U%uY>`bh%0zW-Zq-V6Vb}fm?_&zBbw(x~e?k^IWH5a0(=~*u}m&4)|v*O_f z{MEAE5Cm`>b>&ZJ*#a5cG2FBeOdmbHPK$HL8jHj z7mn4=6F`XaIwO!xTPnr<7jmb9lJ1-*Q@n*i-^`%QCr%sffpw?X1P6Dka~$9PVsql} zCVqpTd%JM2r1X5ZODxnE3fG97 zqKe)ABL8mu>9F)lMKqRIgnH_C$Q=sYTxbIB7Rn9h+Qb1+rovAXR~Ghf#;2(AdY4#B zgQ3jOzV@KP3K=DXNknRqv4vY1)`p{iU;Th0oPF)d)c#hml#n$JHa>}k z42bTZCAfvE(O7D}dtD~R%pHe5-H2-_n{@TTbgM}^^G}UFw+sKisOk(xZ?!1JH6`@$ z|E`SaGg0;#sJcmID)YCR3zpAaAQ*!%UOGF`yqDJDJINzADO>$6L5IDG>&a&UZBb z+ncfU}%xd)cNGda1oTk=+p?#ueDR3f&8%s3WO;QOBp&|rNq}GapvdJEoxnq~RP7rZP zMlll$z8O&ZF&KOY6*k&QkiK9AFDAL|rw$T@JcGD6i}oQ;EH>RgF@TVN`p&z2)ltl) zXFdt0&jKrtu9RfQx0o+G)Hvgt+6QK@Ot8*JE-rNveL9&va`AQnb(LUod%)3-0L%4q zuW^@x80kr8kVlDGWFa$}Iy%-UHi$_$O6+12iGj6~Lkw1RYsOw&st!TBrCzLI&pB9sfz7enBz?2NVB?P zHu^Ib1(r$LzaGzB==Ke92Ks|L;=uUi!;YV8RdElI8q*D2I$ z!`^u*8Cw(02!a8`q%yK~W{t%@GZyQw#U<^A_pfbbs#`xh6}$yN7lGg|JtKgQikNUI zk^~~;4R`i-sAk&^2}av^X^U-7S~qohKKPfv%Bi28+V+MA?h7Wy@Zkh)A_Kp~0h-N)qbvVme7nT|dpjAa;elE90*M7<2n>gxbpuy?KD*#SW$3Qn>-M(AmlQn-Fk-L%yJVNGno zrwV?7;OC4{Y-W7M*v{v=X)Bgfg`Ob+#6;%C@#ygkGf>)D(qZu|E9K+9Z_ewtu6 zZLv@wgIwiGVUsA_*x`~YA423D3=lRVx1^_HEaJQg`N9gDyA^i+62(Wi+dc&lB*qj4 z3y)?BVZ2m)0~Sap`0p0-emzdG;=(b#^}n`Af0Zd3%~h_W(gvQqJroOsR`?%xAJU8)}eN1Y8%(??~)kj<(w%F z^YfCQWri%lfGmQ-;MZTDHuspZ&~bEdpc}qceO1$h(Pd)HxE>!Uuxm~`0D(+0jCIyK zIV1!$J+?R(I`EB`&t80-4mLL=SF*$`%jnLuK9MZ8xBs-GuOYsrRqxlF(L3E&)C@MS z_PIn`FbH*-K6CplBRdrx=~4(B`c|OrhlP0AfuwQn7sBRbuQ+UhJ32HUUy&UbOY{q$ zV6(5xigk9r36wIkm_cOair5oNC=&p}P?fHw`P^-eU{ef) zctISS7(pRO%4m-C?UZI?FRgw<#H##}6vO0XuR@S-XITGFC^(iT=k7ufZ`_aR*=+E! zE?M}#D)ed2|K5t;EDD?l9BO3mS(7r2TBAY3=l{tBkoI%Ne}mbxLddN7HT%YbGwgYQ zovG{GSBIcc%&I2c0Tc&iMIk)=s-f5Hjp-;8?uZsXe$u2ht-R1Acd+4e7Tl4IU?lBo z+gP)htVZDss$X}dM^6@NDJD1rQXY=Ps01D3jgoyBiO~pudJ_upr-t>G+>TNXF+-$Y zQF1PY=U`Adh?b2jSP}@9&RhJY$Wx(?Cl6C1^J=Vyyu7|DX#g;>$vO_1fU{dOC3v$` zxN>Z__Cpxlf8CaC;eX3NEEsX_YHse5E~SZ4bf81|`1&!uVa4*0CguDwF^cu0dO+Yo z49KJ!KsYmrT8^0>25SiE#L>Z=OQz{gWCH#4D9HrzegF3qXB{cC*8bm_5<~v6{PyiH z>~K-_h!|S;;kRFbZbEE$Wt^oxvB-~XZ7t$}JV-WZr=s29k zN+tw)F$VxC06D!|j{;LR%-}|Cs)d|chH6`%8l1bVaUYr?JH9X7z3I-X!H|*zi-!{w z2WSed^9GCI2728fpa4H$Qp811@GmXTjD&Y@$eF1-005Uyt7ohSG5?iuJ5xJ(6{w}e zxINA0@+gT}A~uWHJwP@m_`$I`o(IX62H*ErE?!*i-(_jV+e*ffeNuNvyIXQM7MrWi zQ)JaP$$9bs9%=qD483#+JgkYxD+nytg!}XSiAO#uj(i{adSPyGHpjbbHzoC}&nu3^ zkT*m_U`kN#!6;75`lGTCi>8~yXZX;d*b$+(-=}hSbQsP;iM)ZZEN*^knNb&?=aAG? zWygr8cFd%UWSXecQGB|Y1<3%I)us}sM5>F5kJ9<;$hk58+_{m%^0+FP9IBL&UzP;V z77Xuuyf!^-dF95WN9BD72rF z(Y_1w^%3OOv`9#aY2q+)p(-Ua?DawbEs?p~+ufQh<*ryE-0bsj>*FtHliSUQ`Mz3R zO|%SIv_k>4Vt>D>FC92PIlopFit_B4P2mKKhejIkdv+7--823w)_%$KKysBgq=mDL?l=(DG*IfM*UDR>Gx|)iwFcHdZ zJ3opEyd$h>DvD`ho;%>)A>M;Unu-IUn9)i7zae`SI-yCa57fTq^0qjx4DddoyLqde zEn1V5>=k^97|`;+?B@wI+(@c1iQU96y4Rz%MnO|OCU*+Rm_vqJV5}LvCUm z3^GA4f0FlhMFE}1^Cj#qdx%}7cTT@hw7PvB z#Vw|47(%W-Cxd1L^!s=Fn4mB%N6!^9G}N$WL2KKI7<`0JqI6~H_DJ7)V18)Lh%z)a z`X^&dBTEy?aUE4w=dPv~5xkY}YSMMzIExMbRisi<=iO!3dbH(om06r*XfYJ!&P)9+ zH<#)rkF#ZAs47Ek`GL(GcSePc!IHtJ84Ve=#R|pq#$t!fHi92Y>Ch%B4Ny16xa4OG z!Q*De=qL3gZdxjBG76^aVis|7!M7xFcRFYo`ITZ;M-3V)Z` zA)b|pV#vr=A3XYv*UO|S9hg*K9N3hb=o}g~$&0NMbVk7iv5;1gWyIRdgFW*- zt1AF_Fbhb2Yex+$R`mxJp3NCi5Dm2!Cn=4$k=RH2?~fR;eB=NgdzvgEuG<%SV8U$K zi{x4NVpW&FCf7-9_47HY21KF|lsg z4S@m1Id04a1wk1dvnq9Z<^oaP7j-S?xGDEqrT5~w=S&_#*R69~GA2htTmo0OB@!6hA$r=;8?hR=QAlFqeA z?}5KoG3{2dT0pWporG~kp^~bEs4ImliU!H7mQ}1yA-3Al&GP;|?4G)Nu^S*Z?UL+j zBY@kgZuLFnXC)3d#V<}=QM|GREx%6StkCdVA1AFKw-DsaStzSpy(z@0K^W9p$6zTZgjF-~3SAe|kU!mX3zDO~%3n4}mvS%se4GVTgPlrQqrAh~oEEvd#oA=A0yr z5hz(0&0Ud%&{XO6LNm)Wz2`tGoh%!dZ$AH3yB)-VPUNHKi^?Ya*t_k{qHR&}9h-7E zH@l>|tu_ar9;LfQfu4kZ*nLzGUh&26K`4W9m4h)+peuuFsBVS=BW(5~E7t6Px2}Gw zapP1=?M}Yj?OraESil=-Me@cM#9wm8PyfaTGZVcVIL= znZC*eFcRi^Z>T^ox!)eyWp@Hj8IZG zg(J=;u-iFDT@j2gZVzrO+icX7nvztgy<_Id@O@)`TXP)p3|_xlJ9;NrU}#6~%8io+ zrT@ms56RoPb+HCNDWoGQh@6<4q6kKW#`nw1W-gq4mKrI6b7@&prkyfwAK3MnozZ>M zg4p-df^fgeH8Xii^Q?{?pZp`%6)*!NkRQ<36Lf1jCtcxgyTsIHb^=ge`x zhe2w1tceJ1!5|Z)FR710b^$jn6Z_N*(V`d`XcCHTiNnoEVJeY_aip3x!pcGpLZGBn z#T1H#LV;6fSam`}&Az-Gmyl}LDOY!6b?KawzU;!(Gj*WAsHE+4;^zr0?F*TKs zwXU_<3IK!X--zE#wBkyhfMa*vY62tM+RK#jo~*U7uKUmiQ|aMn1I_{)j5g!TyD#4W z`w>)4q2&(m&K5I|MuBmchz&nicg4_w+=XbK!GF#FLr z;bEW12K{f1@{tM_cH!T?zX>6LACrip zkg52yNsa`!FOPHiRO5($l3l!e2D)(1|G7eeM3s;Bt!wT@OF@6L`BaYiyzG4T!EQI3 z+jpCDY_=N~^W<3AIHP8>LBVcnr|JZ*M$irSp8}kf`DO zvttrZnCGJAQgh;3*ZG(nchpkJ9?NLtoXd8XZ3B5(-#^XV%_u8pK%J-S*w=3`C+uCj zZsE|yTGpc;tg060cf&DjQK_l)!*3@}_xA&NdZ9-!rn_ui+0=H)|6*$Xmr-M#RNZ>l zXCrloCSQRgBP$iB>*aeAJs0(-8}^R~vqsHT;92siptfbRz{?2jS+_66!gnsQ(1MVt zx9|SfkGy_4JMG8%J3{?2C%wmI`JDce^>90$Eo-@YTNbsf7>wod>!^tk(&y^%osg+S z6fT10j*hK>@BMlJZAt#gF9zH@#xtq1NPl)8gc&mV>ddCuNaH?X!9rSJW`*k-pZ z*)|#ogRr)t9m7V$2=5EhXw0s9D$&NJH8;+^tj?-=U|vnj^*=1B8eAS)1|3P{zTr`Q zzVJ>K7}G|PGhCGKUbuWJRqZH;C%n0{=+q0+9ivfzjc395e+Gn8%aha9>T*M@->PPw zmK&^R4PADphWzGN$htTt8B<t((QmXGTB@7wQI^a~3+<{1>pw%_<)pAh}{#69%) zmvph;L%?~kx-( z5I+?soQo^3SXLF#(aGt-PxQ%Rx_XA)UXN!+DpG*rY+!jrMua!wVEC=;R@m6$cFt%^ zN4iuen5{T!&5qaS=JvK}>I39v#J%WH&_@_FP5|#A(Y4T$2q4FH+e4saoFiH2o-Rn{ z>X;6I4m_l>eCY@R!7uMBI)#MOObl;$XU9lNiUZ(a#kzG@E>}vu(%k&i{n66#r;01? zf2~+oNqZ!C)$&wOv3~t&*OeB*mDlmm>+pWdC)|Do?V+IZ&#}Yh$_0;*Kto>~mr9RR z6%Wwv2@hBGV}%3N4L!A_^)yuFQ03Hxd`AbdO4V5MjDOV2V$)yAyC!zGG3K{1927=? z-7<|XcNykM1}TG5#kzGV9tiV5VbdfRc7k=CePZ=*(n!;Y88b$hq~_UCJ5Q9BcpNDz zfy=$rjwdE|j@-ba>y}s9CSDJ!Z^1|-*P+^T4)p3+9lCnD;BCxm2hzf1HR*r4DO3!& zG(y+L9;N&y=m-OdM!$MW*9b{*$pY;)e2miXlS2Ppw{GuXhUdFM=s~N72j2%~4DL02 zLLm*`MKAos)9tD=)7{fEx1tND&=!j<>x{?s*b;<}QlLQLCnt+j6xvd_&T1X6bKJZl zt8#ls^E!Il|J%npLCv}Sr^>7&o5~h+_dP%k%n@W;QWoMH^nTW$HsA{^`Yild^2oB{ z0oPrT_ahHidks~m-A|kHy_74xOXN+{P?g9u%nIT0bHT?Ha4|9i&AjaK=3%%B`=yWp zE@g3Ti6!(^m}tV7h9HT&Y+YkDBY)LVsk)Z^QO59VpJ7Ht;%WqlA*mJsHlS9kXnjXgZ3w{qcX*=bJe-u8v$68LeYQPrHSzl|%$1#c+ld+S&hMn2@ z@KAyn!T0>pI;e$-R)I3<&n%?f|6(Hbmilfn(g$yNdoh$h| z^uCj6I%hO>PYTtjimZ?!((v+C3WYvC7TF@gpV>D<2UxQI0XDiCdl3`PETOo1nlAYA zTQIxVmAZ&>`oUe#(dB^Z;R?hDSTJriZ_we7mSV|ZUtBq43F;~JY8f*qGX#00Lo4wX zNodS9zu-rj5PR^lI8l+7Fh_%M>0(IkElw6hBqbWb>#LC>hfsK}tLC?|!485+JXRM+ z64?-e0oC;of@2aGenDCPNup~2nD73?W2FlN!_Or{NfckP9EZt@w%7Mzc62!oso{V>-hd4^iz7nkwxP9rEtjjs8o(4${H# za;ioHP!S0YAuc-?-9;1-l!8(nS3f1}uRVP#E`~9IQm20DDSEeT!)`eG*3BLA-HIZv z+247mu`~PW!dnnTIkgHWAhc9xonv7sq~Qb87MYOs(cbLR2kl9V7s^q24E;0$GxAfn zU_6-2+5cq>8EC)}L=F!QJg4g2K68;luQ23&S4#V(21qNka3GQ*EYSUUplFtwFY+ev z`0;jJ0c9Na3bG1dcZ|Gvax z7UQ#b(LO}edza7sj5*e}mz0{K6anxo#B+8cqAPxaG;ht>anudzQ2h2cvTl9l0unxv zGI1IWL*WVgQrI*-CUwiu9wZVQv;@g&(CflG{Z)kXXVdj^-%h%rDCj3bDfUi9*3 zhf??inBlHc?r@9-m-pAC^Xe{c(G`eznRFOAwn$@=mva)_LKFc(JZ09K9e|u837s)O zLn9{m^$HRifXK>-7-!r7e;FkUPSOM_CgP3$FdzY(95)5$RLKe~20=mtkuj`5BJ|q! z!d4Uwh;jgA3Nm6znIt5G;4R2uYQO>_ItY@Oi(enLkw|b7F(gJwDh>jj?QjwV$VeIR zzn?HRFyO=pP39`()_=&}egrHhasauAs*H{FV_*wWhE$G3S#&X0Kt^aiib-PRRf!Q2 zH@q(9AR0)1G#x;PSbQuJ4n4TA(G3wnm=Hnyr)Qc*K~DH@#6N>yAn1bVVVIE~8r%0M zV=LZv8NYaq<1%qckxvnap2HZsx$J#8*E<^Y?XKrkpURucdxvl;^wCK5h6<351?LCG z!q_ADf;7fB+%X@TyxlYa8B46RcztSpR8?m|FKljBGJgAGd`-cmElX>y`4o)F^pI&W zN+V+qItYZ2iLS+To}x;0Ga)jW=9-_L?C%t>W*E&=93e?+`%3fKS*DME96gw72_I@n zY8g-_ysvQH!xuC%<%&&QLv33J;#Gn*Fq`E9ah@C$Fk(4Gb_x9QE0`0`XQIvk2q{EfMFRd6QkDdX@UjBIA>c9 zC<|Nzo2tI!*x`7%lV8Ow3JF2@`5{ppVj8&!J}nJW%EcH(6SEZ5eK|mlqU|TR6(s-N zym4L?Qmh^p&8=sKZ}(-@l~wZ-eYVmjQHR{#!x4O0O6i?9r{sh-0cY_BPiWcVVs((81fLokd7qI zIP)|m@t5GfCv|Yr9GCi;``nM9n|?4nSZ^1VI_u#jACtqK>?$5>OD*^Ei^HQ(V}cIR z#|W`IrnU1CjQ_B)2;_@Mj$oBELp8#4q7rT-2^RM31xJDag73KtfGgru=On#lbdP6=3#g91EB zQ59zy+{0F`ASpcBJlk>tva>c-?jiScH z4oIZ|!RTnp7a(DD4K@Szb;}I^3sK#Hj@5W>ybNd~qA_%4Q4~R>&BOPBPQCU{Cq@5GjK>90Swv zi;hYgQrI#B&^y~jy8dE+xc2ZR0DqYDAWFAZP3rM{-zzt$_x2QUqhTkb!#9izoPx_% z$l4>eyfydM zMQ+pQ;P1C|H(KO^wr$t41Ti8JMz5f#7y*pwUeokeN|A2PxGvo?fmlcypJZ|-4-W#b zoWh-;p?htYI?Clg+`<<&wpN4~w;L^-c_aI3k7-hhfRIy)@AUF;7I8-yLo6`c75=C; zu~$3d$Td>u#CXxK@C#LbsD}|Eg$Oe;2nN~3hKrrZh;*Ii%bemv*5{^#JK#>Z0xvJ~ zotDID z$fB}rU47L@?;%t+bxxL>Ozla$Agtv+KebyMPNWb8AuKvrwzhb$$&?klSw;(gm=VEa z%07=0x^1ru&zMYAL7QdN@T(aSd^7wHQ^(x-K79BIf-gYja2*mi&2q)HEyaz_(b|2MXeD2 zN!0~=`h_A$O)0D{fDq_E>u}W&0R0#Oh95*2sq=su*NDQ;1$wE7)-m4=F#;nLLG%Zl zfRlhF0bc-B1IChexMG)Ri4wr}u$T&-@SAg`bYC|MIGQQ=J^c0Sma|~-4H|FFg-D+r z)UKd8{{#&eKFS~?pFIs)ME!{tJymYA@vVn2QFE9{V9F>D+`c!TvQ$C9YaNkgbhe^` zW=!M4Q!DaN>k_DjzkBO!&%#7y#~AE+*OpJ@2Byob*4&A9b_Z6xucZD9fvssBM{eJS zD$4?jyr=OAb#43)-qqr^%8rnp&AWO?I|dcfZm_d_;uAiG7^v-E#4KIY6JlYcIX-0P z_>oyToRGAS%j3uCcu$MNLOv$MFSLaC#V1@ZPELE4l|}TDDaZ-=7@rVY{mi>=EY#1t zrg~X>MR3oS$)1@3A1%qg3kXCa;H#{6X_JDF4}1CVD*at4GG!B_K50EYx=5gwfO{?U zQ62i3p@mKaP9-2(oWyui zE^{9MC3NE-=(%!wd&ZJw?w>kbp{5-~}1FF9zx$Mg@A z3MPUOGk#IkT&^14=`&RygXfLyk ziQlX!U3!ClWR_`}kdk(_k4&J-rzZ9Z>3LYO@bQ}|-mHqgK7%_T7B>8{O9g&#bX7 zI*>`x9&Gv14}s{dPytp)?ATCLX_kUcxg#fY9XARWg5 z-RhmtuL$~GQPeR3dUOFIv?+jGB}4LAch)r=?JNCp)QHC92YnT_tE#VmWFMREzvYiB z(uGmqUdW67_E6Vvf@};ePA=^7p=Ks^K_VZefG#I7C-uDjKyvou?c*)Z$I7ge%O0fE z1<(Dt9b~M5tiDJ=PQrIp*?Oig)kLTLP8IKz+7NWV-b_}%gBfW*CMd2!@m@*~*59#H zPy!mWcE+T-%`po05x{Xstwv7E=^a5$pErW)~sF$an!E-GY=Qo^3|ADeY=ckAqp5z*^Q@=(bL|(Ggm%>O?Ied;1FB5O}7o) z%L3WVG${_zopT|-da(-B+4am*CW^#?@jUE_PjI0mvO!5XIrn)7ky*=Qg|g4C6#Sm9q$u=6|Y*$^Yn?^Dj)YanR6rYrQQ@Yh=sC z(9;<4VcBghzXw3R)GXWwI)s0)+=U9CF<`Zr$hd$nq)NC{S-s$1RW-n!@U2meP<3VNn{WA9&-56oyK z^^`-Nu3Svognj6Fl*T2-)#v=s?j{OH+pneUl#-g4m9+k103Wn1 zCgQz`A>)Fu6?J8t10&!)JHX@Q#MaYc56>uSTe`nvQn}i}=GoSpi~PeEy>oHdV(Bsn zD(}v^^LuLXt^I2U7Y0xTdLGAc#}HGx0rb|xxS{FF$VFX|%HwAiX@BJ%fm+~qZ5mf* z^7nva50`BHq<f-8d*E2+pNp*TYWC$I~EQ7oXE0EP0wy9qF&oYSO`0|xQIS(paxvSIdrD)1qf^L( zyg*cLh35*79(|C`tWF}w#L=xt$n+)z1wZxrWVEB9!4XshIy;K>13F{z5Y1+KDAdim zI12ErF%TS+U{NV7rXVSND|w8SxgI=QKxHP-Z)n99VD~HlQ)^GI+cz$cFvp{{&#XG1 z*(*7$YyEUF+n{YC3>qbw#&Z*M|FhfAsm!+T16sjrzFbfz@%eR*e?dsY4!3$;Lr1HR(SSRnt+YXkIr!C_(UZftrV@6+-rC-5=8cwV}Vwlw(ZDJL(a2 z#3^#ityswLE}q8rE?h)gLaL zMc20@AXXr*of3lRR$IYkRu0}DkMvd%LrCgYE9hv506{?9vZX#Gu$@Uby_@gPwzTw; zfmF*i&nrbXvYWx>P~uLvZbq~j!h3WhrI-~4g3JK=8Q`}Myrc3<)j@#iQ+R`WSpip?im?(_F}Ss={@2P@5&I+9cu7+pnS&DS9E%e_RQvIP9~d97~Ux&N}=lpV?_z7VIf_0BTb zCe8r$0&k*naL7T+r6Mb{*x zk8W%nU0%~rf@+=1oPA8bKDuMSCrM+)ACIYhd`&(s#?xAm^7!I=LAny{X%A25G-q>9 zPWuHDM(G_giI^9AdTvxI7kk$E!61>p&@RUF7v1JV503@*y8=dhGdrhcGGW}lec9FP z_JXae-)xRg;1KMu3zIU%&OZfuvPe9 z$`z)R?~}+c%8AUs1uafBqkB<*fq54}gwa+m@hD;oj{;KQ>)zznk{vW#CyvW%H{=Hd7rHx#eH>* z{DZ<6J1fwP!P}utJ-xidKA}+V7w==W4(;lUON)5dwLG!t!}WBqHO8UFU}sV46);LR z@q?hLdoF_|cd(Vhp@`HDS^`lFeho(e1nOwMv(UYMx^0GRoduC1YqBkb>nfNS5quzF} zc46_%^rQ^ho&%QuWv*R;FD3(Q{&D`7C65;Ty04|+JIDZP)i8oEr4*$jiehz#o$Be8 zYK_?c^^-OPv_!9j>%N|Mc5Fy}o1ie zY%G2|vrwvre%U~-V0SS;cOAT$A~_Kaur*YhA@8GsE&IZxN@Qz>?A5_(=r}grcc(}} zo27-7kp+qRv_6_GgJP2mO*tF*cllbqqWeKOjEZnb&^| zozjv<&#d-ME&FtST$Yc z7`OKKeZsPx*~uvE-wRbnFy*yFGsFY(1+O*=iqKrk*~kX0jXouE|8~kQg1E>mdggJs zcWTvEs-MtV`E9FVvg@Q5)- z#LO$rIFpJw#a&Tw6RjevC7t}IWkoq}GaI*_-ED=jq6jFsNh5Js`j+BA%bC3LRw1RD z&dj}KYSJuC(WrlyoBE3YUtIdbYURi17#m7T_3_wr*sl_b^pJf%i?6t4-}Saje2u-C zA-YnKRc;zKU5n{6!qN(pmBqvK^}FPwU?hGYyWu`pdnDYkR&{-{RcRBb8LoMJ$s>oQ zGw!!b4g)*q6GQln<2)&1Y$Q_hS5?@_Vd^noMD zGUK?=E)5LlAA{)njg&GWgIaPrdSSuykiZ^bw}puW$jb+5HvdM@vLE{+GnQ*F3i$Go z>peXtOIYM|I2*fVyRh;}(p)0rLc_s0tn3*#BK_prgbzBB(A#LbU8Gz}dG2^l_g=A> zr9nM2Xq}}mBPf`NhUf9#{VWh>79$Rg)JkvT3mhEl+F#}zCKQKUG8zAGR0v^AFpEqz zO5S@e7n4@)dwisTRcWOJdG2#WuqUI((QXl|h#v~`J@)jg+hkkL(>-Byy@<>`<1XXj zYOw9GBhU>!tQlEjr65jzY&hAsCG5@5WZQoDe`I>Sp1=N8^UrO6^9)qEqnB*$yatZ+u3exr_Xp`7DeKs2ab?h zamr{q#3zBx*tZpY65Iq3h_CfF08bwh0hGqTctBwo}| zf!FO4oc4IFi=lBl1)&r!MWDdr7$nexLn$sLpd4Q%p_rQJFq-b>f)LXf5MYy-ps-ae zC}j_qfE!1+3xdKcfB|w10O~~~fG`fa5qRM)3_9T%9L8aqfQfhs3H5|@m_bvy;ADD* z0o5$Z1TTZV4>~RPRnm?s&_`Br#hjnO(u@Q~N@MVc^@s87gT%|{b%?m$-7i>e5=P}1 zf7~aWKNPtm@e9|m1s6fCSBYtTAlgfNJq6-RUWhF&A@8e*2kBRv2Z7ABO>FbrmbAPb z*>LyyRxb0~&9|%Tw;j{EByFv1Ja`&;$Q$ZGWSi{7yyScQjK{?KvXtA^2BSMJep85+ z`x9PPjL*wl?!WyOPj;R;3ERu9bN$JPLtmvAh8EO2ENWmbYS}NTgH_&Lg?K?t{*D$T z{I<1L!%8Iayb(TB3?n*@369;}d|*rN5h>w!2ti_Q8=Iu~JN70{bdqIJJ>5S~Oy1kK zBg`YYc*-)Bg#7fyCQI&Ko`!qt+c!IreY!@`n`wJ9UuM3d|6K5vc<_fnzXF7iC=4|G zzv{gTHtaZX;sQVjMoi`WMSer=LcaFCux=!WmPwA*AL?~FYC4+=XC=h z1S2Sh6C_15EXNC?BrB?>8>VGDuIC3~6enqx7iCp9ZPyRuG%xG6ALn&Hug~!}7|Zd3 zD9MVd>4s_9j_dhB7{y7NwexJ}6FL`hauO*c%-c3jU7!YEGCEHBEcZrZLN#%W&GZ9mTIeqQen7KbMgN#tB2 zEyYDnk18JpwapD74XU6rCt1HS!sv}Ms`vj_P;A#AyCw#u>kuR1_sqp$RXEPy_^K1N z^N>u-<(T-1I)%2Pued_EMzO33y}{LtD(0qklf7S~w^>QpDIKy|B-N zrO0COTE!qqeD=#E*W&3XC%HPK&X?h^M%X}kspEGCENlm`S79i^L8g-(M@Za}$FBMfYH__at+D{##1Rsg~?r9+NUvGvhOl zo85wBPH~3FZQ4ir_cvPQpI>o@?z&o?vH1|M(=OYpN6IOAmh3`sd0xMpwOcI5!hh zl3o&wGF)Hwr#+-u+#r9fG8k4?{kV!|0V;1%y%2a3Q*-piwgV~ts{6xSzm#SIsm zh~w5GHy9(?a5a z=#U$5z-Khp$ofV&?)xma68jXR3{=8bITeGMP#c6Lj$~9C!{qGD_BQQ_AQniY>?EkfpaN``o11(2G7P zQLz#0D#zW0Se|5N3>NW)NpjC9Tc|Vk(OQogv8x2mb#_+z0JVGg<%t zmXh4EQo{V?!2pa}Jf%9nlOhpbb z>)AdahYjo*%U%R&{0+{qafxdF;Z9Ln%y*5b?KKn|X5fKT5>DC_=)09;xhj^3?W;uL z857Y{dJs==Ys&MoOjw{kU`M#R4%hlvN&fO$FCcS!RN&$*ZUKusZ2Wp{xte?Iv zC0fnzmB#as>l?O%F9G9ugQpCrL*}m-tV3B4;wDnAar|sB zqw3XRmvu!QfZgLeRuzOenDES~h9i*;HAp;uv@SK40O`FBvh9vPM+PjHqX~?ucUqhN zcQM&WWtMQEvUPj_N6&-9-bR$QW0pOuJXk9kB;Nx9cZP}REhq}ATF|k>R_FODJL-cI z7W>ET`8Vs75u2C7@e4`TF+kHblg8pgCg62+^GD*h#}LI*kXO`E#4yqEyGp%~kMu4# zh?;m^OvJgwL1eqaj_kx&ToMPdL`8^YO3GgD;`v}!dX3iKY`A(!$_pvBl9jEIO8Hyu zpY<3Xmn20GK4HqJF4?|TfCxuDQhTrepB7UD@2|i zg}oT4p*~J^|8x~%e>F`NV^iP7I>m}?(kUb4TzY-0dTrx^TCn&dsK{bB)#+59mC30Y zRl0^;47F>H=u5e^dEDCi-``fIWF$;yYgqALKMZNX*cnVwcrYrJAQ>kMn3V#U=r^_r zUhn6l!hs*6`}AsF#Q=}5vnm@#XRmF@=3w-^M*7KDu@rTKIbE@^wMcpqujA0twXlJ8 z$0pIwcoN~Ggj(C255WE$_e3T)X8$Lw8=T4W0 zuJbMSt7(pS>x_-z@+9V+TLd#taDL+E%Qo9HuK&M=~R zh@J8*pr0tbx!f%B@T&DMDL_Uy2=TzC;02!HLng5ALW0l6R*>>KrKJrRI>+y|1f?yQ4%cf4x6aB| zZ)=ujtzF=SvIFVN^EkYlPUsujfEFvJkR8^tppF50Yk*>X_@X+~HS!Ld&oVj^mIXcF zU2JQpp3x;FdXGi;Y7RcpT^t8$CxN(R!{e(h>=|r(kj;cWRz;{2*5#Y8pex?A&27&> OvGs=*>()Ih1OWi|n4ByC diff --git a/public/webfonts/fa-regular-400.eot b/public/webfonts/fa-regular-400.eot deleted file mode 100644 index a4e598936b3eb6ceb0080dccccdcd49bfe95ca98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34034 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 - - - -Created by FontForge 20201107 at Wed Aug 4 12:25:29 2021 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/webfonts/fa-regular-400.ttf b/public/webfonts/fa-regular-400.ttf deleted file mode 100644 index 7157aafbacdb095b479ae52f59e28e19ce61d79a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33736 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 b5?7;>Z>>sQQ*PhZo%{_-QW_Bdb7%h#hbB&U diff --git a/public/webfonts/fa-regular-400.woff2 b/public/webfonts/fa-regular-400.woff2 deleted file mode 100644 index 56328948b3b1bacb23a13af9d727fd75c0343448..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13224 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

      j< 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%0D?I3ov(fVN| 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 M_kYo)8A$*D0I2Z0cmMzZ diff --git a/public/webfonts/fa-solid-900.woff2 b/public/webfonts/fa-solid-900.woff2 deleted file mode 100644 index 2217164f0c05a385d7d0d83e030fdbae01e99304..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78268 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'd1Mlv9Dcqdu7tF zHrt(U)fw4UmAqLq1AL5jTw>|`6DLMSOd|suHmFns#+65Gwn59qn)PWzxhn!!RD5s{ z3Foo=U&tW-6sgD7b3^3ThZ~qd>IC+e=z)*eu(;qloeOkfZmZB9&_L5HO-n$7Q(r9& z-cFp8Az@TOLzxO0CTlD}?i{ip%rNNOu~So<12yj0y;G;8M6Z^mxRIC$72HoYn_H3T zfXg1q8oM~2*LXsVOS*g;N!|7A638=A(_L?M2WKZ+|N5W}rk`N639a?^e@MICDg^8A z{vswT_YrDGIw3yB(y)`O*AwY9S_Wu9(p!U$!PAMq?U#z2fY^OYz9^#$dgC+CcO1Ud)NI zs1s-{eK-kyzF^XtMh!Chci zY9^18h2uiOSQpS{@JeCeQ>~S3)oLGXbOZ^|w1TwM6pr8t2V!io#%8=#{WUmGeiK1k zY2_Equ?}gq?oef{Z{7hum#SE<9?kQb{~BhX0v5_x}hQh~w0n zKSmf+Nu%0BDpPVbb<-x|BjxHg^k;Fv3ef;!QD@U>&~9DI0yeJ`dvg&iI~Bt`bxApw zY6fjZg`iXJim%UELFmrTX+gjX7d#q3tDg6D1cBw7TqGL1iw);kO-IV~(2k43u+FlW z)of3C`hw>lnE1`gVUA?VF}$MGl-)+VAH^U;@@}qHUB!d}CZsy8se}*9Y^W$ba-7oZ zktBg7l=I;Bm0lJAg&~h><&qgUrDz@XNmI(A%|&<=m6D3!GxIkuPMQ0J+Lf~S!*Ba- zx+qWQ!tw3M)D+~JMXBNIQWkyKA6l^F0{^OG{RWR%&=GnVjFibw4$Z zBR+k+X#oPDL7>H$O@E#Zy1DET7x9wUWU4)l(A{cyhP?Xc<^>35{(LdiT+aJs6ShU{ z4$n{6vM8nL2?dTPYykjbK{f^yCr+{BZ=mvwpj#$hBnY(iRZ;ME`dba-8B^ZjeWclzJdM z7dMtg{#}Mz5@#U8RR*Cm4uoT2E|cl$ANL=Sy1P?^tPCdlrY}XSXR(AS5s#uC-2dZg zY-|kd(tG+eqo3ytvF-Dc;%Q@4ywq!-EhG?xL)6zp{TC+V2qjP(aoa8&`s-8m%1Oe? z_Jz!9W-nhQld1T<+b?gw@@AjTkA#yY`l^sUmKl|mcRZWR+<0`(-oirge1{)t948u; zM$&BZfST|>-{ha1Q?UQ=V@r5VoqqxM>nPOGynlZ)`V5POc$6trcH+w5wL$__vSdIWqbBEH& z(H_3W7R&k<(*Z%q3T@N^a7L6W${1ycx;qEfhZ}GXLo2$PvY{9U59h->u)~MD8w~HX z<)d#q^0dS6bT*s^X33IBP%!A9=lEBaPQ)Qm(yK^waShiK#hL+pH~o(R>Q4r>zEU9* z8@(j>;I%_Ul7?KnEFRD1wvx~mEN|Q>6B`sOP|TlHulWKKQIS8(GD}}P;2-+5cmDZz z-ZMmZzW+aTxb4P`w&BqKKf5EE;r;IX{NAS)f9AXIdiRD@3fM=&eqz}KGHmOy2U(>8 zh!Bb>^dlZxRlTG+{kt-x{aEL*ySAg0haY7k-ohme-!?0Cu!T#$y3gkw=zUp01axHc<|k{^MY+mh7}vubMx zhtpBdN#gu#_VKNECOPXl0gGMXu2ehNvItzC;A{C(Qp(^{En()}yWA*c6!-4Eiv-=o zKq@J=I7FG6hO}6a)2AuVU#P3M)1AK#7ePi8tKUT;Q?Yc~0}(Q!@&5=UdA5a=j;br8eyM8As`dGc0~^Jk88~^;Q?&1bHfAIG(*bS)bO02E z5|whfQtxR>MJXH_u52?`4w*Oi1d|oCxOp;86&9727FC-w z4hWA*-3En;q00$L4OTe31$F@u9Er{fNC}DzQeRf%>5cHK4voPhBX*ywI1rwZgB)c0 zku|`d?%wTcZBA}(sCQY2lr5mIfY|`2bC$IeEw0-j(B0~)P{n;M#3ay?m6^eGX-%=7 z+nHmgx(L0Oi{m{hRwU+dcv4QVc^{a1=GJ{e5d^Y>W2a2-t5w*l&Fe-6$_EEJ*124E z+wiG(K~p~nroO+oN#mc=HaIjoSUxaVPjO-~IM{xYjLvj|(X#QLx@hIc;64;Q3yFWhwD*f5KhbQS4p6iRA5qNEfu%(I~3~hC~r64MoX5`DPPyu=B4Z| zfeQ(eL5LzKy2hk3(nxCGgR=O11n_vl;Ff^mZ_DPN@N8w+WQcrA5L87FwT$+tEPgZ} zk#E`tB}go-x4fy_cAH5!O2%8)xW$-e0*XvBRhBc(3yP%`rHGZh%hSLPq1L>}(T8W| z*K9elmx#qauVzxrMF}YDP8tA6$%cMWJLuaznc26WV+5Kny=br`k#!LccrE6LaaO{7A*d=PwkpD%f;+57{_%@(n)glotkrw3s_H63wA4v%5_&zlVh-fUmsOxy#$%Gm%BoAEEkV&%AT0)si#u)RWJ!;UU~gr~ zajf3pRKBw$qS=sI;L}qp*}7n16UpkjWUIlh(-~bXSA9LTOo|~!ozAYZlC6uAWLvlZ zXcIb>wY5r31mWOW?ns8hXJP5j;zQ5bQ4^HrA^dBypS~6s*a9$Wyc#~vS5zt##^ao# zk63{Qd7-Fhzh!aRh6i=R)zn%q$!yA#3E&RcrLN8CB`c%R(I8d7VE*8v*JOmID}s;C z*ibrSu;~;`wxE4p0YcKiW|nkfk_Xw}67c`6ZUZ08*kVsV;hLq1k45?bcE0DLv+$N_ z(%5=kQOwamWKFv4?)#Nflbm&U$Eo;h5Ce!ilHeVJ{p^3Wo@TsF$Krf_>f_~C2Qy>t$Coj2A5SlWbB5PQ zuN}NU+Emt(RJFATVrfpvjoXZPsqMG5f78PmLoKfs2wDXJ^8)6-T06v$(EslLrS1S? zZ&pgqEV?u)8IXF(vm=@S_p+!g@?Nd;e{C@P{vsr9l zIa%GcZD@f+=oLx?bRY0}36tiB#H)?-DJR1!Up8im4fnD(CsIuutf{Ws~ zIR`s)NWHmWiUS1F*2{SJVo}D-o_au|$>U@+EjwL8ZE@B|*G?bPB|~r2AbwI`Gt1U= zc=lMy8qjHWgA@<{kB=sQS2ODZ60XKPsd{?Ho(SQD$pj~W>fVPjnHyL*!h?GSA;97);S&>3& zH6hCcsEDoMYZPJ1Wb^7$xmXRWDn*86Oa5s9$;eHutqGB2mV_^;HBT}{N0WCkkPkS2 zeFMEmc~AKpnJ1mV{S6j@#=ySR8s<|14PYy$1{NWGQy95mweji0hsjPXP~*jcQKQS= zG;_u|Km;%jXVgX}KnZVe{E%-HOc64|%dI+KyiVVP#1B|F^g?*YGj=?p-}C%6CA+NL zxuEZJ(i|ScQlb#w_$>>Z5?3+eI>(LokK;hM_1-fB`oueb`;AK{CV3sWQ^vXzkylxU zZ3g`^(?H!b^9XVLhh;~%$>QdWofBvyY1NljI}Em6wj*D1Q8~KfSfb+qp7W|akf9xp z<7mn#7dNVSH}johU7RyWN7F_lZq0dRzHcUCGEFIB9H{P8s|47*6BSb)YrE_nSz6(I zmuC09eca0jZ=e?DMD|=3=?iMjrZZO)*FqbZ0|dZx zM-p1^1s|D&CM;}dZtYmn)06(K&EL3M^R;osRu?Nw*8GjB1JvWap*s{?0Fk4}xA?b~xLjdAD@W7n1{ zU_+51(a=y_CwSYs2Fsgm6MFrlx-&31Y8)LI_y`<2<|8{|+ST))56iynD5^Ngs4NU* z{j%cfieFgkT%Gfm*VR9+H~`!Imlg}c@&;JZB{n_I=m&I>t*(g*I6vXSIIlgSOzcfm zPV5Zs>u!Ddiy|x8dh#T-SjAbu-&8(3Ps-^`Q>;f}89fkcq!gEb4GubJz$u0qENWg3 zcW(b&Zcb9ZsxiJft{HzS(ED|g;lqTX-fg)qYxY4deQ$2?Ljv{jw3{+pGV)t$KCUAW zQQuJcGl2p~jUW^m2VRvSl7ILC&m_Bx#dNpSgkW^~ zJGa&zFFFZ@5yE06vWQ912$@AH@*`z{&#lN*@>eBuwih#mNtO_4bsM)u#{g9xu__P| z2Cg8TUC^H^A{sBTMP*37sYD9bnP6-^Xb?u3bW(V=ODT{?#-+(J+}Bm4#}--SwXT;o z#cn?NSru^AailHOHwCtI`(CEd@ij2SMQI#9Rsl2ZKDpF#i%R)J2iya@&@_@Exjg8R zc$_sI>97gKf3eXE$-HYd2e<3&&`z}5wwJXQFJQf1>if0Wb3Hr>U-p{i5r(yuQz|Ex zXaoV&`dDwx*#XeDwb%T3{8}gBde{B*k|Q!jQ94|Kd3xkU6}(ilxT^PcwsPB$J&ncH zgJY3~lGHx5tEE()BKmV{@3v$&28pU&X>46#Bip9CNlj(tAvTeltXAGHvQB zt$$VO703u&#oY0Lol#>Xs(c!rJcOp@uv}r*h1C{rpQBA-?2_hs499I(5CjiCti)XC zyddOu33UZG-&_^4{jM}Dh+2LaBq22I8W=hvyQ zIV(L2MCm_e&~*89NcO)&yQ*HFehLlV20~BwFC;kzcq$64-i$jDLTPe@o4H7itS;7- z+Ad9)I@60Zs;}XpXDotOpt>VHk6nq*t~s$z%f$ShJ7Hyk6i>W2@JqtKHy zF2^9&dAP7RO2rswlZ%xulVPAdcB^Mv#!``<}f$F6Mj+{ zKXW~suSZ;tt~%H zviIDU3o8D|;@8lQia?G7~^Kuu33upv`BnTF8}9uJH*8VA_S0$_ZfCp9tM+HNob z<$YA$Ox~z^7IA3VtpHTo{v-NJl&;K+$67@PHe^ru7GNUN*$j)u&V%AJuBOBKvQf3W z@qr=3RwQ7O5rfDEap)67UX3};jQ_FnATG^^3vo{w&+a*scX;boNGNP5VuuqBsQ3Q{ znralM|NrgaBQnam`6b{2fMa9BpdLgido=jN|KfCUWc*X(M8(N!w$MEk+Q0vB-kF~4 z@fo!cT^NnaDEIs~i1D&r2`XvBjB+?J_bjsbwdpyGH3(AhtZ(@0xSU0)c+DGhwrRmur3jH9N86_#?k{*ULB7KRz$ zB<|hLTZR>;HcrR2m7GnJ2?l_cuxi5w29~PnrFG$DzeE>C7p-lcNvK<_O0-1)t#+Ie z(BXd$kBdox(~S5STWiq`4>5_|N_NArE$Stw+<@%$?S>2hYxG0LC!7+)s#Q5a)AKA#@ z-k_gz!C1zq!+`6T)s*Yd^kvGn-ZPe3wa72JJaH7Dd zyb`~PQTbFZ|IRMs?p#;Sb>f}mV=O6$E}(Ppf>X*mMrM49aLd>2lyU9fEwA)ZiED5d zF5n1)B?=<*wY-7|I`1Oyc4DAuMhBxxU1gAm2DcT9!Pj+9U*cbWor?lR$+~uv5THOj zoKVqDbEu^v2nNmjAqO}B*=01b4Ur(m7{Khwd?+3N5dG2g&EFEybR-xNM&VMZ2Al0d zLMZhwG5!&w-g6^iA~s@O`3O(^N_zZjs)2aiACGYd*ZB9dorQ5v`4C`L^KSm`TIFp= zM8Z_;bT=MNvk1p5yV7f4t7@BHvAnc&Pv!T8UhKmSd5iM*Ffq?^y7L$1ZMbsn!wx=O zy2vF>SC8YEywX!i>Nh903lK_dp*(h9076T~02$D#QoMw0g5Mc)GK?(XVA_-QDyQRV zHoa-LRM=-2bUGvf8@I3l90q6_^^LB?4?A;3)79=aCC$I?q&xh@m5V&cvWv}q3&7mB z+5_b#MTIC%z@X%2Y9$AM{&P1|yU}E2`90v8oL(TGQN(8!av2&riI`4ja0{9I3mLug zWA3Rf7`WBzw@UxVs;7q|Pjd*yve>|?C-I$d=-P}aQME08{$x<@~ozQcdnyaSsFaMtc` zVHav08JXxiNcg_N%5L(8O7K>6o0+FZkC2sn3+aDkoKQ&%v{J1Zzp8}ZS!9I12I|6c z$&qzjCwKW6B0?Mi5;{4tauL%(Hvu+SR>JcTF(CvKGqGarlNpagq88};Jq6(W`RpS! zq5p^2V6Q#)*C-+FNH)b#wVj2cvR3ee4_1|(uY~0n&!pnz9=nknbRzw#cG*B}{-zOJ zpXydjJHkP3&b&o$!@a&oo5#jo+K`#=jeBn1rrC{3tW};(GYfH{Uz#gV;WU6w2=iGI zG(zM9@p7ATFScZ1Wb&NZCk;4CsZ@d+rmuRGdD!ntrZN8aZ62Km(S^3>3WpqrPF=op zRvMa?!!X`2XWuZ6&BF#wx#N}^LSEbRf97K=^4HZWEhcS$&aaqhnnK7MynI;obVMkn zs!C}{$$SPf2y@B(_tKG3q+VHqf-+=V#~V$lqV{h7^h?&|B}?S|kvx%VP-<1jyEI#1g8jfi)Iw|-3y}7&kdNd;OCg&DYtirS3Se&ufiDOyTDh^mRhg5 z)+|Uy*ZmoHs2SjVpVV8!k5B~*aWTV!(SYlMzB{#cd#lxEmwJStrq_mKhf`SL)NoXF zJlLYurCmj-gRQfMlVhJV&QlSMqCR)_F>)UtHVXIYA;4U@?5WSlo*Y)lY zYUf~)D3X1zEAD^(p_aFLw)-uLVgRg0hn~@qE0&pTot@S^cn7HNRk2)0-VurxOUQki zVzw=_WV~eh$V71C%w?yKdWfl6zfdGe-|{y>|31>cDbnZPg~9i{FM}u1H#*naauv}{ z9^EI6oHCl!?eV0_xYh>LR-D7P@-v+TwX)Rar^<<(D~)uef$t>Ombs)HK#`O~-?3}0 zcigERy@N{vDi!ck+**mkJc#dc*PQ@m&|>5Flia4+Zl!%T&KOHR1|)zm;Zy)Bs8O<> zD{B{U*S}!~%7`2>RFecX5}_TxQUA9Hs91#@|0`&%^4iqSGFENU3^VHHPMAc5gfoC9 zmfwLG+5#4XHi^1hs z8jE6B2m-}>7X~TSC|Fy~-H8GGTss#Z#{$3$4p(zI?);)Sr9YIgzh5}m&h{AD#aY_x zJVg`JuoDNk0Fi{u&Alwn*_%i{q(M$U<%d=RUs_lpaQa~Nu8vU&1|SR!cv1TWNqWlB z8W@!J14t~=F7t%&)fdPhZCpxjRjIU;8p6W4Y;dc`2}M#48iVU04X82RG(jU{O7ZZg z%-7#xRT0)FjCZZm$+YX%@3!bsN}6C|hyrUB4-CvM4I)t?1SQr|t$P_8_byQe>CN0wwJcKFb3IlC4ut&oke&VUKaN9%sB-_^arwkxme9 z-yuKlDK^2chT}V*>%UR7-7kyijz*+mETKty{fs1CmV{%+0d~A<&4MyX zYIZvAQR5H6;~U5!u@Q2eF)oOLq7&({U|MsC_RzXN*lCr<%y=?q^h6`kw7EjO{7RZ_ zdu8duDJg6cg+Pntki;Ny%%VjON0#KWLLJjjLRovwcHL-t`os}Uy8v(BF22Af($h!5 zj(It$z=>xnki<(RISPW;FMoNUvVs(qanyelq{oJBT1y!6%pXf+2!UsLbVNz!>&Lf7 zeh}T7Ttg2h&4Ch_47xG;Jbb5g1SvekTjQ2c(g^`8hF^@i67X~j|5hL?qsjGOo#3tc z9x_~I&o86#nP>+&Z`4^~>hvEM8|r}I$M<0T@(=e9Y}-01h4tZ+lQ17%i+Vha-p!kp z*oxQF0J67hj+>n~w`+AC%5fYXcgMD+;kK}W?z$vLPMgg=%$}6gNi?)gp_CB;8Hyx; zajRuC4pST}5%c&{fX?SCB0RTY_8wxTgae#dVY&x0eVSC5mL`r}{@S>*3S&H>j0#zP zaI{#HCZ@$ai@*ok$N4q)w$%Pwgb&hU1mP@!` zPPIGj67#=y0-NhQ<%`^!pyq{SUClb*qLO2ZHf58|-`iposEz)B9ZJaVo+uC<=U3o}w(Hy1!w3B)@$otu}Ta;4UPKtX>2B zk;;IZbSI!Agqh%zhpkJd37AIVjQDh^IJMF#5AkQy0bw^I8#W5@b}jnmxVW0J57gD{ z2u?)cq~^xsjTGS3^zW~R*v(hly#KwA)93w(=3GsdgfGvjRHy4b_HU{pBR#&3(N~tW zH|_Umf1~MO?p&S>nWs&20iJ&@#E-V_<-$MX`&BVp;n`un|4PX!^w_})!}>G#@1I$3 zsMvl>g_cSG4fjca?~i%?3)^mIw~YmkvF)jc-OKssjFIL^%w!*>Q_ z)1FEv@1fn$88dV+Tf*gw!(K9X@&Q$Ot%t)lK^RW%>`W>=vX+VVA7_dkx_a9lXt;^AE|s(oD;X(3?11HsQqp z5XVbVC5c{w4Ka6V6?hFzDXOP~#{U*io5wkTC>s7-KCL1Bb{fMwe#$a~Wus&?9*xs= zQ&}j9WC(@-7R)|=%7`ma$s%uc?BZB*>~*vecH!OUtz#Az$F-zT;{1$O80<3o=FG8; zp0>_00U)GJ%|IF>n@-}aHW-mpXNlNVg=b9k*|m?wXu6cRV+BS-R#02kLWw~U0g1BjmVUG`Vh>o1c|||_6d8lXgbQUmt%SS(&0RD7W8vh z>OmWqW+Q-W8%miIxhp(DwmRq@dc+&mLpb2{{p8M_nui_LL7k|RE{UtCsDV*$iYbUVjMmP-;$B%srTQc+M~8{XkT(HVv}k397@n46f7JdShh7Iym_BY4%$8 z`WuQ9ET7@pY@$a&$JvnI>NtkRD<0=!jZJozGcSoic;{I0i^w2P0~VM1LOdN30K&V( zMvO=9@$iGS#7qJ-eNi2%%)!g5I6*W2 zpwr449lhA7Dh3cUJu%~FlX~=PVXeUfk4XB>xI+a;l&Dw2 zs54Be>gJ4@&+NY?RA>or`A&g==$jT+#ZNoFfG2i+-Nn0>cr9lA)eQ*9<5zcdp1@hO ze<13ARl|uYOFcs|=4KpHU;mcOj?T4mb&oj!m}uU|W3g1pWQi2=N!axKL?bzg1{1ex zf-4d6yz?Po?+q@Tn*N4+i~9{Q;TKAe|E6Pm+x1q0mwCXLNIFekPOb}Ys3W=KL-la^ zKdq(z#>>gq$pIb0CYN{%Hr&t|4%cetlzj|*5O2eG)0E8G@Ll*G7UWx{J3Kc1{kva3 zf}Jn~|8jhb2k=+h55a4i8O%PNlK|IJ8aX)du};iR*e&d#9JSimP5MJ=Q>WC{9^ck2#TqC|afhuhtdM(`s^;j?5R z&k>zvEr7-pc-a;r&5D=95jtAmKJr*mw z{cE`IE(mbJF?q=RP#rcn915t_0);ibvkhaiF~`vEvE+z5uEO>3<^s5ir^CnX(++qD zHTI(sy>1c$BfrA}&BJl?{SM@8UGm!hW z3RWPVraMy6f($-Itj&NNtoJtX5vimi%s_#JD&cD>e#WhswlW6E&m&{v7Jq^|I@|jl zfXL%hp!KUzFP~T%<;i2j*8<$_ahZ^s)VwM+;y!hcx=$)&fdcB_tZJ-EcoUS8uV;L( zJ#@=p?R(=coLvhd-NS(5&*qOd8%816ShUfWoBNtzHI~8Haz;$pi?3tpEtK{LT&<(c zuIaj2^JwdE#aIUO9MsxJn_DX@f3UZXx?1(wt@idcE^xioi8WZ}RMem5iSKU6VZ1+9 zAWnA+;S3IDVazIHy%&Hh?TT=Za_(-yIOyb;s)?()v@PHykX%0qpE-cKx46Ag= zW9y2f*Tel)SqiGPda7dWQrMkiU%>V@68^DY;*dDr!a+yy@m5FXhP8zz^>F@4hqhz2 z4iR+TcMq|exW~6E@C0m~=5_PC%yILz)xWdt{5W&h{Hk>dJolDtva2w;w@kZ*D(=27ac#Q}`%9 z*3qZyW`*o2R^!w0$u8a#Ql-~i2#2%f8>xkNFY&yENJA*^(%nMp#w<9Sd;u))fOkWs zD{j;$FS-fYw|n$-jD?u76Vga;lCmmN!3;5v~W zwSM(S9ir!N#h#m4f!5M4u%&4Ya}9;T*vp{0=n>)A1@9K2Re;_B`MYT^4G~Gin40o^ zp>KX(yvP@tcitE(4~si6E^yR;cZV&`=jdMO9 z-cg>qQ^zb{1$x>$&l0p+0tm7TxX);>{wl;iZP_!(EQIH4OYic6a~Hqq6r>_%7sNK{ zZ&8|r=c$d`y^-p%?ZxTWcC9Zj+hZ1`K012XDzHy8Pu$iI39h;1C|7=#yh)vl3$|O@VwU-n7~GTS(pPR?OSxL65v< zJt)27ZrE~)4wJiMSFW13qet<1vJqir@dg61d3sYH^md%P1?aG*Xa`e; zb}YyPN3K@EKbyJl7&vaB!<#OXMU@66M;ZihwV=|dH3T;V&Rub^kOe)Ok4moq>fI4e?*=oKLR3uw=-zG>|V3e zqKQO2oakrSKb3x>cwI@EZnA`&qn^Yezaz1V$3l*~p-_-4bk`al=U*zgff{CZGaz#a zCn)zsI)aztAspV(8PAED{|TM4Gtl-2c2l>&Unlk|v*;9f)-&O96_C;OPr@!PW&%Ea znkGv4@#98Q&AjpQ*u^tgmCvj2YX-OSgFBf7OM@zS`E({s`;a5mvXs%vPc~*hHV=*d z_$wHLn%yTJ=)|wnL7uVzrOh?}nuLRyVJ}yl$nQdMKScnst3oXEOoGTr z97PNRH+^-QsRC1}3~`3g>iOGwP?`N`PK@et(hQT~T#H@@huFUhV&*)`R!#^4_qE8B zWTpr2_0>xX<*HfpeY4%O;iCAAuvD8hCDpoA5gM2g(Zwn0hbzL-^lh3pK6@)2(~=e5aGne_hDq3q#TkM; zrLE~y3g7WTGiJDbQM>9;!SU*>5dXhh?z_*H=j&hUhoO?fmzm0AA7D%szr})~f8;x}cn@@{JioY`_+X)* zoI#vI=ip!@Z*G}BRg5*h?!#0D$U)CXvgcFsz%QlcN@*aIc_i0+Q4ZQVa=)3@%EgNA zk15eLX*f;_tPQyNI)3s$^xO9)T`cv0?B)EO%;)0fhlQ})Awr0P3*#V~PZ5T#>zg<4 z`IzOA5qGTmtMyKv%3pgn=J5bZId&<%=?`z_o3yjG?=V#|9KcFMCFc&f)fudBc z6b(GtSofI$L9;N~UOOG&v_x9c-;`0-n1<5f7ucS$FGSaTbf6+Q%LI_r7x#EHp?b6l z@y)&9)hoyLCg&`!d}s%9oTGNc`4B5PW`0bQ5; zh#gxV%3uyTbBI5+cort&Y}kV6orMjM{D}EE(Cc#8-3yT+q!8{Li5>Gdw|o`=o~auk zgTISYhqM|9u|!eLxq`whv~b!!6!1D_($zd?POEEjQZP9>DR{|}>qF6{^1}ocWnKYW z)GNVd#eO!S|Jd|D{vfE_;h<3J&_e+fy6v`LDW}WfaA`BZuS;ZkMgk1kSy>k^>U54g zHr>XJg{@bk(HT#E3F)>pdbbF| zMorXmfX#`jBw93*8>{Yka$-;LmKoqNSQ0@9nQmycQ5|{oxTsWIq%I|UVrRR`2((o| zt$+AheM(ZU0`BF9wAH61P)P!nShuuM-DP&5zTR%n4{j)e7j>d(mS&dJmaYqa!<_=q zZ6kn+ihnJjf=RNLjhwR{!#LbaCPr>^qn|}@Xf@^m^&Ttp!|zPX`p4gnVHN5!J;(1L zA2NOZxvO=m*;VrNp*MhvLa*MyQ+A550y+M}8tOeK<2mypD_vP(Z9wI@YePQT?mSe7 zv$Aqvz@6EhVKb1G-VX&2X@t zL*yV`Z>zDB?V7&nG+v4~KqI=>Cw&)zSHR*=gY-f@h}f6k zHt-AWaKNST631;mm41n2YxJsxaKVFn=;N``{7C&}jw;6@_Lnncyn}*GYz8fShKlYj z3z^UQR$56{AVHt$6mW6a-7mNvD3{V`lUHv8xq)yh&A((jWYOu=u0rU|!?nBESA2$#LyB$xB_I7T2LL=ar1C|)G1y@WAK{J zxy}O`U%4!@E|=Zq(wThftT_|pm6u=>-QMSak9%~`HFB<4@8RITnKNfj1Iyzc80Z~9 zHjnav*qx6p`l|VXJmmk38(=s^agftS-hapYuSj*(dw0~0_alcyD+VCITazAzzfk04 z`R6TP&*BkTMp1y~qmY@qGBPw3*Cu0Cpsbtw|DN-m|6-_2hmh8s?#ZoNlibVeAUoqH zV^k267&!UVb7jfctm2;u)0`*Xi96jL6f3@YZZOW_Sz7wt;-Ok;+Bo(Y%nP55tag;- z{qOx(hF6T9|9dH=sFVyTs5Ogj_URpKm+i;qV3=Bwf+){`fRyJ5Ppow-TN$S9%_c46 zx^{TK=jbl)CcxRBp4_%}$1T)t+KxYVs0h7i5dkJ1#+R?YY%o|+8!{eJ8!!{bb#p(X zQrE^L6c2esr{$s~jSEbohISOGo+@#wjN4Gg93cq@1D7F*L(8UJD3VQ~z>s{YW6a#< z*v!BGbMIBIkjEW6&VrU6n+?q7C<+fdP~@q?3Cx48(EmD6Os0jZ1Nm zYDwJc13r=Y>x0%Ly*j4%#LEu1wNwibZDu|&SS~!s3Z5`c1hWnnG7O>_)VA!xcg zs!mKCOB`Tm=vOn>1{Qi8Zy*q9<82{pBDs?dS5IuYO?QK|`^nI5G=?9Pyqmi_oPmmQ zl_#0!#v-a~TTTFxz&Ly;U_wt*L#&=20@{RTg9Ek2CbiaR9)BPsd)2WEGfW8CJ+;lH zIyRijWj}=3Gr?eucaJr_K!tSNyHARv$qVkzxZtsGpDy&l^e z-B@vb)TkO!sjgd38w>+R2)>7R6)E)Td+FeFHEvwB6_Ym3_gs8jWDPGc7RVd;xkg{!Zcm+4s4r4kG3*^ zJ)+efNguqt_V2E>c)cvTrl`ixBs@+}y}VmT@_OB>_(A>y*6_4u%c<9gnEna6L1Xm_ z|3f0?>%&(!0|tR~3g4}ot|j^0xnrL?GBrhW{^6=3dhcI$U6gAz5sXSBq1>9woq*f& zX6$6lc*O#b8l`C^UXdC2WOJQ@E%VEqEGlWo+6p_Ays+;l%KzrI%%1sQRD@{dQiCQS z3s`CoJyem`G9IH+3+L)rl9cfU$vd}QTa&^lSggCd$@XR<1UOowmw=^)KKRuXipJ%w z;2ypo-v~j+V4_-7FXl82dO=?d8oC&O2@@?+i^@r+DLXD7$d*V!R{rZD`+yQD9#aHeMt~VHhV$Bt?nAji?wQLV= zUh_r&MEsJG%AyTnxGXX3c^K^t{V#a@f}sPp*`1vqpq3k=kCV=P#loLtRU8_gDM4w* zNG-Kj%Oh9oX?s)Yi0rHGw;fZH*wk`3)mDslos8>U0v|@sDc)O%EdJ(M@QzubIkB73 zP{B5`(-M`Gln>wEJ3aeKLItyG{RZ!ng69SGn4LSKeaAM96x$5Ii`@Zm$-_IO#nSW!oTZ*qTf3`ZRwPufOAKM z9(FFzgU7rtF%`kY(2R|DGrJ{fS(m0c;Bvbp0YIC!T*nK!DiTs^sw>Dx_88@^7!ve_ zlg69w`6u`)w$%wn`3*Jl1a^dvOUI`#ZZZ$fT5R(`!zIvgE^0N6m}(VRY0wa$&3@6A(NYlb}w^xJA`BC*m=@PQmvn zun7aRs9WIs{FXlJM|J0=tM)xyzAFrz7&_|Q5b*#(#WsxlIzhFvU3xr1DG6bGjjUQC z?Uy>S6J(P(!vJyL*7)L7{O0}rZEM+&bKw=`uq9!p^U+z^@Fhvf*4y8k%3lBtW2F|F zejD2pnPfda+;meY- z3mP;iK?Uo{Pj7Rpf(>v1+?P}$@N?3&z?8z8vrjy{93++M_Ekat2?sa#aGS8w;P972(6aGA-VYS02nGY} zet|MGv#Sdf78BVq!KQkIyXM{-nn}{WqUNM(967*8J7C?y#8%nqBXy#?e7WHXC#TL9 z`BcCat`zD7Pp94*?Gv2)f!zTp?oeMcGM!LjZV?SwDV>;sX{M5J(bd0=0 zpCYzYZTSA!&Ne{g^@%wB#gao8h|)f@*5UtQDdVFXXS{exTKZe$pa_P9bOz`N$a89a zbEd=cr^7_)pQy%Ke3ho>%;l@KpN2O%&6OEVKHYSQi&5QjRF5DWc@&+ zFo*WWJ_(Xw2&zR`7<=(P&}cGn)4{tY9s^;$P?J_W%{N|0o*|9dh;D`xi|(usx5Lj2 zdKfEabjMQSyp;=cGCXp_rx@n^YMx~CDa^%P&EC}Vb4@-hiwhSW%9<@n$l+Z|%)Du> zbl~%Qb)C(vXlLm~)!?AUEmi2G~KCP0#eE8xsS@>6rCoF-) zTH{igXCz7Bj`^1R{BYlXtmx9Vc8}2sVBTD-km{NOLb7U9c$FR{Apyr0hclq@T5S5- z;M6O}S~@{;tEz{b)Rj$j9iWzAr~ca?T!e&ud$sL=mMDS!T;(~j6+ ze|3--4X97#-nYSviEljqlGtpEfTKQW%F_Nf9HpoEBO7gJ49=O5%cmjxiv&> z&C}}##h17LWkKmw>-g*7`~C-?Jgs3KT|;ClHS^o{X@E{nRop3;z4Z5wVc<6?2C5P3_I4U}20HWc(ZP_2cOOjCRmWDW!f zcI~3}Xa0P8S%=&adXoc#=;7K03oN?0B@mkktp~u*8T>3Z(gj&+CxEE1Q{0HKVftmD zou{Cw!C`V0#!R;0kbQHcPjpVuXIhb7|*A&gH<`B+T^%@>tBvAXpf2iu& z$3H!XFR{3J>1M(5du}52=Be$oBcB5bi{!G4lFIi{Clm@#QD*7WN%Vqe^qJ{o%Wi7K zNzXZT58{gVXcmFR6}E#*le}EB>S#A;b$NwKjE);C5qO7R;xbKwL!yMIZg#lxh~)JZ zI2;0v(IKS(?xQ1`$$j^4QoLFCgmC77(v#QWw5`_lE$HguwnmO-lr1CilWdfHgmItY~QyP?&Y?DL% zJtA2zhH5X%J{^FyP#qMGB zIw)_WI9Ve$g0jua%EvB?4jd3gTzP(<7yi-^x%lK6t2qSM2Szkdi6M;i9oyAAcPh2@ znM7)R$bO5+pM}}8;>^6q_BW>1#pgPRs zvt(%*QM=n4H60=gKg(|YV)>%~;h=2jWo@b&;!V9zb?qd=q_RTorWku5fNPwI6c1ON zgBY65A*tDcEAGt-d3b&V4>mx?ie~=VfDiYd1@$!luUCe>BDLhO9GtCLki90nZDrav zUO}-V2*x{4Ccjv* ze=Fsl?DI;SxQzHXtdV0)RTSK)T{IPtp_yl=4&oMofutjEmkJ)9Bvig=do<{#v ze~c%b+f9yl%(iNHSN1_r!n&|hq%Vw!XTS3gpu;Wr-Md4CeI0<3HkiG_ZXbT0&ob0($EllCb*?V>T}Mq1dEIpFr{}EM7dLpuN8)Ob#f#gPY*5 zKA!Cn8Bd(uc*y5iD<3rHSya+)H1EW)qETgl2pU;toGmZC?3|z`erL|n_+WeMy%ZB= zlj#*VC;mK6z+LR{r|h%m1$3l7Mnf_cMVIYkbEVSb&K8Y3iKXt9%xuW4qM$u{^7ZXL zRL#6(s*k=szk6666o-@n14EC&MO0X;i{RYgcENWI%7GsQKO8*xR`6Cv2_N{y1g}{m z{(_h`(~BKoLKQH4Vd^QfiFiP@zFsuxkJ{}tZ{PMX-F4HxB~2HH1sY#JQ4o{s+m(^p zH2E)q-w;74&i_eNbr|{^kK1rq$o&H444FedLs?Vpo5}jR`n?uuE%`KA`}*w5rTAxD zs^p7NpKV+IJNfz!FcTnB@T#7X4KDW5QY*qqtd1|STh|Fo_9y!&VcV-@vKl1Lo%fCR zYCUq;;MLGp4*6!dh@&HdbA!5;Kl9_>>NOTuL}PRUa;S5<7ST|=bw`qq1)dUI{Tf*G zpHvnUt~EKBL2Y0z+A};4Iqz)zu`KyTV+<`~Vb*UdxvXcLU)>JP@b)U^@@{Q|64O7S z;otC_<*+Cz4yK-^BpFGD`MtZ@o3pgvNrM+A%jH5|Pf=Zbe|~UP6vh+E<>AS2Wx}xy za{t6nWd)SXD?rgZl%Kx}`Ms+G6Drdx69QKaukh<#%AKnao!Mnj*{58&{{|J-l;Fop z{o)nP z8O`9uP`j0er&&YncL#s=E&a-cA;tK!hSbf04&t{M>-!}AkE`F3wj5;GQdAWfoxnf5 zaM3n}V(>pDwn7k5IM*r^G@b;PQF2M)GIk~FRhcnCFU$7yS@^qm;Xb>?yVIAn&$udJ ze)uQ)D33>FPIDbX&zvDdx^hSq6e(kFswL6m!=HXplq(KoDWd)vSd~pwh08;A$RulG zQyLaYxu+Bi=7#s(Yt_j11L6RwPj>&Nq4tY6(OkJmgm~df`i9APS0OtNIdHqXbRYWN zQfDbdei^I6?{`X_;|sBG%+k=GAuP{6VILOk9gn2HhWNx%+qEO42ZzSb;!mRCa|5U| zu;BuM=b8y^Go`iMr zJVxVQJ>7ict6N!Cx!L3vB02q3fPYVEahr1ui!)h9mX7a${JiIF-<|o>k4^*9*OGi#pswFdL%8~iY1nQPpJO}&e zU?yCj2Y}o)wL~W~=x|7}jB3e2%40 z0j1MsM^aW+)$~zaCTVQgm^6@NY-}9x8Bn4HC@8k3IL>Fx)wANTg2z(JBf|J1#g>G<<)`C;V|sj<`XZw9=aeG|TZjiSFtOA5sid1FW&k%w)-OODso zN?;o>B2R}LNy!rzEuo$}x=v%arm-os7OBlVCdrO%% z9_s11!^_*)&^9BkC$@Vsq}Q?J(pk8@_f}h+2m##SE&B*0uwM!uG_Bj;fAon-QW9av z;CLvtjQy>O^9k3sD(GuNjx|-*?rqt~TB4`qj_myh#;LR|OMLRw6I9j(1VMs^Jad-=kjmFZrfp zRn{@x3ngZqH~;iY`Y%6$|Dnr&`9lvKcb#ZGUNy@l#VgMZNGQ1>mp4?05K@x{6J!;2 zzim32Haow0L32Ujbf_?;1iI?lEm*|T!>cEHMDrGWuW=3XxZj-60~#Ua2^sj^psdLs zOSOQJ;$uP%&%c^rS5&EtR#Qy&ym)d50v>USe7%-dygMVt-6%&8Z6v?s(T=-xW*!bK4&ebgriXIy{fZi+K31s-rzKVkOn5VdqW$uO+j3y_kZJ)gZS7c zCugyWOh6=q-Olx-7(2Hbn|eAnvqx9_S(cRTj{LdK|Fsh#An1S4bUm?|Ly_3(x!B^W z;?Gjt-`pS;KIP*#i^QVblbbHmR;({$26|3rj|OjbjgAZqj*5f@xdy(85^csm`B%jU zVfhP(EyDm+W%x;N{0*PdiQ!yXtaQV0C0t7{Yg{NC6jLQJ`6--RdWpLGX7<-c-qJ5h z?slBTA%PDy%Sg-4rXt95tiURU@w7AMXC)U+Pj3AWuYaJo;bpEYjb<>e`&miz)WHyhhjPmZE)$KlQT2xe~ z$*%5MTKOIomhQ0LNWI^9kW+_T$JngZ)(o}pHF0;x9*!T$cm+I0r;*Z(%AbR$~)tLWW9u?4H{Ywn1?oH;=s0!Y^&A%?p1&qR1B+RZ9>mPnz zXDIJXgWX_VbriqfN4z8C5hg`UerP7mNI2FvlUrR?aTl+zG8%yKR_%N8P{-9#0^pA` zcoV|bDF-33gAm|mTFJvkU0{V9g^LwbcGdik%*b#0JSDL%27Ip5QC()U5^H@)@4?<# zd}`#v=;)I~_2sdiCqrd6F&FTyhPV#oqZH@OvIcd*N>p zprLKA%cXbIBi43Yngeq_zc4bp+h!GVF)jM#uch<8L|B*89hyRzX7>^r!o9`1As4)_x!*|9iF=YLIb zWpRVH<1s$WV*Yi4$2uNf+2#Aybm z02H~LDrWpn%sGz{oPvvOov?1W&|Tw-(}3YaL9rX0Q3)F#?7Ydyfn&B3C-LJd(lXf6 zNSIg9k?!-6`j(6-Wc2$Xqii{%zf)5l%@hiA!09=ls|GPn0iODll7p%Obz15yaA#Bk zC_qAs;Lmnu{hy^NOsYx9%aN^!XV7PdA0xioB+I*ar>;6ddP{ywnk0kHANeMw;BUmZ zSS#pTMt0^4o`ZKHZn0_NwVghdYJ3~)J9_g?|?+4|RPbfhYz7v&M%)z@OsqtJW68^(d4x=6mzR?#X`>>Oj zuU^mtp1Y_fzmhl@?&NofV7UPcO~EX>qS7lqsN`vvvc#cL*mSjeo6G%s5gNjHQWPeL z5^Bwh5eQw>FYN5b#%!E+D$J_m){?4Ykr>Y5ByP1Djrju$BTqNdVU}T{y8uBjm^Xz7 zBRpuH3`)a{o4~2jds1-tuoy=1cvV_=e)lIzPZ4hAlrNDTe+mR}flkmZNQ7p`8Ip;g z350?#ylPF1h?zEPhj|^`B^x!nDEc0BhKQP%xHoa&j{D<(QeLt}7v0Cc;SlCqk* z3oe&?xk2IX{w(E#un2G1naIxL{7X*%^PZt^3tn5vr`$fB8&EJxqIDDDl3g^?KjaRx z$mZAiZD|kdu;KSLFz>B+7kq>&XHXHHKP!1YnNL2d2z4})Dhc`Pwc12l@trLd9*+N1 z1>1gql9{pjPBAU9_WKXWB&mv47r^$6kSuI@X?WugHW=RYZQ}>=%G8D%w93J4{i+IXkT3(buFn= ztU#p9dzXDd)F0lf{?mM7CeSUV$@rLARaL_cgvQLof5Q5Aq6o+8xjk9#jd{_66UVtHJ>zfsbXf?@dYt1E zJb1>Z7GqjMu=u{#@WSzO(3_%Nm7wg*5znf7ut{{^8_OXJ*G3&eTwm^H7mJxx-k$+# zyNdsdGpp|30JI+hq~?D%6RcuNnJU(E$ni$At^{uXU$>jfU0~wwjy^A2M|9SD41 zeSm8=bH62fGXBAL%y;`Q@7s5I|Nq(Eoj1h!C@oalc( zHJF0_Pr2hnBLz@T+>XmaRx{_GCLKn(sgH7K-jC36!lG41$gn0}jmlP@{Ar8SjUb7p zdd90~Co@UtsR1U0eiw9Ft47d`L#>)HafIA?U}Q31Ja%-A*yLmeWl z6_5pd|H=rc8$W&A_f*4o7jZpUE^bU>z8!unqc-DwG32ZuiTtbGGF;k*$(G1CH|6Oy$4+qO9*D zUh7-;5Hj%d9{_Z^HQf@kn2(KK8DmLT8kTxqEPwMR>!jzJTS2@qD$R_Xj zMcm=#>Ey~ShIp;#UVIYYi@>2o=spE^mDMmT>D_3tAlAm&37== zhVItIjVN%XvN?`B;@T#hzZ0qzc(xt&Z72ABE0N`MgD%^$IVB~W?987I#Vcpd)E@zI zPE4mj8Gk5K=pH@Ko01)KdAPV3%t`JJayWvzoqyA2PHot*9!1||MNPvVr)JWI+EO_j zg}3}$TIyoC^71H0n0$X?CBVu$`d{+3wOuIHO&sikcWtdeP(|E6%_LU6MA_^uEnBgC z)OKd0YVs_6vkv+7BTOTCrLC$ifF&UwH!O%zW1(`-M%C}O=lg_TDk3b zGP%4g_Nw5x_&8qTQb&mqIBkredXxI{X=wsSb{Q<-+JPKDt@=_jo`a78^x4vn&IM!%!NZidkuBPAKWedNvPHUEfkqqzI(;sL|Y+AcZ?(G*`u}Ynu zxO_zqM*7dJ;?IkhJ?PFp&topUT6SoC7Iz}hmX@3vCFRJ41XxZlo|>~>$-fqMwHj5z zYy0YJA3z7H?n|p92MtW=OR}NHh9w%bPR`X`(Q-F|PB>Ml6TlgHEFbhWIxJs^uy11M z+tbLJW@1fC-|%zb&8>Y0=Js7DI^OD<%27?X6Kkq~o)K_SG(IwQ-BE0|zlwfsi;pm>JXcMG1^SJRU=_L**)##`{_ zcy*-Ml1{p2Ap#RkU4ND}E|pB{&tdcEd96^OKm!j*U$=x3qKLb4abTwQw^VeBToAgtxwFV~O{j`CJ(ubY^V520PgFo~NIG z3V)m@THldl)MBu2cm87loIH8lT;w(9UmSB_SX{W_{)r|ZnTU$p1MBI(lgvCAcMQOtwVRu{-F3SdkI#`z!Ev}l6Dm3qx;RCO5t_0#``z7;YmsKq;T4HV36t`wg z+@_zNmVc5y_a=zA;sC6))C4ZEGkHF@-;GWS|GO7Vh={6%Br54Us)f&%&XTGK`}SuA zZ}1aA43K6~b=Lmw=YK7(CLmKo1|H0q_PiuI?71(1U+adtgJ^{j;eR_Rp`Q;XI9%F0(y1fP*AL&_-#E!qduHV_1oQfi==kZ)DMrq@s7uT~B$x_3rP3{mW% zU}pw(W_a4-71E&PRxfdtJ{`N2#Acyqj6u>|>$%XH_a<}yez{BWddqjh>mvCbs`#e( z^@YnJYre=Ahg6?`t=h$}oS)0cGU=H`*OzwE{w_3*{#;uB69Auei)skb?F*mJFVG=bm(P-qBo2e41pt$jnIj)W5Vg z`+5`4&-P(ZCQ}^oNRZ77W4OF_9M#qP3)4Bv;Fb`MW2z7jwu`G^*=IbOFe{%BH6vYU zjv~wD#UsIo8y|$>dG@S_*(0NrW&ZC*qalOa19P@omP1)bG<3TTV8l7v%f8L# zvGkk&aqSO-5-|M%AI&c2ELJo`L;SGvmKg%x6!x*juZN1;<>4+)Y%*(9HD*mX_;G2^ z$jGLFk#}cSktllxM+{V{dBE_pfDYgf<)nd%Kj(G)O{EN-CvqIPYuEpTG*S;S6X%fGFp0c_x5!yhULO$*06w!d@{wN4{3m_OhXJm3mjFO?JtMsiA4uU0m z5~!~B=&B(jrIfp3s;5)aRmCh|JP;Z@B(UWAy*7-rnk_3p9sCY$H}@IV_R(X7ydM`} zh{N4bNu-i5Q$Ni^&hZa@L+ciQL>y`%0*(Y{uZ04jKq@F{K{oH-s}33{_bG<$Rm0nZ zwrepc0OKM*xH39B#Q#g=g{;=ht@~`=Ke((5-mZ}B){GdrnFIjsFaV81Nm3(yBRfN2 z*9(ZO<+s%CalMT(4PDJkea5ZoKI4&_r0Z$+lZpUE+C&1|5Xba1j%|yle@N|nJo!|Q zr%k$VdBoWpSR!sSYvTeqk~&!QFs@cj43JO7UN&Fhz=eP2|1E|6RS_7YipW3TXK|0H z^-EP4*+;}whBbDse^l3>@0VUTKwzk$RtSN-ap6Iq&oCai>il=SZ9WW&n0>S&OKssu zktJ-Dxnh}qH{_e>tp_YQ4{gj@)Hk!R1@YZamQWc1lbPBwKb_HdV8jK0nMQbuFXhM{ zu2RL$LwOTf znL312vwXT;#z;(dJ$wESrstn+M3Rl#NDIOyVaMcDm~?1Nq8z-u z4fEk)*bOFj__ou2TJ+IO-j{oUb04Fw?>=_kKcK{>UFoBw`M{Y0j0_yzg~gH|ulC=~ zg%(`zzxrc!hT&tetclwyf;WR8Z^)o z+NjTQ`Y@KI@Gme7b`4++<|FiZ0SztQ>8%R=K8N7%(u99xTZXSqdCDg#o)3E3xEWnM zLu=V|1fcxj1etA+O#z z(Uv*fgR3U^wkZlG;2LKyII=*+T^Ky7og%%3w{z_H6-Y`(_LcSOgR9r?_m-4`_86u| z#*#gxE6c|v=yH{J<7vl>#c5e>56;h`xx0yz1W|3t4bB9&tg!oX32r=k$I`T{V)5~` z@!7c8rgY(OVS;Z}A~R7$e?1x&rU%->7RJ0$M@)S9FcG1C6O%wekaD0#9QE=Xy(c~s zURmv<6bwYVqZmmhbChypLL`(ZjDW&CpIW`C431Z>jR}oa?1Qie@otD%?7@Q!J`2|2 z6GC_d!DFtYGz0;dnbO?0BIVyYnS&6vP}#`BWUe*9h=x6C)_WUPxMbayg&vbhnZ**@KUEWOQq0Ezh&O#+zqb2bBCaR_0@+xe?+aN zt`+!-#5wPHeDNNY1}#PBDTboHBSh>-F*8)e5~d%*<2}ntDR;?sF=hk(JNyWqWp>7x zz@j1yfJ|8wQUCw0|E15{tqr{JTncDU739an#;~uCR2lR%Y!&=t(r9@>)}0I5z}@p= zr0O2JGR!fy$JJKZ8ZO|epfl6SN3F57+kRyG+-}d0ZQ8Z#W~q0* zK*+EZ&sfcOP`JR%v5d#k`Itr^K^TYkvz@oBLE6%!HtPxvlZzn&lNS6Z!PNI&=a8jIvlSg*4cIWfwhUGwJcYQ)~DU?(<5QH*SP z*%V5{@Co@}FUnJ-Y9r=q(H=9WQSUJ(;W#;7(|>`A=|(PGS7o)XP7D{0dCCU@%lvop zSk>Hho>(+?C?Y5vRYEq1tYN*bi9o84Zzmpe21$J+K<#uS9TIW)6BeUw#Ov?tjG4}C zM^pUBq`Q6nEXqqKRpRA_NM};KmQhwTn zmftSo(@N_2oZGO@&XGHB;Z`w`L&Nu-S$G%jkU-alLfV_Px&*1JZr#mGtveJa|bSL z2Ro)vqkRG^3X(nA#N0L+?4B$#CKI5755JJM<`=^3zaVC}v?TDzRrK9Lu5|Wnu9SI; z>lo7Y%{>2+l#CY^;IkVR&E9s(tyueQj=B=SF3@uF!egfLj)Vz4m_PUyr76!_@BO87 z2=L8oHw@=hrMKK?W@L&tVG}IfY@%6Ee=r_~GhumC0nQ7S*-ZWdo29;=-cpr!ynA^i z1dOZy;;qk-UuvCw_fj_)VodG`1kIhB*MYz$1_JZeya++E^TQQ#i=sV!3SS1ra9|I` zbZ6QFATZ$EFd>%FSzk<_%eTX60xtRZSp&+uG@PUlpRZ^Gu8*^hh_bR_A>t`ht3NTB zw@s{9yE$oq?Jv*d!gcERXMbv+^JFj2Swtz!m9FD39$v=d)%Q%&L;d~FInC7Lyt3!V zaJBB!HKL)>TT))VDdO&{tAXs_SeGgkrrYgy9-F;W*JSv}uvs}fLo`pROxhV&X^=C$ zP@uavjKlbcPlr7f*7{*%$3j>bP-<7Re8E9pM~<80&_9-7Q8{`B>_m84i(gy^#|>#j z=oyWoV2cJBZsnn))DEnm;@lnClvy!_uUi6m%(E!jJH)x_Lu4Q#{!e30EdjYi45l&Z zM2t`vFeDJJUVQAdBSs(u`Cp7^;~c#6M`TmG4f(wnpYQ%Rg+;O`#O3&Eh?823o=WP1R+dim>IK4HaI1V=EVf-}Ouj&CA;!WM@4 za5wfl`x7btsL0WAe^25KGIul&P0t=pM9+zYVmb^=o7-RUep3@Gz5Gvwddud(@O&Hx zW|A`Ph`GgbBcYZGKLV0yj1uuSjzBTY&RV2>5_%M!YlYI{ zrR1ee5z--JcwL(--QpF@hJl4sI!>9hh~{ad3fx77UT0F}w0`RmWcPXpHS z+&04CF1>=o%sOGnL5H_hc>g?0t;31iBAj{Nwqc1)l@9r|AftKN9@}vGHqD`n%+wgY zcu139!%?WmamSK=5TNh!4|fuG~NB@RmIAQd5R|NgO^(ZEhfyV&aSX=F+NYb3v~`FSfE@fzs~>4z} z8v<4Lyj>oT4$?&u%Ijz*LWCa!eYkeYZqiN>Rs+koJu}lWp@KSm085PH-O0wc7SH7Q z+a5uWC?#)Z@oVW2`vo~5`DA-Bs)}9;*6dwwckRiF%nnuE*_d^a|LKbq`I$5SYTn@m zg?bL)v4>Ql*&iQ4dtCPAdo_M}{EJx|Q-1a}$bWw=dTVq%;4?paHHRf#Gmkpq@PDnC zI6KW35l95;|L$7qnxBgx;_bv0c>I$ryc^D^jg!(Uo4tEARGt~F#9A|AC~u?(Y5w|) z-F&oVQ3(S=c>8ugKYgBuxq?xV6lCpDMrGE+wUQ@Kh#b_5m-fl_=0adZcaz|TMAUAa z^I{$pL^OXK1%6S5>Mm-nzEp6+H%CW-f0POgZ_O`z zhQeN-o!5lr&l0V_mssMM$#&sVRb z9_7~*>QXBrhL|ZJL`9*GE-jeqaijoM05~Rt8iPL4lg9hg3HZcEXFNS8yVg-1j0)N+ zcs?DkDy72d>>$POfo=#yOL1njsmrw+G8*5XVq8nJM**CwwwYYXn9DHG>G3!S=Pym< zLtQpWmn%UP{Zlr$i(P_Jpg)U72GxTjKV93uKifw(=Zv`Dv`_&=YZ-gRz8%*mA6XIP zFj+}fU2O8T#n?!5PhH_@y%-O^sR=S4Gn*$6WZ?&?)e$K^CZ_uB1Y44`;ZVS#)?w4WJc7M9=o( zchWb#W4%@?Gmw6=?n`HIOW3EGm6LPB+`el_f!Lm$*90;|t()klyIfJVXw|HYruZl2 z(O@ZlyiT7~lCUA`E{i0TBxwo#HDSJ(mx==)TEPRD@S?mG+g9{!0m0o-MBey1dSFB2 z1SexF4oipwIy1t)js1Yk=jy{VnLg#^bZFk06Co^^<~pu`n}S-m%VG5pKZ0X11)=pnLjDlI_6NG%X`ARARoyKPJY4a zO3YfLL_&kmb?e^eaig-+n%N%3%{!tQ2jMG;13_KgaaqffBn@|KM{i|VWd|!YyZJB_ z37SPYuJ5X>N}4)`0-c6#d}+C9SJMtX*^U!PV6fnhXA2l$xK<`k(!|hSKGKAwsjdKg$}i zwxUZ(%Oq^AAEc=}K+`KbPGMxYV|et7Jw?0Z0MqZ^SM>o#?Yy>f8UwyBMsO*i!i$7_ z$re)s5eKmDzXJW5{9nTT0p6@=Z1!dYwD z=8aPYog`EXFTXWMV4h_GeuwiESug()unM#`S;VraiAMazx`#XqaZeX@-S|*E>C}b? z&G9-UUy{%pj=+G`yh(`M0qR{Ku*CI^~f|Ct{CJ^!Hell#C#{KSFUPttxZZg^PB zfXn>FLj&t6uU%9SCk^(~=&76FS~{zd#*JNNGfusBl1u2R!QOnFvM~;=CH5$+@{rcu z_WU(*$)VF3itNj?-@e+f4!R$(1+JqqZ+b;BsB!l-@c*u#&J&)l`5?>~-nx~$C(?P8 z*o%*5V3TL04-A$M4!R#)IxjCT1<>lI!lNv)crt!gcMEFEx*+brImo(aokbv>J;lhFSnC)C~iup`<*ZM)5@@76kf1{=*c@$?k*Qk zXSiF!5R291@Pv%Lv?jKTo5tH&0eeV z^`NtP<~eMO7v3H3!{CuRBAr%oUzH2=Et)>odH~fODw6UVs@38!+0sd+eswiI7NZL$ zVobLH!#CeNbpGU@S-42`~MeepOr(>`N8yyy`~qt{fIpeMM>R z6)6iBMr6BBKTI;@Uu(I>f+MutmrhP_b*J7T*+z8|Go)=6UXT(H4XH8GLTt8AlX-QK zZG=lt^WMob$BN1sGG?}+$%)MiBIBKefB;LB(R z5IO!u;A_sfOUEf(IMww#p}PSDa~g?E^GwtM5ZYMk04?k z?N>XkX~N&|Sgl-6GzLs+7q7k;nGi2NYEt9p+DH4eC$jo`1xItf6u3+t$GYYPwZy4u z`kB5q`L|Si6~q41xVxxKtXzf{{0Za)7p$D={z7TCw3|NpJsklnQ&;v4F+mCyq2qF@xK(t5H=`+B+a{bgsXm?Fm^rZYJ`WO>zNF0mI^Il9Y3BU>>LN^kWQBGUzIm$i@dd$#EzR{QU5sb(!4QK4}? z5aIgOdbh)@hLeOwse|;?9UvN>9SK~8p00OI+Mn^$D4+uMN3rd?KNDM9?&En1uJ_kq zati^|S+4!Pjg+Kku(g9iU`v4JI&AXV8b_FUJEHLy>u{_&MN~V=YYkZ2fm|&2(scXk zRPUdr0DLug7dyzRJ{=TAfTE!bY8L>aZyM*y91Hui0X?Z1sNCY8N9Ns;(xmTZw_`Rl z-ra^=V;{wHX;@rFX5(g;R-+^<$fu;2w;L6s8XRnoK-r^A;b!EeswTKNB~#4F$k{g1@7$2)`FyO_Df-cIi$+2f<>_ET9FWciO_w5c!ie(e*p&rF6u=g#5t&qR$Fj_uGgNYT)C6ax5g-2)CGrz(?<>G5SZ=H>`c`fgtlX6)RI) zdjC|3KVT=_9q{O&G-C~|XR^gOPe23)_k9|YA)bXHWF z5BQYuzj35mPblQ&&u^*fynGQ}2*Hr`jJ82(>c!futT)*&&yM*?SEHF*u#CP{R)qga zqntUei}5ezojq-X;i%ecy0UbR7|wMTmNAnQ7jG}(wf)=b6IBLkW6BRxK-t&5kjQ)X zau$+mv>02|KR)TPoXS*ZDmZq<^$i@g)A?u-??u^*)oN!oxO%JJuV!+lHQKe(FhSF` z!=!pQpA&DrnKl&FIzsU8wq9(SYW25Y+A$2(=a%AYKM*=;#Kw~?>E`RE7(lgMzV`F# z1T3O|0zvv6I}bTA)!r!)MDra7zu1ZK`u#q|!9icjsD3*t@QhoIFs+Tk^(Jr(qmSmL z$bElbVf(py&iSt+<)fdTTB0<`# zKo52kGh#=r9WLj>RfgM5)Bsm6`bIKN#mDfHZHnpC44q#Wc6XU_JF2p1z@9HLnU!IZ zWVIWY^2Wf*h9;rvaVsb)q(J%KxVdnT2JJEhl6inWx)?apMaTMH>EExs>eIj1(0GSz}-LLt$ZqQP6D~aR4^Yp zVh4G0sYb$y1FRdXvcuD7t}8%u=XQo5zdi00d308lioGO}? znI`TBrics%Ufrw8Kx#rUVPqD^!URsT3DEqLqcIM@d_VwS#09i|A0Qc0_|B2f1|@S7 zqO)lt7-(%?-FkNmPPzS4mFo1YiO?szMReK&OtY;l!jggV>Gkqmh?x0LphXW4a|JNJ zP>r_JD1BTo(ad6Pma^uT)1{ieQK?1Qn5x|=>IVvEua5Nv&I|bHJ+{-_@~qqE`Wup~ z`e5&UWjzC|(9n3b0{lSI$O_>-1Jo?(!-ZDJB!gXzFI+>)s`J; zkK~j=AU}=joBRC6oqVdE2>R0BmVig}Sx zkv`)va}!I75Z~MMq8;!1I}(UH z-K@G(fLA0p^{tmT@?JqPr?ODkGdiVBZ<9rI?&|(!CqyjoQD?M>$L8uOFCQ6lw-bIO;IwEHmp;?t;v z;+;ZW<$Wa-d{?HmM5N8w#9W5&pr1!zjyegE#~^0mcyxfX>t=W{;LadyzR#Jv&zKfA-#RE{g9F_I z#?pRTmmj3*v{&W&lx4;F(B|TjW2KQz&{72A#y=3Yy?Ug~`mY7e)405+pfdy5H%mbL zUa;^sqVp|gIEMu2_LlwY$7E@x%|))yh2Jke!4e8ykICw~W(tmzzjFzIGtb~jA~L7l z^eyw?R_)l>!B`>~eSt3>&dtj>H@@o)F0k7gP6mFZK~$?D;IY)(Zc6hb1dcE<c5A`1CZRpAzBBy%u*FM$UI<`CIkoHS*NGg@A$7L;VrQh9c4b4 z%Dd0x?ZP;1k?=`)?yQ7Q97K#w3c0c!qwc7cIa#Am0Z6ilrC+VTJsJn#H1bjrWnaNa z<9p^oV00^@PUp_yGK5vJ*L0ugYRXDyA~w3f;6+g*t3ND;rv{O?Hun2r=YpCdPdHm4 zCQ4!YGO%`_0&5@!Oy<2#Uu5GT!l%Di8q4Ql-Mg2!B2Vh*qhmf^1ZZ~s`te7tuY3o8 z4~u{A+Ou=(Vms3a$2L@48%>SXm>*oA4a&A2741i-=ZL~mP&gKeqPz10b-?;jW`K}q&f z^irEV%+A`kA@11FXi$#0Oj>A4=ITN$!ko?yb((7WJiaEh`)LX$i{%^kZ^Ez!Uu00z z2>H7sbXR(XxQ8XnCa8nIpAOH84R$d<4q8hn_}wbVI6K2shQ*coc=c~cK*VV&?@c?7 z*0?XUZ#5HHTkdGn-#(EWn>7tG;?4nsP$0*t*Q7+LMS##tvF_wzE74NMrsI#(8K&tBv9WvV zXN|&7-@YM20?u)H2sIt4!D4u()rSeu-02adF=HxC7k6&X=_dJ|v^T%?L`_EC(-;HS z+!%2u<@G(zam#>1YkQhw-HUgUDv3P89hsS%qZ!;qTCGCzu&WgTVtR;BzfUSKx)@$6 z&OWGyc_D7geOumEo#K8M0X*hX+>TkuX#WyNSAM=&<)UsphW~SqB(XR-q?N^HAL%zd z6KpWCoJzXRNvj})ZGk0>upy6;b>3YBy)!XodS=Q4`;i4^d}8mnFBl3wM3+Kjp!d=# zj*r0pqk~r7$3XV}7v9&aVW$_jZZcePzGP_CdCn3|9);toih0Z^YCx%dsmoot0jw$J zUJ>OpF>Cdfzo#%5g0JS$Z_IH4gt^DtVGK zY^49$VQ~n*WKP0FnG6)L?DK6txpQI#2FXUA4KC8!KMM(g+ER>uaq9$Cq&l4=Z5kW5 z6qojO+4fD*7ltZ7Bl&X$9~KmTbx&e6_1Ft~n^qhUciz!1t}vE~0*x23s}*4Eh@g-W zEe;arfIs%obsV7ki1W||n$P3TbobSw4VhFN<3PYId=oH`D(gWJ)eXe)-d>;TWaouh z+)vIe#P7fy9jqgWmLa&LelkFppZNHq z#qNbL8h0{fkSy`oudetS0LQas`t~_oEc#k+&{0yB#&pb>oYys;b8hPIMN4AJpV+T1 z>)WZlbW7^qxZ@WQTZh?QMp6;_(4vz$P#uQGw7nQ$9wi#<@A=eGvgX_tL1_-?-8IMb zR>m=@pNjCu!N?>CGtE7kU`$?4*}izyav2p20+zfRt9c{R4QqVFhZi-4)Ds^_=u1j~ zjr5>rw&lFR{C&r7vHGj%9C>*m;IpjP4f&@D2yU{eiB0rpr4>15OnF!0?!{QIlow-z2EnV#D&JvSXHwt^q} z7brPXAMPcw8ol3phtBcBX~^TK`_j{&m`3=x&Ex%5=G6@} z6X8^_M=wF5>4DtWs9cpQ#w1C}>X>E;5>$x|VqMntg|h3^TPyT!?KvoCMC1uUQhE&4 zksbjezrZY*!=(jNs>cf>FvCdPIoM|A90C^%WjG+sZe+1H2wZkUD zTnPn2PXSdl0ysOI%#%iqCP(gN1{qbDas!?29RM!jOyf&UwU%M9ZbMZfRMm(_8D~jo z-gwZS$te?eE<7*MpgryReL*{uE*ImflJ60m69-@c$Fuct6IxD!kq+eH6{eC#uA$+m z&i-Ifn&@db?GF0&znX)&H~2DSY(A*5O8-fzi{hE?!BE@_uI6ez5*(*ZCJDgmiZSiB?RWC(dD=XBE zQDkky4xLoLU?c&R=+1NbiAcCO&4dBtvronrb_PXYtE+RplCw0=GlI@l>0}Pkk>2pI zRCn@~KctMnBLA1xGayLpdwvY~bGh4QZ}*0|=ivUe`=0+#z!H1<8<_M@7T!AI&eya# z97y`m_1%^&GiEN;0^mcR#9r^dGEgvAcj0*?VbPX@WW=2nkKv!XCTq}7-l9$UtWkAp zn7Xqxo0@`>2O0`m-Pt-y5fBg?;v5W*2op|{Q$A&0eD#R&vV`%UnTSPhFxB)#ywk%) z9sk99VIH}%Gm974_#|2TvY3tb?gGr-*wu(u@D~Q7Z(_Se!c|SpJwaNrs#8ilEYd`- zGk<78kk)vUc_WdidkSPT&e>;9=WG*CJUCl5Bb z;;;28_PF#%8_%I#`E^9#9Zq@zv1Qp`E#{`OL0c&JinVF7r1w>Fc@HQXUj9ri)lv5j3fW^rw=H58{lOaI={1>(s664rnT&TfWu^A>q0 zt|mM^P4)~xYr&-PN{iuN(RGvj*^NgPId3HN$WFoP*G&B|f@EdL{1nRzrKK?GH09}EC%MW-86)w)Ed>}OLnVm($8Bh#4xh29+|()!^#%U=<=sb zP-N2_v-EU?W0an)IG^TxBQI0}H@mzCJ?G7Q@#2ouf;%BAHkzGCuqa>AVFHDu5Q^)_bBo~B4i}@{#S}mN+ws&m(@p$YW);DXnE8k(ffrRV zwmv}DPiehz%MQuRwDWy6e7*QS{5Pl|mErwRx+L^)_<{%)w!h#t+-*L3b3e~`@+iMR zD~gJro8c!5|J#ani1AcYx&`sb>3P{oVSScyOnopTCEK{z)0h=EB^5+-xwnywKD0eL z*Vi{T9ZQk&LINarbwB-p9km|PvoKy}>HK0gD8VD5$s}|G{F0;5CZCUR)1YBQ*vo^+E6YG;WDT zq4Olg^p{P9>%s^`usdxOvV#iKPF3S)Q%SFUW@n*FGghSJl`WEv>rzREdNr1Ze%x7bXx>Z7=i6zv6^lPG%!B8?2Zjp}@8@G% z)Nq2;{Id$p5K1*LJP93X{`aK8JNzEVfs|%HI~2GHt(dti;yQnc=1A6%(5}?XJqsU| z^v1@{>P!65*Y?jt`l7@Z8xV!;bDh!YuNfZscm9n zo&vMc(@D5z%>Y~UoP#3jK5aWhJsh9JJ`5~*f;%a=+}Y(_@_%PT97Np7d7&o-kpE1I zV~w)_E9Zf2ObI3ZuvL22N4v!!LhzE;@usu)vWyHwjB1*zbOur$m#~Ij_>_x7`G*@@zQNQgsNGEvxNV$+TJyTzh~B zI1F)*!G@>Sz>I9`pLmaa)gki%K|~@%=WAftWY4xlRb_RBkq1B^PqfENVPC!nLKksJ zHg!`}by{}$VU&4lwtaEb0X+E;Fn~k8W>b0{6De4JM-3u}lQ4xy6)~sNu!T(?LI)Ey zh)^X_CsVbERV`vilQj)nHE|zFygNC$Il4N(zBw;rGe_=&O3uSf-v6N(kE}cowOo(A zd>6zRmdrdB)m)a{d>-VOjyksLS-*J+J3RO4_>J>Se8- z`JsD23-`93KLbt-`Qo}ygH8?m=JvM(PY(U+=GXrH`TOzn_17UYQp_;&xC&R&^fC0X zQioE_GPauby3PN!6X)~Csb$B;9WZ$B^0&Ra^KVB_m%oRPm!GGvw?7zyp%5mzk|OIi z1MQ|GFF(Y8NR1k6ee^aZ0?;5F^`HQFDMU>^Yv}#$~{qy7N!^_j#gUEu6 z*#wlChMal`q?-R#X#a0MPz8b^O?4#xPk^5U|0&bPQ!TTs{s|>wA{HR#PsFkr}$eKkho4Gr< z{BO|Ri73F3|1U7aqKinX{a5IrQNNsaYvad}Kb?Gb`~TI$v9~akIniBJBfZ&Ngq)Ux zp%9z=a7+A!K6y&+BTE>mofCB&L57rw9v@~zWINsF3KSb>?ZdaU?LbW6Jm4w!P|%gr zNT>?wTkwFn&YwM+WArN$XfxHp?p!l_wG7dqcm^bF6~ybSbUu+*cRH~e99hBAcQZMn z@jJ%(#m@)P&7+MUBMwtBd~wJRmPGqs6XE`h!Bc;0d`=~1P=FXoA0v)DJc56Hr0?H_xHXIgq+KBf{8hE_3Dxeu-ceLB$1{=p}Gs9A@ z^Ml4VY9H}SsL7CN>DM*l2j4=lVXj?8dMsCZF|H9}vwQ4VjGTXhQvVY=nQh&g7s;wP zYSGgsWXCXv<+lAX3gx~;Yl3>OR-a^Kj0UewQud-XMk+U~4{knQ6=&aAbaXj}mk9P- zpHKe@Ju*qIESKU*xT0HU%A>{8Qx`U_`^HjUCEhQ_-oP7dt|9cYPsVuf7u8@f?X|Rz zk*s95rT0Bp)IW~lbxbB&kf->09yeQtN63)n(v{r!OtrAhciR!}1W|B|FhG9Z9P(rS zLo}I`;K60#K|4T#cu@wcm;=&+9AV&Uv~0Xvg%dxg_P!E*GgfKYX1LJJc+BD-HQdun zRNcwnvNq$7my}^duV6{*HeB*j)T{9LRn<1pVeI9G;^Omz8G?R{6`a#k~xYpx|2w2A7@rTBxMj=a-Y^@TO;H-8xu}^1Z!P1;-ot(e@To(NV7!>l;$h zAnz0dAU1wdpxM)t`PDUfC~``vVM!V3HqVXe*Pf%&OwSssB!!-CDUq_c!7GW*aOhjV zPmpVvmnc$eG6MT|XMn-_{Mttyo}TWpX%uUkz~)PB?(;jae8LbbicQnXkQ|o-s+TAW z7JU>91uw^r12aih5XlTm^lj^QaStN?>Rv3i2%R{{r$U!zBvz=_DOrEYb=_GKbZTmh z&$8Evs|Im`6CzXyxiZ8NlJGKS8zYSiP?RSMg|BCaKkMpM5iTY;O6%E>&y5jmH6Kr* z_Dau{$@JeC`~xqACb7zb!Dk+9Hg9mwYmcW(4=P4uiS$JVwYPDP+|R2ONvH;SjU($8 z4fu=sVPY&+o5c>)UOf@tzUCs12LbWI8H$7rgN;b})YkzKta5^``lQ)kN`SE1Tk<2kFw_J%Xu{26}zzM9$0?p#kcbd&O2(T zF!yxP;ASu?LQPLNx5`F?-oIEm83wHX%StDvoZd~?#DMNaIUYbREId^0}` zO(oMQDT=gyFTi%+;ZeBmSG;x~erVYPMI2SM!%(VTY(bNW1`S`#IVZG}@Yb+KIdV(Y zf7MHuQ}trz_y8IwH|xao^S%L|BNiMubCB-FJ|_>kchz9WtQ&JdworbfZPi44h8iW; zaxQi9hYi|lD0O71*e1F!A~XtW-Z8%Cuo2kS1cii_>)sU*JE1qKL_ZyPaLx$V;(rsZAhd&5gE_ zJ;?~mvIhibOwU(E1JnI_oKG09&azg$c+AN9fN|^aP(-NVDyCYxB6)zi52 z#J^Ki>*+COb;^Ofr!Vszzv2%R=^r+5<>koUt^T?&g?yU912-H$T0^?RnJzF(f8PYP zDlYyXmMf(*)!D;brj@sO+W{)auD#YQp-crY2~K?AwfOV>_+_cZdXZIL=&A!PKlqk} zP(o~$<}H`45l$ni{#?4vNKMr&rlipC{8C(&srFjUHfG6Jo=A-Ix=xx6MjNH)TtT6o zmp^t*w1PfAzdx*t7j^3B90a67E%B0v?a1<6^p;rT&xNK!X9p|8xU5&KG2at5w%8tK z46BU45sWs8Kx`AHfXx%0C~1uHRr>CgMD<6+4rE<)G}6y|U9=eK`As%1NW@ni*p<7! z@EvV1O@o1n68dGOaHQbkHYn~}P;^~z{037~s6fOD=nB#IkjG@am6$->wv{XcOT`gZ zdu08u`y?{BKkG z!cXFeC7)pfcN^3AlYM)NPxaU=^-_K_EK=4UV=!C*hFyCnv`Ex27_3wH7N}{Vc<=2v#Y8B}?I_z&!YiqVRrbdc~1~?I=!W=k1cxg1Z0y&X9 zV=p0uI7vAiy&wg?StN7~GN0fWCV&`pA45s^DoVdR{kfki6G7+)0QitzkKcZR_Bq)q z@rDwu{5;<+WWPe~lKI-SbrhWBvw)0brH+jwefHFzuze2S;{3f9mBBuf$0-9$TnL>-O4 zf^tpxUcP;h9*t`AvavhgpC3EP+_%pDT(!gI(NUtZ z^KDOOvp#N7v;m1G7{%F|b;bhMN3)%?(Q{mxG>W^A zd~UhU?tI3-=MT^xO@>2?O>^HmOQ!d$t%3AqxPS{io$X*buMgqR8 zc^vtc^o-kbt{~c$$?L<-%n*Sm9E;L5nP zQ{xxM?q}K}C4fz%Iboo$JLCt$V_z3QJ`VoSBdhlsGRBc3*~whD7T2r2l@ahAZd8Kh zU_HZ6hkI>TG@$Fj69QyzX zx4K=tS0k0mm*Eu;MZ&I2j}@=%r+0M>2trdP1{;2=c+Q9n@B?QVaQ@}8jJ84OLpt6e zcdU*7PA#)WXV&oHbF9+6VCSiKX!EC(p1<(7EAl&1)mgw zJ@4pq%NlSbhWdiq!O|Fl8sedvwtI>a=f|;ESJ&EqjK|jVJ`YoY1=zaUno*Y;AGhN>T3ryy9KHrJQDtrO#jcQ%*ICy`Qu{w-7_gld&4GpRMWqDUSzI1vX zo*e+Q)D@DQUN9x&IRMi!TBRP3Bf>OAyv3Z-aD)`psOQp z&AanPbYn9)G5fK((t-AT69AvA)ssCA5>t&X6YZMW}f=m!a>`Kk@mGvuCoVn5psDfS< zZB2sFJ_j|9wfy_DgRlASm=!HN_m9}+R@yyTfannT#E^$XH?-PUdArdR!dI~Dc(EII z<;Ux)?r84&{D?el!e4Qwe8DT{9wD@P0JW2B$(ITjVknOi2p9YzjYY~JKWWAj9`Ko& zwYleplqCtKB7EhKOBLZ42;yc_0A905cUe8H;l|s_3(eXy6}Om`i|z;GMV|%R%+MDj z)o&e0-w7g1f=ws>TzM9Nd145W6`X~vE_8CxWL_7|dvRS!z5ezn70OFHk^>y_YyXGM zc;aqtm%AMXMUOPT-BA*CoeZ1&?H1hfY(6}4SX$P?>xwPLA+@Np2boUq0fWPdYj%Fk zLw6rK)lt7L$e8O)x@zVmu9fWhUx6=ZQuchp^!TtnsBDVO(YZ{s^4G$RoND;Vj-)pv zHSv&Q&PAEcJ|%XE4f3hjND~@36N!Z+1Z&` zW^RJOrDgddQ53xJcI+@%%r%wxk$TERln~bf4{O6gxa#I}B44t$l#Eew0g+}QqBmAf87QV; zkcmPxm)Z6F)K?<^WF0LaM6WWMs)VCAb6V>yltsT|;^-nSJjq}W7`4I+um3VG!t2Q5 zec%RhkZqy(a-QgpAS=r?>l7$d4<+*Gl0#64uvLX(!WOu7T`8naJz@?Y6ye!*&`9nB zSjmw)uGhle!p7QwHv`R%nn*CLMVUCb239SWsff?dVRAu1Q(-U-r<{8Pm3^50vj8-* zmyeq=p?0WD#q-26swKP!Jnm_r^X5=xE6A7~CK^@x(TM6bO&;0VoE)ktg#9k!np*+s zMfl5dSFw#y0i)gX@{bbgDx1xZ|APLkqkQ@ybW1+UCddWK`xxk2dQRN(iW7_Fi5m`> z|6;{?B7s6TCW+=-b0N(e**A3{Jqf)-JP`X(zJk)48_JdVikDO;Bv1VF zgN0pOa1~4xh89a}^Y>@gZ0ip|JV%>$O7%wTG?swq*juBFHlq^Z+I{?;`RgftP=ryc zK%CVc7a+I6eY6EtTQs<@5FlUaYAfpGfGZAb$C@c@(5iHmUh&xJS8TB8s(E{G7Au;X z^1Tm~j7(KRLuS6|2L+tg8WnZliSFmbJ>$4t4uB zR|71UYEF{X%K_T*Dxoe=)y2;B{JG}t;gKU>2SlJHht)U54KqnPKA4WDZ?y})-|ME_ z9qDiEntVf^I@9RrdN?p<*y>Ir@9cSp6`WLt;`Y>~Dd7#Vz~Y`lq3b}c=X&i%R5E8q z@~=`ZkqyiC)txxn2_|nR1Eq^iEy)lbm!2)NSROLL;sl`*@<>~kbEe^%7Ce@TFJ~CY z_1A$?aP(KYSgy%i;eaYT5@e#ORgBE+@~AJDKxAciTT&*?cD#-iwj;R(QF8|XLxR+- zYFbCYJ0a|QA>bDWBYGLYfT`T(S^&M+%OdZXi>JU{=DNbdJ2fk0GgHbYLFK_~(2!Zo z*PTn`*DrA=@$)ilT%FwliVUrjugU_Qbu8oCo@QMNUIwF-cSCf9$Y*l80njm|3en`T z&Rc^7BxuN8vh^J_`h%x-H!5P5&X!Rgj+fJljHSIi9H|J%=7k+RaBJW&`r>hA9dgUd z7nMf`TTrE7fbIZf-aIV{(|I}%To&1Z@=oQXhNNeK5g1e3H#0K0ly}eBKU~l-5kiDC z5J<+^xB>z?4~Nfe|It>4I@g*!l7p?QjDnukUlHeoEc)}+?RRgNnL1YSftzm5cImWe z#;CWN9i|`cYqj3)+4S3^Uw3s|?}jfoZK3taWYdRitQz|}C9XBnvy==oOD`J)?t;K> V=VGRl8m#`%cC;f?eCPlA^&g!Ko?QR{ diff --git a/src/App.svelte b/src/App.svelte deleted file mode 100644 index c8e8ebb78..000000000 --- a/src/App.svelte +++ /dev/null @@ -1,129 +0,0 @@ - - - - -{#if !$isLoading} - -
      - {#if !$compatibility.platformAllowed} - - - {:else} - - {/if} - - -{/if} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 000000000..ff4f3b2cc --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,3 @@ +const App = () =>

      Hi!

      + +export default App; \ No newline at end of file diff --git a/src/StaticConfiguration.ts b/src/StaticConfiguration.ts deleted file mode 100644 index ff3bbd69b..000000000 --- a/src/StaticConfiguration.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -/** - * Static configuration values. These values are not expected to change, while the application is running. - */ -import { PinTurnOnState } from './components/output/PinSelectorUtil'; -import MBSpecs from './script/microbit-interfacing/MBSpecs'; - -export enum HexOrigin { - UNKNOWN, - MAKECODE, - PROPRIETARY, -} - -class StaticConfiguration { - public static readonly connectTimeoutDuration: number = 10000; - public static readonly requestDeviceTimeoutDuration: number = 30000; - - // After how long should we consider the connection lost if ping was not able to conclude? - public static readonly connectionLostTimeoutDuration: number = 3000; - - // Which pins are supported? - public static supportedPins: MBSpecs.UsableIOPin[] = [0, 1, 2]; - public static readonly defaultOutputPin: MBSpecs.UsableIOPin = 0; // Which pin should be selected by default? - // In milliseconds, after turning on, how long should an output be on for? - public static readonly defaultPinToggleTime = 1500; - public static readonly defaultPinTurnOnState: PinTurnOnState = PinTurnOnState.X_TIME; - public static readonly pinIOEnabledByDefault: boolean = true; - - // How long may gesture names be? - public static readonly gestureNameMaxLength = 18; - - // Default required confidence level - public static readonly defaultRequiredConfidence = 0.8; - - // Duration before assuming the microbit is outdated? (in milliseconds) - public static readonly versionIdentificationTimeoutDuration = 4000; - - // Link to the MakeCode firmware template - public static readonly makecodeFirmwareUrl = - 'https://makecode.microbit.org/#pub:54705-16835-80762-83855'; - - public static readonly isMicrobitOutdated = (origin: HexOrigin, version: number) => { - // Current versions, remember to update these, whenever changes to firmware are made! - if (origin === HexOrigin.UNKNOWN) return true; - - const versionNumbers = new Map(); - versionNumbers.set(HexOrigin.MAKECODE, 1); - versionNumbers.set(HexOrigin.PROPRIETARY, 1); - return versionNumbers.get(origin) !== version; - }; -} -export default StaticConfiguration; diff --git a/src/__tests__/cookie.test.ts b/src/__tests__/cookie.test.ts deleted file mode 100644 index 398f3de0e..000000000 --- a/src/__tests__/cookie.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @vitest-environment jsdom - */ - -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import CookieManager from '../script/CookieManager'; -import Cookies from 'js-cookie'; - -describe('Cookie tests', () => { - test('Should get feature flag if set', () => { - const someFlag = 'someflag'; - Cookies.set('fflags', 'someflag + some junk'); - expect(CookieManager.hasFeatureFlag(someFlag)).toBeTruthy(); - }); - - test('Should NOT get feature flag if NOT set', () => { - const someFlag = 'someflag'; - Cookies.set('fflags', 'some junk'); - expect(CookieManager.hasFeatureFlag(someFlag)).toBeFalsy(); - }); - - test('Should NOT get feature flag if no fflags cookie is set', () => { - const someFlag = 'someflag'; - expect(CookieManager.hasFeatureFlag(someFlag)).toBeFalsy(); - }); -}); diff --git a/src/__tests__/datafunctions.test.ts b/src/__tests__/datafunctions.test.ts deleted file mode 100644 index 6258ea933..000000000 --- a/src/__tests__/datafunctions.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { determineFilter, Filters } from '../script/datafunctions'; -function enumKeys(obj: O): K[] { - return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[]; -} -describe('Data functions architecture test', () => { - test('All filters should be implemented in determineFilter', () => { - for (const filter of enumKeys(Filters)) { - expect(() => determineFilter(Filters[filter])).not.toThrow(); - } - }); -}); diff --git a/src/__tests__/default-build-config.test.ts b/src/__tests__/default-build-config.test.ts deleted file mode 100644 index a5564468e..000000000 --- a/src/__tests__/default-build-config.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import fs from 'fs'; - -describe('Default build config test', () => { - test('Windi config should not be ml-machine', () => { - const fileContent = fs.readFileSync('windi.config.js'); - expect(fileContent.includes("primary: '#2B5EA7'")).toBeFalsy(); - expect(fileContent.includes("secondary: '#2CCAC0'")).toBeFalsy(); - }); -}); diff --git a/src/__tests__/fixtures/gesture-data-bad-labels.json b/src/__tests__/fixtures/gesture-data-bad-labels.json deleted file mode 100644 index 79f01fc5a..000000000 --- a/src/__tests__/fixtures/gesture-data-bad-labels.json +++ /dev/null @@ -1,1024 +0,0 @@ -[ - { - "ID": 1705437833024, - "name": "Circle", - "recordings": [ - { - "ID": 1705437969952, - "data": { - "x": [ - -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, -0.06, - -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, -0.064, -0.06, - -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, -0.06, -0.06, -0.068, - -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, -0.056, -0.056, -0.056, -0.056, - -0.06, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, - -0.064, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, - -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, -0.052, - -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, -0.06, -0.056, - -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, -0.056, -0.068, -0.06, - -0.06, -0.052, -0.06 - ], - "y": [ - 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, 0.772, - 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, 0.772, 0.76, 0.764, - 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, 0.764, 0.76, 0.76, 0.764, - 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, - 0.76, 0.756, 0.756, 0.772, 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, - 0.756, 0.76, 0.764, 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, - 0.764, 0.764, 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, - 0.76, 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, 0.76, - 0.764, 0.752 - ], - "z": [ - -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, -0.692, - -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, -0.688, -0.692, -0.684, - -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, -0.7, -0.7, -0.708, -0.7, -0.696, - -0.692, -0.696, -0.7, -0.7, -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, - -0.7, -0.692, -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, - -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, -0.696, - -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, -0.708, -0.7, -0.712, - -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, -0.696, -0.704, -0.696, -0.708, - -0.7, -0.7, -0.7, -0.704, -0.7, -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, - -0.7 - ] - } - }, - { - "ID": 1705437870493, - "data": { - "x": [ - -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, -0.7, -0.648, - -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, -0.752, -0.704, -0.68, - -0.692, -0.708, -0.72, -0.756, -0.764, -0.768, -0.772, -0.736, -0.748, -0.74, - -0.772, -0.764, -0.752, -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, - -0.748, -0.748, -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, - -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, -0.748, - -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, -0.756, -0.744, -0.732, - -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, -0.772, -0.74, -0.732, -0.74, - -0.764, -0.776, -0.772, -0.764, -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, - -0.812, -0.808 - ], - "y": [ - -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, -0.548, -0.552, - -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, -0.484, -0.5, -0.504, -0.508, - -0.484, -0.492, -0.496, -0.484, -0.48, -0.476, -0.484, -0.492, -0.468, -0.484, - -0.476, -0.484, -0.492, -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, - -0.492, -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, -0.468, - -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, -0.48, -0.492, -0.476, - -0.472, -0.476, -0.484, -0.484, -0.484, -0.484, -0.492, -0.488, -0.456, -0.48, - -0.484, -0.472, -0.476, -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, - -0.472, -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, -0.472, - -0.428, -0.436 - ], - "z": [ - -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, -0.504, - -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, -0.584, -0.56, - -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, -0.576, -0.596, -0.572, - -0.604, -0.592, -0.568, -0.572, -0.56, -0.584, -0.584, -0.612, -0.572, -0.568, - -0.576, -0.588, -0.6, -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, - -0.588, -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, -0.592, - -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, -0.592, -0.624, -0.572, - -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, -0.58, -0.564, -0.552, -0.564, - -0.58, -0.592, -0.588, -0.584, -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, - -0.636, -0.608 - ] - } - }, - { - "ID": 1705437864426, - "data": { - "x": [ - 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, 0.876, - 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, 0.98, 0.952, - 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, 0.956, 0.936, 0.948, - 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, 0.948, 0.94, 0.928, 0.928, 0.94, - 0.96, 0.94, 0.94, 0.94, 0.936, 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, - 0.956, 0.96, 0.952, 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, - 0.944, 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, - 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, 0.968, - 0.968, 0.944 - ], - "y": [ - -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, -0.076, - -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, -0.072, -0.072, -0.08, - -0.076, -0.084, -0.104, -0.088, -0.104, -0.088, -0.116, -0.116, -0.132, - -0.116, -0.108, -0.104, -0.12, -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, - -0.112, -0.1, -0.1, -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, - -0.116, -0.108, -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, - -0.096, -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, - -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, -0.096, - -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, -0.08, -0.1, - -0.096, -0.092, -0.092, -0.112, -0.088 - ], - "z": [ - 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, -0.196, - -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, -0.248, -0.172, -0.14, - -0.136, -0.188, -0.204, -0.14, -0.116, -0.104, -0.18, -0.216, -0.192, -0.168, - -0.132, -0.16, -0.188, -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, - -0.176, -0.152, -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, - -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, -0.168, -0.16, - -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, -0.172, -0.168, -0.168, - -0.18, -0.176, -0.168, -0.16, -0.148, -0.148, -0.164, -0.184, -0.184, -0.176, - -0.144, -0.156, -0.176, -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, - -0.176, -0.14, -0.144 - ] - } - }, - { - "ID": 1705437860385, - "data": { - "x": [ - 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, -0.052, - -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, -0.04, -0.056, -0.052, - -0.052, -0.056, -0.056, -0.044, -0.044, -0.056, -0.052, -0.048, -0.048, - -0.048, -0.048, -0.048, -0.06, -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, - -0.056, -0.056, -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, - -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, -0.056, - -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, -0.052, -0.056, - -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, -0.052, -0.048, -0.056, - -0.056, -0.048, -0.052, -0.056, -0.048, -0.056, -0.048, -0.048, -0.052, - -0.052, -0.048, -0.052, -0.052, -0.044 - ], - "y": [ - 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, 0.224, - 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, 0.228, 0.224, 0.224, - 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, 0.228, 0.228, 0.228, 0.232, 0.236, - 0.232, 0.232, 0.228, 0.22, 0.232, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, - 0.224, 0.224, 0.232, 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, - 0.228, 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, 0.228, - 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, 0.228, 0.228, 0.236, - 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, 0.228, 0.228, 0.228, 0.228, 0.224, - 0.232, 0.232, 0.232 - ], - "z": [ - 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, 0.996, 1.012, - 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, 1.012, 1.012, 1.012, 1, - 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, 1.008, 1.008, 0.996, 0.996, 1.008, - 1.012, 1.008, 1.012, 1.008, 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, - 1.008, 1.004, 1.004, 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, - 1.004, 1, 1, 1, 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, - 1.004, 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, - 0.996, 1.004, 1.008, 1.012, 1.008, 1.004, 1.008, 1.008, 1.008 - ] - } - }, - { - "ID": 1705437854703, - "data": { - "x": [ - -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, -0.104, - -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, -0.064, -0.068, - -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, -0.072, -0.068, -0.072, - -0.068, -0.056, -0.064, -0.068, -0.068, -0.068, -0.068, -0.06, -0.072, -0.076, - -0.072, -0.068, -0.064, -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, - -0.068, -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, - -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, -0.076, -0.06, - -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, -0.064, -0.064, -0.072, -0.068, - -0.064, -0.064, -0.088, -0.06, -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, - -0.064, -0.064, -0.064, -0.068, -0.072, -0.068, -0.06 - ], - "y": [ - 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, 0.96, 0.96, - 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, 0.948, 0.952, 0.944, - 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, 0.944, 0.952, 0.952, 0.952, - 0.952, 0.952, 0.948, 0.948, 0.948, 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, - 0.948, 0.952, 0.948, 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, - 0.948, 0.944, 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, - 0.952, 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, - 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, 0.952, - 0.948, 0.948, 0.948, 0.948 - ], - "z": [ - -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, -0.396, -0.42, - -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, -0.44, -0.432, -0.428, - -0.428, -0.432, -0.428, -0.428, -0.432, -0.428, -0.424, -0.432, -0.436, - -0.428, -0.428, -0.432, -0.432, -0.428, -0.424, -0.436, -0.428, -0.432, - -0.432, -0.436, -0.424, -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, - -0.436, -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, - -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, -0.42, -0.428, - -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, -0.428, -0.436, -0.432, -0.436, - -0.44, -0.428, -0.432, -0.432, -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, - -0.424, -0.424, -0.432, -0.424, -0.432, -0.436 - ] - } - }, - { - "ID": 1705437847993, - "data": { - "x": [ - 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, 0.768, - 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, 0.76, 0.748, - 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, 0.764, 0.76, 0.748, - 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, 0.756, 0.748, 0.752, 0.744, - 0.744, 0.752, 0.756, 0.756, 0.752, 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, - 0.756, 0.752, 0.748, 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, - 0.756, 0.752, 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, - 0.736, 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, - 0.736, 0.74, 0.752, 0.74 - ], - "y": [ - 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, 0.248, - 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, 0.268, 0.268, 0.272, - 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, 0.264, 0.264, 0.268, 0.268, - 0.276, 0.276, 0.256, 0.256, 0.264, 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, - 0.276, 0.276, 0.284, 0.268, 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, - 0.276, 0.28, 0.272, 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, - 0.268, 0.272, 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, - 0.3, 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, - 0.284, 0.276, 0.272, 0.276 - ], - "z": [ - -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, -0.564, -0.552, - -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, -0.568, -0.572, -0.552, -0.548, - -0.54, -0.544, -0.556, -0.56, -0.56, -0.544, -0.536, -0.54, -0.532, -0.556, - -0.564, -0.572, -0.556, -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, - -0.552, -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, -0.536, - -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, -0.54, -0.568, -0.56, - -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, -0.544, -0.548, -0.548, -0.56, - -0.556, -0.544, -0.552, -0.56, -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, - -0.556, -0.56, -0.568, -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, - -0.564, -0.56 - ] - } - } - ], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - }, - { - "ID": 1705437876740, - "name": "Shake", - "recordings": [ - { - "ID": 1705438034019, - "data": { - "x": [ - -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, -0.536, -0.456, - -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, -0.916, -0.956, -1.032, -1.1, - -1.228, -1.284, -1.312, -1.36, -1.356, -1.296, -1.224, -1.096, -0.844, -0.964, - -0.948, -0.984, -0.896, -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, - -0.392, -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, - -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, -0.36, -0.476, - -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, -0.532, -0.532, -0.608, - -0.652, -0.668, -0.652, -0.764, -0.892, -0.92, -0.864, -0.848, -0.884, -0.872, - -0.848, -0.888, -0.952, -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, - -0.732, -0.84 - ], - "y": [ - 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, 0.392, 0.376, - 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, 0.2, 0.152, 0.128, 0.16, - 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, 0.284, 0.372, 0.284, 0.348, 0.412, - 0.408, 0.376, 0.36, 0.396, 0.452, 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, - 0.312, 0.34, 0.356, 0.332, 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, - 0.324, 0.308, 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, - 0.348, 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, - 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, 0.292, - 0.324 - ], - "z": [ - -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, -0.388, - -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, -0.204, -0.272, -0.308, - -0.308, -0.388, -0.424, -0.512, -0.592, -0.66, -0.696, -0.772, -0.812, -0.968, - -0.948, -1.02, -1.168, -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, - -1.12, -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, - -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, -0.556, -0.532, - -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, -0.392, -0.356, -0.364, - -0.388, -0.376, -0.388, -0.42, -0.408, -0.416, -0.376, -0.372, -0.368, -0.412, - -0.464, -0.492, -0.516, -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, - -0.976, -0.972 - ] - } - }, - { - "ID": 1705438029559, - "data": { - "x": [ - -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, -0.644, -0.536, - -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, -0.94, -0.912, -0.888, -0.816, - -0.836, -0.872, -0.844, -0.848, -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, - -0.664, -0.636, -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, - -0.364, -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, 0.068, - -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, -0.172, -0.264, - -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, -0.612, -0.552, -0.576, -0.676, - -0.804, -0.876, -0.864, -0.856, -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, - -0.856, -0.792, -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, - -0.668, -0.588, -0.508 - ], - "y": [ - 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, 0.132, - 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, 0.232, 0.256, - 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, 0.296, 0.292, 0.304, - 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, 0.368, 0.352, 0.356, 0.328, - 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, - 0.24, 0.248, 0.232, 0.196, 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, - 0.176, 0.152, 0.144, 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, - 0.252, 0.188, 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, - 0.276, 0.248, 0.236 - ], - "z": [ - -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, -1.064, - -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, -0.672, -0.712, - -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, -0.524, -0.484, -0.468, -0.516, - -0.54, -0.596, -0.624, -0.6, -0.596, -0.6, -0.624, -0.632, -0.656, -0.632, - -0.7, -0.744, -0.76, -0.768, -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, - -0.78, -1.112, -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, - -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, -1.064, -0.996, - -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, -0.796, -0.808, -0.812, -0.74, - -0.76, -0.72, -0.744, -0.728, -0.704, -0.692, -0.636, -0.644, -0.656, -0.676, - -0.616 - ] - } - }, - { - "ID": 1705438027130, - "data": { - "x": [ - -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, -0.532, - -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, -1.056, -0.968, -0.9, - -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, -0.8, -0.788, -0.748, -0.728, - -0.636, -0.56, -0.532, -0.504, -0.492, -0.432, -0.424, -0.464, -0.428, -0.408, - -0.336, -0.296, -0.252, -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, - -0.168, -0.136, -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, - -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, -0.628, -0.624, - -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, -0.84, -0.864, -0.888, -0.924, - -0.936, -0.88, -0.848, -0.888, -0.88, -0.832, -0.768, -0.724, -0.68, -0.664, - -0.664, -0.62, -0.572 - ], - "y": [ - 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, 0.164, - 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, 0.208, 0.212, - 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, 0.324, 0.316, 0.32, - 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, 0.276, 0.248, 0.232, 0.252, - 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, 0.176, 0.156, 0.136, 0.16, 0.18, - 0.208, 0.224, 0.204, 0.18, 0.188, 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, - 0.104, 0.112, 0.084, 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, - 0.1, 0.14, 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, - 0.268, 0.276, 0.284, 0.296 - ], - "z": [ - -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, -1.176, - -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, -0.964, -0.904, -0.824, - -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, -0.568, -0.56, -0.528, -0.468, - -0.492, -0.488, -0.48, -0.492, -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, - -0.704, -0.732, -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, - -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, -1.208, -1.208, - -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, -1.076, -1.072, -1.028, - -1.028, -0.984, -0.944, -0.968, -0.96, -0.964, -0.964, -0.952, -0.904, -0.856, - -0.808, -0.788, -0.756, -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, - -0.604, -0.592, -0.588 - ] - } - }, - { - "ID": 1705437979405, - "data": { - "x": [ - 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, -0.204, - -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, -0.136, -0.28, -0.392, - -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, -0.464, -0.516, -0.616, -0.648, - -0.6, -0.52, -0.48, -0.468, -0.468, -0.46, -0.432, -0.404, -0.392, -0.304, - -0.256, -0.208, -0.204, -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, - -0.792, -0.776, -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, - -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, -0.104, - 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, 0.092, 0.04, 0, - -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, 0.312, 0.188, 0.036, -0.06, - -0.156, -0.248 - ], - "y": [ - 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, 0.364, - 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, 0.392, 0.364, - 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, 0.312, 0.296, 0.312, - 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, 0.376, 0.316, 0.292, 0.308, - 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, 0.708, 0.728, 0.74, 0.748, 0.716, - 0.68, 0.624, 0.592, 0.564, 0.536, 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, - 0.416, 0.408, 0.42, 0.408, 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, - 0.448, 0.44, 0.464, 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, - 0.348, 0.368, 0.412, 0.484, 0.5 - ], - "z": [ - -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, -0.652, - -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, -1.364, -1.312, - -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, -1.004, -0.996, -1.036, - -1.056, -1.032, -0.996, -0.932, -0.868, -0.864, -0.824, -0.808, -0.764, - -0.708, -0.652, -0.624, -0.632, -0.644, -0.628, -0.612, -0.616, -0.692, - -0.788, -0.884, -0.932, -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, - -0.784, -0.74, -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, - -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, -0.92, - -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, -1.092, -1.132, - -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 - ] - } - }, - { - "ID": 1705437977128, - "data": { - "x": [ - -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, 0.16, - 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, -0.42, -0.596, - -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, -0.8, -0.868, -0.968, - -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, -0.456, -0.396, -0.436, -0.476, - -0.416, -0.364, -0.296, -0.292, -0.304, -0.332, -0.324, -0.272, -0.208, - -0.156, -0.112, -0.08, -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, - 0.168, 0.176, 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, - 0.156, 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, -0.404, - -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, -0.58, -0.52, -0.428 - ], - "y": [ - 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, 0.316, 0.5, - 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, 0.52, 0.44, 0.44, 0.464, - 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, 0.604, 0.584, 0.62, 0.608, 0.58, - 0.56, 0.564, 0.576, 0.592, 0.58, 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, - 0.456, 0.44, 0.4, 0.376, 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, - 0.38, 0.392, 0.436, 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, - 0.244, 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, - 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, 0.428 - ], - "z": [ - -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, -0.816, - -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, -1.18, -1.196, -1.236, - -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, -1.04, -0.964, -0.888, -0.856, - -0.772, -0.696, -0.62, -0.564, -0.6, -0.592, -0.536, -0.48, -0.432, -0.448, - -0.48, -0.484, -0.516, -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, - -0.62, -0.668, -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, - -0.84, -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, - -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, -1.152, - -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, -1.044, -1, -1, -1.012 - ] - } - }, - { - "ID": 1705437922352, - "data": { - "x": [ - -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, 0.064, 0.064, - -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, -0.688, -0.728, -0.772, - -0.784, -0.768, -0.732, -0.696, -0.648, -0.576, -0.544, -0.52, -0.504, -0.492, - -0.492, -0.484, -0.436, -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, - -0.248, -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, - -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, -0.088, - -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, -0.176, -0.176, - -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, -0.312, -0.376, -0.46, -0.436, - -0.348, -0.26, -0.268, -0.376, -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, - -0.456, -0.436, -0.448, -0.46 - ], - "y": [ - 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, 0.884, 1.036, - 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, 1.236, 1.188, 1.176, - 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, 1.036, 1.004, 1.004, 1, 0.992, - 0.98, 0.94, 0.92, 0.896, 0.884, 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, - 0.76, 0.728, 0.712, 0.724, 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, - 0.696, 0.732, 0.752, 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, - 0.932, 0.892, 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, - 0.932, 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, 0.928, - 0.9 - ], - "z": [ - -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, -0.868, -0.848, - -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, -0.504, -0.444, -0.416, -0.38, - -0.36, -0.316, -0.26, -0.2, -0.152, -0.136, -0.116, -0.116, -0.108, -0.044, - -0.004, 0.036, 0.072, 0.072, 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, - 0.096, 0.092, 0.068, -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, - -0.324, -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, -0.56, - -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, -0.628, -0.656, -0.672, - -0.524, -0.46, -0.468, -0.532, -0.596, -0.604, -0.6, -0.644, -0.66, -0.748, - -0.752, -0.76, -0.676, -0.608, -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, - -0.372, -0.38 - ] - } - }, - { - "ID": 1705437913803, - "data": { - "x": [ - 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, 0.86, - 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, 0.204, 0.044, - -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, -0.536, -0.488, -0.404, - -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, -0.548, -0.376, -0.444, -0.496, - -0.496, -0.428, -0.344, -0.724, -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, - 0.372, 0.34, 0.296, 0.22, 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, - 0.456, 0.5, 0.516, 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, - 0.344, 0.252, 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, - -0.16, -0.212, -0.316, -0.412, -0.42, -0.38, -0.296, -0.244 - ], - "y": [ - 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, -0.192, - -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, -0.044, -0.004, - 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, 0.36, 0.396, 0.424, 0.4, - 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, 0.332, 0.344, 0.244, 0.068, -0.096, - -0.092, -0.06, -0.02, 0.452, -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, - 0.06, 0.072, 0.072, 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, - 0.076, 0.04, 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, - 0.304, 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, - 0.536, 0.528, 0.552, 0.604, 0.648, 0.696, 0.64, 0.596 - ], - "z": [ - -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, -0.576, - -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, -0.28, -0.18, -0.248, - -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, -0.524, -0.58, -0.66, -0.78, - -0.808, -0.784, -0.732, -0.76, -0.744, -0.744, -0.8, -0.904, -1.032, -1.076, - -1.14, -1.3, -1.504, -1.62, -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, - -1.08, -1.064, -1.1, -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, - -1.332, -1.292, -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, - -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, -0.736, -0.712, - -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, -0.64, -0.692, -0.724, - -0.752, -0.768, -0.772, -0.792 - ] - } - }, - { - "ID": 1705437908508, - "data": { - "x": [ - -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, -1.108, - -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, -0.868, -0.908, - -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, -0.484, -0.424, -0.42, -0.436, - -0.408, -0.352, -0.272, -0.216, -0.208, -0.236, -0.188, -0.148, -0.104, - -0.084, -0.08, -0.076, -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, - -0.212, -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, -0.76, - -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, -0.972, -0.992, -1, - -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, -0.796, -0.692, -0.564, -0.404, - -0.388, -0.312, -0.32, -0.324, -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, - -0.508, -0.508, -0.508, -0.496 - ], - "y": [ - 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, 0.712, - 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, 0.96, 0.972, 0.96, - 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, 0.876, 0.832, 0.824, 0.8, 0.74, - 0.716, 0.692, 0.672, 0.628, 0.572, 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, - 0.54, 0.528, 0.54, 0.544, 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, - 0.472, 0.492, 0.52, 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, - 0.872, 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, 1, - 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, 0.776, 0.772, - 0.772, 0.776 - ], - "z": [ - -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, -0.036, - -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, -0.464, -0.508, - -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, -0.664, -0.72, -0.704, -0.744, - -0.784, -0.856, -0.884, -0.94, -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, - -0.744, -0.744, -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, - -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, -0.012, - 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, 0.148, 0.088, - 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, -0.592, -0.684, -0.652, - -0.912, -0.672, -0.604, -0.6, -0.584, -0.564, -0.504, -0.504, -0.496, -0.484, - -0.472, -0.46, -0.456, -0.448 - ] - } - }, - { - "ID": 1705437905957, - "data": { - "x": [ - -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, -0.496, - -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, -0.884, -0.936, - -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, -0.964, -0.872, -0.824, -0.796, - -0.832, -0.868, -0.804, -0.716, -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, - -0.516, -0.5, -0.476, -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, - -0.228, -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, -0.244, - -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, -0.928, -0.972, -1.004, - -0.976, -0.928, -0.92, -0.94, -0.952, -0.904, -0.848, -0.828, -0.856, -0.844, - -0.804, -0.78, -0.764, -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, - -0.64, -0.6, -0.588 - ], - "y": [ - 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, 0.92, 0.992, - 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, 0.856, 0.848, 0.812, - 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, 0.724, 0.708, 0.7, 0.692, - 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, - 0.492, 0.472, 0.508, 0.524, 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, - 0.808, 0.812, 0.848, 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, - 0.916, 0.86, 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, - 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, 0.716, - 0.724, 0.712 - ], - "z": [ - -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, -0.612, - -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, -0.356, -0.328, - -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, -0.204, -0.176, -0.148, - -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, 0.188, 0.216, 0.204, 0.164, 0.088, - 0.032, 0.044, 0.04, 0.068, 0.06, 0.016, -0.048, -0.124, -0.208, -0.296, - -0.404, -0.428, -0.4, -0.38, -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, - -0.8, -0.872, -0.88, -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, - -0.272, -0.28, -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, - -0.376, -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, - -0.38, -0.356, -0.32, -0.328 - ] - } - }, - { - "ID": 1705437896254, - "data": { - "x": [ - -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, -0.712, - -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, -0.808, -0.84, -0.944, - -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, -1.076, -1.048, -1.048, -1.112, - -1.144, -1.188, -1.208, -1.228, -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, - -1.02, -0.956, -0.912, -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, - -0.732, -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, - -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, -0.496, -0.48, - -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, -0.404, -0.46, -0.532, - -0.568, -0.484, -0.38, -0.444, -0.572, -0.728, -0.728, -0.616, -0.644, -0.656, - -0.704, -0.688, -0.644, -0.652 - ], - "y": [ - 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, 0.472, - 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, 0.244, 0.232, - 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, 0.056, 0.076, 0.104, - 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, 0.28, 0.308, 0.324, 0.348, 0.38, - 0.404, 0.408, 0.408, 0.384, 0.372, 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, - 0.38, 0.364, 0.412, 0.32, 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, - 0.372, 0.368, 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, - 0.468, 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, 0.32, - 0.32, 0.316 - ], - "z": [ - -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, -0.556, -0.524, - -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, -0.644, -0.72, -0.756, -0.744, - -0.724, -0.7, -0.684, -0.716, -0.716, -0.68, -0.668, -0.656, -0.708, -0.736, - -0.696, -0.712, -0.684, -0.7, -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, - -0.524, -0.496, -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, - -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, -0.392, -0.444, - -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, -0.716, -0.76, -0.76, - -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, -0.728, -0.66, -0.7, -0.756, - -0.86, -0.776, -0.76, -0.812, -0.876, -0.872, -0.908, -0.96, -0.976, -0.996, - -1.032, -0.976, -0.96 - ] - } - } - ], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - }, - { - "ID": 1705437793875, - "name": "Still", - "recordings": [ - { - "ID": 1705437992143, - "data": { - "x": [ - -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, -1.3, - -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, -2.04, -2.008, - -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, -0.232, -1.132, -2.04, - -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, 0.608, 0.332, -0.004, -0.876, - -2.04, -2.04, -2.04, -1.688, -1.396, -0.828, -0.032, 0.36, 0.492, 0.288, - 0.008, -0.492, -1.368, -2.04, -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, - 0.664, 0.544, 0.248, -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, - -0.612, 0.16, 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, - -1.668, -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, - -2.04, -2.04, -1.992 - ], - "y": [ - 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, 0.324, - 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, 0.328, 0.364, - 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, 1.076, -0.116, 0.048, - 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, 0.524, 0.416, 0.252, 0.308, - 0.376, 0.316, 0.356, 0.644, 0.672, 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, - 0.256, 0.272, 0.404, 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, - 0.284, 0.28, 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, - 0.452, 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, 0.288, - 0.204, 0.476, 1.112, 0.2 - ], - "z": [ - -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, -0.096, - -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, -0.108, 0.196, 0.268, - 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, -0.26, -0.092, -0.204, 0.304, 0.78, - 0.364, 0.108, -0.24, -0.528, -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, - 0.552, 0.42, 0.128, 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, - -0.004, -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, - -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, -0.288, - -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, 0.504, 0.196, - -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, -0.072, -0.176, 0.136, - 0.34, 0.7 - ] - } - }, - { - "ID": 1705437989960, - "data": { - "x": [ - -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, -1.496, - -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, 0.864, 0.256, - -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, 0.42, 0.608, 0.488, - 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, -1.588, -1.08, -0.512, -0.028, - 0.216, 0.268, 0.052, -0.38, -0.684, -0.788, -1.056, -1.768, -2.04, -2.04, - -1.344, -0.8, -0.436, -0.172, 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, - -1.544, -2.02, -2.016, -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, - -0.46, -0.816, -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, - 0.06, 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 - ], - "y": [ - 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, 0.456, 0.188, - 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, 0.32, 0.252, 0.208, 0.152, - 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, - 0.596, 0.644, 0.732, 0.58, 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, - 0.38, 0.284, 0.22, 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, - 0.348, 0.34, 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, - 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, 0.556, - 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, 0.328, 0.536, - 0.78, 0.7 - ], - "z": [ - -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, -0.596, -0.696, - -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, -0.888, -0.948, -0.972, -1.192, - -1.148, -0.808, -0.636, -0.42, 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, - -1.032, -0.972, -0.656, -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, - -0.364, -0.744, -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, - 1.116, 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, -0.7, - -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, -1.508, -1.86, - -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, 0.256, -0.24, -0.712, - -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, 0.18, 0.576, 1.292, 0.92 - ] - } - }, - { - "ID": 1705437987590, - "data": { - "x": [ - -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, 0.872, - 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, -0.312, -0.144, 0.076, - 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, 0.004, -1.244, -0.88, -0.372, -0.04, - 0.252, 0.616, 1.016, 1.332, 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, - -0.312, -0.052, 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, - -1.504, -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, - 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, 1.568, 1.692, - 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, 0.008, 0.68, 1.264, 1.68, - 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 - ], - "y": [ - 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, 0.068, - 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, 0.588, 0.452, - 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, 0.372, 0.344, 0.456, 0.368, - 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, 0.104, 0, 0.484, 0.416, 0.384, 0.356, - 0.32, 0.216, 0.244, 0.284, 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, - 0.336, 0.26, 0.224, 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, - 0.42, 0.396, 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, - 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, 0.044, 0.08, - 0.352, 0.632 - ], - "z": [ - -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, -0.384, - -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, -0.844, -0.48, - -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, -1.596, -1.952, -2.016, - -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, -0.06, -0.324, -0.676, -1.124, - -1.784, -2.04, -1.748, -1.408, -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, - -0.396, -0.836, -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, - 0.112, 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, - -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, -2.04, - -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, -0.472, -0.992, - -1.876, -2.04 - ] - } - }, - { - "ID": 1705437985014, - "data": { - "x": [ - -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, -1.04, -0.82, - -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, 1.188, 0.936, 0.456, 0.132, - -0.5, -1.94, -2.04, -2.04, -1.536, -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, - 0.812, 0.396, -0.2, -1.508, -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, - 0.776, 0.716, 0.46, -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, - 0.148, 0.316, 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, - -0.792, -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, - -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, 0.452, - 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, 0.076, 0.544, - 0.528 - ], - "y": [ - 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, 0.512, - 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, 0.164, 0.048, -0.22, - 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, 0.236, 0.132, 0.064, 0.124, 0.152, - 0.304, 0.248, 0.896, 1.34, 1.08, 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, - 0.236, 0.24, 0.66, 1.26, 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, - 0.124, 0.192, 0.244, 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, - 0.076, 0.092, 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, - 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, 0.672, - 0.496, 0.26, 0.232, 0.228 - ], - "z": [ - -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, -0.892, - -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, -1.144, -1.044, - -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, -0.476, -0.416, -0.764, - -0.892, -1.332, -1.872, -1.152, -0.744, -0.904, -0.704, -0.028, -0.296, - -0.256, -0.592, -0.848, -1, -1.404, -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, - -0.14, -0.3, -0.576, -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, - -0.492, -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, - -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, -1.328, - -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, -0.256, -0.28, -0.424, - -0.608, -0.652, -1.504, -1.412 - ] - } - }, - { - "ID": 1705437822437, - "data": { - "x": [ - -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, -0.896, -0.96, - -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, -0.204, -0.12, -0.164, - -0.248, -0.236, -0.088, -0.644, -1.068, -0.812, -0.644, -0.56, -0.372, -0.292, - -0.272, -0.076, -0.04, -0.08, -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, - -0.828, -0.72, -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, - -0.272, -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, -0.16, - -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, -0.872, -0.844, -0.72, - -0.624, -0.572, -0.48, -0.392, -0.356, -0.292, -0.244, -0.208, -0.288, -0.372, - -0.364, -0.216, -0.464, -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, - -0.468, -0.34, -0.272 - ], - "y": [ - 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, -1.216, - -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, 1.692, 1.224, - 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, -0.204, 0.568, 0.92, 1.304, - 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, -2.04, -2.04, -2.032, -1.288, -0.796, - 0.108, 1.112, 1.588, 1.832, 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, - -2.04, -1.68, -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, - -0.08, -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, - 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, -2.04, -2.004, - -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 - ], - "z": [ - -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, -0.764, - -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, -1.164, -1.144, - -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, -0.172, -0.32, -0.58, -0.672, - -0.952, -0.988, -1.092, -1.16, -1.104, -1.032, -0.924, -0.872, -0.736, -0.256, - 0.276, -0.392, -0.444, -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, - -0.884, -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, - -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, -0.372, - -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, -1.208, -0.992, -0.828, - -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, -0.084, -0.124, -0.208, -0.512, - -0.816, -1.18, -1.332 - ] - } - }, - { - "ID": 1705437819739, - "data": { - "x": [ - -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, -0.928, - -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, 0.14, -0.124, -0.264, - -0.324, -0.376, -0.636, -1.312, -1.216, -1.036, -0.744, -0.256, 0.188, 0.132, - 0.112, 0.164, 0.008, -0.132, -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, - -0.988, -0.648, -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, - -0.336, -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, -0.084, - 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, -0.468, -1.032, - -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, -0.132, -0.22, -0.088, -0.164, - -0.252, -0.32, -0.368, -0.432, -0.604, -0.876, -1.016, -1, -0.92, -0.848 - ], - "y": [ - -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, -1.256, - -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, 1.132, 0.848, - 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, -0.496, -0.092, 0.288, - 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, -1.436, -1.448, -1.124, -0.968, - -0.736, -0.62, 0.052, 0.148, 0.328, 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, - -0.828, -1.444, -1.42, -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, - 0.456, 0.816, 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, - -0.58, -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, - 0.036, -0.832, -1.244, -1.18, -0.9, -0.764, -0.8 - ], - "z": [ - -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, 1.76, 2.04, - 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, -2.04, -1.44, -1.12, -0.88, - -0.312, 0.328, 0.988, 1.404, 1.352, 0.476, -0.152, -1.276, -2.04, -2.04, - -2.04, -2.04, -1.668, -1.176, -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, - 0.636, -0.136, -1.344, -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, - -0.616, 0.18, 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, - -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, 1.32, 0.712, - 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, -1.692, -1.396, -1.04, - -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, 0.68 - ] - } - }, - { - "ID": 1705437816208, - "data": { - "x": [ - -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, 0.436, - 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, -1.112, -0.712, - -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, -0.164, -0.204, -0.144, - 0.252, -0.232, -1.312, -1.132, -0.92, -0.764, -0.432, -0.072, 0.096, 0.184, - 0.156, 0.052, -0.02, -0.068, 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, - -0.576, -0.328, -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, - -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, 0.052, 0.092, - 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, -1.376, -1.048, -0.872, - -0.776, -0.608, -0.324, -0.128, -0.004, 0.024, 0.024, -0.008, -0.12, -0.248, - -0.384, -0.356 - ], - "y": [ - -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, 1.008, - 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, -2.04, -2.04, -1.464, - -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, 1.88, 1.476, 0.836, -0.228, -2.04, - -2.04, -2.04, -2.024, -1.712, -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, - 1.084, 0.184, -2.04, -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, - 1.648, 1.532, 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, - -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, -0.748, - -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, 0.868, 1.392, - 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 - ], - "z": [ - -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, -0.072, - -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, 0.736, 0.544, 0.124, - -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, -1.136, -1, -0.92, -0.82, - -0.736, -0.34, 0.824, 0.26, -0.084, -0.072, -0.176, -0.42, -0.6, -0.852, - -1.132, -1.308, -1.16, -0.968, -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, - -0.364, -0.6, -0.932, -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, - 0.392, 0.376, -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, - -1.024, -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, - -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, -0.972, - -0.912, -0.944 - ] - } - }, - { - "ID": 1705437808407, - "data": { - "x": [ - -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, -0.464, -0.328, - -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, 0.116, 0.068, -0.084, -0.348, - -0.396, -0.628, -0.752, -0.744, -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, - -0.308, -0.324, -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, - -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, -0.94, -0.844, - -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, 0.284, 0.272, 0.288, 0.232, - 0.152, 0.004, -0.28, -0.408, -0.656, -0.836, -1.068, -1.036, -1.016, -0.952, - -0.844, -0.724, -0.68, -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, - 0.292, 0.42, 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 - ], - "y": [ - 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, 0.556, - 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, 0.144, 0.472, - 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, 0.524, 0.688, 0.664, - 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, 0.408, 0.18, 0.468, 0.744, - 0.724, 0.684, 0.88, 1.072, 1.108, 1, 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, - 0.5, 0.404, 0.364, 0.368, 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, - 0.908, 1.184, 1.192, 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, - 0.812, 0.78, 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, - 0.452, 0.732, 0.976 - ], - "z": [ - -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, -0.76, - -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, -0.492, -0.796, - -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, -1.488, -1.508, -1.12, - -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, -0.012, 0, 0.016, 0.028, -0.012, - -0.3, -0.692, -1.236, -1.444, -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, - -1.524, -1.288, -0.888, -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, - 0.144, 0.18, 0.116, -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, - -1.304, -1.312, -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, - -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, -1.076, -1.304 - ] - } - }, - { - "ID": 1705437804840, - "data": { - "x": [ - 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, -0.084, -0.284, - -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, 0.512, 0, -0.616, 0.156, - 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, 0.764, 0.416, 0.432, 0.444, 0.32, - 0.312, 0.348, 0.32, 0.472, 0.292, 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, - 0.708, 0.684, 0.516, 0.46, 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, - 0.032, 0.092, 0.136, 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, - 0.62, 0.572, 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, - 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, 0.72, 0.7, - 0.48, 0.304, 0.052 - ], - "y": [ - -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, 0.144, - 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, 1.212, 0.732, -1.24, - -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, -0.816, -0.044, 0.556, 1.332, - 1.496, 1.224, 1.184, 1.48, 1.644, 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, - -1.224, -1.252, -1.116, -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, - 1.844, 1.728, 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, - -0.976, -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, - 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, -0.936, - -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, 0.248 - ], - "z": [ - -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, -0.296, - -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, -0.284, -0.884, - -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, -0.732, -0.292, -0.34, - -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, -0.752, -0.66, -0.652, -0.716, - -0.632, -0.48, -0.452, -0.532, -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, - -1.24, -1, -0.872, -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, - -0.54, -0.552, -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, - -1.1, -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, - -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, -0.504, -0.652, - -0.704, -0.916 - ] - } - } - ], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - } -] diff --git a/src/__tests__/fixtures/gesture-data.json b/src/__tests__/fixtures/gesture-data.json deleted file mode 100644 index a8f9852be..000000000 --- a/src/__tests__/fixtures/gesture-data.json +++ /dev/null @@ -1,1024 +0,0 @@ -[ - { - "ID": 1705437793875, - "name": "Shake", - "recordings": [ - { - "ID": 1705437992143, - "data": { - "x": [ - -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, -1.3, - -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, -2.04, -2.008, - -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, -0.232, -1.132, -2.04, - -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, 0.608, 0.332, -0.004, -0.876, - -2.04, -2.04, -2.04, -1.688, -1.396, -0.828, -0.032, 0.36, 0.492, 0.288, - 0.008, -0.492, -1.368, -2.04, -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, - 0.664, 0.544, 0.248, -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, - -0.612, 0.16, 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, - -1.668, -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, - -2.04, -2.04, -1.992 - ], - "y": [ - 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, 0.324, - 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, 0.328, 0.364, - 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, 1.076, -0.116, 0.048, - 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, 0.524, 0.416, 0.252, 0.308, - 0.376, 0.316, 0.356, 0.644, 0.672, 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, - 0.256, 0.272, 0.404, 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, - 0.284, 0.28, 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, - 0.452, 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, 0.288, - 0.204, 0.476, 1.112, 0.2 - ], - "z": [ - -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, -0.096, - -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, -0.108, 0.196, 0.268, - 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, -0.26, -0.092, -0.204, 0.304, 0.78, - 0.364, 0.108, -0.24, -0.528, -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, - 0.552, 0.42, 0.128, 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, - -0.004, -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, - -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, -0.288, - -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, 0.504, 0.196, - -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, -0.072, -0.176, 0.136, - 0.34, 0.7 - ] - } - }, - { - "ID": 1705437989960, - "data": { - "x": [ - -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, -1.496, - -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, 0.864, 0.256, - -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, 0.42, 0.608, 0.488, - 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, -1.588, -1.08, -0.512, -0.028, - 0.216, 0.268, 0.052, -0.38, -0.684, -0.788, -1.056, -1.768, -2.04, -2.04, - -1.344, -0.8, -0.436, -0.172, 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, - -1.544, -2.02, -2.016, -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, - -0.46, -0.816, -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, - 0.06, 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 - ], - "y": [ - 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, 0.456, 0.188, - 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, 0.32, 0.252, 0.208, 0.152, - 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, - 0.596, 0.644, 0.732, 0.58, 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, - 0.38, 0.284, 0.22, 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, - 0.348, 0.34, 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, - 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, 0.556, - 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, 0.328, 0.536, - 0.78, 0.7 - ], - "z": [ - -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, -0.596, -0.696, - -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, -0.888, -0.948, -0.972, -1.192, - -1.148, -0.808, -0.636, -0.42, 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, - -1.032, -0.972, -0.656, -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, - -0.364, -0.744, -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, - 1.116, 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, -0.7, - -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, -1.508, -1.86, - -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, 0.256, -0.24, -0.712, - -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, 0.18, 0.576, 1.292, 0.92 - ] - } - }, - { - "ID": 1705437987590, - "data": { - "x": [ - -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, 0.872, - 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, -0.312, -0.144, 0.076, - 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, 0.004, -1.244, -0.88, -0.372, -0.04, - 0.252, 0.616, 1.016, 1.332, 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, - -0.312, -0.052, 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, - -1.504, -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, - 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, 1.568, 1.692, - 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, 0.008, 0.68, 1.264, 1.68, - 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 - ], - "y": [ - 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, 0.068, - 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, 0.588, 0.452, - 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, 0.372, 0.344, 0.456, 0.368, - 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, 0.104, 0, 0.484, 0.416, 0.384, 0.356, - 0.32, 0.216, 0.244, 0.284, 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, - 0.336, 0.26, 0.224, 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, - 0.42, 0.396, 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, - 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, 0.044, 0.08, - 0.352, 0.632 - ], - "z": [ - -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, -0.384, - -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, -0.844, -0.48, - -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, -1.596, -1.952, -2.016, - -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, -0.06, -0.324, -0.676, -1.124, - -1.784, -2.04, -1.748, -1.408, -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, - -0.396, -0.836, -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, - 0.112, 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, - -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, -2.04, - -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, -0.472, -0.992, - -1.876, -2.04 - ] - } - }, - { - "ID": 1705437985014, - "data": { - "x": [ - -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, -1.04, -0.82, - -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, 1.188, 0.936, 0.456, 0.132, - -0.5, -1.94, -2.04, -2.04, -1.536, -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, - 0.812, 0.396, -0.2, -1.508, -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, - 0.776, 0.716, 0.46, -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, - 0.148, 0.316, 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, - -0.792, -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, - -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, 0.452, - 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, 0.076, 0.544, - 0.528 - ], - "y": [ - 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, 0.512, - 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, 0.164, 0.048, -0.22, - 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, 0.236, 0.132, 0.064, 0.124, 0.152, - 0.304, 0.248, 0.896, 1.34, 1.08, 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, - 0.236, 0.24, 0.66, 1.26, 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, - 0.124, 0.192, 0.244, 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, - 0.076, 0.092, 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, - 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, 0.672, - 0.496, 0.26, 0.232, 0.228 - ], - "z": [ - -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, -0.892, - -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, -1.144, -1.044, - -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, -0.476, -0.416, -0.764, - -0.892, -1.332, -1.872, -1.152, -0.744, -0.904, -0.704, -0.028, -0.296, - -0.256, -0.592, -0.848, -1, -1.404, -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, - -0.14, -0.3, -0.576, -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, - -0.492, -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, - -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, -1.328, - -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, -0.256, -0.28, -0.424, - -0.608, -0.652, -1.504, -1.412 - ] - } - }, - { - "ID": 1705437822437, - "data": { - "x": [ - -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, -0.896, -0.96, - -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, -0.204, -0.12, -0.164, - -0.248, -0.236, -0.088, -0.644, -1.068, -0.812, -0.644, -0.56, -0.372, -0.292, - -0.272, -0.076, -0.04, -0.08, -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, - -0.828, -0.72, -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, - -0.272, -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, -0.16, - -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, -0.872, -0.844, -0.72, - -0.624, -0.572, -0.48, -0.392, -0.356, -0.292, -0.244, -0.208, -0.288, -0.372, - -0.364, -0.216, -0.464, -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, - -0.468, -0.34, -0.272 - ], - "y": [ - 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, -1.216, - -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, 1.692, 1.224, - 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, -0.204, 0.568, 0.92, 1.304, - 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, -2.04, -2.04, -2.032, -1.288, -0.796, - 0.108, 1.112, 1.588, 1.832, 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, - -2.04, -1.68, -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, - -0.08, -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, - 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, -2.04, -2.004, - -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 - ], - "z": [ - -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, -0.764, - -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, -1.164, -1.144, - -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, -0.172, -0.32, -0.58, -0.672, - -0.952, -0.988, -1.092, -1.16, -1.104, -1.032, -0.924, -0.872, -0.736, -0.256, - 0.276, -0.392, -0.444, -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, - -0.884, -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, - -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, -0.372, - -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, -1.208, -0.992, -0.828, - -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, -0.084, -0.124, -0.208, -0.512, - -0.816, -1.18, -1.332 - ] - } - }, - { - "ID": 1705437819739, - "data": { - "x": [ - -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, -0.928, - -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, 0.14, -0.124, -0.264, - -0.324, -0.376, -0.636, -1.312, -1.216, -1.036, -0.744, -0.256, 0.188, 0.132, - 0.112, 0.164, 0.008, -0.132, -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, - -0.988, -0.648, -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, - -0.336, -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, -0.084, - 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, -0.468, -1.032, - -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, -0.132, -0.22, -0.088, -0.164, - -0.252, -0.32, -0.368, -0.432, -0.604, -0.876, -1.016, -1, -0.92, -0.848 - ], - "y": [ - -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, -1.256, - -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, 1.132, 0.848, - 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, -0.496, -0.092, 0.288, - 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, -1.436, -1.448, -1.124, -0.968, - -0.736, -0.62, 0.052, 0.148, 0.328, 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, - -0.828, -1.444, -1.42, -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, - 0.456, 0.816, 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, - -0.58, -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, - 0.036, -0.832, -1.244, -1.18, -0.9, -0.764, -0.8 - ], - "z": [ - -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, 1.76, 2.04, - 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, -2.04, -1.44, -1.12, -0.88, - -0.312, 0.328, 0.988, 1.404, 1.352, 0.476, -0.152, -1.276, -2.04, -2.04, - -2.04, -2.04, -1.668, -1.176, -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, - 0.636, -0.136, -1.344, -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, - -0.616, 0.18, 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, - -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, 1.32, 0.712, - 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, -1.692, -1.396, -1.04, - -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, 0.68 - ] - } - }, - { - "ID": 1705437816208, - "data": { - "x": [ - -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, 0.436, - 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, -1.112, -0.712, - -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, -0.164, -0.204, -0.144, - 0.252, -0.232, -1.312, -1.132, -0.92, -0.764, -0.432, -0.072, 0.096, 0.184, - 0.156, 0.052, -0.02, -0.068, 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, - -0.576, -0.328, -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, - -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, 0.052, 0.092, - 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, -1.376, -1.048, -0.872, - -0.776, -0.608, -0.324, -0.128, -0.004, 0.024, 0.024, -0.008, -0.12, -0.248, - -0.384, -0.356 - ], - "y": [ - -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, 1.008, - 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, -2.04, -2.04, -1.464, - -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, 1.88, 1.476, 0.836, -0.228, -2.04, - -2.04, -2.04, -2.024, -1.712, -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, - 1.084, 0.184, -2.04, -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, - 1.648, 1.532, 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, - -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, -0.748, - -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, 0.868, 1.392, - 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 - ], - "z": [ - -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, -0.072, - -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, 0.736, 0.544, 0.124, - -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, -1.136, -1, -0.92, -0.82, - -0.736, -0.34, 0.824, 0.26, -0.084, -0.072, -0.176, -0.42, -0.6, -0.852, - -1.132, -1.308, -1.16, -0.968, -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, - -0.364, -0.6, -0.932, -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, - 0.392, 0.376, -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, - -1.024, -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, - -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, -0.972, - -0.912, -0.944 - ] - } - }, - { - "ID": 1705437808407, - "data": { - "x": [ - -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, -0.464, -0.328, - -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, 0.116, 0.068, -0.084, -0.348, - -0.396, -0.628, -0.752, -0.744, -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, - -0.308, -0.324, -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, - -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, -0.94, -0.844, - -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, 0.284, 0.272, 0.288, 0.232, - 0.152, 0.004, -0.28, -0.408, -0.656, -0.836, -1.068, -1.036, -1.016, -0.952, - -0.844, -0.724, -0.68, -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, - 0.292, 0.42, 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 - ], - "y": [ - 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, 0.556, - 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, 0.144, 0.472, - 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, 0.524, 0.688, 0.664, - 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, 0.408, 0.18, 0.468, 0.744, - 0.724, 0.684, 0.88, 1.072, 1.108, 1, 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, - 0.5, 0.404, 0.364, 0.368, 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, - 0.908, 1.184, 1.192, 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, - 0.812, 0.78, 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, - 0.452, 0.732, 0.976 - ], - "z": [ - -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, -0.76, - -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, -0.492, -0.796, - -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, -1.488, -1.508, -1.12, - -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, -0.012, 0, 0.016, 0.028, -0.012, - -0.3, -0.692, -1.236, -1.444, -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, - -1.524, -1.288, -0.888, -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, - 0.144, 0.18, 0.116, -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, - -1.304, -1.312, -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, - -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, -1.076, -1.304 - ] - } - }, - { - "ID": 1705437804840, - "data": { - "x": [ - 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, -0.084, -0.284, - -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, 0.512, 0, -0.616, 0.156, - 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, 0.764, 0.416, 0.432, 0.444, 0.32, - 0.312, 0.348, 0.32, 0.472, 0.292, 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, - 0.708, 0.684, 0.516, 0.46, 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, - 0.032, 0.092, 0.136, 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, - 0.62, 0.572, 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, - 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, 0.72, 0.7, - 0.48, 0.304, 0.052 - ], - "y": [ - -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, 0.144, - 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, 1.212, 0.732, -1.24, - -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, -0.816, -0.044, 0.556, 1.332, - 1.496, 1.224, 1.184, 1.48, 1.644, 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, - -1.224, -1.252, -1.116, -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, - 1.844, 1.728, 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, - -0.976, -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, - 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, -0.936, - -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, 0.248 - ], - "z": [ - -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, -0.296, - -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, -0.284, -0.884, - -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, -0.732, -0.292, -0.34, - -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, -0.752, -0.66, -0.652, -0.716, - -0.632, -0.48, -0.452, -0.532, -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, - -1.24, -1, -0.872, -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, - -0.54, -0.552, -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, - -1.1, -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, - -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, -0.504, -0.652, - -0.704, -0.916 - ] - } - } - ], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - }, - { - "ID": 1705437833024, - "name": "Still", - "recordings": [ - { - "ID": 1705437969952, - "data": { - "x": [ - -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, -0.06, - -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, -0.064, -0.06, - -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, -0.06, -0.06, -0.068, - -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, -0.056, -0.056, -0.056, -0.056, - -0.06, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, - -0.064, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, - -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, -0.052, - -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, -0.06, -0.056, - -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, -0.056, -0.068, -0.06, - -0.06, -0.052, -0.06 - ], - "y": [ - 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, 0.772, - 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, 0.772, 0.76, 0.764, - 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, 0.764, 0.76, 0.76, 0.764, - 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, - 0.76, 0.756, 0.756, 0.772, 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, - 0.756, 0.76, 0.764, 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, - 0.764, 0.764, 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, - 0.76, 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, 0.76, - 0.764, 0.752 - ], - "z": [ - -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, -0.692, - -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, -0.688, -0.692, -0.684, - -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, -0.7, -0.7, -0.708, -0.7, -0.696, - -0.692, -0.696, -0.7, -0.7, -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, - -0.7, -0.692, -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, - -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, -0.696, - -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, -0.708, -0.7, -0.712, - -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, -0.696, -0.704, -0.696, -0.708, - -0.7, -0.7, -0.7, -0.704, -0.7, -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, - -0.7 - ] - } - }, - { - "ID": 1705437870493, - "data": { - "x": [ - -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, -0.7, -0.648, - -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, -0.752, -0.704, -0.68, - -0.692, -0.708, -0.72, -0.756, -0.764, -0.768, -0.772, -0.736, -0.748, -0.74, - -0.772, -0.764, -0.752, -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, - -0.748, -0.748, -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, - -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, -0.748, - -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, -0.756, -0.744, -0.732, - -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, -0.772, -0.74, -0.732, -0.74, - -0.764, -0.776, -0.772, -0.764, -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, - -0.812, -0.808 - ], - "y": [ - -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, -0.548, -0.552, - -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, -0.484, -0.5, -0.504, -0.508, - -0.484, -0.492, -0.496, -0.484, -0.48, -0.476, -0.484, -0.492, -0.468, -0.484, - -0.476, -0.484, -0.492, -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, - -0.492, -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, -0.468, - -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, -0.48, -0.492, -0.476, - -0.472, -0.476, -0.484, -0.484, -0.484, -0.484, -0.492, -0.488, -0.456, -0.48, - -0.484, -0.472, -0.476, -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, - -0.472, -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, -0.472, - -0.428, -0.436 - ], - "z": [ - -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, -0.504, - -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, -0.584, -0.56, - -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, -0.576, -0.596, -0.572, - -0.604, -0.592, -0.568, -0.572, -0.56, -0.584, -0.584, -0.612, -0.572, -0.568, - -0.576, -0.588, -0.6, -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, - -0.588, -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, -0.592, - -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, -0.592, -0.624, -0.572, - -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, -0.58, -0.564, -0.552, -0.564, - -0.58, -0.592, -0.588, -0.584, -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, - -0.636, -0.608 - ] - } - }, - { - "ID": 1705437864426, - "data": { - "x": [ - 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, 0.876, - 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, 0.98, 0.952, - 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, 0.956, 0.936, 0.948, - 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, 0.948, 0.94, 0.928, 0.928, 0.94, - 0.96, 0.94, 0.94, 0.94, 0.936, 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, - 0.956, 0.96, 0.952, 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, - 0.944, 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, - 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, 0.968, - 0.968, 0.944 - ], - "y": [ - -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, -0.076, - -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, -0.072, -0.072, -0.08, - -0.076, -0.084, -0.104, -0.088, -0.104, -0.088, -0.116, -0.116, -0.132, - -0.116, -0.108, -0.104, -0.12, -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, - -0.112, -0.1, -0.1, -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, - -0.116, -0.108, -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, - -0.096, -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, - -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, -0.096, - -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, -0.08, -0.1, - -0.096, -0.092, -0.092, -0.112, -0.088 - ], - "z": [ - 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, -0.196, - -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, -0.248, -0.172, -0.14, - -0.136, -0.188, -0.204, -0.14, -0.116, -0.104, -0.18, -0.216, -0.192, -0.168, - -0.132, -0.16, -0.188, -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, - -0.176, -0.152, -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, - -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, -0.168, -0.16, - -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, -0.172, -0.168, -0.168, - -0.18, -0.176, -0.168, -0.16, -0.148, -0.148, -0.164, -0.184, -0.184, -0.176, - -0.144, -0.156, -0.176, -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, - -0.176, -0.14, -0.144 - ] - } - }, - { - "ID": 1705437860385, - "data": { - "x": [ - 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, -0.052, - -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, -0.04, -0.056, -0.052, - -0.052, -0.056, -0.056, -0.044, -0.044, -0.056, -0.052, -0.048, -0.048, - -0.048, -0.048, -0.048, -0.06, -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, - -0.056, -0.056, -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, - -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, -0.056, - -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, -0.052, -0.056, - -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, -0.052, -0.048, -0.056, - -0.056, -0.048, -0.052, -0.056, -0.048, -0.056, -0.048, -0.048, -0.052, - -0.052, -0.048, -0.052, -0.052, -0.044 - ], - "y": [ - 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, 0.224, - 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, 0.228, 0.224, 0.224, - 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, 0.228, 0.228, 0.228, 0.232, 0.236, - 0.232, 0.232, 0.228, 0.22, 0.232, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, - 0.224, 0.224, 0.232, 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, - 0.228, 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, 0.228, - 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, 0.228, 0.228, 0.236, - 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, 0.228, 0.228, 0.228, 0.228, 0.224, - 0.232, 0.232, 0.232 - ], - "z": [ - 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, 0.996, 1.012, - 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, 1.012, 1.012, 1.012, 1, - 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, 1.008, 1.008, 0.996, 0.996, 1.008, - 1.012, 1.008, 1.012, 1.008, 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, - 1.008, 1.004, 1.004, 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, - 1.004, 1, 1, 1, 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, - 1.004, 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, - 0.996, 1.004, 1.008, 1.012, 1.008, 1.004, 1.008, 1.008, 1.008 - ] - } - }, - { - "ID": 1705437854703, - "data": { - "x": [ - -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, -0.104, - -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, -0.064, -0.068, - -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, -0.072, -0.068, -0.072, - -0.068, -0.056, -0.064, -0.068, -0.068, -0.068, -0.068, -0.06, -0.072, -0.076, - -0.072, -0.068, -0.064, -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, - -0.068, -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, - -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, -0.076, -0.06, - -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, -0.064, -0.064, -0.072, -0.068, - -0.064, -0.064, -0.088, -0.06, -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, - -0.064, -0.064, -0.064, -0.068, -0.072, -0.068, -0.06 - ], - "y": [ - 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, 0.96, 0.96, - 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, 0.948, 0.952, 0.944, - 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, 0.944, 0.952, 0.952, 0.952, - 0.952, 0.952, 0.948, 0.948, 0.948, 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, - 0.948, 0.952, 0.948, 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, - 0.948, 0.944, 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, - 0.952, 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, - 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, 0.952, - 0.948, 0.948, 0.948, 0.948 - ], - "z": [ - -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, -0.396, -0.42, - -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, -0.44, -0.432, -0.428, - -0.428, -0.432, -0.428, -0.428, -0.432, -0.428, -0.424, -0.432, -0.436, - -0.428, -0.428, -0.432, -0.432, -0.428, -0.424, -0.436, -0.428, -0.432, - -0.432, -0.436, -0.424, -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, - -0.436, -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, - -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, -0.42, -0.428, - -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, -0.428, -0.436, -0.432, -0.436, - -0.44, -0.428, -0.432, -0.432, -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, - -0.424, -0.424, -0.432, -0.424, -0.432, -0.436 - ] - } - }, - { - "ID": 1705437847993, - "data": { - "x": [ - 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, 0.768, - 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, 0.76, 0.748, - 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, 0.764, 0.76, 0.748, - 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, 0.756, 0.748, 0.752, 0.744, - 0.744, 0.752, 0.756, 0.756, 0.752, 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, - 0.756, 0.752, 0.748, 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, - 0.756, 0.752, 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, - 0.736, 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, - 0.736, 0.74, 0.752, 0.74 - ], - "y": [ - 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, 0.248, - 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, 0.268, 0.268, 0.272, - 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, 0.264, 0.264, 0.268, 0.268, - 0.276, 0.276, 0.256, 0.256, 0.264, 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, - 0.276, 0.276, 0.284, 0.268, 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, - 0.276, 0.28, 0.272, 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, - 0.268, 0.272, 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, - 0.3, 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, - 0.284, 0.276, 0.272, 0.276 - ], - "z": [ - -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, -0.564, -0.552, - -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, -0.568, -0.572, -0.552, -0.548, - -0.54, -0.544, -0.556, -0.56, -0.56, -0.544, -0.536, -0.54, -0.532, -0.556, - -0.564, -0.572, -0.556, -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, - -0.552, -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, -0.536, - -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, -0.54, -0.568, -0.56, - -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, -0.544, -0.548, -0.548, -0.56, - -0.556, -0.544, -0.552, -0.56, -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, - -0.556, -0.56, -0.568, -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, - -0.564, -0.56 - ] - } - } - ], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - }, - { - "ID": 1705437876740, - "name": "Circle", - "recordings": [ - { - "ID": 1705438034019, - "data": { - "x": [ - -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, -0.536, -0.456, - -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, -0.916, -0.956, -1.032, -1.1, - -1.228, -1.284, -1.312, -1.36, -1.356, -1.296, -1.224, -1.096, -0.844, -0.964, - -0.948, -0.984, -0.896, -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, - -0.392, -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, - -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, -0.36, -0.476, - -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, -0.532, -0.532, -0.608, - -0.652, -0.668, -0.652, -0.764, -0.892, -0.92, -0.864, -0.848, -0.884, -0.872, - -0.848, -0.888, -0.952, -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, - -0.732, -0.84 - ], - "y": [ - 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, 0.392, 0.376, - 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, 0.2, 0.152, 0.128, 0.16, - 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, 0.284, 0.372, 0.284, 0.348, 0.412, - 0.408, 0.376, 0.36, 0.396, 0.452, 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, - 0.312, 0.34, 0.356, 0.332, 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, - 0.324, 0.308, 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, - 0.348, 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, - 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, 0.292, - 0.324 - ], - "z": [ - -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, -0.388, - -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, -0.204, -0.272, -0.308, - -0.308, -0.388, -0.424, -0.512, -0.592, -0.66, -0.696, -0.772, -0.812, -0.968, - -0.948, -1.02, -1.168, -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, - -1.12, -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, - -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, -0.556, -0.532, - -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, -0.392, -0.356, -0.364, - -0.388, -0.376, -0.388, -0.42, -0.408, -0.416, -0.376, -0.372, -0.368, -0.412, - -0.464, -0.492, -0.516, -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, - -0.976, -0.972 - ] - } - }, - { - "ID": 1705438029559, - "data": { - "x": [ - -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, -0.644, -0.536, - -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, -0.94, -0.912, -0.888, -0.816, - -0.836, -0.872, -0.844, -0.848, -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, - -0.664, -0.636, -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, - -0.364, -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, 0.068, - -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, -0.172, -0.264, - -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, -0.612, -0.552, -0.576, -0.676, - -0.804, -0.876, -0.864, -0.856, -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, - -0.856, -0.792, -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, - -0.668, -0.588, -0.508 - ], - "y": [ - 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, 0.132, - 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, 0.232, 0.256, - 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, 0.296, 0.292, 0.304, - 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, 0.368, 0.352, 0.356, 0.328, - 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, - 0.24, 0.248, 0.232, 0.196, 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, - 0.176, 0.152, 0.144, 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, - 0.252, 0.188, 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, - 0.276, 0.248, 0.236 - ], - "z": [ - -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, -1.064, - -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, -0.672, -0.712, - -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, -0.524, -0.484, -0.468, -0.516, - -0.54, -0.596, -0.624, -0.6, -0.596, -0.6, -0.624, -0.632, -0.656, -0.632, - -0.7, -0.744, -0.76, -0.768, -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, - -0.78, -1.112, -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, - -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, -1.064, -0.996, - -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, -0.796, -0.808, -0.812, -0.74, - -0.76, -0.72, -0.744, -0.728, -0.704, -0.692, -0.636, -0.644, -0.656, -0.676, - -0.616 - ] - } - }, - { - "ID": 1705438027130, - "data": { - "x": [ - -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, -0.532, - -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, -1.056, -0.968, -0.9, - -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, -0.8, -0.788, -0.748, -0.728, - -0.636, -0.56, -0.532, -0.504, -0.492, -0.432, -0.424, -0.464, -0.428, -0.408, - -0.336, -0.296, -0.252, -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, - -0.168, -0.136, -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, - -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, -0.628, -0.624, - -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, -0.84, -0.864, -0.888, -0.924, - -0.936, -0.88, -0.848, -0.888, -0.88, -0.832, -0.768, -0.724, -0.68, -0.664, - -0.664, -0.62, -0.572 - ], - "y": [ - 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, 0.164, - 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, 0.208, 0.212, - 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, 0.324, 0.316, 0.32, - 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, 0.276, 0.248, 0.232, 0.252, - 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, 0.176, 0.156, 0.136, 0.16, 0.18, - 0.208, 0.224, 0.204, 0.18, 0.188, 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, - 0.104, 0.112, 0.084, 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, - 0.1, 0.14, 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, - 0.268, 0.276, 0.284, 0.296 - ], - "z": [ - -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, -1.176, - -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, -0.964, -0.904, -0.824, - -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, -0.568, -0.56, -0.528, -0.468, - -0.492, -0.488, -0.48, -0.492, -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, - -0.704, -0.732, -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, - -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, -1.208, -1.208, - -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, -1.076, -1.072, -1.028, - -1.028, -0.984, -0.944, -0.968, -0.96, -0.964, -0.964, -0.952, -0.904, -0.856, - -0.808, -0.788, -0.756, -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, - -0.604, -0.592, -0.588 - ] - } - }, - { - "ID": 1705437979405, - "data": { - "x": [ - 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, -0.204, - -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, -0.136, -0.28, -0.392, - -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, -0.464, -0.516, -0.616, -0.648, - -0.6, -0.52, -0.48, -0.468, -0.468, -0.46, -0.432, -0.404, -0.392, -0.304, - -0.256, -0.208, -0.204, -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, - -0.792, -0.776, -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, - -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, -0.104, - 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, 0.092, 0.04, 0, - -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, 0.312, 0.188, 0.036, -0.06, - -0.156, -0.248 - ], - "y": [ - 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, 0.364, - 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, 0.392, 0.364, - 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, 0.312, 0.296, 0.312, - 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, 0.376, 0.316, 0.292, 0.308, - 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, 0.708, 0.728, 0.74, 0.748, 0.716, - 0.68, 0.624, 0.592, 0.564, 0.536, 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, - 0.416, 0.408, 0.42, 0.408, 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, - 0.448, 0.44, 0.464, 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, - 0.348, 0.368, 0.412, 0.484, 0.5 - ], - "z": [ - -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, -0.652, - -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, -1.364, -1.312, - -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, -1.004, -0.996, -1.036, - -1.056, -1.032, -0.996, -0.932, -0.868, -0.864, -0.824, -0.808, -0.764, - -0.708, -0.652, -0.624, -0.632, -0.644, -0.628, -0.612, -0.616, -0.692, - -0.788, -0.884, -0.932, -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, - -0.784, -0.74, -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, - -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, -0.92, - -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, -1.092, -1.132, - -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 - ] - } - }, - { - "ID": 1705437977128, - "data": { - "x": [ - -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, 0.16, - 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, -0.42, -0.596, - -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, -0.8, -0.868, -0.968, - -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, -0.456, -0.396, -0.436, -0.476, - -0.416, -0.364, -0.296, -0.292, -0.304, -0.332, -0.324, -0.272, -0.208, - -0.156, -0.112, -0.08, -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, - 0.168, 0.176, 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, - 0.156, 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, -0.404, - -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, -0.58, -0.52, -0.428 - ], - "y": [ - 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, 0.316, 0.5, - 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, 0.52, 0.44, 0.44, 0.464, - 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, 0.604, 0.584, 0.62, 0.608, 0.58, - 0.56, 0.564, 0.576, 0.592, 0.58, 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, - 0.456, 0.44, 0.4, 0.376, 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, - 0.38, 0.392, 0.436, 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, - 0.244, 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, - 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, 0.428 - ], - "z": [ - -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, -0.816, - -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, -1.18, -1.196, -1.236, - -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, -1.04, -0.964, -0.888, -0.856, - -0.772, -0.696, -0.62, -0.564, -0.6, -0.592, -0.536, -0.48, -0.432, -0.448, - -0.48, -0.484, -0.516, -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, - -0.62, -0.668, -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, - -0.84, -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, - -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, -1.152, - -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, -1.044, -1, -1, -1.012 - ] - } - }, - { - "ID": 1705437922352, - "data": { - "x": [ - -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, 0.064, 0.064, - -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, -0.688, -0.728, -0.772, - -0.784, -0.768, -0.732, -0.696, -0.648, -0.576, -0.544, -0.52, -0.504, -0.492, - -0.492, -0.484, -0.436, -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, - -0.248, -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, - -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, -0.088, - -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, -0.176, -0.176, - -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, -0.312, -0.376, -0.46, -0.436, - -0.348, -0.26, -0.268, -0.376, -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, - -0.456, -0.436, -0.448, -0.46 - ], - "y": [ - 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, 0.884, 1.036, - 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, 1.236, 1.188, 1.176, - 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, 1.036, 1.004, 1.004, 1, 0.992, - 0.98, 0.94, 0.92, 0.896, 0.884, 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, - 0.76, 0.728, 0.712, 0.724, 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, - 0.696, 0.732, 0.752, 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, - 0.932, 0.892, 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, - 0.932, 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, 0.928, - 0.9 - ], - "z": [ - -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, -0.868, -0.848, - -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, -0.504, -0.444, -0.416, -0.38, - -0.36, -0.316, -0.26, -0.2, -0.152, -0.136, -0.116, -0.116, -0.108, -0.044, - -0.004, 0.036, 0.072, 0.072, 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, - 0.096, 0.092, 0.068, -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, - -0.324, -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, -0.56, - -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, -0.628, -0.656, -0.672, - -0.524, -0.46, -0.468, -0.532, -0.596, -0.604, -0.6, -0.644, -0.66, -0.748, - -0.752, -0.76, -0.676, -0.608, -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, - -0.372, -0.38 - ] - } - }, - { - "ID": 1705437913803, - "data": { - "x": [ - 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, 0.86, - 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, 0.204, 0.044, - -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, -0.536, -0.488, -0.404, - -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, -0.548, -0.376, -0.444, -0.496, - -0.496, -0.428, -0.344, -0.724, -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, - 0.372, 0.34, 0.296, 0.22, 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, - 0.456, 0.5, 0.516, 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, - 0.344, 0.252, 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, - -0.16, -0.212, -0.316, -0.412, -0.42, -0.38, -0.296, -0.244 - ], - "y": [ - 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, -0.192, - -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, -0.044, -0.004, - 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, 0.36, 0.396, 0.424, 0.4, - 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, 0.332, 0.344, 0.244, 0.068, -0.096, - -0.092, -0.06, -0.02, 0.452, -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, - 0.06, 0.072, 0.072, 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, - 0.076, 0.04, 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, - 0.304, 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, - 0.536, 0.528, 0.552, 0.604, 0.648, 0.696, 0.64, 0.596 - ], - "z": [ - -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, -0.576, - -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, -0.28, -0.18, -0.248, - -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, -0.524, -0.58, -0.66, -0.78, - -0.808, -0.784, -0.732, -0.76, -0.744, -0.744, -0.8, -0.904, -1.032, -1.076, - -1.14, -1.3, -1.504, -1.62, -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, - -1.08, -1.064, -1.1, -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, - -1.332, -1.292, -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, - -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, -0.736, -0.712, - -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, -0.64, -0.692, -0.724, - -0.752, -0.768, -0.772, -0.792 - ] - } - }, - { - "ID": 1705437908508, - "data": { - "x": [ - -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, -1.108, - -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, -0.868, -0.908, - -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, -0.484, -0.424, -0.42, -0.436, - -0.408, -0.352, -0.272, -0.216, -0.208, -0.236, -0.188, -0.148, -0.104, - -0.084, -0.08, -0.076, -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, - -0.212, -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, -0.76, - -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, -0.972, -0.992, -1, - -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, -0.796, -0.692, -0.564, -0.404, - -0.388, -0.312, -0.32, -0.324, -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, - -0.508, -0.508, -0.508, -0.496 - ], - "y": [ - 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, 0.712, - 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, 0.96, 0.972, 0.96, - 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, 0.876, 0.832, 0.824, 0.8, 0.74, - 0.716, 0.692, 0.672, 0.628, 0.572, 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, - 0.54, 0.528, 0.54, 0.544, 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, - 0.472, 0.492, 0.52, 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, - 0.872, 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, 1, - 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, 0.776, 0.772, - 0.772, 0.776 - ], - "z": [ - -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, -0.036, - -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, -0.464, -0.508, - -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, -0.664, -0.72, -0.704, -0.744, - -0.784, -0.856, -0.884, -0.94, -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, - -0.744, -0.744, -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, - -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, -0.012, - 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, 0.148, 0.088, - 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, -0.592, -0.684, -0.652, - -0.912, -0.672, -0.604, -0.6, -0.584, -0.564, -0.504, -0.504, -0.496, -0.484, - -0.472, -0.46, -0.456, -0.448 - ] - } - }, - { - "ID": 1705437905957, - "data": { - "x": [ - -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, -0.496, - -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, -0.884, -0.936, - -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, -0.964, -0.872, -0.824, -0.796, - -0.832, -0.868, -0.804, -0.716, -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, - -0.516, -0.5, -0.476, -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, - -0.228, -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, -0.244, - -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, -0.928, -0.972, -1.004, - -0.976, -0.928, -0.92, -0.94, -0.952, -0.904, -0.848, -0.828, -0.856, -0.844, - -0.804, -0.78, -0.764, -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, - -0.64, -0.6, -0.588 - ], - "y": [ - 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, 0.92, 0.992, - 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, 0.856, 0.848, 0.812, - 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, 0.724, 0.708, 0.7, 0.692, - 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, - 0.492, 0.472, 0.508, 0.524, 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, - 0.808, 0.812, 0.848, 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, - 0.916, 0.86, 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, - 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, 0.716, - 0.724, 0.712 - ], - "z": [ - -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, -0.612, - -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, -0.356, -0.328, - -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, -0.204, -0.176, -0.148, - -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, 0.188, 0.216, 0.204, 0.164, 0.088, - 0.032, 0.044, 0.04, 0.068, 0.06, 0.016, -0.048, -0.124, -0.208, -0.296, - -0.404, -0.428, -0.4, -0.38, -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, - -0.8, -0.872, -0.88, -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, - -0.272, -0.28, -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, - -0.376, -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, - -0.38, -0.356, -0.32, -0.328 - ] - } - }, - { - "ID": 1705437896254, - "data": { - "x": [ - -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, -0.712, - -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, -0.808, -0.84, -0.944, - -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, -1.076, -1.048, -1.048, -1.112, - -1.144, -1.188, -1.208, -1.228, -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, - -1.02, -0.956, -0.912, -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, - -0.732, -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, - -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, -0.496, -0.48, - -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, -0.404, -0.46, -0.532, - -0.568, -0.484, -0.38, -0.444, -0.572, -0.728, -0.728, -0.616, -0.644, -0.656, - -0.704, -0.688, -0.644, -0.652 - ], - "y": [ - 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, 0.472, - 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, 0.244, 0.232, - 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, 0.056, 0.076, 0.104, - 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, 0.28, 0.308, 0.324, 0.348, 0.38, - 0.404, 0.408, 0.408, 0.384, 0.372, 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, - 0.38, 0.364, 0.412, 0.32, 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, - 0.372, 0.368, 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, - 0.468, 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, 0.32, - 0.32, 0.316 - ], - "z": [ - -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, -0.556, -0.524, - -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, -0.644, -0.72, -0.756, -0.744, - -0.724, -0.7, -0.684, -0.716, -0.716, -0.68, -0.668, -0.656, -0.708, -0.736, - -0.696, -0.712, -0.684, -0.7, -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, - -0.524, -0.496, -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, - -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, -0.392, -0.444, - -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, -0.716, -0.76, -0.76, - -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, -0.728, -0.66, -0.7, -0.756, - -0.86, -0.776, -0.76, -0.812, -0.876, -0.872, -0.908, -0.96, -0.976, -0.996, - -1.032, -0.976, -0.96 - ] - } - } - ], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - } -] diff --git a/src/__tests__/fixtures/test-data-shake-still.json b/src/__tests__/fixtures/test-data-shake-still.json deleted file mode 100644 index 23acfa6c3..000000000 --- a/src/__tests__/fixtures/test-data-shake-still.json +++ /dev/null @@ -1,573 +0,0 @@ -[ - { - "ID": 1705437793875, - "name": "Shake", - "recordings": [ - { - "ID": 1718706806260, - "data": { - "x": [ - 2.04, 2.04, 2.008, 1.516, 1.152, 0.612, 0.056, -0.528, -1.224, -1.716, -1.632, - -1.072, -0.512, 0.408, 1.4, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.68, 1.304, - 0.836, 0.248, -0.412, -1.152, -1.776, -1.868, -1.384, -0.744, 0.232, 1.376, - 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.584, 1.156, 0.656, 0.096, -0.6, -1.352, - -1.792, -1.644, -0.996, -0.316, 0.612, 1.648, 2.04, 2.04, 2.04, 2.04, 2.04, - 2.04, 1.5, 1.096, 0.6, -0.012, -0.656, -1.272, -1.596, -1.408, -0.872, -0.22, - 0.544, 1.488, 2.036, 2.04, 2.04, 2.04, 2.04, 2.04, 1.948, 1.42, 0.928, 0.428, - -0.216, -0.788, -1.272, -1.604, -1.396, -0.856, -0.268, 0.544, 1.488, 2.04, - 2.04, 2.04 - ], - "y": [ - 1.236, 0.976, 0.7, 0.58, 0.496, 0.316, 0.176, 0.004, -0.152, -0.248, -0.312, - -0.372, -0.356, -0.024, 0.372, 0.816, 1.24, 1.416, 1.416, 1.216, 0.932, 0.688, - 0.552, 0.388, 0.208, -0.028, -0.188, -0.292, -0.388, -0.464, -0.504, -0.268, - 0.248, 0.78, 1.28, 1.52, 1.492, 1.26, 0.924, 0.628, 0.48, 0.332, 0.164, - -0.032, -0.172, -0.288, -0.428, -0.416, -0.34, -0.036, 0.5, 0.996, 1.296, - 1.52, 1.42, 1.168, 0.804, 0.6, 0.484, 0.344, 0.164, -0.028, -0.168, -0.264, - -0.304, -0.3, -0.18, 0.084, 0.512, 0.832, 1.08, 1.228, 1.3, 1.268, 1.064, - 0.764, 0.62, 0.452, 0.312, 0.12, -0.088, -0.184, -0.324, -0.404, -0.392, - -0.308, -0.024, 0.408, 0.844, 1.252, 1.536 - ], - "z": [ - 2.04, 2.024, 1.264, 0.56, 0.044, -0.34, -0.664, -1.004, -1.416, -1.656, - -1.488, -0.916, -0.496, -0.2, 0.276, 0.928, 1.888, 2.04, 2.04, 2.04, 1.448, - 0.712, 0.192, -0.2, -0.576, -0.888, -1.384, -1.7, -1.568, -1.144, -0.636, - -0.208, 0.288, 0.952, 1.824, 2.04, 2.04, 2.04, 1.416, 0.68, 0.112, -0.328, - -0.676, -1, -1.424, -1.568, -1.324, -0.852, -0.404, 0.104, 0.492, 1.204, - 1.948, 2.04, 2.04, 1.98, 1.352, 0.632, 0.124, -0.352, -0.744, -0.988, -1.328, - -1.368, -1.192, -0.812, -0.388, -0.004, 0.368, 0.892, 1.448, 1.944, 2.04, - 2.036, 1.696, 1.1, 0.38, -0.032, -0.4, -0.732, -0.844, -1.196, -1.332, -1.12, - -0.764, -0.428, -0.068, 0.4, 0.708, 1.248, 1.852 - ] - } - }, - { - "ID": 1718706797873, - "data": { - "x": [ - -0.732, -0.276, 0.012, 0.188, 0.368, 0.704, 1.132, 1.32, 1.36, 1.184, 0.884, - 0.628, 0.396, 0.156, -0.036, -0.388, -0.748, -1.092, -0.984, -0.592, -0.212, - 0.056, 0.348, 0.612, 0.892, 1.228, 1.304, 1.272, 1.04, 0.792, 0.628, 0.452, - 0.168, -0.14, -0.5, -0.9, -1.008, -0.736, -0.308, 0.064, 0.284, 0.532, 0.84, - 1.216, 1.42, 1.384, 1.148, 0.928, 0.736, 0.536, 0.328, 0.092, -0.156, -0.504, - -0.872, -1.112, -0.776, -0.324, 0.036, 0.292, 0.472, 0.772, 1.248, 1.452, - 1.488, 1.3, 0.996, 0.736, 0.492, 0.268, 0.032, -0.268, -0.672, -1.088, -1.1, - -0.7, -0.228, 0.116, 0.44, 0.644, 0.928, 1.196, 1.392, 1.328, 1.088, 0.808, - 0.584, 0.368, 0.144, -0.108, -0.48 - ], - "y": [ - -1.64, -0.936, -0.288, 0.332, 1.16, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, - 1.276, 0.536, -0.084, -0.608, -1.136, -1.7, -1.756, -1.372, -0.9, -0.376, - 0.52, 1.464, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.84, 1.208, 0.46, -0.22, - -0.972, -1.644, -1.968, -1.796, -1.296, -0.66, 0.092, 1.008, 2.04, 2.04, 2.04, - 2.04, 2.04, 2.04, 2.036, 1.344, 0.704, 0.104, -0.516, -1.112, -1.672, -2.036, - -1.672, -1.064, -0.416, 0.46, 1.224, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2, - 1.332, 0.704, 0.156, -0.512, -1.228, -1.816, -1.864, -1.424, -0.768, -0.08, - 0.832, 1.788, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.7, 1.14, 0.524, -0.06, - -0.744 - ], - "z": [ - -0.536, -0.372, -0.476, -0.588, -0.716, -0.54, -0.004, 0.384, 0.452, 0.28, - -0.072, -0.48, -0.68, -0.752, -0.636, -0.74, -0.876, -0.952, -0.732, -0.408, - -0.192, -0.224, -0.34, -0.292, -0.12, 0.292, 0.46, 0.464, 0.156, -0.1, -0.376, - -0.652, -0.844, -0.932, -0.868, -0.864, -0.696, -0.424, -0.152, -0.156, -0.42, - -0.512, -0.488, -0.04, 0.272, 0.384, 0.16, -0.076, -0.376, -0.604, -0.668, - -0.616, -0.508, -0.54, -0.688, -0.748, -0.548, -0.404, -0.392, -0.56, -0.616, - -0.372, 0.196, 0.584, 0.764, 0.624, 0.16, -0.24, -0.54, -0.696, -0.78, -0.756, - -0.792, -0.904, -0.8, -0.5, -0.264, -0.352, -0.348, -0.312, 0.024, 0.396, - 0.696, 0.736, 0.476, 0.136, -0.236, -0.516, -0.62, -0.66, -0.684 - ] - } - }, - { - "ID": 1718706789113, - "data": { - "x": [ - 2.04, 2.04, 2.04, 2.04, 1.856, 1.276, 0.684, 0.096, -0.62, -1.172, -1.54, - -1.592, -1.34, -0.828, -0.212, 0.588, 1.496, 2.04, 2.04, 2.04, 2.04, 2.04, - 2.04, 2.04, 1.504, 0.88, 0.268, -0.368, -0.896, -1.464, -1.768, -1.608, - -1.104, -0.452, 0.248, 1.216, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.66, - 1.088, 0.532, -0.084, -0.672, -1.288, -1.596, -1.6, -1.316, -0.76, 0.004, - 0.784, 1.784, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.872, 1.344, 0.716, 0.168, - -0.436, -1.032, -1.548, -1.752, -1.528, -1.028, -0.1, 1.092, 2.04, 2.04, 2.04, - 2.04, 2.04, 2.04, 2.04, 1.616, 1.088, 0.408, -0.196, -0.88, -1.46, -1.552, - -1.448, -0.96 - ], - "y": [ - -0.248, -0.212, -0.224, -0.232, -0.228, -0.196, -0.132, -0.08, 0.036, 0.232, - 0.392, 0.332, 0.136, -0.052, -0.176, -0.232, -0.284, -0.268, -0.196, -0.208, - -0.192, -0.228, -0.276, -0.26, -0.232, -0.16, -0.1, -0.008, 0.184, 0.376, - 0.452, 0.26, -0.016, -0.26, -0.364, -0.356, -0.276, -0.244, -0.172, -0.108, - -0.144, -0.184, -0.232, -0.216, -0.156, -0.092, -0.032, 0.052, 0.232, 0.368, - 0.316, 0.124, -0.124, -0.272, -0.336, -0.368, -0.376, -0.396, -0.344, -0.328, - -0.336, -0.312, -0.264, -0.244, -0.176, -0.124, -0.076, 0.112, 0.404, 0.464, - 0.256, -0.044, -0.3, -0.504, -0.516, -0.52, -0.484, -0.448, -0.42, -0.368, - -0.312, -0.244, -0.184, -0.12, 0.028, 0.228, 0.44, 0.396, 0.216, -0.076 - ], - "z": [ - 1.396, 1.324, 1.044, 0.564, 0.164, -0.164, -0.396, -0.564, -0.652, -0.892, - -1.004, -0.796, -0.512, -0.328, -0.28, -0.176, 0.064, 0.496, 0.88, 1.22, - 1.208, 1.08, 0.8, 0.316, -0.084, -0.404, -0.544, -0.616, -0.844, -0.996, - -0.96, -0.632, -0.28, -0.096, 0.076, 0.12, 0.264, 0.576, 0.952, 1.128, 1.192, - 1.008, 0.608, 0.148, -0.164, -0.42, -0.572, -0.684, -0.852, -0.956, -0.76, - -0.596, -0.336, -0.22, -0.08, 0.104, 0.536, 1, 1.124, 1.12, 1.028, 0.716, - 0.288, -0.02, -0.288, -0.468, -0.68, -0.94, -1.148, -1.116, -0.692, -0.292, - -0.072, -0.052, 0.04, 0.336, 0.8, 1.06, 1.036, 0.748, 0.368, -0.02, -0.308, - -0.496, -0.672, -0.804, -0.984, -0.864, -0.544, -0.2 - ] - } - }, - { - "ID": 1718706779890, - "data": { - "x": [ - -1.548, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.484, -0.74, - -0.032, 0.68, 1.348, 1.836, 2.04, 1.992, 1.532, 0.892, 0.192, -0.668, -1.624, - -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.516, -0.944, -0.368, - 0.308, 0.94, 1.572, 2.04, 2.04, 1.688, 1.068, 0.444, -0.396, -1.288, -2.04, - -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.748, -1.164, -0.664, -0.16, - 0.368, 0.788, 1.16, 1.5, 1.608, 1.38, 0.828, 0.272, -0.352, -1.188, -2.04, - -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.684, -1.044, -0.56, 0.024, 0.768, - 1.476, 1.768, 1.728, 1.38, 0.768, 0.336, -0.392, -1.576, -2.04, -2.04, -2.04, - -2.04, -2.04, -2.04, -2.04, -1.476, -0.944 - ], - "y": [ - 0.588, 0.692, 0.696, 0.708, 0.804, 0.704, 0.58, 0.496, 0.36, 0.248, 0.096, - -0.12, -0.384, -0.58, -0.732, -0.7, -0.464, -0.124, 0.144, 0.32, 0.4, 0.536, - 0.652, 0.632, 0.628, 0.58, 0.508, 0.404, 0.3, 0.224, 0.108, -0.088, -0.232, - -0.436, -0.72, -0.72, -0.532, -0.236, 0.008, 0.272, 0.412, 0.396, 0.416, - 0.516, 0.54, 0.484, 0.404, 0.232, 0.104, 0.048, -0.024, -0.108, -0.216, - -0.264, -0.348, -0.472, -0.556, -0.496, -0.308, -0.168, -0.048, 0.056, 0.236, - 0.204, 0.292, 0.388, 0.356, 0.324, 0.176, 0.076, 0.012, 0.008, -0.032, -0.196, - -0.444, -0.572, -0.544, -0.408, -0.156, -0.044, 0.1, 0.352, 0.212, 0.1, 0.028, - 0.036, 0.092, 0.108, 0.012, -0.072, -0.112 - ], - "z": [ - -0.832, -0.824, -0.684, -0.528, -0.164, 0.232, 0.268, -0.156, -0.62, -0.94, - -0.916, -0.732, -0.428, -0.128, 0.06, 0.26, 0.38, 0.248, -0.052, -0.464, - -0.716, -0.932, -0.736, -0.372, 0.008, 0.252, 0.224, -0.156, -0.484, -0.712, - -0.764, -0.632, -0.392, -0.088, 0.016, 0.104, 0.208, 0.28, 0.14, -0.324, - -0.66, -0.78, -0.572, -0.18, 0.004, 0.04, 0.02, -0.1, -0.344, -0.54, -0.668, - -0.676, -0.508, -0.292, -0.084, 0.052, 0.024, 0.128, 0.148, 0.2, -0.116, - -0.52, -0.62, -0.408, 0.024, 0.328, 0.388, 0.232, -0.116, -0.444, -0.564, - -0.648, -0.664, -0.592, -0.464, -0.2, 0.148, 0.492, 0.664, 0.528, 0.188, - -0.332, -0.632, -0.64, -0.468, -0.252, -0.02, 0.132, 0.028, -0.18, -0.432 - ] - } - }, - { - "ID": 1718706773634, - "data": { - "x": [ - -0.892, -0.768, -0.66, -0.468, -0.236, -0.028, 0.168, 0.56, 1.188, 1.368, - 0.772, 0.044, -0.28, -0.448, -0.552, -0.8, -1.128, -1.216, -1.164, -0.964, - -0.672, -0.404, -0.156, 0.028, 0.276, 0.704, 1.336, 1.444, 0.784, 0.096, - -0.236, -0.408, -0.624, -1.048, -1.364, -1.356, -1.224, -1.016, -0.736, - -0.424, -0.188, 0.032, 0.216, 0.448, 0.804, 1.252, 1.068, 0.544, 0.1, -0.128, - -0.324, -0.712, -1.136, -1.492, -1.496, -1.224, -0.792, -0.436, -0.2, 0.048, - 0.268, 0.66, 1.092, 1.38, 1.036, 0.352, -0.088, -0.32, -0.516, -0.876, -1.232, - -1.28, -1.232, -1.016, -0.732, -0.432, -0.212, 0.036, 0.296, 0.788, 1.2, - 1.192, 0.668, 0.068, -0.224, -0.464, -0.712, -0.916, -1.096, -1.132 - ], - "y": [ - -2.04, -2.04, -2.04, -1.98, -1.152, -0.24, 0.632, 1.668, 2.04, 2.04, 2.04, - 1.408, 0.456, -0.696, -1.936, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, - -1.556, -0.8, -0.112, 0.664, 1.672, 2.04, 2.04, 2.04, 1.104, 0.228, -0.928, - -2.032, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.704, -1.04, -0.416, - 0.284, 1.172, 1.904, 2.04, 2.04, 1.616, 0.724, -0.028, -1.208, -2.04, -2.04, - -2.04, -2.04, -2.04, -2.04, -1.924, -1.132, -0.32, 0.432, 1.292, 2.04, 2.04, - 2.04, 1.516, 0.7, -0.328, -1.54, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, - -1.92, -1.044, -0.168, 0.744, 1.812, 2.04, 2.04, 2.024, 1.124, 0.34, -0.54, - -1.588, -2.04, -2.04, -2.04 - ], - "z": [ - 0.532, 0.264, -0.124, -0.568, -0.896, -0.932, -0.804, -0.712, -0.792, -0.724, - -0.376, -0.108, -0.356, -0.676, -0.76, -0.472, 0.228, 0.66, 0.732, 0.388, - -0.172, -0.628, -0.816, -0.8, -0.712, -0.732, -0.928, -0.856, -0.484, -0.288, - -0.428, -0.728, -0.696, -0.26, 0.312, 0.732, 0.744, 0.42, -0.092, -0.468, - -0.852, -0.928, -0.884, -0.712, -0.64, -0.588, -0.38, -0.18, -0.188, -0.476, - -0.748, -0.56, -0.016, 0.66, 1.004, 0.716, 0.012, -0.556, -0.824, -0.936, - -0.852, -0.768, -0.744, -0.676, -0.432, -0.096, -0.208, -0.584, -0.872, - -0.624, -0.108, 0.268, 0.432, 0.232, -0.232, -0.668, -0.916, -0.996, -0.944, - -0.828, -0.596, -0.304, 0.024, 0.108, -0.204, -0.544, -0.704, -0.704, -0.5, - -0.284 - ] - } - }, - { - "ID": 1718706765639, - "data": { - "x": [ - -0.228, -0.092, -0.24, -0.212, -0.136, -0.076, -0.08, -0.212, -0.316, -0.444, - -0.696, -0.808, -0.86, -0.704, -0.46, -0.368, -0.268, -0.216, -0.16, -0.108, - -0.136, -0.192, -0.272, -0.24, -0.212, -0.108, -0.108, -0.324, -0.548, -0.764, - -0.916, -0.948, -0.836, -0.648, -0.516, -0.46, -0.38, -0.308, -0.296, -0.252, - -0.172, -0.144, -0.072, 0.008, 0.052, 0.036, -0.096, -0.304, -0.596, -0.748, - -0.96, -0.884, -0.656, -0.42, -0.32, -0.308, -0.296, -0.208, -0.212, -0.196, - -0.156, -0.184, -0.232, -0.276, -0.304, -0.288, -0.412, -0.444, -0.424, - -0.548, -0.488, -0.436, -0.304, -0.252, -0.328, -0.312, -0.352, -0.312, -0.28, - -0.312, -0.268, -0.336, -0.292, -0.244, -0.188, -0.24, -0.32, -0.636, -0.564, - -0.672, -0.52 - ], - "y": [ - 0.156, 0.344, 0.556, 0.664, 0.652, 0.508, 0.236, -0.128, -0.548, -1.116, - -1.524, -1.8, -1.712, -1.388, -1.22, -1.036, -0.796, -0.548, -0.284, 0.016, - 0.416, 0.708, 0.864, 0.816, 0.612, 0.304, -0.028, -0.492, -1.044, -1.504, - -1.704, -1.764, -1.592, -1.328, -1.112, -0.9, -0.684, -0.488, -0.284, -0.04, - 0.284, 0.604, 0.804, 0.796, 0.588, 0.28, -0.128, -0.704, -1.212, -1.608, - -1.796, -1.776, -1.536, -1.248, -1.012, -0.748, -0.52, -0.324, -0.072, 0.232, - 0.472, 0.6, 0.672, 0.596, 0.42, 0.132, -0.296, -0.904, -1.492, -1.872, -2.008, - -1.804, -1.52, -1.284, -1.06, -0.864, -0.644, -0.448, -0.148, 0.208, 0.504, - 0.716, 0.788, 0.72, 0.46, 0.124, -0.38, -0.9, -1.348, -1.612, -1.752 - ], - "z": [ - 0.804, 1.304, 1.548, 1.548, 1.308, 0.928, 0.388, -0.352, -1.12, -1.984, -2.04, - -2.04, -2.04, -2.04, -2.04, -2.008, -1.324, -0.656, -0.016, 0.58, 1.032, 1.44, - 1.764, 1.648, 1.28, 0.764, 0.08, -0.788, -1.712, -2.04, -2.04, -2.04, -2.04, - -2.04, -2.04, -1.836, -1.172, -0.472, 0.104, 0.592, 1.116, 1.676, 1.86, 1.72, - 1.248, 0.568, -0.144, -1.124, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, - -1.976, -1.42, -0.844, -0.188, 0.432, 0.872, 1.264, 1.5, 1.46, 1.112, 0.66, - 0.164, -0.528, -1.348, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.852, - -1.204, -0.708, -0.148, 0.456, 1.052, 1.464, 1.624, 1.476, 1.176, 0.716, 0.12, - -0.54, -1.46, -2.04, -2.04, -2.04 - ] - } - }, - { - "ID": 1718706760274, - "data": { - "x": [ - -0.2, -0.024, 0.12, 0.308, 0.468, 0.468, 0.292, 0.004, -0.26, -0.404, -0.484, - -0.6, -0.68, -0.712, -0.712, -0.632, -0.524, -0.408, -0.18, 0.004, 0.176, - 0.304, 0.416, 0.404, 0.28, 0.092, -0.14, -0.296, -0.444, -0.596, -0.668, -0.7, - -0.652, -0.592, -0.52, -0.448, -0.256, -0.092, 0.056, 0.212, 0.432, 0.592, - 0.432, 0.084, -0.2, -0.376, -0.504, -0.628, -0.772, -0.752, -0.76, -0.676, - -0.612, -0.492, -0.3, -0.104, 0.084, 0.264, 0.464, 0.524, 0.336, 0.024, - -0.236, -0.372, -0.528, -0.636, -0.74, -0.784, -0.788, -0.672, -0.524, -0.476, - -0.276, -0.156, 0.036, 0.24, 0.392, 0.476, 0.276, 0.016, -0.236, -0.364, - -0.476, -0.624, -0.752, -0.812, -0.84, -0.72, -0.636, -0.524, -0.252 - ], - "y": [ - -0.528, -0.732, -0.76, -0.728, -0.64, -0.552, -0.476, -0.412, -0.332, -0.276, - -0.26, -0.136, 0.188, 0.536, 0.688, 0.544, 0.192, -0.172, -0.476, -0.664, - -0.712, -0.6, -0.492, -0.364, -0.32, -0.276, -0.232, -0.192, -0.16, -0.012, - 0.18, 0.444, 0.564, 0.516, 0.276, -0.068, -0.372, -0.516, -0.54, -0.512, - -0.48, -0.508, -0.444, -0.36, -0.284, -0.284, -0.28, -0.176, 0.052, 0.344, - 0.544, 0.444, 0.184, -0.16, -0.388, -0.516, -0.596, -0.552, -0.444, -0.42, - -0.316, -0.228, -0.188, -0.228, -0.172, -0.064, 0.076, 0.316, 0.536, 0.496, - 0.236, -0.092, -0.376, -0.516, -0.564, -0.524, -0.456, -0.488, -0.396, -0.328, - -0.344, -0.396, -0.332, -0.164, 0.08, 0.312, 0.44, 0.296, 0.004, -0.304, - -0.524 - ], - "z": [ - 1.668, 0.78, -0.188, -1.188, -1.884, -2.04, -1.928, -1.268, -0.472, 0.396, - 1.224, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.5, 0.64, -0.212, -0.976, - -1.52, -1.804, -1.704, -1.184, -0.496, 0.236, 1.08, 2.008, 2.04, 2.04, 2.04, - 2.04, 2.04, 2.04, 1.644, 0.88, 0.088, -0.884, -1.732, -2.04, -1.964, -1.328, - -0.496, 0.332, 1.204, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.472, 0.708, - -0.176, -0.996, -1.64, -1.944, -1.676, -1.064, -0.396, 0.332, 1.18, 2.04, - 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.556, 0.744, -0.108, -0.96, -1.608, - -1.956, -1.732, -1.116, -0.348, 0.552, 1.556, 2.04, 2.04, 2.04, 2.04, 2.04, - 2.04, 1.9, 1.18 - ] - } - } - ], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - }, - { - "ID": 1705437833024, - "name": "Still", - "recordings": [ - { - "ID": 1718706751151, - "data": { - "x": [ - 0.444, 0.416, 0.412, 0.416, 0.44, 0.428, 0.44, 0.404, 0.432, 0.432, 0.428, - 0.424, 0.416, 0.412, 0.416, 0.424, 0.436, 0.424, 0.412, 0.42, 0.42, 0.428, - 0.428, 0.416, 0.412, 0.456, 0.424, 0.412, 0.404, 0.412, 0.416, 0.428, 0.436, - 0.412, 0.412, 0.396, 0.42, 0.436, 0.42, 0.416, 0.408, 0.4, 0.388, 0.404, - 0.428, 0.408, 0.404, 0.408, 0.42, 0.44, 0.42, 0.4, 0.396, 0.432, 0.424, 0.396, - 0.4, 0.392, 0.392, 0.424, 0.416, 0.408, 0.412, 0.412, 0.412, 0.408, 0.404, - 0.42, 0.424, 0.404, 0.4, 0.416, 0.412, 0.408, 0.388, 0.396, 0.408, 0.392, - 0.396, 0.404, 0.416, 0.396, 0.408, 0.404, 0.42, 0.408, 0.396, 0.38, 0.392, - 0.4, 0.42 - ], - "y": [ - -0.828, -0.82, -0.82, -0.808, -0.804, -0.804, -0.804, -0.828, -0.816, -0.808, - -0.816, -0.808, -0.816, -0.82, -0.816, -0.82, -0.816, -0.82, -0.816, -0.816, - -0.808, -0.808, -0.824, -0.816, -0.824, -0.864, -0.808, -0.812, -0.824, - -0.824, -0.824, -0.812, -0.812, -0.82, -0.824, -0.836, -0.84, -0.816, -0.812, - -0.816, -0.812, -0.804, -0.804, -0.804, -0.836, -0.824, -0.824, -0.828, - -0.824, -0.828, -0.816, -0.816, -0.828, -0.824, -0.816, -0.804, -0.8, -0.804, - -0.812, -0.816, -0.82, -0.816, -0.824, -0.824, -0.824, -0.82, -0.824, -0.82, - -0.828, -0.824, -0.828, -0.836, -0.816, -0.836, -0.812, -0.816, -0.82, -0.816, - -0.812, -0.816, -0.816, -0.828, -0.828, -0.824, -0.824, -0.816, -0.816, - -0.812, -0.82, -0.812, -0.824 - ], - "z": [ - 0.384, 0.384, 0.384, 0.38, 0.392, 0.376, 0.376, 0.372, 0.368, 0.384, 0.384, - 0.392, 0.392, 0.384, 0.376, 0.372, 0.388, 0.4, 0.388, 0.376, 0.38, 0.384, - 0.392, 0.384, 0.376, 0.36, 0.408, 0.408, 0.38, 0.368, 0.364, 0.388, 0.4, - 0.392, 0.4, 0.38, 0.376, 0.392, 0.404, 0.4, 0.4, 0.372, 0.372, 0.38, 0.388, - 0.372, 0.376, 0.38, 0.392, 0.404, 0.404, 0.4, 0.376, 0.384, 0.4, 0.38, 0.388, - 0.372, 0.38, 0.4, 0.396, 0.4, 0.392, 0.396, 0.392, 0.376, 0.384, 0.404, 0.408, - 0.392, 0.384, 0.388, 0.388, 0.404, 0.396, 0.408, 0.396, 0.388, 0.388, 0.408, - 0.404, 0.4, 0.392, 0.396, 0.392, 0.408, 0.4, 0.392, 0.396, 0.392, 0.408 - ] - } - }, - { - "ID": 1718706742225, - "data": { - "x": [ - -1.024, -1.004, -0.976, -0.98, -1.024, -1.04, -1.02, -0.992, -0.98, -1.008, - -1.032, -1.024, -1.012, -1.004, -1.008, -1.016, -1.04, -1.012, -0.984, -0.988, - -1.008, -1.004, -0.996, -1.012, -0.988, -0.976, -0.996, -1.004, -1.016, - -0.984, -0.972, -0.984, -1.008, -1.032, -1.036, -1, -0.992, -1.032, -1.036, - -1.036, -0.996, -0.984, -0.996, -1.008, -1.032, -1.024, -0.984, -0.984, - -1.008, -1.024, -1.036, -1.012, -0.992, -1.024, -1.008, -0.976, -1.016, - -0.984, -0.984, -1.012, -1.036, -1.016, -1.016, -1, -1.016, -1.036, -1.004, - -0.996, -0.988, -1.004, -1.016, -1.012, -1.008, -0.984, -1, -1.016, -1.032, - -0.992, -0.984, -1.012, -1.024, -1.036, -1.004, -0.98, -0.992, -1.032, -1.016, - -0.996, -0.988, -1.016, -1.036 - ], - "y": [ - 0.028, 0.02, 0.008, 0, 0.016, 0.02, 0.032, 0.02, 0.016, 0.016, 0.032, 0.04, - 0.056, 0.056, 0.036, 0.024, 0.012, 0.004, 0.004, 0.004, 0.008, 0.012, 0.016, - 0.02, 0.02, 0.02, 0.02, 0.016, 0.008, 0.008, 0, 0, 0.016, 0.036, 0.032, 0.024, - 0.02, 0.028, 0.028, 0.028, 0.012, 0.02, 0.032, 0.012, 0.008, 0.012, 0.016, - 0.004, 0.012, 0.032, 0.028, 0.032, 0.008, 0.008, 0.012, 0.004, -0.004, 0.012, - 0.008, 0.012, 0.02, 0.028, 0.016, 0.02, 0.004, 0.02, 0.036, 0.048, 0.036, - 0.012, 0.024, 0.032, 0.028, 0.012, 0, 0.004, 0.012, 0.008, 0, 0.012, 0.02, - 0.036, 0.028, 0.016, 0.024, 0.02, 0.012, 0.02, 0.02, 0.032, 0.024 - ], - "z": [ - -0.22, -0.188, -0.172, -0.172, -0.192, -0.216, -0.204, -0.2, -0.188, -0.176, - -0.172, -0.2, -0.192, -0.164, -0.156, -0.184, -0.236, -0.232, -0.224, -0.208, - -0.2, -0.184, -0.196, -0.208, -0.192, -0.196, -0.196, -0.212, -0.204, -0.196, - -0.192, -0.204, -0.208, -0.196, -0.196, -0.164, -0.14, -0.16, -0.176, -0.204, - -0.196, -0.168, -0.144, -0.156, -0.22, -0.212, -0.184, -0.168, -0.204, -0.204, - -0.2, -0.184, -0.164, -0.196, -0.184, -0.172, -0.192, -0.176, -0.152, -0.16, - -0.204, -0.192, -0.204, -0.18, -0.188, -0.208, -0.204, -0.172, -0.144, -0.16, - -0.172, -0.184, -0.18, -0.18, -0.2, -0.224, -0.22, -0.18, -0.16, -0.176, - -0.188, -0.192, -0.188, -0.168, -0.14, -0.164, -0.188, -0.188, -0.18, -0.188, - -0.184 - ] - } - }, - { - "ID": 1718706735565, - "data": { - "x": [ - -0.06, -0.084, -0.088, -0.096, -0.104, -0.088, -0.076, -0.092, -0.084, -0.08, - -0.076, -0.08, -0.08, -0.084, -0.084, -0.068, -0.068, -0.084, -0.088, -0.08, - -0.08, -0.092, -0.088, -0.092, -0.088, -0.092, -0.096, -0.088, -0.088, -0.092, - -0.076, -0.068, -0.072, -0.076, -0.076, -0.072, -0.068, -0.096, -0.096, -0.08, - -0.084, -0.064, -0.068, -0.084, -0.076, -0.08, -0.064, -0.068, -0.084, -0.08, - -0.06, -0.056, -0.056, -0.06, -0.08, -0.068, -0.076, -0.092, -0.084, -0.076, - -0.068, -0.072, -0.088, -0.1, -0.1, -0.084, -0.08, -0.068, -0.08, -0.084, - -0.072, -0.06, -0.06, -0.068, -0.072, -0.08, -0.092, -0.092, -0.084, -0.084, - -0.076, -0.076, -0.08, -0.08, -0.076, -0.08, -0.096, -0.108, -0.096, -0.084 - ], - "y": [ - 1.012, 1.02, 1.02, 1.004, 1, 1.004, 1, 1.016, 1.02, 1.016, 1.02, 1.032, 1.048, - 1.032, 1.036, 1.008, 1.008, 1.02, 1.024, 1.02, 1.008, 1.016, 1.012, 1.012, - 1.016, 1.008, 1.012, 1.012, 1.016, 1.004, 1.004, 1.008, 1.016, 1.04, 1.02, - 1.004, 1.008, 1.056, 1.028, 1.024, 1.028, 1.012, 1.02, 1.02, 1.016, 1.008, - 0.992, 1.008, 1.02, 1.028, 1.016, 1.012, 1.02, 1.024, 1.024, 1.004, 1.012, - 1.028, 1.016, 1.008, 1.012, 1.016, 1.028, 1.024, 1.024, 0.996, 0.996, 1.02, - 1.02, 1.012, 1.004, 1.02, 1.024, 1.02, 1.012, 1.016, 1.02, 1.016, 1.028, - 1.012, 1.004, 1.012, 1.024, 1.016, 1.008, 1, 1.016, 1.028, 1.024, 1.008 - ], - "z": [ - 0.076, 0.076, 0.084, 0.084, 0.076, 0.088, 0.1, 0.064, 0.064, 0.064, 0.08, - 0.096, 0.1, 0.088, 0.072, 0.08, 0.088, 0.072, 0.076, 0.084, 0.092, 0.1, 0.076, - 0.072, 0.08, 0.088, 0.08, 0.068, 0.056, 0.068, 0.08, 0.084, 0.092, 0.088, - 0.088, 0.084, 0.084, 0.06, 0.084, 0.092, 0.108, 0.1, 0.084, 0.052, 0.04, - 0.056, 0.056, 0.072, 0.08, 0.096, 0.1, 0.092, 0.096, 0.088, 0.084, 0.1, 0.096, - 0.08, 0.08, 0.072, 0.076, 0.096, 0.108, 0.092, 0.08, 0.076, 0.092, 0.096, - 0.076, 0.056, 0.072, 0.088, 0.084, 0.1, 0.084, 0.088, 0.08, 0.1, 0.096, 0.088, - 0.088, 0.096, 0.1, 0.096, 0.08, 0.08, 0.088, 0.096, 0.076, 0.064 - ] - } - }, - { - "ID": 1718706729969, - "data": { - "x": [ - 0.992, 0.996, 0.988, 0.992, 1, 0.996, 0.956, 0.96, 0.972, 1, 0.996, 0.972, - 0.984, 1.004, 1.012, 1.02, 1, 0.984, 0.988, 0.98, 0.984, 0.972, 0.988, 0.968, - 1.028, 1.004, 1.028, 1, 0.98, 0.972, 1.012, 1.008, 0.992, 0.976, 0.992, 0.992, - 1.008, 0.988, 0.972, 0.972, 0.988, 1.008, 1, 0.956, 0.984, 1, 1, 0.996, 0.996, - 0.992, 0.996, 0.992, 0.984, 0.98, 0.988, 0.98, 1.004, 0.996, 0.964, 0.956, - 1.008, 1.016, 0.98, 0.984, 1, 1.004, 1.008, 0.984, 0.988, 0.984, 0.984, 1.016, - 0.984, 0.984, 0.984, 1, 0.992, 0.992, 0.976, 0.972, 1.016, 1, 0.992, 0.968, - 0.976, 1.012, 1.012, 0.988, 0.968, 0.968 - ], - "y": [ - 0.012, 0.02, 0.024, 0.024, 0.02, 0.032, 0.04, 0.044, 0.04, 0.004, 0.024, - 0.008, 0.004, -0.004, 0.004, 0, 0, 0, 0.008, 0.02, 0.024, 0.004, 0.004, 0.008, - 0.048, 0.004, 0, -0.008, 0, 0.004, -0.008, 0.004, -0.004, 0.012, 0, 0, 0, - -0.008, -0.004, 0.016, 0.004, -0.004, 0, 0, 0.004, 0.012, 0.008, -0.004, 0, 0, - 0.008, -0.008, -0.008, 0.004, 0.02, 0.008, -0.008, -0.008, -0.012, 0.004, - -0.004, -0.016, -0.012, -0.004, -0.004, -0.012, -0.016, -0.004, 0.004, 0.008, - 0, -0.02, -0.032, -0.016, -0.012, -0.008, -0.004, 0, -0.02, -0.008, -0.008, - -0.008, -0.008, -0.012, 0, -0.016, -0.008, -0.012, -0.008, 0 - ], - "z": [ - -0.16, -0.14, -0.152, -0.152, -0.156, -0.16, -0.172, -0.168, -0.16, -0.168, - -0.16, -0.164, -0.152, -0.148, -0.14, -0.14, -0.152, -0.164, -0.168, -0.164, - -0.164, -0.172, -0.16, -0.168, -0.152, -0.128, -0.14, -0.144, -0.148, -0.168, - -0.156, -0.152, -0.14, -0.164, -0.16, -0.152, -0.136, -0.156, -0.164, -0.148, - -0.144, -0.136, -0.132, -0.164, -0.168, -0.152, -0.152, -0.152, -0.144, - -0.148, -0.148, -0.16, -0.168, -0.164, -0.136, -0.144, -0.14, -0.148, -0.156, - -0.176, -0.156, -0.144, -0.156, -0.14, -0.148, -0.164, -0.148, -0.144, -0.136, - -0.14, -0.16, -0.136, -0.16, -0.152, -0.152, -0.148, -0.136, -0.144, -0.16, - -0.168, -0.144, -0.132, -0.144, -0.156, -0.168, -0.152, -0.14, -0.144, -0.16, - -0.156 - ] - } - }, - { - "ID": 1718706724105, - "data": { - "x": [ - -0.036, -0.056, -0.044, -0.028, -0.028, -0.044, -0.06, -0.036, -0.024, -0.02, - -0.056, -0.06, -0.032, -0.032, -0.032, -0.044, -0.04, -0.032, -0.032, -0.052, - -0.052, -0.036, -0.024, -0.028, -0.048, -0.072, -0.056, -0.056, -0.024, - -0.016, -0.052, -0.072, -0.032, 0.088, -0.036, -0.056, -0.048, -0.036, -0.028, - -0.036, -0.036, -0.04, -0.032, -0.032, -0.04, -0.04, -0.044, -0.052, -0.024, - -0.044, -0.048, -0.056, -0.04, -0.032, -0.032, -0.04, -0.04, -0.052, -0.044, - -0.028, -0.036, -0.048, -0.04, -0.052, -0.052, -0.032, -0.036, -0.052, -0.052, - -0.048, -0.052, -0.036, -0.036, -0.032, -0.044, -0.036, -0.06, -0.052, -0.04, - -0.04, -0.036, -0.028, -0.052, -0.048, -0.04, -0.04, -0.048, -0.076, -0.072, - -0.044 - ], - "y": [ - -0.984, -0.976, -0.98, -0.98, -0.984, -0.98, -0.992, -0.996, -0.992, -0.996, - -0.996, -1, -1.008, -0.992, -0.992, -1, -1, -0.988, -0.98, -0.964, -0.972, - -0.992, -1, -1, -0.996, -0.984, -0.98, -0.992, -1.008, -1.024, -1.016, -0.992, - -0.992, -1.072, -0.972, -0.988, -0.992, -0.988, -0.984, -0.984, -0.992, - -1.004, -0.992, -0.992, -0.996, -0.992, -0.984, -0.984, -0.988, -0.992, - -1.008, -0.996, -1, -1, -0.992, -0.988, -0.996, -0.996, -0.988, -0.984, - -0.992, -0.996, -0.996, -0.984, -0.98, -0.992, -0.988, -0.98, -0.988, -0.996, - -0.996, -0.996, -0.988, -0.992, -0.992, -0.98, -0.98, -0.984, -0.996, -1.004, - -1, -0.996, -0.996, -0.988, -0.984, -0.984, -0.988, -0.984, -0.988, -0.976 - ], - "z": [ - -0.228, -0.228, -0.216, -0.192, -0.184, -0.204, -0.216, -0.212, -0.188, - -0.184, -0.2, -0.216, -0.22, -0.204, -0.216, -0.212, -0.228, -0.224, -0.2, - -0.208, -0.208, -0.2, -0.2, -0.2, -0.204, -0.208, -0.204, -0.204, -0.196, - -0.196, -0.2, -0.236, -0.22, -0.22, -0.188, -0.208, -0.216, -0.22, -0.2, - -0.204, -0.224, -0.232, -0.212, -0.2, -0.208, -0.216, -0.208, -0.212, -0.212, - -0.22, -0.212, -0.228, -0.204, -0.216, -0.208, -0.212, -0.216, -0.228, -0.228, - -0.22, -0.2, -0.216, -0.232, -0.212, -0.208, -0.22, -0.208, -0.196, -0.208, - -0.22, -0.22, -0.216, -0.22, -0.212, -0.212, -0.2, -0.212, -0.228, -0.232, - -0.216, -0.212, -0.208, -0.208, -0.216, -0.204, -0.196, -0.204, -0.228, - -0.228, -0.212 - ] - } - }, - { - "ID": 1718706718209, - "data": { - "x": [ - -0.028, -0.044, -0.068, -0.068, -0.056, -0.028, -0.036, -0.064, -0.064, - -0.036, -0.04, -0.06, -0.06, -0.052, -0.032, -0.04, -0.048, -0.076, -0.064, - -0.036, -0.032, -0.048, -0.044, -0.052, -0.04, -0.032, -0.052, -0.064, -0.052, - -0.04, -0.044, -0.056, -0.052, -0.04, -0.048, -0.064, -0.048, -0.052, -0.04, - -0.056, -0.048, -0.056, -0.048, -0.08, -0.04, 0.04, -0.036, -0.036, -0.044, - -0.044, -0.04, -0.044, -0.048, -0.044, -0.048, -0.036, -0.044, -0.052, -0.044, - -0.044, -0.032, -0.048, -0.06, -0.044, -0.036, -0.048, -0.06, -0.056, -0.052, - -0.04, -0.04, -0.048, -0.056, -0.064, -0.06, -0.044, -0.048, -0.076, -0.052, - -0.044, -0.048, -0.056, -0.064, -0.06, -0.06, -0.06, -0.068, -0.06, -0.048, - -0.044 - ], - "y": [ - -0.024, -0.028, -0.024, -0.024, -0.016, -0.024, -0.028, -0.032, -0.02, -0.028, - -0.032, -0.024, -0.024, -0.016, -0.02, -0.02, -0.024, -0.016, -0.016, -0.012, - -0.032, -0.036, -0.036, -0.032, -0.008, -0.016, -0.024, -0.02, -0.02, -0.028, - -0.02, -0.02, -0.028, -0.02, -0.032, -0.028, -0.032, -0.028, -0.028, -0.02, - -0.016, -0.024, -0.024, -0.028, -0.028, -0.072, -0.044, -0.04, -0.04, -0.024, - -0.024, -0.024, -0.044, -0.024, -0.02, -0.028, -0.04, -0.04, -0.044, -0.028, - -0.028, -0.032, -0.04, -0.04, -0.02, -0.016, -0.024, -0.036, -0.032, -0.036, - -0.036, -0.04, -0.04, -0.032, -0.028, -0.028, -0.04, -0.032, -0.04, -0.036, - -0.036, -0.04, -0.032, -0.028, -0.036, -0.032, -0.032, -0.032, -0.036, -0.048 - ], - "z": [ - 1.016, 1.016, 1.008, 1.012, 1, 1.028, 1.028, 1.02, 1.008, 1.008, 1.024, 1.024, - 1.016, 1.016, 1.028, 1.016, 1.02, 1.02, 0.98, 1.028, 1.04, 1.02, 1.032, 1.012, - 1.02, 1.016, 1.012, 1.016, 1.044, 1.016, 1.02, 0.972, 1.004, 1.036, 1.048, - 1.004, 1.008, 1.004, 1, 0.988, 1.012, 1.032, 1.016, 1.032, 1.008, 0.92, 1.036, - 1.016, 1.032, 1.024, 1.036, 1.012, 1.008, 1.024, 1.012, 1.024, 1.012, 1, 1, - 1.008, 1.056, 1.032, 0.988, 1.008, 1.032, 1.02, 1.024, 1.004, 1.004, 1.012, - 1.044, 1.028, 1.016, 1.036, 1.02, 1.024, 1.028, 1.008, 1.02, 1.004, 1.008, - 1.012, 1.004, 1.012, 1.032, 1.036, 1.036, 1.02, 1.02, 1.02 - ] - } - }, - { - "ID": 1718706711804, - "data": { - "x": [ - 0.004, -0.004, 0, 0, 0, 0.024, 0.012, 0, 0.004, -0.008, 0.004, 0.024, 0.012, - 0.004, 0, 0.004, 0.008, -0.008, 0.004, -0.012, 0.016, 0.02, 0.012, 0.004, 0, - 0.008, 0.024, 0.02, 0.012, 0, 0.004, 0.004, 0.004, 0.008, 0.008, 0.004, 0.004, - 0, 0, 0.016, 0.016, 0.012, 0.008, -0.004, -0.004, 0.016, 0.004, 0.008, -0.008, - 0, 0.012, 0.012, 0, 0, 0.004, 0.008, 0.004, 0.008, 0.008, 0.028, 0.012, - -0.004, -0.02, -0.004, 0, -0.004, -0.008, -0.004, 0.004, 0, 0.004, -0.004, - 0.004, 0.008, 0.004, -0.004, 0, -0.008, -0.004, 0.004, -0.004, -0.004, -0.004, - 0.004, -0.004, 0.004, 0.004, 0, -0.004, -0.004, 0.008 - ], - "y": [ - -0.04, -0.048, -0.04, -0.056, -0.036, -0.048, -0.048, -0.048, -0.048, -0.056, - -0.044, -0.048, -0.048, -0.044, -0.048, -0.048, -0.044, -0.028, -0.032, - -0.032, -0.04, -0.068, -0.036, -0.044, -0.044, -0.044, -0.052, -0.04, -0.044, - -0.036, -0.036, -0.044, -0.028, -0.04, -0.032, -0.04, -0.052, -0.04, -0.044, - -0.044, -0.044, -0.04, -0.04, -0.04, -0.048, -0.052, -0.048, -0.044, -0.036, - -0.036, -0.04, -0.04, -0.04, -0.044, -0.044, -0.048, -0.044, -0.036, -0.036, - -0.044, -0.044, -0.044, -0.056, -0.06, -0.052, -0.044, -0.04, -0.048, -0.06, - -0.052, -0.056, -0.036, -0.048, -0.044, -0.052, -0.044, -0.044, -0.036, - -0.048, -0.048, -0.04, -0.044, -0.04, -0.052, -0.048, -0.052, -0.044, -0.044, - -0.052, -0.048, -0.048 - ], - "z": [ - -1.06, -1.06, -1.068, -1.068, -1.052, -1.028, -1.032, -1.06, -1.096, -1.092, - -1.068, -1.064, -1.072, -1.072, -1.084, -1.068, -1.056, -1.064, -1.056, - -1.056, -1.048, -1.048, -1.064, -1.072, -1.08, -1.076, -1.052, -1.056, -1.06, - -1.056, -1.084, -1.064, -1.06, -1.064, -1.072, -1.072, -1.068, -1.08, -1.076, - -1.056, -1.056, -1.064, -1.06, -1.076, -1.08, -1.052, -1.072, -1.064, -1.06, - -1.052, -1.064, -1.064, -1.072, -1.076, -1.064, -1.064, -1.068, -1.064, - -1.056, -1.052, -1.044, -1.068, -1.092, -1.072, -1.064, -1.064, -1.064, -1.06, - -1.068, -1.068, -1.076, -1.064, -1.068, -1.06, -1.068, -1.064, -1.064, -1.056, - -1.064, -1.064, -1.068, -1.068, -1.068, -1.064, -1.056, -1.064, -1.056, - -1.068, -1.084, -1.064, -1.068 - ] - } - } - ], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - }, - { - "ID": 1705437876740, - "name": "Circle", - "recordings": [], - "output": {}, - "confidence": { - "currentConfidence": 0, - "requiredConfidence": 0.8, - "isConfident": false - } - } -] diff --git a/src/__tests__/getPrediction.test.ts b/src/__tests__/getPrediction.test.ts deleted file mode 100644 index 52295ddf5..000000000 --- a/src/__tests__/getPrediction.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { getPrediction } from '../script/getPrediction'; -import { GestureData } from '../script/stores/mlStore'; - -interface GestureConfig { - name: string; - currentConfidence: number; - requiredConfidence: number; -} - -const createGesture = (g: GestureConfig): GestureData => ({ - name: g.name, - ID: 1, - recordings: [], - output: {}, - confidence: { - currentConfidence: g.currentConfidence, - requiredConfidence: g.requiredConfidence, - isConfident: g.currentConfidence >= g.requiredConfidence, - }, -}); - -describe('getPrediction', () => { - test('1 gesture, under required confidence', () => { - const gesture1 = createGesture({ - name: 'gesture 1', - currentConfidence: 0.7, - requiredConfidence: 0.9, - }); - expect(getPrediction([gesture1])).toEqual(undefined); - }); - test('1 gesture, over required confidence', () => { - const gesture1 = createGesture({ - name: 'gesture 1', - currentConfidence: 0.7, - requiredConfidence: 0.5, - }); - expect(getPrediction([gesture1])).toEqual(gesture1); - }); - test('>1 gesture, one over required confidence', () => { - const gesture1 = createGesture({ - name: 'gesture 1', - currentConfidence: 0.7, - requiredConfidence: 0.5, - }); - const gesture2 = createGesture({ - name: 'gesture 2', - currentConfidence: 0.7, - requiredConfidence: 0.8, - }); - expect(getPrediction([gesture1, gesture2])).toEqual(gesture1); - }); - test('>1 gesture, both over required confidence', () => { - const gesture1 = createGesture({ - name: 'gesture 1', - currentConfidence: 0.7, - requiredConfidence: 0.5, - }); - const gesture2 = createGesture({ - name: 'gesture 2', - currentConfidence: 0.9, - requiredConfidence: 0.8, - }); - expect(getPrediction([gesture1, gesture2])).toEqual(gesture2); - }); - test('>1 gesture, both under required confidence', () => { - const gesture1 = createGesture({ - name: 'gesture 1', - currentConfidence: 0.4, - requiredConfidence: 0.5, - }); - const gesture2 = createGesture({ - name: 'gesture 2', - currentConfidence: 0.3, - requiredConfidence: 0.8, - }); - expect(getPrediction([gesture1, gesture2])).toEqual(undefined); - }); -}); diff --git a/src/__tests__/i18n.test.ts b/src/__tests__/i18n.test.ts deleted file mode 100644 index dc5c6a036..000000000 --- a/src/__tests__/i18n.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { get } from 'svelte/store'; -import { MockInstance } from 'vitest'; -import { spyOn } from '@vitest/spy'; - -describe('Initialization tests', () => { - let windowSpy: MockInstance<[], any>; - - beforeEach(() => { - windowSpy = spyOn(window, 'window', 'get'); - }); - - afterEach(() => { - windowSpy.mockRestore(); - localStorage.clear(); - vitest.resetModules(); - }); - - // Welsh disabled for now - test.skip('Language is set to welsh when it is the preferred browser option', async () => { - windowSpy.mockImplementation(() => ({ - navigator: { - languages: ['cy', 'en'], - }, - location: { - search: '', - }, - })); - const { t, waitLocale } = await import('../i18n'); - await waitLocale(); - const getText = get(t); - - const translatedText = getText('alert.isRecording'); - expect(translatedText).toBe("Rydych chi'n recordio ar hyn o bryd!"); - }); - - test('Language falls back to english when an unsupported language is selected', async () => { - windowSpy.mockImplementation(() => ({ - navigator: { - languages: ['random-language'], - }, - location: { - search: '', - }, - })); - const { t, waitLocale } = await import('../i18n'); - await waitLocale(); - const getText = get(t); - - const translatedText = getText('alert.isRecording'); - expect(translatedText).toBe('You are currently recording!'); - }); - - // Welsh disabled for now - test.skip('Language is set to welsh when it is defined query string', async () => { - windowSpy.mockImplementation(() => ({ - navigator: { - languages: ['some languages'], - }, - location: { - search: '?l=cy', - }, - })); - const { t, waitLocale } = await import('../i18n'); - await waitLocale(); - const getText = get(t); - - const translatedText = getText('alert.isRecording'); - expect(translatedText).toBe("Rydych chi'n recordio ar hyn o bryd!"); - }); -}); diff --git a/src/__tests__/license-identifiers.test.ts b/src/__tests__/license-identifiers.test.ts deleted file mode 100644 index c32e3ba08..000000000 --- a/src/__tests__/license-identifiers.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import fs from 'fs'; -import * as path from 'path'; - -// Place files you wish to ignore by name in here -const ignoredFiles: RegExp[] = [ - /^\.DS_Store$/, - /^.+\.json$/, - /\.(gif|svg|png|jpg|jpeg)$/, - /^README.md$/, -]; -const directoriesToScan = ['./src/', './microbit/v2/source/', './microbit/v1/source/']; - -const licenseIdentifierStringSPDX = 'SPDX-License-Identifier:'; - -const readFile = (fileLocation: string, expect: string) => { - const fileContent = fs.readFileSync(fileLocation); - return fileContent.toString().toLowerCase().includes(expect.toLowerCase()); -}; - -type DirectoryContents = { - files: string[]; - folders: string[]; -}; - -const readDirectory = (directory: string, ignoreList: RegExp[]): DirectoryContents => { - const files: string[] = []; - const folders: string[] = []; - const filesRead = fs.readdirSync(directory); - filesRead.forEach(file => { - if (ignoreList.some(ignoreRegExp => ignoreRegExp.test(file))) return; - const fileLocation = path.join(directory, file); - const stats = fs.statSync(fileLocation); - if (stats.isFile()) { - files.push(fileLocation); - } else { - folders.push(fileLocation); - } - }); - return { files: files, folders: folders }; -}; - -const flattenDirectory = (directory: string): string[] => { - const files: string[] = []; - const content = readDirectory(directory, ignoredFiles); - const filesFromSubFolders: string[] = []; - content.folders.forEach(value => { - const subFolderFlat = flattenDirectory(value); - subFolderFlat.forEach(value => filesFromSubFolders.push(value)); - }); - filesFromSubFolders.forEach(value => files.push(value)); - content.files.forEach(value => files.push(value)); - return files; -}; - -const filesMissingIdentifier = (files: string[], expects: string[]): string[] => { - const filesWithMissingIdentifier: string[] = []; - - for (let i = 0; i < files.length; i++) { - for (const expect of expects) { - if (!readFile('./' + files[i], expect)) { - if (!filesWithMissingIdentifier.includes(files[i])) { - filesWithMissingIdentifier.push(files[i]); - } - } - } - } - return filesWithMissingIdentifier; -}; - -describe('License identifier tests', () => { - test( - 'All files should contain license identifier', - () => { - const flatten = directoriesToScan.reduce((acc: any, current) => { - return acc.concat(flattenDirectory(current)); - }, []); - - const faultyFiles = filesMissingIdentifier(flatten, [licenseIdentifierStringSPDX]); - expect( - faultyFiles.length, - 'Some files do not contain identifier! ' + - faultyFiles - .map(val => `\n \u001b[35m${val} \u001b[0mis missing license identifier`) - .join(), - ).toEqual(0); - }, - 60000 * 10, - ); -}); diff --git a/src/__tests__/microbit-usb-conection.test.ts b/src/__tests__/microbit-usb-conection.test.ts deleted file mode 100644 index ca60c3df4..000000000 --- a/src/__tests__/microbit-usb-conection.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import MockUSBDevice, { TestableMicrobitUSB } from './mocks/mock-usb'; - -describe('Microbit USB connection tests', () => { - beforeAll(() => { - /** Adds the usb property to the navigator for mocking */ - Object.assign(navigator, { - usb: { - predefined: undefined, - requestDevice(options?: USBDeviceRequestOptions): any { - const result = this.predefined - ? this.predefined - : Promise.resolve(new MockUSBDevice().build()); - this.predefined = undefined; - return Promise.resolve(result); - }, - }, - }); - }); - - test('Serial number 9900 should be a version 1', async () => { - const mockUsbDevice = new MockUSBDevice().withSerialNumber('9900serno').build(); - const connection = new TestableMicrobitUSB(mockUsbDevice); - expect(connection.getModelNumber()).toBe(1); - }); - - test('Serial number 9901 should be a version 1', async () => { - const mockUsbDevice = new MockUSBDevice().withSerialNumber('9901serno').build(); - const connection = new TestableMicrobitUSB(mockUsbDevice); - expect(connection.getModelNumber()).toBe(1); - }); - - test('Serial number 9903 should be a version 2', async () => { - const mockUsbDevice = new MockUSBDevice().withSerialNumber('9903serno').build(); - const connection = new TestableMicrobitUSB(mockUsbDevice); - expect(connection.getModelNumber()).toBe(2); - }); - - test('Serial number 9904 should be a version 2', async () => { - const mockUsbDevice = new MockUSBDevice().withSerialNumber('9904serno').build(); - const connection = new TestableMicrobitUSB(mockUsbDevice); - expect(connection.getModelNumber()).toBe(2); - }); -}); diff --git a/src/__tests__/ml.test.ts b/src/__tests__/ml.test.ts deleted file mode 100644 index 3ed84fe99..000000000 --- a/src/__tests__/ml.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2024, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import * as tf from '@tensorflow/tfjs'; -import { makeInputs, trainModel } from '../script/ml'; -import { gestures } from '../script/stores/Stores'; -import gestureData from './fixtures/gesture-data.json'; -import gestureDataBadLabels from './fixtures/gesture-data-bad-labels.json'; -import testdataShakeStill from './fixtures/test-data-shake-still.json'; -import { PersistantGestureData } from '../script/domain/Gestures'; - -let tensorFlowModel: tf.LayersModel | void; -beforeAll(async () => { - // No webgl in tests running in node. - tf.setBackend('cpu'); - - // This creates determinism in the model training step. - const randomSpy = vi.spyOn(Math, 'random'); - randomSpy.mockImplementation(() => 0.5); - - gestures.importFrom(gestureData); - tensorFlowModel = await trainModel(); -}); - -const getModelResults = (data: PersistantGestureData[]) => { - const x: number[][] = []; - const y: number[][] = []; - const numActions = data.length; - data.forEach((action, index) => { - action.recordings.forEach(recording => { - x.push(makeInputs(recording.data)); - const label = new Array(numActions); - label.fill(0, 0, numActions); - label[index] = 1; - y.push(label); - }); - }); - - if (!tensorFlowModel) { - throw Error('No model returned'); - } - - const tensorFlowResult = tensorFlowModel.evaluate(tf.tensor(x), tf.tensor(y)); - const tensorFlowResultAccuracy = (tensorFlowResult as tf.Scalar[])[1] - .dataSync()[0] - .toFixed(4); - const tensorflowPredictionResult = ( - tensorFlowModel.predict(tf.tensor(x)) as tf.Tensor - ).dataSync(); - return { - tensorFlowResultAccuracy, - tensorflowPredictionResult, - labels: y, - }; -}; - -describe('Model tests', () => { - test('returns acceptable results on training data', async () => { - const { tensorFlowResultAccuracy, tensorflowPredictionResult, labels } = - getModelResults(gestureData); - const d = labels[0].length; // dimensions - for (let i = 0, j = 0; i < tensorflowPredictionResult.length; i += d, j++) { - const result = tensorflowPredictionResult.slice(i, i + d); - expect(result.indexOf(Math.max(...result))).toBe( - labels[j].indexOf(Math.max(...labels[j])), - ); - } - expect(tensorFlowResultAccuracy).toBe('1.0000'); - }); - - // The action names don't matter, the order of the actions in the data.json file does. - // Training data is shake, still, circle. This data is still, circle, shake. - test('returns incorrect results on wrongly labelled training data', async () => { - const { tensorFlowResultAccuracy, tensorflowPredictionResult, labels } = - getModelResults(gestureDataBadLabels); - const d = labels[0].length; // dimensions - for (let i = 0, j = 0; i < tensorflowPredictionResult.length; i += d, j++) { - const result = tensorflowPredictionResult.slice(i, i + d); - expect(result.indexOf(Math.max(...result))).not.toBe( - labels[j].indexOf(Math.max(...labels[j])), - ); - } - expect(tensorFlowResultAccuracy).toBe('0.0000'); - }); - - test('returns correct results on testing data', async () => { - const { tensorFlowResultAccuracy } = getModelResults(testdataShakeStill); - // The model thinks two samples of still are circle. - // 14 samples; 1.0 / 14 = 0.0714; 0.0714 * 12 correct inferences = 0.8571 - expect(parseFloat(tensorFlowResultAccuracy)).toBeGreaterThan(0.85); - }); -}); diff --git a/src/__tests__/mocks/mock-bluetooth-accelerometer-service.ts b/src/__tests__/mocks/mock-bluetooth-accelerometer-service.ts deleted file mode 100644 index 13527eacf..000000000 --- a/src/__tests__/mocks/mock-bluetooth-accelerometer-service.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import MBSpecs from '../../script/microbit-interfacing/MBSpecs'; -import { MockBluetoothCharacteristicProperties } from './mock-bluetooth'; - -class MockBluetoothAccelerometerService implements BluetoothRemoteGATTService { - readonly device: BluetoothDevice; - readonly isPrimary: boolean = false; - readonly uuid: string = ''; - private characteristic; - - constructor(device: any) { - this.device = device; - this.characteristic = new MockBluetoothAccelerometerCharacteristic(this); - } - - oncharacteristicvaluechanged(ev: Event): any {} - - onserviceadded(ev: Event): any {} - - onservicechanged(ev: Event): any {} - - onserviceremoved(ev: Event): any {} - - addEventListener( - type: 'serviceadded', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: 'servicechanged', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: 'serviceremoved', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: 'serviceadded' | 'servicechanged' | 'serviceremoved' | string, - listener: - | ((this: this, ev: Event) => any) - | EventListenerOrEventListenerObject - | null, - useCapture?: boolean | AddEventListenerOptions, - ): void {} - - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean { - return false; - } - - getCharacteristic( - characteristic: BluetoothCharacteristicUUID, - ): Promise { - if (characteristic === MBSpecs.Characteristics.ACCEL_DATA) { - return Promise.resolve(this.characteristic); - } - return Promise.resolve(this.characteristic); // always returns characteristic - } - - getCharacteristics( - characteristic?: BluetoothCharacteristicUUID, - ): Promise { - return Promise.resolve([]); - } - - getIncludedService(service: BluetoothServiceUUID): Promise { - return Promise.resolve(this); - } - - getIncludedServices( - service?: BluetoothServiceUUID, - ): Promise { - return Promise.resolve([]); - } - - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void {} -} - -export class MockBluetoothAccelerometerCharacteristic - implements BluetoothRemoteGATTCharacteristic -{ - readonly properties: BluetoothCharacteristicProperties = - new MockBluetoothCharacteristicProperties(); - readonly service: BluetoothRemoteGATTService; - readonly uuid: string = ''; - - constructor(service: BluetoothRemoteGATTService) { - this.service = service; - } - - oncharacteristicvaluechanged(ev: Event): any {} - - addEventListener( - type: 'characteristicvaluechanged', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: 'characteristicvaluechanged' | string, - listener: - | ((this: this, ev: Event) => any) - | EventListenerOrEventListenerObject - | null, - useCapture?: boolean | AddEventListenerOptions, - ): void {} - - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean { - return false; - } - - getDescriptor( - descriptor: BluetoothDescriptorUUID, - ): Promise { - return Promise.resolve(undefined as unknown as BluetoothRemoteGATTDescriptor); - } - - getDescriptors( - descriptor?: BluetoothDescriptorUUID, - ): Promise { - return Promise.resolve([]); - } - - readValue(): Promise { - return Promise.resolve(undefined as unknown as DataView); - } - - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void {} - - startNotifications(): Promise { - return Promise.resolve(undefined as unknown as BluetoothRemoteGATTCharacteristic); - } - - stopNotifications(): Promise { - return Promise.resolve(undefined as unknown as BluetoothRemoteGATTCharacteristic); - } - - writeValue(value: BufferSource): Promise { - return Promise.resolve(undefined); - } - - writeValueWithResponse(value: BufferSource): Promise { - return Promise.resolve(undefined); - } - - writeValueWithoutResponse(value: BufferSource): Promise { - return Promise.resolve(undefined); - } -} - -export default MockBluetoothAccelerometerService; diff --git a/src/__tests__/mocks/mock-bluetooth-gattcharacteristic.ts b/src/__tests__/mocks/mock-bluetooth-gattcharacteristic.ts deleted file mode 100644 index 6fe67bc1f..000000000 --- a/src/__tests__/mocks/mock-bluetooth-gattcharacteristic.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -class MockBluetoothCharacteristicProperties implements BluetoothCharacteristicProperties { - readonly authenticatedSignedWrites: boolean = false; - readonly broadcast: boolean = false; - readonly indicate: boolean = false; - readonly notify: boolean = false; - readonly read: boolean = false; - readonly reliableWrite: boolean = false; - readonly writableAuxiliaries: boolean = false; - readonly write: boolean = false; - readonly writeWithoutResponse: boolean = false; -} - -class MockBluetoothGattcharacteristic implements BluetoothRemoteGATTCharacteristic { - readonly properties: BluetoothCharacteristicProperties = - new MockBluetoothCharacteristicProperties(); - readonly service: BluetoothRemoteGATTService; - readonly uuid: string = ''; - - constructor(service: BluetoothRemoteGATTService) { - this.service = service; - } - - oncharacteristicvaluechanged(ev: Event): any {} - - addEventListener( - type: 'characteristicvaluechanged', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: 'characteristicvaluechanged' | string, - listener: - | ((this: this, ev: Event) => any) - | EventListenerOrEventListenerObject - | null, - useCapture?: boolean | AddEventListenerOptions, - ): void {} - - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean { - return false; - } - - getDescriptor( - descriptor: BluetoothDescriptorUUID, - ): Promise { - return Promise.resolve(undefined as unknown as BluetoothRemoteGATTDescriptor); - } - - getDescriptors( - descriptor?: BluetoothDescriptorUUID, - ): Promise { - return Promise.resolve([]); - } - - readValue(): Promise { - return Promise.resolve(undefined as unknown as DataView); - } - - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void {} - - startNotifications(): Promise { - return Promise.resolve(undefined as unknown as BluetoothRemoteGATTCharacteristic); - } - - stopNotifications(): Promise { - return Promise.resolve(undefined as unknown as BluetoothRemoteGATTCharacteristic); - } - - writeValue(value: BufferSource): Promise { - return Promise.resolve(undefined); - } - - writeValueWithResponse(value: BufferSource): Promise { - return Promise.resolve(undefined); - } - - writeValueWithoutResponse(value: BufferSource): Promise { - return Promise.resolve(undefined); - } -} - -export default MockBluetoothGattcharacteristic; diff --git a/src/__tests__/mocks/mock-bluetooth-gattservice.ts b/src/__tests__/mocks/mock-bluetooth-gattservice.ts deleted file mode 100644 index f6a8e162a..000000000 --- a/src/__tests__/mocks/mock-bluetooth-gattservice.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import MockBluetoothGattcharacteristic from './mock-bluetooth-gattcharacteristic'; - -class MockBluetoothGattservice implements BluetoothRemoteGATTService { - readonly device: BluetoothDevice; - readonly isPrimary: boolean = false; - readonly uuid: string = ''; - - constructor(device: BluetoothDevice) { - this.device = device; - } - - oncharacteristicvaluechanged(ev: Event): any { - /* Empty */ - } - - onserviceadded(ev: Event): any { - /* Empty */ - } - - onservicechanged(ev: Event): any { - /* Empty */ - } - - onserviceremoved(ev: Event): any { - /* Empty */ - } - - addEventListener( - type: 'serviceadded', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: 'servicechanged', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: 'serviceremoved', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: 'serviceadded' | 'servicechanged' | 'serviceremoved' | string, - listener: - | ((this: this, ev: Event) => any) - | EventListenerOrEventListenerObject - | null, - useCapture?: boolean | AddEventListenerOptions, - ): void { - /* Empty */ - } - - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean { - return false; - } - - getCharacteristic( - characteristic: BluetoothCharacteristicUUID, - ): Promise { - return Promise.resolve(new MockBluetoothGattcharacteristic(this)); - } - - getCharacteristics( - characteristic?: BluetoothCharacteristicUUID, - ): Promise { - return Promise.resolve([]); - } - - getIncludedService(service: BluetoothServiceUUID): Promise { - return Promise.resolve(undefined as unknown as BluetoothRemoteGATTService); - } - - getIncludedServices( - service?: BluetoothServiceUUID, - ): Promise { - return Promise.resolve([]); - } - - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void { - /* Empty */ - } -} - -export default MockBluetoothGattservice; diff --git a/src/__tests__/mocks/mock-bluetooth-info-service.ts b/src/__tests__/mocks/mock-bluetooth-info-service.ts deleted file mode 100644 index a63392608..000000000 --- a/src/__tests__/mocks/mock-bluetooth-info-service.ts +++ /dev/null @@ -1,234 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import MBSpecs from '../../script/microbit-interfacing/MBSpecs'; -import { MockBluetoothCharacteristicProperties } from './mock-bluetooth'; - -class MockInfoService implements BluetoothRemoteGATTService { - readonly device: BluetoothDevice; - readonly isPrimary: boolean = false; - readonly uuid: string = ''; - modelNumberCharacteristic: BluetoothRemoteGATTCharacteristic = - new MockBluetoothModelNumberCharacteristic(0, this); - - constructor(device: any) { - this.device = device; - } - - oncharacteristicvaluechanged(ev: Event): any {} - - onserviceadded(ev: Event): any {} - - onservicechanged(ev: Event): any {} - - onserviceremoved(ev: Event): any {} - - withModelNumber(modelNumberCharacteristic: BluetoothRemoteGATTCharacteristic) { - this.modelNumberCharacteristic = modelNumberCharacteristic; - return this; - } - - addEventListener( - type: 'serviceadded', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: 'servicechanged', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: 'serviceremoved', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: 'serviceadded' | 'servicechanged' | 'serviceremoved' | string, - listener: - | ((this: this, ev: Event) => any) - | EventListenerOrEventListenerObject - | null, - useCapture?: boolean | AddEventListenerOptions, - ): void {} - - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean { - return false; - } - - getCharacteristic( - characteristic: BluetoothCharacteristicUUID, - ): Promise { - if (characteristic === MBSpecs.Characteristics.MODEL_NUMBER) { - return Promise.resolve(this.modelNumberCharacteristic); - } - return Promise.reject(undefined); - } - - getCharacteristics( - characteristic?: BluetoothCharacteristicUUID, - ): Promise { - return Promise.resolve([]); - } - - getIncludedService(service: BluetoothServiceUUID): Promise { - return Promise.reject(undefined); - } - - getIncludedServices( - service?: BluetoothServiceUUID, - ): Promise { - return Promise.resolve([]); - } - - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void {} -} - -export class MockBluetoothModelNumberCharacteristic - implements BluetoothRemoteGATTCharacteristic -{ - readonly properties: BluetoothCharacteristicProperties = - new MockBluetoothCharacteristicProperties(); - readonly service: BluetoothRemoteGATTService; - readonly uuid: string = ''; - - constructor( - public versionNumber: number, - service: BluetoothRemoteGATTService, - ) { - this.service = service; - } - - oncharacteristicvaluechanged(ev: Event): any {} - - addEventListener( - type: 'characteristicvaluechanged', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: 'characteristicvaluechanged' | string, - listener: - | ((this: this, ev: Event) => any) - | EventListenerOrEventListenerObject - | null, - useCapture?: boolean | AddEventListenerOptions, - ): void {} - - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean { - return false; - } - - getDescriptor( - descriptor: BluetoothDescriptorUUID, - ): Promise { - return Promise.reject(undefined); - } - - getDescriptors( - descriptor?: BluetoothDescriptorUUID, - ): Promise { - return Promise.resolve([]); - } - - readValue(): Promise { - const v2VersionString = 'BBC micro:bit V2.0'; - const v1VersionString = 'BBC micro:bit'; - - const selectedVersionString = - this.versionNumber === 1 ? v1VersionString : v2VersionString; - const enc = new TextEncoder(); - const buff = enc.encode(selectedVersionString); - const dataView = new DataView(buff.buffer); - return Promise.resolve(dataView); - } - - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void {} - - startNotifications(): Promise { - return Promise.reject(undefined); - } - - stopNotifications(): Promise { - return Promise.reject(undefined); - } - - writeValue(value: BufferSource): Promise { - return Promise.resolve(undefined); - } - - writeValueWithResponse(value: BufferSource): Promise { - return Promise.resolve(undefined); - } - - writeValueWithoutResponse(value: BufferSource): Promise { - return Promise.resolve(undefined); - } -} - -export default MockInfoService; diff --git a/src/__tests__/mocks/mock-bluetooth.ts b/src/__tests__/mocks/mock-bluetooth.ts deleted file mode 100644 index 6ccca29e1..000000000 --- a/src/__tests__/mocks/mock-bluetooth.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -export class MockBluetoothCharacteristicProperties - implements BluetoothCharacteristicProperties -{ - readonly authenticatedSignedWrites: boolean = false; - readonly broadcast: boolean = false; - readonly indicate: boolean = false; - readonly notify: boolean = false; - readonly read: boolean = false; - readonly reliableWrite: boolean = false; - readonly writableAuxiliaries: boolean = false; - readonly write: boolean = false; - readonly writeWithoutResponse: boolean = false; -} diff --git a/src/__tests__/mocks/mock-microbit-bluetooth.ts b/src/__tests__/mocks/mock-microbit-bluetooth.ts deleted file mode 100644 index 8d3921ae6..000000000 --- a/src/__tests__/mocks/mock-microbit-bluetooth.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import MBSpecs from '../../script/microbit-interfacing/MBSpecs'; -import MockInfoService, { - MockBluetoothModelNumberCharacteristic, -} from './mock-bluetooth-info-service'; -import MockBluetoothAccelerometerService from './mock-bluetooth-accelerometer-service'; -import MockBluetoothGattservice from './mock-bluetooth-gattservice'; - -class MockBTDevice implements BluetoothDevice { - readonly id: string = ''; - readonly watchingAdvertisements: boolean = false; - public gatt: BluetoothRemoteGATTServer; - public willFailConnection = false; - private microbitVersion = 0; - private listeners: DeviceListener[] = []; - - constructor() { - this.gatt = new MockGattServer(this, this); - } - - public withMicrobitVersion(versionNumber: 1 | 2) { - this.microbitVersion = versionNumber; - return this; - } - - public withFailingConnection() { - this.willFailConnection = true; - return this; - } - - public build() { - let gatt = new MockGattServer(this, this); - if (!this.microbitVersion) { - this.microbitVersion = 1; - } - let deviceInfoService = new MockInfoService(this); - const modelNumberCharacteristic = new MockBluetoothModelNumberCharacteristic( - this.microbitVersion, - deviceInfoService, - ); - deviceInfoService = deviceInfoService.withModelNumber(modelNumberCharacteristic); - gatt = gatt.withDeviceInfo(deviceInfoService); - this.gatt = gatt; - return this; - } - - onadvertisementreceived(ev: BluetoothAdvertisingEvent): any { - /* Empty */ - } - - oncharacteristicvaluechanged(ev: Event): any { - /* Empty */ - } - - ongattserverdisconnected(ev: Event): any { - /* Empty */ - } - - onserviceadded(ev: Event): any { - /* Empty */ - } - - onservicechanged(ev: Event): any { - /* Empty */ - } - - onserviceremoved(ev: Event): any { - /* Empty */ - } - - addEventListener( - type: 'gattserverdisconnected', - listener: (this: this, ev: Event) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: 'advertisementreceived', - listener: (this: this, ev: BluetoothAdvertisingEvent) => any, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject, - useCapture?: boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void; - addEventListener( - type: 'gattserverdisconnected' | 'advertisementreceived' | string, - listener: - | ((this: this, ev: Event) => any) - | ((this: this, ev: BluetoothAdvertisingEvent) => any) - | EventListenerOrEventListenerObject - | null, - useCapture?: boolean | AddEventListenerOptions, - ): void { - this.listeners.push({ eventType: type, listener: listener }); - } - - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean; - dispatchEvent(event: Event): boolean { - this.listeners.forEach(value => { - if (event.type == value.eventType) { - if (value.listener) { - value.listener(value); - } - } - }); - return false; - } - - forget(): Promise { - return Promise.resolve(undefined); - } - - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void; - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void { - /* Empty */ - } - - watchAdvertisements(options?: WatchAdvertisementsOptions): Promise { - return Promise.resolve(undefined); - } -} - -class MockGattServer implements BluetoothRemoteGATTServer { - connected: boolean; - readonly device: BluetoothDevice; - mockDevice: MockBTDevice; - deviceInfoService: BluetoothRemoteGATTService; - accelerometerService: BluetoothRemoteGATTService; - - constructor(device: BluetoothDevice, mock: MockBTDevice) { - this.connected = false; - this.device = device; - this.mockDevice = mock; - this.deviceInfoService = new MockInfoService(device); - this.accelerometerService = new MockBluetoothAccelerometerService(device); - } - - withDeviceInfo(deviceInfoService: BluetoothRemoteGATTService) { - this.deviceInfoService = deviceInfoService; - return this; - } - - connect(): Promise { - if (this.mockDevice.willFailConnection) { - return Promise.reject(undefined); - } - this.connected = true; - return Promise.resolve(this); - } - - disconnect(): void { - this.device.dispatchEvent(new Event('gattserverdisconnected')); - this.connected = false; - } - - getPrimaryService(service: BluetoothServiceUUID): Promise { - if (service === MBSpecs.Services.DEVICE_INFO_SERVICE) { - return Promise.resolve(this.deviceInfoService); - } - if (service === MBSpecs.Services.ACCEL_SERVICE) { - return Promise.resolve(this.accelerometerService); - } - if (service === MBSpecs.Services.BUTTON_SERVICE) { - return Promise.resolve(new MockBluetoothGattservice(this.device)); - } - if (service === MBSpecs.Services.UART_SERVICE) { - return Promise.resolve(new MockBluetoothGattservice(this.device)); - } - console.warn( - 'The primary service, you tried to fetch were unknown. Was this on purpose?', - ); - return Promise.reject(undefined); - } - - getPrimaryServices( - service?: BluetoothServiceUUID, - ): Promise { - return Promise.resolve([]); - } -} - -type DeviceListener = { - eventType: 'gattserverdisconnected' | 'advertisementreceived' | string; - listener: any; -}; - -export default MockBTDevice; diff --git a/src/__tests__/mocks/mock-usb.ts b/src/__tests__/mocks/mock-usb.ts deleted file mode 100644 index abd548fef..000000000 --- a/src/__tests__/mocks/mock-usb.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import USBConfiguration from 'dapjs'; -import USBInterface from 'dapjs'; -import USBAlternateInterface from 'dapjs'; -import MicrobitUSB from '../../script/microbit-interfacing/MicrobitUSB'; - -class SimpleUSBInterfaceAlternate implements USBAlternateInterface { - alternateSetting = 0; - interfaceClass = 0; - interfaceSubclass = 0; - interfaceProtocol = 0; - interfaceName?: string; - endpoints: USBEndpoint[] = []; -} - -class SimpleUSBInterface implements USBInterface { - interfaceNumber: number; - alternate: USBAlternateInterface; - alternates: USBAlternateInterface[] = []; - claimed = false; - - constructor() { - this.alternate = new SimpleUSBInterfaceAlternate(); - this.interfaceNumber = 1; - } -} - -class SimpleUSBConfig implements USBConfiguration { - configurationValue: number; - configurationName?: string; - interfaces: USBInterface[]; - - constructor() { - this.configurationName = 'some name'; - this.configurationValue = 1; - this.interfaces = []; - this.interfaces.push(new SimpleUSBInterface()); - } -} - -class MockUSBDevice implements USBDevice { - configurations: USBConfiguration[] = []; - readonly deviceClass: number = 0; - readonly deviceProtocol: number = 0; - readonly deviceSubclass: number = 0; - readonly deviceVersionMajor: number = 0; - readonly deviceVersionMinor: number = 0; - readonly deviceVersionSubminor: number = 0; - readonly opened: boolean = false; - readonly productId: number = 0; - readonly usbVersionMajor: number = 0; - readonly usbVersionMinor: number = 0; - readonly usbVersionSubminor: number = 0; - readonly vendorId: number = 0; - interfaceNumber = 0; - public serialNumber = ''; - - public withSerialNumber(serno: string) { - this.serialNumber = serno; - return this; - } - - public build() { - this.configurations = []; - const usbConfig = new SimpleUSBConfig(); - this.configurations = [usbConfig]; - this.interfaceNumber = 1; - return this; - } - - claimInterface(interfaceNumber: number): Promise { - return Promise.resolve(); - } - - clearHalt(direction: USBDirection, endpointNumber: number): Promise { - return Promise.resolve(); - } - - close(): Promise { - return Promise.resolve(); - } - - controlTransferIn( - setup: USBControlTransferParameters, - length: number, - ): Promise { - return Promise.resolve(new USBInTransferResult('ok', undefined)); - } - - controlTransferOut( - setup: USBControlTransferParameters, - data?: BufferSource, - ): Promise { - return Promise.resolve(new USBOutTransferResult('ok', 1)); - } - - forget(): Promise { - return Promise.resolve(); - } - - isochronousTransferIn( - endpointNumber: number, - packetLengths: number[], - ): Promise { - return Promise.resolve(new USBIsochronousInTransferResult([], undefined)); - } - - isochronousTransferOut( - endpointNumber: number, - data: BufferSource, - packetLengths: number[], - ): Promise { - return Promise.resolve(new USBIsochronousOutTransferResult([])); - } - - open(): Promise { - return Promise.resolve(); - } - - releaseInterface(interfaceNumber: number): Promise { - return Promise.resolve(); - } - - reset(): Promise { - return Promise.resolve(); - } - - selectAlternateInterface( - interfaceNumber: number, - alternateSetting: number, - ): Promise { - return Promise.resolve(); - } - - selectConfiguration(configurationValue: number): Promise { - return Promise.resolve(); - } - - transferIn(endpointNumber: number, length: number): Promise { - return Promise.resolve(new USBInTransferResult('ok', undefined)); - } - - transferOut(endpointNumber: number, data: BufferSource): Promise { - return Promise.resolve(new USBOutTransferResult('ok', 1)); - } -} - -export default MockUSBDevice; - -export class TestableMicrobitUSB extends MicrobitUSB { - /** Just overrides the protected constructor to be able to use the Mock USB device to test */ - public constructor(usbDevice: USBDevice) { - super(usbDevice); - } -} diff --git a/src/__tests__/patternMatrixTransforms.test.ts b/src/__tests__/patternMatrixTransforms.test.ts deleted file mode 100644 index da49f6871..000000000 --- a/src/__tests__/patternMatrixTransforms.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { - getHighlightedColumns, - transformColumnsToMatrix, - transformMatrixToColumns, - updateMatrixColumns, -} from '../script/patternMatrixTransforms'; - -describe('transformMatrixToColumns', () => { - test('2 x 2', () => { - const dim = 2; - const matrix = [1, 2, 3, 4]; - const columns = [ - [1, 3], - [2, 4], - ]; - expect(transformMatrixToColumns(matrix, dim)).toEqual(columns); - }); -}); - -describe('transformColumnsToMatrix', () => { - test('2 x 2', () => { - const matrix = [1, 2, 3, 4]; - const columns = [ - [1, 3], - [2, 4], - ]; - expect(transformColumnsToMatrix(columns)).toEqual(matrix); - }); -}); - -describe('getHighlightedColumns', () => { - test('cell position is above lit up cell', () => { - const matrixColumns = [ - [false, false, true], - [false, false, false], - [false, false, false], - ]; - const cellPos = { colIdx: 0, rowIdx: 0 }; - const highlightedColumns = [ - [true, true, false], - [false, false, false], - [false, false, false], - ]; - expect(getHighlightedColumns(matrixColumns, cellPos)).toEqual(highlightedColumns); - }); - test('cell position is below lit up cell', () => { - const matrixColumns = [ - [true, true, true], - [false, false, false], - [false, false, false], - ]; - const cellPos = { colIdx: 0, rowIdx: 2 }; - const highlightedColumns = [ - [true, true, false], - [false, false, false], - [false, false, false], - ]; - expect(getHighlightedColumns(matrixColumns, cellPos)).toEqual(highlightedColumns); - }); - test('cell position is on lit up cell', () => { - const matrixColumns = [ - [false, true, true], - [false, false, false], - [false, false, false], - ]; - const cellPos = { colIdx: 0, rowIdx: 1 }; - const highlightedColumns = [ - [false, false, false], - [false, false, false], - [false, false, false], - ]; - expect(getHighlightedColumns(matrixColumns, cellPos)).toEqual(highlightedColumns); - }); -}); - -describe('updateMatrixColumns', () => { - test('cell position is above lit up cell', () => { - const matrixColumns = [ - [false, false, true], - [false, false, true], - [false, false, true], - ]; - const cellPos = { colIdx: 1, rowIdx: 0 }; - const newMatrixColumns = [ - [false, false, true], - [true, true, true], - [false, false, true], - ]; - expect(updateMatrixColumns(matrixColumns, cellPos)).toEqual(newMatrixColumns); - }); - test('cell position is below lit up cell', () => { - const matrixColumns = [ - [false, false, true], - [true, true, true], - [false, false, true], - ]; - const cellPos = { colIdx: 1, rowIdx: 2 }; - const newMatrixColumns = [ - [false, false, true], - [false, false, true], - [false, false, true], - ]; - expect(updateMatrixColumns(matrixColumns, cellPos)).toEqual(newMatrixColumns); - }); - test('cell position is on lit up cell', () => { - const matrixColumns = [ - [false, false, true], - [false, false, true], - [false, false, true], - ]; - const cellPos = { colIdx: 0, rowIdx: 2 }; - expect(updateMatrixColumns(matrixColumns, cellPos)).toEqual(matrixColumns); - }); -}); diff --git a/src/__tests__/serialProtocol.test.ts b/src/__tests__/serialProtocol.test.ts deleted file mode 100644 index 96b5b719e..000000000 --- a/src/__tests__/serialProtocol.test.ts +++ /dev/null @@ -1,391 +0,0 @@ -/** - * @jest-environment jsdom - */ -/** - * (c) 2024, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { - MicrobitSensorState, - splitMessages, - processPeriodicMessage, - processResponseMessage, -} from '../script/microbit-interfacing/serialProtocol'; - -describe('splitMessages', () => { - it('splits a single message', () => { - const message = 'P0E80495C4341\n'; - - const got = splitMessages(message); - - expect(got.messages.length).toEqual(1); - expect(got.messages[0]).toEqual(message.slice(0, -1)); - expect(got.remainingInput).toEqual(''); - }); - - it('splits multiple messages', () => { - const message1 = 'P21998AEC2F80'; - const message2 = 'P21FFF000FFF3'; - const message3 = 'P45000FFF0002'; - - const msgs = splitMessages(message1 + '\n' + message2 + '\n' + message3 + '\n'); - - expect(msgs.messages.length).toEqual(3); - expect(msgs.messages[0]).toEqual(message1); - expect(msgs.messages[1]).toEqual(message2); - expect(msgs.messages[2]).toEqual(message3); - expect(msgs.remainingInput).toEqual(''); - }); - - it('splits multiple messages with extra input', () => { - const message1 = 'P188009584343'; - const message2 = 'P19998AEC2F80'; - const message3 = 'P201234'; - - const msgs = splitMessages(message1 + '\n' + message2 + '\n' + message3); - - expect(msgs.messages.length).toEqual(2); - expect(msgs.messages[0]).toEqual(message1); - expect(msgs.messages[1]).toEqual(message2); - expect(msgs.remainingInput).toEqual(message3); - }); - - it('sets the remainingInput from incomplete message', () => { - const message1 = 'R[0]INCOMPLETE'; - - const msgs = splitMessages(message1); - - expect(msgs.messages.length).toEqual(0); - expect(msgs.remainingInput).toEqual(message1); - }); - - it('processes empty input correctly', () => { - const msgs = splitMessages(''); - - expect(msgs.messages.length).toEqual(0); - expect(msgs.remainingInput).toEqual(''); - }); - - it('throws away empty lines correctly', () => { - const msgs = splitMessages('\n\n\n'); - - expect(msgs.messages.length).toEqual(0); - expect(msgs.remainingInput).toEqual(''); - }); - - it('throws away remainingInput of invalid type', () => { - const message1 = 'R[0]START[]'; - const message_invalid_start = 'NOT_A_VALID_MSG_START'; - - const msgs = splitMessages(message1 + '\n' + message_invalid_start); - - expect(msgs.messages.length).toEqual(1); - expect(msgs.messages[0]).toEqual(message1); - expect(msgs.remainingInput).toEqual(''); - }); - - it('throws messages of invalid type', () => { - const msgs = splitMessages('P00\nC11\nR22\nX33\n'); - - expect(msgs.messages.length).toEqual(3); - expect(msgs.messages[0]).toEqual('P00'); - expect(msgs.messages[1]).toEqual('C11'); - expect(msgs.messages[2]).toEqual('R22'); - expect(msgs.remainingInput).toEqual(''); - }); -}); - -describe('processPeriodicMessage', () => { - it('extracts the micro:bit state from the message', () => { - const message = 'P0E80495C4341'; - - const got = processPeriodicMessage(message); - - const want: MicrobitSensorState = { - accelerometerX: 4, - accelerometerY: 348, - accelerometerZ: -972, - buttonA: 1, - buttonB: 0, - }; - expect(got).toEqual(want); - }); - - it('processes min/max values correctly', () => { - const message1 = 'P00FFF000FFF0'; - const want1: MicrobitSensorState = { - accelerometerX: 2047, - accelerometerY: -2048, - accelerometerZ: 2047, - buttonA: 0, - buttonB: 0, - }; - const message2 = 'PFF000FFF0003'; - const want2: MicrobitSensorState = { - accelerometerX: -2048, - accelerometerY: 2047, - accelerometerZ: -2048, - buttonA: 1, - buttonB: 1, - }; - - const got1 = processPeriodicMessage(message1); - const got2 = processPeriodicMessage(message2); - - expect(got1).toEqual(want1); - expect(got2).toEqual(want2); - }); - - it('ignores messages that are not periodic', () => { - const message1 = 'C001112223334'; - const message2 = 'R001112223334'; - - const got1 = processPeriodicMessage(message1); - const got2 = processPeriodicMessage(message2); - - expect(got1).toBeUndefined(); - expect(got2).toBeUndefined(); - }); - - it('ignores wrong length input', () => { - const message1 = 'P21998AEC2F8\n'; - const message2 = 'P21998AEC2F'; - - const got1 = processPeriodicMessage(message1); - const got2 = processPeriodicMessage(message2); - - expect(got1).toBeUndefined(); - expect(got2).toBeUndefined(); - }); - - it('detects invalid button values', () => { - const message1 = 'P000000000004'; - const message2 = 'P00000000000F'; - - const got1 = processPeriodicMessage(message1); - const got2 = processPeriodicMessage(message2); - - expect(got1).toBeUndefined(); - expect(got2).toBeUndefined(); - }); - - it('detects invalid hex values', () => { - const message1 = 'Pz01112223334'; - - const got1 = processPeriodicMessage(message1); - - expect(got1).toBeUndefined(); - }); -}); - -describe('processResponseMessage', () => { - it('processes valid Handshake responses', () => { - const message1 = 'R[0]HS[1]'; - const message2 = 'R[1122aabb]HS[255]'; - - const got1 = processResponseMessage(message1); - const got2 = processResponseMessage(message2); - - expect(got1).toEqual({ - message: message1, - messageId: 0, - type: 'HS', - value: 1, - }); - expect(got2).toEqual({ - message: message2, - messageId: 0x1122aabb, - type: 'HS', - value: 255, - }); - }); - - it('processes valid Radio Frequency response', () => { - const message = 'R[1234]RF[42]'; - - const got = processResponseMessage(message); - - expect(got).toEqual({ - message: message, - messageId: 0x1234, - type: 'RF', - value: 42, - }); - }); - - it('processes valid Remote micro:bit ID response', () => { - const message = 'R[1234]RMBID[4294967295]'; - - const got = processResponseMessage(message); - - expect(got).toEqual({ - message: message, - messageId: 0x1234, - type: 'RMBID', - value: 4294967295, - }); - }); - - it('processes valid Software Versions responses', () => { - const message1 = 'R[1234]SWVER[0.0.0]'; - const message2 = 'R[1234]SWVER[99.99.99]'; - const message3 = 'R[1234]SWVER[1.2.3]'; - - const got1 = processResponseMessage(message1); - const got2 = processResponseMessage(message2); - const got3 = processResponseMessage(message3); - - expect(got1).toEqual({ - message: message1, - messageId: 0x1234, - type: 'SWVER', - value: '0.0.0', - }); - expect(got2).toEqual({ - message: message2, - messageId: 0x1234, - type: 'SWVER', - value: '99.99.99', - }); - expect(got3).toEqual({ - message: message3, - messageId: 0x1234, - type: 'SWVER', - value: '1.2.3', - }); - }); - - it('processes valid Hardware Versions responses', () => { - const message1 = 'R[1234]HWVER[0]'; - const message2 = 'R[1234]HWVER[9999]'; - - const got1 = processResponseMessage(message1); - const got2 = processResponseMessage(message2); - - expect(got1).toEqual({ - message: message1, - messageId: 0x1234, - type: 'HWVER', - value: 0, - }); - expect(got2).toEqual({ - message: message2, - messageId: 0x1234, - type: 'HWVER', - value: 9999, - }); - }); - - it('processes valid Zstart response', () => { - const message = 'R[1234]ZSTART[]'; - - const got = processResponseMessage(message); - - expect(got).toEqual({ - message: message, - messageId: 0x1234, - type: 'ZSTART', - value: '', - }); - }); - - it('processes valid Stop response', () => { - const message = 'R[1234]STOP[]'; - - const got = processResponseMessage(message); - - expect(got).toEqual({ - message: message, - messageId: 0x1234, - type: 'STOP', - value: '', - }); - }); - - it('processes valid Error response', () => { - const message = 'R[1234]ERROR[1]'; - - const got = processResponseMessage(message); - - expect(got).toEqual({ - message: message, - messageId: 0x1234, - type: 'ERROR', - value: 1, - }); - }); - - it('throws away messages that are not a response', () => { - // First a valid response to stablish a baseline - expect(processResponseMessage('R[0]STOP[]')).not.toBeUndefined(); - // Now non-response messages types - expect(processResponseMessage('C[0]STOP[]')).toBeUndefined(); - expect(processResponseMessage('P[0]STOP[]')).toBeUndefined(); - expect(processResponseMessage('P0E80495C4341')).toBeUndefined(); - // Invalid message types - expect(processResponseMessage('Z[0]STOP[]')).toBeUndefined(); - expect(processResponseMessage('9[0]STOP[]')).toBeUndefined(); - }); - - it('throws away response messages with invalid IDs', () => { - // First a valid response to stablish a baseline - expect(processResponseMessage('R[0]STOP[]')).not.toBeUndefined(); - // Now invalid IDs - expect(processResponseMessage('R[]STOP[]')).toBeUndefined(); - expect(processResponseMessage('R[123456789]STOP[]')).toBeUndefined(); - expect(processResponseMessage('R[12G]STOP[]')).toBeUndefined(); - expect(processResponseMessage('R[-1]STOP[]')).toBeUndefined(); - expect(processResponseMessage('R[1.1]STOP[]')).toBeUndefined(); - expect(processResponseMessage('R[word]STOP[]')).toBeUndefined(); - }); - - it('throws away response messages with invalid number values', () => { - // First valid messages to stablish a baseline - expect(processResponseMessage('R[0]RF[10000]')).not.toBeUndefined(); - expect(processResponseMessage('R[0]RMBID[4294967295]')).not.toBeUndefined(); - // Now invalid values - expect(processResponseMessage('R[0]RMBID[4294967296]')).toBeUndefined(); - expect(processResponseMessage('R[0]RF[-1]')).toBeUndefined(); - expect(processResponseMessage('R[0]RF[1-2]')).toBeUndefined(); - expect(processResponseMessage('R[0]RF[1,2]')).toBeUndefined(); - expect(processResponseMessage('R[0]RF[1F]')).toBeUndefined(); - expect(processResponseMessage('R[0]RF[word]')).toBeUndefined(); - }); - - it('throws away response messages with invalid version values', () => { - // First valid messages to stablish a baseline - expect(processResponseMessage('R[0]SWVER[1.1.1]')).not.toBeUndefined(); - // Now invalid values - expect(processResponseMessage('R[0]SWVER[1]')).toBeUndefined(); - expect(processResponseMessage('R[0]SWVER[1.1]')).toBeUndefined(); - expect(processResponseMessage('R[0]SWVER[123.1.1]')).toBeUndefined(); - expect(processResponseMessage('R[0]SWVER[1.123.1]')).toBeUndefined(); - expect(processResponseMessage('R[0]SWVER[1.1.123]')).toBeUndefined(); - expect(processResponseMessage('R[0]SWVER[11.22.AA]')).toBeUndefined(); - expect(processResponseMessage('R[0]SWVER[word]')).toBeUndefined(); - }); - - it('throws away response messages with invalid empty value', () => { - // First a valid response to stablish a baseline - expect(processResponseMessage('R[0]STOP[]')).not.toBeUndefined(); - // Now non-response messages types - expect(processResponseMessage('R[0]STOP[0]')).toBeUndefined(); - expect(processResponseMessage('R[0]STOP[-1]')).toBeUndefined(); - expect(processResponseMessage('R[0]STOP[a]')).toBeUndefined(); - }); - - it('throws away other types of invalid responses', () => { - expect(processResponseMessage('R0STOP[]')).toBeUndefined(); - expect(processResponseMessage('[0]STOP[]')).toBeUndefined(); - expect(processResponseMessage('R[0]STOP')).toBeUndefined(); - expect(processResponseMessage('R[0][]')).toBeUndefined(); - expect(processResponseMessage('R[0]')).toBeUndefined(); - expect(processResponseMessage('R[0]STOP[]STOP[]')).toBeUndefined(); - expect(processResponseMessage('R[0]R[1]STOP[]')).toBeUndefined(); - expect(processResponseMessage('R[0]STOP[]S')).toBeUndefined(); - expect(processResponseMessage('R[0]RF[1')).toBeUndefined(); - expect(processResponseMessage('R[0]RF1]')).toBeUndefined(); - }); -}); diff --git a/src/__tests__/smoothenXYZData.test.ts b/src/__tests__/smoothenXYZData.test.ts deleted file mode 100644 index 5bfca5b5a..000000000 --- a/src/__tests__/smoothenXYZData.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { smoothenXYZData } from '../script/smoothenXYZData'; - -describe('smoothenXYZData', () => { - test('smoothen empty data', () => { - const xyz = { - x: [], - y: [], - z: [], - }; - expect(smoothenXYZData(xyz)).toEqual(xyz); - }); - test('smoothen xyz data', () => { - const xyz = { - x: [1, 1, 1, 1, 1], - y: [4, 4, 12, 10, 10], - z: [8, 8, 24, 20, 20], - }; - const smoothXYZData = { - x: [1, 1, 1, 1, 1], - y: [4, 4, 6, 7, 7.75], - z: [8, 8, 12, 14, 15.5], - }; - expect(smoothenXYZData(xyz)).toEqual(smoothXYZData); - }); -}); diff --git a/src/__tests__/translations.test.ts b/src/__tests__/translations.test.ts deleted file mode 100644 index 323be2434..000000000 --- a/src/__tests__/translations.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import en from './../messages/ui.en.json'; -// TODO: actually import the right one! -const da = en; - -// We're moving translations to Crowdin so expect the English to be the only -// updated language for now -describe.skip('Translation tests', () => { - test('Should be same number of translations', () => { - const danishTranslationCount = Object.keys(da).length; - const englishTranslationCount = Object.keys(en).length; - expect(danishTranslationCount).toEqual(englishTranslationCount); - }); - - test('Translations are the same', () => { - const danishTranslationKeys = Object.getOwnPropertyNames(da) as (keyof typeof da)[]; - for (let i = 0; i < danishTranslationKeys.length; i++) { - const danishKey = danishTranslationKeys[i]; - // @ts-ignore - as translations are intentionally allowed to differ - expect(en[danishKey], 'Something not the same -> ' + danishKey).toBeDefined(); - } - }); - - // Mostly an architecture test - test('Translations should be in the same order', () => { - const danishTranslationKeys = Object.getOwnPropertyNames(da); - const englishTranslationKeys = Object.getOwnPropertyNames(en); - for (let i = 0; i < englishTranslationKeys.length; i++) { - const danishKey = danishTranslationKeys[i]; - const englishKey = englishTranslationKeys[i]; - expect(danishKey, `Not the same order found for -> ${danishKey}`).toEqual( - englishKey, - ); - } - }); -}); - -export {}; diff --git a/src/__tests__/unusedTranslations.test.ts b/src/__tests__/unusedTranslations.test.ts deleted file mode 100644 index e6c33fc85..000000000 --- a/src/__tests__/unusedTranslations.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import en from './../messages/ui.en.json'; -import * as fs from 'fs'; -import * as path from 'path'; - -const ignoredFiles: RegExp[] = [ - // Alphabetical ignore list - /^smoothie.js$/, - /^__tests__$/, - /^ui.[a-z-]+.json$/, -]; - -const readFile = (fileLocation: string, expect: string) => { - const fileContent = fs.readFileSync(fileLocation); - return fileContent.toString().toLowerCase().includes(expect.toLowerCase()); -}; - -type DirectoryContents = { - files: string[]; - folders: string[]; -}; - -const readDirectory = (directory: string, ignoreList: RegExp[]): DirectoryContents => { - const files: string[] = []; - const folders: string[] = []; - const filesRead = fs.readdirSync(directory); - filesRead.forEach(file => { - if (ignoreList.some(pattern => pattern.test(file))) return; - const fileLocation = path.join(directory, file); - const stats = fs.statSync(fileLocation); - if (stats.isFile()) { - files.push(fileLocation); - } else { - folders.push(fileLocation); - } - }); - return { files: files, folders: folders }; -}; - -const flattenDirectory = (directory: string): string[] => { - const files: string[] = []; - const content = readDirectory(directory, ignoredFiles); - const filesFromSubFolders: string[] = []; - content.folders.forEach(value => { - const subFolderFlat = flattenDirectory(value); - subFolderFlat.forEach(value => filesFromSubFolders.push(value)); - }); - filesFromSubFolders.forEach(value => files.push(value)); - content.files.forEach(value => files.push(value)); - return files; -}; - -const filesIncludesExpression = (files: string[], expect: string): boolean => { - for (let i = 0; i < files.length; i++) { - if (readFile('./' + files[i], expect)) { - return true; - } - } - return false; -}; - -test( - 'All translations should be used', - () => { - const allowedUnused = [ - // We have some strings we plan to reinstate - /^popup.outdatedmicrobit/, - ]; - const translationKeys = Object.getOwnPropertyNames(en); - const flatten = flattenDirectory('./src/'); - for (let i = 0; i < translationKeys.length; i++) { - const translationKey = translationKeys[i]; - if (allowedUnused.some(regexp => regexp.test(translationKey))) { - continue; - } - expect( - filesIncludesExpression(flatten, translationKey), - "unused translation --> '" + - translationKey + - "' \n confirm with command .. \n grep -rnw ./src -e '" + - translationKey + - "'", - ).toEqual(true); - } - }, - 60000 * 10, -); diff --git a/src/__viteBuildVariants__/ml-machine/windi.config.js b/src/__viteBuildVariants__/ml-machine/windi.config.js deleted file mode 100644 index f9daee663..000000000 --- a/src/__viteBuildVariants__/ml-machine/windi.config.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -//Ml-Machine colors - -export default { - theme: { - extend: { - colors: { - primary: '#2c92d6', - primarytext: '#000000', - secondary: '#a0a0a0', - secondarytext: '#FFFFFF', - info: '#98A2B3', - backgrounddark: '#F5F5F5', - backgroundlight: '#ffffff', - infolight: '#93c5fd', - link: '#6c4bc1', - warning: '#cd0365', - disabled: '#8892A3', - primaryborder: '#E5E7EB', - infobglight: '#E7E5E4', - infobgdark: '#57534E', - infoiconlight: '#FFFFFF7F', - infoicondark: '#787878', - infotextlight: '#ffffff', - infotextdark: '#787878', - }, - }, - }, -}; diff --git a/src/components/3d-inspector/RecordingInspector.svelte b/src/components/3d-inspector/RecordingInspector.svelte deleted file mode 100644 index 957359dcb..000000000 --- a/src/components/3d-inspector/RecordingInspector.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - - - -{#if isOpen} -
      -
      - -
      -
      -{/if} diff --git a/src/components/3d-inspector/View3D.svelte b/src/components/3d-inspector/View3D.svelte deleted file mode 100644 index 2e13037a4..000000000 --- a/src/components/3d-inspector/View3D.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - - -{#if webGlCompatible} - -{:else} -
      -
      - {$t('compatibility.webgl.notSupported')} -
      -
      -{/if} diff --git a/src/components/3d-inspector/View3DLive.svelte b/src/components/3d-inspector/View3DLive.svelte deleted file mode 100644 index 1c99df7a7..000000000 --- a/src/components/3d-inspector/View3DLive.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - - - - diff --git a/src/components/3d-inspector/View3DUnsafe.svelte b/src/components/3d-inspector/View3DUnsafe.svelte deleted file mode 100644 index ad9ca878c..000000000 --- a/src/components/3d-inspector/View3DUnsafe.svelte +++ /dev/null @@ -1,212 +0,0 @@ - - - - -
      - -
      diff --git a/src/components/3d-inspector/View3DUtility.ts b/src/components/3d-inspector/View3DUtility.ts deleted file mode 100644 index 3d292b4e7..000000000 --- a/src/components/3d-inspector/View3DUtility.ts +++ /dev/null @@ -1,158 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { writable } from 'svelte/store'; -import * as THREE from 'three'; -import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; - -// TODO: Why is this a class? No methods mutates object state, and -// only one method uses class state. -// Refacor to pure functions and simply create the loader where needed -class Live3DUtility { - private loader: GLTFLoader; - - constructor() { - this.loader = new GLTFLoader(); - } - - // TODO: Fix naming - createSceneWith(array: THREE.Object3D[]) { - return new THREE.Scene().add(...array); - } - - instantiateRenderer(canvas: HTMLCanvasElement, width: number, height: number) { - const renderer = new THREE.WebGLRenderer({ antialias: true, canvas }); - renderer.setClearColor('#ffffff', 0); - renderer.setSize(width, height); - renderer.outputColorSpace = THREE.SRGBColorSpace; - renderer.toneMapping = THREE.ACESFilmicToneMapping; - renderer.toneMappingExposure = 1.5; - renderer.setPixelRatio(window.devicePixelRatio); - - return renderer; - } - - instantiateLighting() { - const light1 = new THREE.PointLight(0xffffff, 0.7, 250); // 100, 5000 - light1.position.set(3, 6, 3); - light1.lookAt(new THREE.Vector3(-3, -6, -3)); - - const light2 = new THREE.PointLight(0xffffff, 0.7, 250); // 100, 5000 - light2.position.set(5, 0, 5); - light2.lookAt(new THREE.Vector3(-5, 0, -5)); - - return this.createSceneWith([light1, light2]); - } - - setupLightingIn(scene: THREE.Scene): void { - scene.add(this.instantiateLighting()); - } - - instantiateCameraSetup(width: number, height: number, perspective = 85) { - const camera = new THREE.PerspectiveCamera(perspective, width / height, 0.1, 1000); - - // Position - camera.position.z = 5; - camera.position.x = 5; - camera.position.y = 4; - - // Rotation - camera.lookAt(new THREE.Vector3(-5, -2, -5)); - - return camera; - } - - /** - * Apply pose to a mesh - * @param mesh to which the pose is applied - * @param pos the new position - * @param dir the new direction - */ - applyPose( - mesh: THREE.Mesh, // TODO: Fix any - pos: THREE.Vector3, - dir: THREE.Vector3, - ) { - mesh.position.set(pos.x, pos.y, pos.z); - mesh.rotation.x = this.toRadian(dir.x); - mesh.rotation.y = this.toRadian(dir.y); - mesh.rotation.z = this.toRadian(dir.z); - } - - toRadian(degrees: number) { - return (degrees / 180) * Math.PI; - } - - instantiateBar( - color: THREE.ColorRepresentation, - ): THREE.Mesh { - const material = new THREE.MeshLambertMaterial({ color }); - const cylinder = new THREE.CylinderGeometry(0.4, 0.4, 2, 16); - const cylinderMesh = new THREE.Mesh(cylinder, material); - return cylinderMesh; - } - - /** - * Instantiate and add three bars to a scene. Used for visualising three data points in 3D - * @param scene the scene in which the bars should be added - * @returns The bars - */ - setupBarsIn(scene: THREE.Scene): Bars { - const barX = this.instantiateBar(0xff1515); - const barY = this.instantiateBar(0x10ff10); - const barZ = this.instantiateBar(0x2222ff); - scene.add(barX, barY, barZ); - this.applyPose(barX, new THREE.Vector3(-4, -5, -5), new THREE.Vector3(0, 0, 90)); - this.applyPose(barY, new THREE.Vector3(-5, -5, -4), new THREE.Vector3(90, 0, 0)); - this.applyPose(barZ, new THREE.Vector3(-5, -4, -5), new THREE.Vector3(0, 90, 0)); - - return { barX, barY, barZ }; - } - - async loadMicrobitModel(onProgress?: (event: ProgressEvent) => void) { - return new Promise((resolve, reject) => { - // When loaded. Extract the actual model from the loaded model. Rotate it properly. Resolve. - const onFinished = (gltf: GLTF) => { - const model = gltf.scene.children[0].children[0].children[0]; - const group = new THREE.Scene(); - group.add(model); - model.lookAt(0, 0, 1); - resolve(group); - }; - - this.loader.load( - `${import.meta.env.BASE_URL}models/microbit.gltf`, - onFinished, - onProgress, - reject, - ); - }); - } -} - -export type Bars = { - barX: THREE.Mesh; - barY: THREE.Mesh; - barZ: THREE.Mesh; -}; - -export type Vector3 = { - x: number; - y: number; - z: number; -}; - -export default Live3DUtility; - -export const graphInspectorState = writable<{ - isOpen: boolean; - dataPoint: Vector3; - inspectorPosition: { x: number; y: number }; -}>({ - isOpen: false, - dataPoint: { x: 0, y: 0, z: 0 }, - inspectorPosition: { x: 0, y: 0 }, -}); diff --git a/src/components/DialogHeading.svelte b/src/components/DialogHeading.svelte deleted file mode 100644 index b50e20ea7..000000000 --- a/src/components/DialogHeading.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -

      diff --git a/src/components/FrontPageContentTile.svelte b/src/components/FrontPageContentTile.svelte deleted file mode 100644 index 650099684..000000000 --- a/src/components/FrontPageContentTile.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - -
      - -
      diff --git a/src/components/Gesture.svelte b/src/components/Gesture.svelte deleted file mode 100644 index 596bc669c..000000000 --- a/src/components/Gesture.svelte +++ /dev/null @@ -1,388 +0,0 @@ - - - - - - - - - - {$t('content.data.recordingDialog.title', { values: { action: $nameBind } })} - - -
      -
      - {#if countdownIdx < countdownConfigs.length} -

      - {countdownConfigs[countdownIdx].value} -

      - {:else} -

      - {$t('content.data.recordingDialog.recording')} -

      - {/if} -
      - -
      -
      -
      -
      - {$t('content.data.recording.button.cancel')} - - - - - -
      - -
      - {#if !showAddActionWalkThrough} -
      - - - -
      - {/if} - - -
      -
      -
      - -{#if showAddActionWalkThrough} -
      - -

      - {$t('content.data.addActionWalkThrough')} -

      -
      -{:else} - -
      - -
      -
      - - - -
      - {#if hasRecordings} - {#each $gesture.recordings as recording (String($gesture.ID) + String(recording.ID))} - - {/each} - {/if} -
      -
      -
      -{/if} - -{#if isGestureNamed && showWalkThrough && !hasRecordings && !showCountdown && !isThisRecording} - -
      -
      -
      - -

      - {$t('content.data.addRecordingWalkThrough')} -

      -
      -
      -{/if} diff --git a/src/components/GestureTilePart.svelte b/src/components/GestureTilePart.svelte deleted file mode 100644 index a9bdaaa16..000000000 --- a/src/components/GestureTilePart.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - - - -
      - -
      diff --git a/src/components/HtmlFormattedMessage.svelte b/src/components/HtmlFormattedMessage.svelte deleted file mode 100644 index 8a3166f55..000000000 --- a/src/components/HtmlFormattedMessage.svelte +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - {@html $t(id, adaptedOptions)} - diff --git a/src/components/HtmlFormattedMessage.test.ts b/src/components/HtmlFormattedMessage.test.ts deleted file mode 100644 index 09c01c7ef..000000000 --- a/src/components/HtmlFormattedMessage.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import '@testing-library/jest-dom'; - -import { render, screen } from '@testing-library/svelte'; -import { IntlMessageFormat } from 'intl-messageformat'; - -import HtmlFormattedMessage from './HtmlFormattedMessage.svelte'; - -const messages: Record = { - simple: 'An example', - link: 'Before linked After', - html: "'Before 'Bold'", -}; - -const tValue = (id: string, options: any) => { - // Skipped the svelte-i18n layer but tested the XML-like parsing behaviour below it - return new IntlMessageFormat( - messages[id] ?? 'Unknown', - undefined, - undefined, - options, - ).format(options.values); -}; - -vitest.mock('../i18n', () => ({ - t: { - subscribe(cb: any) { - cb(tValue); - return () => {}; - }, - }, -})); - -describe('HtmlFormattedMessage', () => { - it('renders simple example', () => { - render(HtmlFormattedMessage, { id: 'simple' }); - const dom = screen.getByText('An example'); - expect(dom).toMatchInlineSnapshot(` - - An example - - `); - }); - - it('renders a link', () => { - render(HtmlFormattedMessage, { - id: 'link', - options: { - values: { - link: (chunks: string[]) => { - const link = document.createElement('a'); - Object.assign(link, { - href: '/', - innerText: chunks.join(''), - }); - return link; - }, - }, - }, - }); - const dom = screen.getByText(/Before/); - expect(dom).toMatchInlineSnapshot(` - - Before - - After - - `); - }); - - it('needs care due to lack of html escaping', () => { - render(HtmlFormattedMessage, { - id: 'html', - }); - const dom = screen.getByText(/Bold/); - expect(dom).toMatchInlineSnapshot(` - - Bold - - `); - }); -}); diff --git a/src/components/IconButton.svelte b/src/components/IconButton.svelte deleted file mode 100644 index cfa048b15..000000000 --- a/src/components/IconButton.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - diff --git a/src/components/LinkOverlay.svelte b/src/components/LinkOverlay.svelte deleted file mode 100644 index b5986e64f..000000000 --- a/src/components/LinkOverlay.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - -{#if isPathType(onClickOrHrefOrPath)} - - - -{:else if typeof onClickOrHrefOrPath === 'string'} - - - -{:else} - -{/if} diff --git a/src/components/LinkOverlayContainer.svelte b/src/components/LinkOverlayContainer.svelte deleted file mode 100644 index 46ced54c9..000000000 --- a/src/components/LinkOverlayContainer.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - -
      - -
      diff --git a/src/components/LoadingBar.svelte b/src/components/LoadingBar.svelte deleted file mode 100644 index c8f138642..000000000 --- a/src/components/LoadingBar.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - -
      -
      -
      diff --git a/src/components/LoadingBlobs.svelte b/src/components/LoadingBlobs.svelte deleted file mode 100644 index 82770284e..000000000 --- a/src/components/LoadingBlobs.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - - - -
      -
      -
      diff --git a/src/components/LoadingSpinner.svelte b/src/components/LoadingSpinner.svelte deleted file mode 100644 index fd5291c0e..000000000 --- a/src/components/LoadingSpinner.svelte +++ /dev/null @@ -1,9 +0,0 @@ - -
      -
      -
      diff --git a/src/components/MediaQuery.svelte b/src/components/MediaQuery.svelte deleted file mode 100644 index afba71ea2..000000000 --- a/src/components/MediaQuery.svelte +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/src/components/MenuTransition.svelte b/src/components/MenuTransition.svelte deleted file mode 100644 index e950ed94e..000000000 --- a/src/components/MenuTransition.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/src/components/NewGestureButton.svelte b/src/components/NewGestureButton.svelte deleted file mode 100644 index db42e70e5..000000000 --- a/src/components/NewGestureButton.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - - - -
      - {$t('content.data.addAction')} -
      -
      diff --git a/src/components/PatternBox.svelte b/src/components/PatternBox.svelte deleted file mode 100644 index 1f7f6edb6..000000000 --- a/src/components/PatternBox.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - -
      diff --git a/src/components/PatternColumnInput.svelte b/src/components/PatternColumnInput.svelte deleted file mode 100644 index ad5a57074..000000000 --- a/src/components/PatternColumnInput.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - - - -
      - - -
      diff --git a/src/components/PatternMatrix.svelte b/src/components/PatternMatrix.svelte deleted file mode 100644 index 564225410..000000000 --- a/src/components/PatternMatrix.svelte +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - -
      - - {#each matrixColumns as column, colIdx} -
      - {#each column as isOn, rowIdx} - { - clearHighlightedColumns(); - updateMatrix(colIdx, rowIdx); - }} - on:mouseenter={() => { - highlightedColumns = getHighlightedColumns(matrixColumns, { colIdx, rowIdx }); - }} - on:mouseleave={clearHighlightedColumns} /> - {/each} - c).length === 0} - {colIdx} - on:keydown={e => { - onKeyDownColumnInput(e, colIdx); - }} - value={column.filter(c => c).length} /> -
      - {/each} -
      diff --git a/src/components/PleaseConnectFirst.svelte b/src/components/PleaseConnectFirst.svelte deleted file mode 100644 index 98e15705f..000000000 --- a/src/components/PleaseConnectFirst.svelte +++ /dev/null @@ -1,48 +0,0 @@ - - - - -
      -

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

      -

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

      -
      - {$t( - $state.showConnectHelp || Microbits.getInputMicrobit() - ? 'actions.reconnect' - : 'footer.connectButton', - )} -
      diff --git a/src/components/PrototypeVersionWarning.svelte b/src/components/PrototypeVersionWarning.svelte deleted file mode 100644 index 88cfa48ad..000000000 --- a/src/components/PrototypeVersionWarning.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - -
      - -

      {$t('prototype.warning')}

      -
      diff --git a/src/components/ReconnectHelp.svelte b/src/components/ReconnectHelp.svelte deleted file mode 100644 index d36ce9c40..000000000 --- a/src/components/ReconnectHelp.svelte +++ /dev/null @@ -1,135 +0,0 @@ - - - - -{#if $state.reconnectState.connectionType !== 'none'} - - - {$t(content.heading)} - - -

      {$t(content.subtitle)}

      -
      -

      {$t(content.listHeading)}

      -
        -
      • {$t(content.bulletOne)}
      • -
      • {$t(content.bulletTwo)}
      • -
      -
      - -
      - - {$t('connectMB.troubleshooting')} - - - {$t('actions.cancel')} - {$t( - $state.showConnectHelp === 'connect' - ? 'footer.connectButton' - : 'actions.reconnect', - )} -
      -
      -
      -{/if} diff --git a/src/components/Recording.svelte b/src/components/Recording.svelte deleted file mode 100644 index e3702e31e..000000000 --- a/src/components/Recording.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - - - -
      - - -
      - onDelete(recording)} - on:focus> - - -
      -
      diff --git a/src/components/ResourcePageLayout.svelte b/src/components/ResourcePageLayout.svelte deleted file mode 100644 index 58a6b28e6..000000000 --- a/src/components/ResourcePageLayout.svelte +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - -
      -
      -
      -
      - Home - -

      - {title} -

      -
      -

      {title}

      -
      -
      - -
      -
      -
      - - -
      -
      -
      -
      diff --git a/src/components/StandardButton.svelte b/src/components/StandardButton.svelte deleted file mode 100644 index 119c2e439..000000000 --- a/src/components/StandardButton.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - -
      - -
      diff --git a/src/components/StartResumeActions.svelte b/src/components/StartResumeActions.svelte deleted file mode 100644 index c91046b27..000000000 --- a/src/components/StartResumeActions.svelte +++ /dev/null @@ -1,98 +0,0 @@ - - - - -
      - {#if hasExistingSession} - navigate(Paths.DATA)} - >{$t('footer.resume')} - {/if} - {$t('footer.start')} -
      - - (showDataLossWarning = false)} - class="w-150 space-y-5"> - - {$t('content.index.dataWarning.title')} - - -
      -

      {$t('content.index.dataWarning.subtitleOne')}

      -

      - -

      -
      - {$t('footer.start')} -
      -
      -
      -
      diff --git a/src/components/TrainModelFirstTitle.svelte b/src/components/TrainModelFirstTitle.svelte deleted file mode 100644 index 549a20782..000000000 --- a/src/components/TrainModelFirstTitle.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - -
      - -

      - {$t('content.model.trainModelFirstHeading')} -

      -
      - {#if !sufficientData} -

      - {$t('content.model.notEnoughDataInfoBody1')} -

      -

      - {$t('content.model.notEnoughDataInfoBody2')} -

      - {:else if $state.hasTrainedBefore} -

      - {$t('content.model.retrainModelBody')} -

      - {:else} -

      - {$t('content.model.trainModelBody')} -

      - {/if} -
      -
      - {#if !sufficientData} - navigate(Paths.DATA)}> - {$t('content.model.addData')} - - {:else} - navigate(Paths.TRAINING)} /> - {/if} -
      -
      diff --git a/src/components/TrainingStatusSection.svelte b/src/components/TrainingStatusSection.svelte deleted file mode 100644 index 4d4594daa..000000000 --- a/src/components/TrainingStatusSection.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - - - -
      -
      -

      - {$t(statusId)} -

      - {#if descriptionId} -

      {$t(descriptionId)}

      - {/if} -
      - -
      diff --git a/src/components/bottom/BottomPanel.svelte b/src/components/bottom/BottomPanel.svelte deleted file mode 100644 index 4923950e5..000000000 --- a/src/components/bottom/BottomPanel.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - - - -
      -
      -
      -
      - - - - {#if $state.reconnectState.reconnecting && $state.isInputConnected} -
      -

      {$t('connectMB.reconnecting')}

      -
      - {/if} -
      - -
      - {#if live3dViewVisible} - - -
      (isLive3DOpen = true)}> - -
      - (isLive3DOpen = false)}> - -
      -
      - -
      -
      -
      - {/if} -
      -
      - -
      -
      diff --git a/src/components/bottom/ConnectedLiveGraphButtons.svelte b/src/components/bottom/ConnectedLiveGraphButtons.svelte deleted file mode 100644 index e9aef7410..000000000 --- a/src/components/bottom/ConnectedLiveGraphButtons.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - - - - -{#if $state.isPredicting || $state.isTraining || $state.isOutputConnected} - {#if Microbits.getOutputMicrobit()} - - {#if !$state.isOutputConnected || $state.isOutputReady} - - {$t('menu.model.disconnect')} - {:else} - {$t('menu.model.connect')} - {/if} - {/if} -{/if} -{#if !$state.isInputConnected} - {$t( - $state.showConnectHelp || Microbits.getInputMicrobit() - ? 'actions.reconnect' - : 'footer.connectButton', - )} -{:else} - {$t('footer.disconnectButton')} -{/if} diff --git a/src/components/bottom/LiveGraphInformationSection.svelte b/src/components/bottom/LiveGraphInformationSection.svelte deleted file mode 100644 index 4da8a72b8..000000000 --- a/src/components/bottom/LiveGraphInformationSection.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - -
      - -

      LIVE

      -
      diff --git a/src/components/connection-prompt/ConnectDialogContainer.svelte b/src/components/connection-prompt/ConnectDialogContainer.svelte deleted file mode 100644 index 619fdac0a..000000000 --- a/src/components/connection-prompt/ConnectDialogContainer.svelte +++ /dev/null @@ -1,405 +0,0 @@ - - - - -
      - - {#if $connectionDialogState.connectionState === ConnectDialogStates.START_RADIO} - { - $connectionDialogState.connectionState = - ConnectDialogStates.START_BLUETOOTH; - flashStage = 'bluetooth'; - } - : undefined} - onNextClick={() => { - $connectionDialogState.connectionState = ConnectDialogStates.CONNECT_CABLE; - flashStage = 'radio-remote'; - }} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.START_BLUETOOTH} - { - $connectionDialogState.connectionState = ConnectDialogStates.START_RADIO; - flashStage = 'radio-remote'; - } - : undefined} - onNextClick={() => - ($connectionDialogState.connectionState = ConnectDialogStates.CONNECT_CABLE)} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.CONNECT_CABLE} - {#if flashStage === 'bluetooth'} - - ($connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_BATTERY)} - onBackClick={() => - ($connectionDialogState.connectionState = - ConnectDialogStates.START_BLUETOOTH)} - onNextClick={() => - usb - ? ($connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_TUTORIAL_USB) - : ($connectionDialogState.connectionState = - ConnectDialogStates.MANUAL_TUTORIAL)} /> - {:else if flashStage === 'radio-remote'} - { - $connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_BATTERY; - flashStage = 'radio-bridge'; - } - : undefined} - onBackClick={() => - ($connectionDialogState.connectionState = ConnectDialogStates.START_RADIO)} - onNextClick={() => { - $connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_TUTORIAL_USB; - flashStage = 'radio-remote'; - }} /> - {:else if flashStage === 'radio-bridge'} - { - $connectionDialogState.connectionState = - ConnectDialogStates.START_BLUETOOTH; - flashStage = 'bluetooth'; - } - : undefined} - onBackClick={() => { - $connectionDialogState.connectionState = ConnectDialogStates.CONNECT_BATTERY; - flashStage = 'radio-remote'; - }} - onNextClick={() => - ($connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_TUTORIAL_USB)} /> - {/if} - {:else if $connectionDialogState.connectionState === ConnectDialogStates.CONNECT_TUTORIAL_USB} - - ($connectionDialogState.connectionState = ConnectDialogStates.CONNECT_CABLE)} - onNextClick={tryMicrobitUSBConnection} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.CONNECT_BATTERY} - {#if flashStage === 'bluetooth'} - - usb - ? ($connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_TUTORIAL_USB) - : ($connectionDialogState.connectionState = - ConnectDialogStates.MANUAL_TUTORIAL)} - onNextClick={() => - ($connectionDialogState.connectionState = ConnectDialogStates.BLUETOOTH)} /> - {:else} - { - $connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_TUTORIAL_USB; - flashStage = 'radio-remote'; - }} - onNextClick={() => { - $connectionDialogState.connectionState = ConnectDialogStates.CONNECT_CABLE; - flashStage = 'radio-bridge'; - }} /> - {/if} - {:else if $connectionDialogState.connectionState === ConnectDialogStates.BLUETOOTH} - { - $connectionDialogState.connectionState = ConnectDialogStates.CONNECT_BATTERY; - }} - onNextClick={() => { - $connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_TUTORIAL_BLUETOOTH; - }} - deviceState={$connectionDialogState.deviceState} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.CONNECT_TUTORIAL_BLUETOOTH} - - ($connectionDialogState.connectionState = ConnectDialogStates.BLUETOOTH)} - onNextClick={tryMicrobitBluetoothConnection} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.BLUETOOTH_CONNECTING} - - {:else if $connectionDialogState.connectionState === ConnectDialogStates.CONNECTING_MICROBITS} - - {:else if $connectionDialogState.connectionState === ConnectDialogStates.BAD_FIRMWARE} - - ($connectionDialogState.connectionState = ConnectDialogStates.MANUAL_TUTORIAL)} - onTryAgain={() => - ($connectionDialogState.connectionState = ConnectDialogStates.CONNECT_CABLE)} - onCancel={endFlow} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.USB_DOWNLOADING} - - {:else if $connectionDialogState.connectionState === ConnectDialogStates.MANUAL_TUTORIAL} - - usb - ? ($connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_TUTORIAL_USB) - : ($connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_CABLE)} - onNextClick={() => - ($connectionDialogState.connectionState = - ConnectDialogStates.CONNECT_BATTERY)} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.USB_TRY_AGAIN} - { - $connectionDialogState.connectionState = ConnectDialogStates.CONNECT_CABLE; - }} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.BLUETOOTH_TRY_AGAIN} - { - $connectionDialogState.connectionState = ConnectDialogStates.BLUETOOTH; - }} /> - {:else if $connectionDialogState.connectionState === ConnectDialogStates.MICROBIT_UNSUPPORTED} - { - $connectionDialogState.connectionState = ConnectDialogStates.START_BLUETOOTH; - }} - onClose={endFlow} /> - {/if} - -
      diff --git a/src/components/connection-prompt/ConnectSameDialog.svelte b/src/components/connection-prompt/ConnectSameDialog.svelte deleted file mode 100644 index b4dada3af..000000000 --- a/src/components/connection-prompt/ConnectSameDialog.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - - - -
      -
      -

      - {$t('connectMB.output.header')} -

      -
      -
      - -
      -

      - {$t('connectMB.outputMB.same')} -

      - - {$t('connectMB.outputMB.sameButton')} -
      - -
      -

      - {$t('connectMB.outputMB.different')} -

      - - {$t('connectMB.outputMB.otherButton')} -
      -
      -
      diff --git a/src/components/connection-prompt/WebBluetoothTryAgain.svelte b/src/components/connection-prompt/WebBluetoothTryAgain.svelte deleted file mode 100644 index 3b6076650..000000000 --- a/src/components/connection-prompt/WebBluetoothTryAgain.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - - - -
      - - {$t('connectMB.bluetooth.heading')} - -
      -

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

      -
      - {$t('actions.cancel')} - {$t('connectMB.tryAgain')} -
      -
      -
      diff --git a/src/components/connection-prompt/WebUsbTryAgain.svelte b/src/components/connection-prompt/WebUsbTryAgain.svelte deleted file mode 100644 index bf342dc7e..000000000 --- a/src/components/connection-prompt/WebUsbTryAgain.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - -
      - {$t('connectMB.usbTryAgain.heading')} -
      - {#if type === 'select microbit'} -

      {$t('connectMB.usbTryAgain.selectMicrobit')}

      - {:else if type === 'close tabs'} -

      {$t('connectMB.usbTryAgain.closeTabs1')}

      -

      {$t('connectMB.usbTryAgain.closeTabs2')}

      - {:else} -

      {$t('connectMB.usbTryAgain.replugMicrobit1')}

      - -
      -

      {$t('connectMB.usbTryAgain.replugMicrobit2')}

      -
        -
      • {$t('connectMB.usbTryAgain.replugMicrobit3')}

      • -
      • {$t('connectMB.usbTryAgain.replugMicrobit4')}

      • -
      -
      -
        - {/if} -
        - {$t('actions.cancel')} - {$t('connectMB.tryAgain')} -
        -
        -
        diff --git a/src/components/connection-prompt/WhatYouWillNeedDialog.svelte b/src/components/connection-prompt/WhatYouWillNeedDialog.svelte deleted file mode 100644 index ff517044a..000000000 --- a/src/components/connection-prompt/WhatYouWillNeedDialog.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - - - -
        - {$t(headingId)} -

        - {#if $state.reconnectState.reconnectFailed} - {$t('reconnectFailed.subtitle')} - {/if} -

        -
        - {#each items as item} -
        - -

        - {$t(item.titleId)} -

        - {#if item.subtitleId} -

        {$t(item.subtitleId)}

        - {/if} -
        - {/each} -
        - -
        -
        - {#if onSwitchClick} - - {$t(switchTextId)} - - {/if} - {#if $state.reconnectState.reconnectFailed} - - {$t('connectMB.troubleshoot')} - - - {/if} -
        - {$t('connectMB.nextButton')} -
        -
        diff --git a/src/components/connection-prompt/arrows/ArrowOne.svelte b/src/components/connection-prompt/arrows/ArrowOne.svelte deleted file mode 100644 index 92e947f56..000000000 --- a/src/components/connection-prompt/arrows/ArrowOne.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -
        -

        1

        -
        -
        -
        diff --git a/src/components/connection-prompt/arrows/ArrowTwo.svelte b/src/components/connection-prompt/arrows/ArrowTwo.svelte deleted file mode 100644 index 09820d556..000000000 --- a/src/components/connection-prompt/arrows/ArrowTwo.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - -
        -

        2

        -
        -
        -
        diff --git a/src/components/connection-prompt/bluetooth/BluetoothConnectDialog.svelte b/src/components/connection-prompt/bluetooth/BluetoothConnectDialog.svelte deleted file mode 100644 index 033f846b4..000000000 --- a/src/components/connection-prompt/bluetooth/BluetoothConnectDialog.svelte +++ /dev/null @@ -1,74 +0,0 @@ - - - - -
        - - {$t('connectMB.pattern.heading')} - -

        {$t('connectMB.pattern.subtitle')}

        -
        -
        - -
        -

        - {$t('connectMB.bluetooth.invalidPattern')} -

        -
        -
        - {$t('connectMB.backButton')} - {$t('connectMB.nextButton')} -
        -
        diff --git a/src/components/connection-prompt/bluetooth/BluetoothConnectingDialog.svelte b/src/components/connection-prompt/bluetooth/BluetoothConnectingDialog.svelte deleted file mode 100644 index 5c3653ac5..000000000 --- a/src/components/connection-prompt/bluetooth/BluetoothConnectingDialog.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - -
        - - {$t('connectMB.bluetooth.heading')} - -
        -

        {$t('connectMB.connecting')}

        - -
        -
        diff --git a/src/components/connection-prompt/bluetooth/ConnectBatteryDialog.svelte b/src/components/connection-prompt/bluetooth/ConnectBatteryDialog.svelte deleted file mode 100644 index cabf2ded6..000000000 --- a/src/components/connection-prompt/bluetooth/ConnectBatteryDialog.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - - - -
        - - {$t('connectMB.connectBattery.heading')} - -
        -

        - {$t('connectMB.connectBattery.subtitle')} - - {$t('connectMB.connectBattery.link')} - - -

        - -
        -
        -
        - {$t('connectMB.backButton')} - {$t('connectMB.nextButton')} -
        diff --git a/src/components/connection-prompt/bluetooth/ConnectCableDialog.svelte b/src/components/connection-prompt/bluetooth/ConnectCableDialog.svelte deleted file mode 100644 index 910d7ac4d..000000000 --- a/src/components/connection-prompt/bluetooth/ConnectCableDialog.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - - - -
        - - {$t(titleId)} - -
        -

        - {$t(subtitleId)} -

        - {$t('connectMB.connectCable.altText')} -
        -
        -
        - {#if onSkipClick} - {$t('connectMB.connectCable.skip')} - {/if} - {#if onAltClick && altClickId} - {$t(altClickId)} - {/if} -
        - {$t('connectMB.backButton')} - {$t('connectMB.nextButton')} -
        -
        diff --git a/src/components/connection-prompt/bluetooth/SelectMicrobitDialogBluetooth.svelte b/src/components/connection-prompt/bluetooth/SelectMicrobitDialogBluetooth.svelte deleted file mode 100644 index 1d5777d13..000000000 --- a/src/components/connection-prompt/bluetooth/SelectMicrobitDialogBluetooth.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - - - -
        - - {$t('connectMB.webPopup')} - -
        - {$t('connectMB.webPopup.webBluetooth.altText')} -
        -

        - {$t('connectMB.webPopup.instruction.heading')}: -

        -
          -
        1. 1. {$t('connectMB.webPopup.instruction1')}
        2. -
        3. - 2. - {$t('connectMB.webPopup.webBluetooth.instruction2')} -
        4. -
        -
        -
        - -
        -
        - -
        -
        -
        -
        - {$t('connectMB.backButton')} - {$t('connectMB.nextButton')} -
        diff --git a/src/components/connection-prompt/bluetooth/StartBluetoothDialog.svelte b/src/components/connection-prompt/bluetooth/StartBluetoothDialog.svelte deleted file mode 100644 index 8ab58f7cd..000000000 --- a/src/components/connection-prompt/bluetooth/StartBluetoothDialog.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - - - - diff --git a/src/components/connection-prompt/radio/ConnectingMicrobits.svelte b/src/components/connection-prompt/radio/ConnectingMicrobits.svelte deleted file mode 100644 index 307ece23e..000000000 --- a/src/components/connection-prompt/radio/ConnectingMicrobits.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - -
        - - {$t('connectMB.radio.heading')} - -
        -

        {$t('connectMB.connecting')}

        - -
        -
        diff --git a/src/components/connection-prompt/radio/StartRadioDialog.svelte b/src/components/connection-prompt/radio/StartRadioDialog.svelte deleted file mode 100644 index ed176e472..000000000 --- a/src/components/connection-prompt/radio/StartRadioDialog.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - - - - diff --git a/src/components/connection-prompt/usb/BrokenFirmwareDetected.svelte b/src/components/connection-prompt/usb/BrokenFirmwareDetected.svelte deleted file mode 100644 index 01890c141..000000000 --- a/src/components/connection-prompt/usb/BrokenFirmwareDetected.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - - - -
        - {$t('connectMB.usb.firmwareBroken.heading')} -
        -

        - {$t('connectMB.usb.firmwareBroken.content1')} -

        -

        - -

        -

        - - {$t('connectMB.usb.firmwareBroken.content3')} - - -

        -
        - {$t('actions.cancel')} - {#if flashStage === 'bluetooth'} - {$t('connectMB.usb.firmwareBroken.skip')} - {/if} - {$t('connectMB.tryAgain')} -
        -
        -
        diff --git a/src/components/connection-prompt/usb/DownloadingDialog.svelte b/src/components/connection-prompt/usb/DownloadingDialog.svelte deleted file mode 100644 index 7c069c1cc..000000000 --- a/src/components/connection-prompt/usb/DownloadingDialog.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - - -
        -

        - {#if flashStage === 'bluetooth'} - {$t('connectMB.usbDownloading.header')} - {:else if flashStage === 'radio-remote'} - {$t('connectMB.usbDownloadingMB1.header')} - {:else if flashStage === 'radio-bridge'} - {$t('connectMB.usbDownloadingMB2.header')} - {/if} -

        -

        - {$t('connectMB.usbDownloading.subtitle')} -

        -
        -
        - -
        -
        -
        diff --git a/src/components/connection-prompt/usb/SelectMicrobitDialogUsb.svelte b/src/components/connection-prompt/usb/SelectMicrobitDialogUsb.svelte deleted file mode 100644 index be4b5ee13..000000000 --- a/src/components/connection-prompt/usb/SelectMicrobitDialogUsb.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - - - -
        - - {$t('connectMB.webPopup')} - -
        - {$t('connectMB.webPopup.webUsb.altText')} -
        -

        - {$t('connectMB.webPopup.instruction.heading')}: -

        -
          -
        1. 1. {$t('connectMB.webPopup.instruction1')}
        2. -
        3. - 2. {$t('connectMB.webPopup.webUsb.instruction2')} -
        4. -
        -
        -
        - -
        -
        - -
        -
        -
        -
        - {$t('connectMB.backButton')} - {$t('connectMB.nextButton')} -
        diff --git a/src/components/connection-prompt/usb/manual/ManualInstallTutorial.svelte b/src/components/connection-prompt/usb/manual/ManualInstallTutorial.svelte deleted file mode 100644 index 25d9f8fc0..000000000 --- a/src/components/connection-prompt/usb/manual/ManualInstallTutorial.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - - - -
        - - {$t('connectMB.transferHex.heading')} - -
        -

        - -

        -

        - {$t('connectMB.transferHex.message')} -

        - {$t('connectMB.transferHex.altText')} -
        - {$t('connectMB.backButton')} - {$t('connectMB.nextButton')} -
        -
        -
        diff --git a/src/components/control-bar/ControlBar.svelte b/src/components/control-bar/ControlBar.svelte deleted file mode 100644 index 6a7790119..000000000 --- a/src/components/control-bar/ControlBar.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - -
        - -
        diff --git a/src/components/control-bar/control-bar-items/AboutDialog.svelte b/src/components/control-bar/control-bar-items/AboutDialog.svelte deleted file mode 100644 index 2f306fc0f..000000000 --- a/src/components/control-bar/control-bar-items/AboutDialog.svelte +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -
        -
        - micro:bit - Aarhus -
        -

        - -

        -
        - {$t('about.microbitHeartImageAlt')} - - -
        {$t('content.index.title')}{import.meta.env.VITE_APP_VERSION}GitHub -
        {$t('about.softwareVersions')}
        -
        -
        -
        - {$t('actions.close')} -
        -
        -
        diff --git a/src/components/control-bar/control-bar-items/ExpandableControlBarMenu.svelte b/src/components/control-bar/control-bar-items/ExpandableControlBarMenu.svelte deleted file mode 100644 index 589df7ee0..000000000 --- a/src/components/control-bar/control-bar-items/ExpandableControlBarMenu.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - - -
        -
        - - -
        - {#if isExpanded} - - - - {/if} -
        diff --git a/src/components/control-bar/control-bar-items/HelpMenu.svelte b/src/components/control-bar/control-bar-items/HelpMenu.svelte deleted file mode 100644 index aa2e6f6b6..000000000 --- a/src/components/control-bar/control-bar-items/HelpMenu.svelte +++ /dev/null @@ -1,90 +0,0 @@ - - - - -
        - -
        - - - - {#if $open} - -
        - - - {$t('helpMenu.helpAndSupport')} - -
        -
        - - - {$t('helpMenu.termsOfUse')} - - - - {$t('helpMenu.privacyPolicy')} - - - - {$t('helpMenu.cookies')} - -
        -
        - - - {$t('helpMenu.about')} - -
        -
        - {/if} -
        -
        diff --git a/src/components/control-bar/control-bar-items/LanguageDialog.svelte b/src/components/control-bar/control-bar-items/LanguageDialog.svelte deleted file mode 100644 index cda58b0bf..000000000 --- a/src/components/control-bar/control-bar-items/LanguageDialog.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - {$t('languageDialog.title')} - - -
        - {#each allLanguages as language} - - {/each} -
        -
        - {$t('actions.close')} -
        -
        -
        diff --git a/src/components/control-bar/control-bar-items/MenuItem.svelte b/src/components/control-bar/control-bar-items/MenuItem.svelte deleted file mode 100644 index 21d631fda..000000000 --- a/src/components/control-bar/control-bar-items/MenuItem.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/src/components/control-bar/control-bar-items/MenuItems.svelte b/src/components/control-bar/control-bar-items/MenuItems.svelte deleted file mode 100644 index aae335c52..000000000 --- a/src/components/control-bar/control-bar-items/MenuItems.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - - - -
        - -
        diff --git a/src/components/control-bar/control-bar-items/SelectLanguageControlBarDropdown.svelte b/src/components/control-bar/control-bar-items/SelectLanguageControlBarDropdown.svelte deleted file mode 100644 index ff72db45d..000000000 --- a/src/components/control-bar/control-bar-items/SelectLanguageControlBarDropdown.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/src/components/control-bar/control-bar-items/SettingsMenu.svelte b/src/components/control-bar/control-bar-items/SettingsMenu.svelte deleted file mode 100644 index daf02db72..000000000 --- a/src/components/control-bar/control-bar-items/SettingsMenu.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - - - -
        - -
        - - - - {#if $open} - -
        - - - {$t('languageDialog.title')} - -
        -
        - {/if} -
        -
        diff --git a/src/components/datacollection/DataPageControlBar.svelte b/src/components/datacollection/DataPageControlBar.svelte deleted file mode 100644 index 5fc02113c..000000000 --- a/src/components/datacollection/DataPageControlBar.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - -
        - - - -
        diff --git a/src/components/datacollection/DataPageMenu.svelte b/src/components/datacollection/DataPageMenu.svelte deleted file mode 100644 index 02d3ec226..000000000 --- a/src/components/datacollection/DataPageMenu.svelte +++ /dev/null @@ -1,56 +0,0 @@ - - - - -
        - - - - {#if $open} - -
        - - - {$t('content.data.controlbar.button.uploadData')} - - - - {$t('content.data.controlbar.button.downloadData')} - - - - {$t('content.data.controlbar.button.clearData')} - -
        -
        - {/if} -
        diff --git a/src/components/datacollection/RecordInformationContent.svelte b/src/components/datacollection/RecordInformationContent.svelte deleted file mode 100644 index d616d0f17..000000000 --- a/src/components/datacollection/RecordInformationContent.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - - - -
        -

        - {$t('content.data.choice.header')} -

        -

        - {$t('content.data.choice.body')} -

        -
        -
        - -
        -

        - {$t('content.index.recordButtonDescription')} -

        -
        -
        diff --git a/src/components/dialogs/AppVersionRedirectDialog.svelte b/src/components/dialogs/AppVersionRedirectDialog.svelte deleted file mode 100644 index 44a815f27..000000000 --- a/src/components/dialogs/AppVersionRedirectDialog.svelte +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - {$t('popup.appVersionRedirect.header')} - - -
        -

        {$t('popup.appVersionRedirect.explain')}

        -
        - {$t('popup.appVersionRedirect.button.redirect')} - {$t('popup.appVersionRedirect.button.stay')} -
        -

        {$t('popup.appVersionRedirect.uk')}

        -
        -
        -
        diff --git a/src/components/dialogs/BaseDialog.svelte b/src/components/dialogs/BaseDialog.svelte deleted file mode 100644 index 73d33c521..000000000 --- a/src/components/dialogs/BaseDialog.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - -{#if isOpen} - -
        - -
        -{/if} diff --git a/src/components/dialogs/CompatibilityWarningDialog.svelte b/src/components/dialogs/CompatibilityWarningDialog.svelte deleted file mode 100644 index c6759e983..000000000 --- a/src/components/dialogs/CompatibilityWarningDialog.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - {$t('popup.compatibility.header')} - - -
        -
        -

        {$t('popup.compatibility.explain')}

        -

        {$t('popup.compatibility.advice')}

        -
        -
        - {$t('actions.close')} -
        -
        -
        -
        diff --git a/src/components/dialogs/PerformanceWarningDialog.svelte b/src/components/dialogs/PerformanceWarningDialog.svelte deleted file mode 100644 index d4c2ad43b..000000000 --- a/src/components/dialogs/PerformanceWarningDialog.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - - - - {}} class="w-150 space-y-5"> - - {$t('performanceWarning.heading')} - - -

        {$t('performanceWarning.content1')}

        -

        {$t('performanceWarning.content2')}

        - - {$t('performanceWarning.troubleshoot')} - - -
        - {$t('performanceWarning.ignore')} -
        -
        - {}}>{$t('actions.cancel')} - {}} - >{$t('performanceWarning.nextButton')} -
        -
        -
        -
        diff --git a/src/components/dialogs/SignUpDialog.svelte b/src/components/dialogs/SignUpDialog.svelte deleted file mode 100644 index bcd9e92c0..000000000 --- a/src/components/dialogs/SignUpDialog.svelte +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - {$t('sign-up.title')} - -
        -
        - -

        {$t('sign-up.content')}

        -
        -
        - - - {#if emailIsValid === false} -

        - {$t('sign-up.invalid')} -

        - {/if} - {#if signUpError} -

        - -

        - {/if} -
        -
        -
        - {$t('sign-up.skip-action')} - - - {$t('sign-up.sign-up-action')} - - - - - -
        -
        -
        diff --git a/src/components/dialogs/StandardDialog.svelte b/src/components/dialogs/StandardDialog.svelte deleted file mode 100644 index e1cdca702..000000000 --- a/src/components/dialogs/StandardDialog.svelte +++ /dev/null @@ -1,128 +0,0 @@ - - - - -
        - {#if $open} -
        -
        - {#if hasCloseButton} -
        - - - -
        - {/if} -
        -

        - -

        - -
        - - {#if !$$slots.heading && !$$slots.body} - - {/if} -
        -
        - {/if} -
        diff --git a/src/components/dialogs/UnsupportedMicrobitWarningDialog.svelte b/src/components/dialogs/UnsupportedMicrobitWarningDialog.svelte deleted file mode 100644 index d1d83f82a..000000000 --- a/src/components/dialogs/UnsupportedMicrobitWarningDialog.svelte +++ /dev/null @@ -1,70 +0,0 @@ - - - - -
        - {$t('connectMB.unsupportedMicrobit.header')} -
        -
        -

        - -

        - {#if bluetooth} -

        - {$t('connectMB.unsupportedMicrobit.withBluetooth')} -

        - {:else} -

        - -

        - {/if} -
        -
        - {#if bluetooth} - {$t('connectMB.unsupportedMicrobit.ctaWithBluetooth')} - {:else} - {$t('actions.close')} - {/if} -
        -
        -
        diff --git a/src/components/graphs/DimensionLabels.svelte b/src/components/graphs/DimensionLabels.svelte deleted file mode 100644 index ad26ad61b..000000000 --- a/src/components/graphs/DimensionLabels.svelte +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - -{#if $state.isInputConnected} -
        - {#each labels as dimension} -
        -

        - {dimension.label} -

        - {/each} -
        -{/if} diff --git a/src/components/graphs/LiveGraph.svelte b/src/components/graphs/LiveGraph.svelte deleted file mode 100644 index adfe60ce2..000000000 --- a/src/components/graphs/LiveGraph.svelte +++ /dev/null @@ -1,98 +0,0 @@ - - - - -
        - - -
        diff --git a/src/components/graphs/RecordingGraph.svelte b/src/components/graphs/RecordingGraph.svelte deleted file mode 100644 index e46f3a180..000000000 --- a/src/components/graphs/RecordingGraph.svelte +++ /dev/null @@ -1,251 +0,0 @@ - - - - -
        -
        - {#if enableInspector && !isNaN(hoverIndex)} -

        - {hoverIndex} -

        - {/if} - - -
        - {#if enableInspector} - - {/if} -
        diff --git a/src/components/information/Information.svelte b/src/components/information/Information.svelte deleted file mode 100644 index d5ce2d295..000000000 --- a/src/components/information/Information.svelte +++ /dev/null @@ -1,113 +0,0 @@ - - - - -
        -
        - {#if iconText !== undefined} -

        - {iconText} -

        - {/if} -
        - -
        - - {#if isOpen} - - -
        - {#if titleText} -

        - {titleText} -

        - {/if} - {#if bodyText} -

        - {bodyText} -

        - {/if} - -
        - {/if} -
        -
        diff --git a/src/components/information/InformationComponentUtility.ts b/src/components/information/InformationComponentUtility.ts deleted file mode 100644 index 64cbbb5cb..000000000 --- a/src/components/information/InformationComponentUtility.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import windi from '../../../windi.config.js'; -export const getInfoBoxColors = (isLightTheme: boolean) => { - return { - backgroundColor: isLightTheme - ? windi.theme.extend.colors.infobglight - : windi.theme.extend.colors.infobgdark, - iconColor: isLightTheme - ? windi.theme.extend.colors.infoiconlight - : windi.theme.extend.colors.infoicondark, - iconTextColor: isLightTheme - ? windi.theme.extend.colors.infotextlight - : windi.theme.extend.colors.infotextdark, - textColor: isLightTheme - ? windi.theme.extend.colors.primarytext - : windi.theme.extend.colors.secondarytext, - }; -}; diff --git a/src/components/output/OutputGesture.svelte b/src/components/output/OutputGesture.svelte deleted file mode 100644 index 8042db080..000000000 --- a/src/components/output/OutputGesture.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - - -{#if variant === 'stack'} - -{/if} - -{#if variant === 'tile'} - -{/if} diff --git a/src/components/output/OutputGestureStack.svelte b/src/components/output/OutputGestureStack.svelte deleted file mode 100644 index d99d26b8d..000000000 --- a/src/components/output/OutputGestureStack.svelte +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - -
        -

        - {$gesture.name} -

        -
        - - - -
        -
        -
        -
        -
        - {#each Array(9) as _, index (index)} -
        - {/each} -
        -
        -

        - {Math.round(meterWidthPct)}% -

        -
        - -

        - {$t('content.model.output.recognitionPoint')} -

        - -
        - {#if enableOutputGestures} - -
        - {$t('arrowIconRight.altText')} - {$t('arrowIconRight.altText')} -
        - {/if} - - -{#if enableOutputGestures} - -
        -
        - (hasLoadedMicrobitImage = true)} /> - -
        - -
        -
        -
        - - -{/if} diff --git a/src/components/output/OutputGestureTile.svelte b/src/components/output/OutputGestureTile.svelte deleted file mode 100644 index 77cb4e3ea..000000000 --- a/src/components/output/OutputGestureTile.svelte +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - -
        -
        -

        {$gesture.name}

        -
        - - -
        - -
        -
        -
        -
        -
        - {#each [75, 50, 25] as line} -
        -

        - {line}% -

        -
        - {/each} -
        -
        -
        -
        -
        -
        - diff --git a/src/components/output/OutputMatrix.svelte b/src/components/output/OutputMatrix.svelte deleted file mode 100644 index ec922e2d6..000000000 --- a/src/components/output/OutputMatrix.svelte +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - -
        - - {#each matrix as button, i} - -
        { - elementClick(i); - }} - on:mouseenter={e => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - elementHover(i, e); - }} /> - {/each} -
        diff --git a/src/components/output/OutputSoundSelector.svelte b/src/components/output/OutputSoundSelector.svelte deleted file mode 100644 index 292ab98ea..000000000 --- a/src/components/output/OutputSoundSelector.svelte +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - -
        -
        - - {#if !hasEnabledSound} - - {/if} -
        - {#if hasEnabledSound} - - {/if} -
        -
        diff --git a/src/components/output/PinSelector.svelte b/src/components/output/PinSelector.svelte deleted file mode 100644 index 563d6a6d5..000000000 --- a/src/components/output/PinSelector.svelte +++ /dev/null @@ -1,132 +0,0 @@ - - - - - -
        - {#each MBSpecs.IO_PIN_LAYOUT as currentPin} - {#if includes(StaticConfiguration.supportedPins, currentPin)} - - {#if largePins.includes(currentPin)} - - -
        { - onPinSelected(currentPin); - }} - class="h-8 w-7 rounded-bl-xl ml-1px rounded-br-xl bg-yellow-300 cursor-pointer" - class:border-yellow-500={selectedPin === currentPin} - class:border-width-2={selectedPin === currentPin} - class:h-9={selectedPin === currentPin} - class:hover:bg-yellow-200={selectedPin !== currentPin} - class:bg-opacity-80={selectedPin !== currentPin}> -

        {currentPin}

        -
        - {:else} - - -
        { - onPinSelected(currentPin); - }} - class:bg-yellow-600={selectedPin === currentPin} - class="bg-yellow-400 h-7 w-1 rounded-bl-xl ml-1px rounded-br-xl hover:bg-yellow-300" /> - {/if} - {:else} - - {#if largePins.includes(currentPin)} - -
        -

        {currentPin}

        -
        - {:else} - -
        - {/if} - {/if} - {/each} -
        - -
        -
        -
        - -

        {$t('content.model.output.pin.option.allTime')}

        -
        -
        - -

        {$t('content.model.output.pin.option.xTime')}

        -
        -
        -
        - {#if turnOnState === PinTurnOnState.X_TIME} -
        -

        {$t('content.model.output.pin.seconds')}

        - -
        - {/if} -
        -
        - diff --git a/src/components/output/PinSelectorUtil.ts b/src/components/output/PinSelectorUtil.ts deleted file mode 100644 index c513de726..000000000 --- a/src/components/output/PinSelectorUtil.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -export enum PinTurnOnState { - ALL_TIME, - X_TIME, -} diff --git a/src/components/playground/LiveDataBufferUtilizationPercentage.svelte b/src/components/playground/LiveDataBufferUtilizationPercentage.svelte deleted file mode 100644 index e0466c0da..000000000 --- a/src/components/playground/LiveDataBufferUtilizationPercentage.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - - -

        Buffer utilization = {utilization * 100}%

        -

        - Buffer utilization should be about 20-50%. Too high means performance spikes may cause - the buffer to lack sufficient data to classify. Too low means performance is wasted -

        diff --git a/src/components/playground/PlaygroundContext.ts b/src/components/playground/PlaygroundContext.ts deleted file mode 100644 index 3f88ac664..000000000 --- a/src/components/playground/PlaygroundContext.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Readable, Subscriber, Unsubscriber, Writable, writable } from 'svelte/store'; -export type PlaygroundContextData = { - messages: any[]; -}; -class PlaygroundContext implements Readable { - private readonly LOG_LIMIT = 15; - private store: Writable; - constructor() { - this.store = writable({ - messages: [], - }); - } - - public subscribe( - run: Subscriber, - invalidate?: ((value?: PlaygroundContextData | undefined) => void) | undefined, - ): Unsubscriber { - return this.store.subscribe(run, invalidate); - } - - public addMessage(...message: any[]) { - this.store.update(store => { - store.messages.push([message]); - if (store.messages.length > this.LOG_LIMIT) { - store.messages.reverse(); - store.messages.pop(); - store.messages.reverse(); - } - return store; - }); - } -} - -const playgroundContext = new PlaygroundContext(); -export default playgroundContext; diff --git a/src/components/playground/PlaygroundGestureView.svelte b/src/components/playground/PlaygroundGestureView.svelte deleted file mode 100644 index 9f801ae02..000000000 --- a/src/components/playground/PlaygroundGestureView.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - - - -
        -
        -

        Gesture: {$gesture.name}

        -
        -

        - {JSON.stringify(reduceData($gesture), null, 2)} -

        -
        diff --git a/src/components/playground/PlaygroundLog.svelte b/src/components/playground/PlaygroundLog.svelte deleted file mode 100644 index 5ed6d9219..000000000 --- a/src/components/playground/PlaygroundLog.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - - -
        - {#each $playgroundContext.messages as message} -

        - > {message.toString()} -

        - {/each} -
        diff --git a/src/components/playground/inputSynthesizer/AccelerometerDataSynthesizer.ts b/src/components/playground/inputSynthesizer/AccelerometerDataSynthesizer.ts deleted file mode 100644 index f34846976..000000000 --- a/src/components/playground/inputSynthesizer/AccelerometerDataSynthesizer.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { - Readable, - Subscriber, - Unsubscriber, - Writable, - get, - writable, -} from 'svelte/store'; -import { liveData } from '../../../script/stores/Stores'; -import LiveData from '../../../script/domain/LiveData'; -import { MicrobitAccelerometerData } from '../../../script/livedata/MicrobitAccelerometerData'; - -type AccelerometerSynthesizerData = { - intervalSpeed: number; - xSpeed: number; - ySpeed: number; - zSpeed: number; -}; - -class AccelerometerSynthesizer implements Readable { - private interval: NodeJS.Timeout | undefined = undefined; - private store: Writable; - - constructor(private liveData: LiveData) { - const defaultSpeed = 50; - this.store = writable({ - intervalSpeed: defaultSpeed, - xSpeed: 1, - ySpeed: 1, - zSpeed: 1, - }); - } - - public subscribe( - run: Subscriber, - invalidate?: ((value?: AccelerometerSynthesizerData | undefined) => void) | undefined, - ): Unsubscriber { - return this.store.subscribe(run, invalidate); - } - - public updateInterval() { - if (this.interval) { - clearInterval(this.interval); - } - this.interval = setInterval(() => { - this.generateData(); - }, get(this.store).intervalSpeed); - } - - public generateData() { - const val = new Date().getTime(); - this.liveData.put({ - accelX: Math.sin(val * get(this.store).xSpeed), - accelY: Math.sin(val * get(this.store).ySpeed), - accelZ: Math.sin(val * get(this.store).zSpeed), - smoothedAccelX: val, - smoothedAccelY: val, - smoothedAccelZ: val, - }); - } - - public setXSpeed(value: number) { - this.store.update(updater => { - updater.xSpeed = value / 1000; - return updater; - }); - } - - public setYSpeed(value: number) { - this.store.update(updater => { - updater.ySpeed = value / 1000; - return updater; - }); - } - - public setZSpeed(value: number) { - this.store.update(updater => { - updater.zSpeed = value / 1000; - return updater; - }); - } - - public setIntervalSpeed(value: number) { - this.store.update(updater => { - updater.intervalSpeed = value; - return updater; - }); - this.updateInterval(); - } -} - -const accelerometerSynthesizer = new AccelerometerSynthesizer(liveData); - -export default accelerometerSynthesizer; diff --git a/src/components/playground/inputSynthesizer/IntervalSlider.svelte b/src/components/playground/inputSynthesizer/IntervalSlider.svelte deleted file mode 100644 index a05b1715a..000000000 --- a/src/components/playground/inputSynthesizer/IntervalSlider.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - -

        Synthesis interval ({$accelerometerSynthesizer.intervalSpeed}), lower is faster

        - setIntervalValue(e.detail[0])} /> diff --git a/src/components/playground/inputSynthesizer/MicrobitAccelerometerDataSynthesizer.svelte b/src/components/playground/inputSynthesizer/MicrobitAccelerometerDataSynthesizer.svelte deleted file mode 100644 index 2c39e3ef0..000000000 --- a/src/components/playground/inputSynthesizer/MicrobitAccelerometerDataSynthesizer.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - - - -
        -

        Live data synthesizer

        -

        Uses sine-waves to produce LiveData

        -
        - -

        x Speed (Frequency)

        - accelerometerSynthesizer.setXSpeed(e.detail[0])} /> -

        y Speed (Frequency)

        - accelerometerSynthesizer.setYSpeed(e.detail[0])} /> -

        z Speed (Frequency)

        - accelerometerSynthesizer.setZSpeed(e.detail[0])} /> -
        - -

        {JSON.stringify($accelerometerSynthesizer, null, 2)}

        -
        diff --git a/src/components/playground/inputSynthesizer/Visualizer.svelte b/src/components/playground/inputSynthesizer/Visualizer.svelte deleted file mode 100644 index 6f2bb216f..000000000 --- a/src/components/playground/inputSynthesizer/Visualizer.svelte +++ /dev/null @@ -1,61 +0,0 @@ - - - - -
        - -
        diff --git a/src/components/skeletonloading/ImageSkeleton.svelte b/src/components/skeletonloading/ImageSkeleton.svelte deleted file mode 100644 index b6fc29b3d..000000000 --- a/src/components/skeletonloading/ImageSkeleton.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - -{#if !hasLoaded} -
        - -
        -{/if} diff --git a/src/global.d.ts b/src/global.d.ts index 7d38bd5c1..00c4d45ea 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -3,5 +3,3 @@ * * SPDX-License-Identifier: MIT */ - -/// diff --git a/src/i18n.ts b/src/i18n.ts deleted file mode 100644 index 6d07321e2..000000000 --- a/src/i18n.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import browserLang from 'browser-lang'; -import { FormatXMLElementFn } from 'intl-messageformat'; -import { getLocaleFromQueryString, init, locale, register } from 'svelte-i18n'; -import { get } from 'svelte/store'; -import { persistantWritable } from './script/stores/storeUtil'; -// waitLocale is exported for testing. -export { t, waitLocale } from 'svelte-i18n'; - -type InterpolationValues = - | Record< - string, - string | number | boolean | Date | FormatXMLElementFn | null | undefined - > - | undefined; - -interface MessageObject { - id: string; - locale?: string; - format?: string; - default?: string; - values?: InterpolationValues; -} -// Not exported from svelte-i18n so replicated here. -export type MessageFormatter = ( - id: string | MessageObject, - options?: Omit, -) => string; - -export const allLanguages = [ - { - id: 'en', - name: 'English', - enName: 'English', - }, - // Welsh translations disabled on this branch. Danish has never worked in this fork. - /*{ - id: 'cy', - name: 'Cymraeg', - enName: 'Welsh', - }, - { - id: 'da', - name: 'Dansk', - enName: 'Danish', - },*/ -]; - -register('en', () => import('./messages/ui.en.json')); - -const initialLocale = - getLocaleFromQueryString('l') || - browserLang({ - languages: allLanguages.map(l => l.id), - fallback: 'en', - }); - -const persistantLocale = persistantWritable('lang', initialLocale); - -locale.subscribe(newLocal => { - if (newLocal) { - persistantLocale.set(newLocal); - } -}); - -init({ - fallbackLocale: 'en', - initialLocale: get(persistantLocale), - // Needed to format style tags. - ignoreTag: false, -}); diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 6e0aa5fce..000000000 --- a/src/main.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import App from './App.svelte'; -import 'virtual:windi.css'; - -const app = new App({ - target: document.getElementById('root')!, -}); - -export default app; diff --git a/src/script/domain/ClassifierInput.ts b/src/main.tsx similarity index 50% rename from src/script/domain/ClassifierInput.ts rename to src/main.tsx index 30449a1b8..8cc546496 100644 --- a/src/script/domain/ClassifierInput.ts +++ b/src/main.tsx @@ -3,10 +3,10 @@ * * SPDX-License-Identifier: MIT */ -import Filters from './Filters'; -interface ClassifierInput { - getInput(filters: Filters): number[]; -} +import App from './App'; +import ReactDOM from "react-dom/client"; -export default ClassifierInput; +ReactDOM.createRoot(document.getElementById("root")!).render( + +); \ No newline at end of file diff --git a/src/menus/DataMenu.svelte b/src/menus/DataMenu.svelte deleted file mode 100644 index 1f2852958..000000000 --- a/src/menus/DataMenu.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - - - -
        -

        {numberOfRecodings}

        -

        {$t('menu.data.examples')}

        -
        diff --git a/src/menus/MenuButton.svelte b/src/menus/MenuButton.svelte deleted file mode 100644 index bfc3c90ef..000000000 --- a/src/menus/MenuButton.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - -
        - -
        - -
        -

        - {$t(title)} -

        -
        - -
        -
        - -
        -
        diff --git a/src/menus/ModelMenu.svelte b/src/menus/ModelMenu.svelte deleted file mode 100644 index 91f04ba81..000000000 --- a/src/menus/ModelMenu.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - - - -
        - {#if !$state.isPredicting} -
        -
        -
        - {$t('menu.model.noModel')} -
        -
        -
        - {:else} -
        -

        - {predictionLabel} -

        -
        -

        - {confidenceLabel} -

        - {/if} -
        diff --git a/src/menus/TrainingMenu.svelte b/src/menus/TrainingMenu.svelte deleted file mode 100644 index d15591d90..000000000 --- a/src/menus/TrainingMenu.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - - - -
        - {#if $state.isPredicting} -
        - -

        - {$t('menu.trainer.TrainingFinished')} -

        -
        - {:else} -
        -
        -
        - {$t('menu.model.noModel')} -
        -
        -
        - {/if} -
        diff --git a/src/pages/DataPage.svelte b/src/pages/DataPage.svelte deleted file mode 100644 index 5037f2289..000000000 --- a/src/pages/DataPage.svelte +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - {title} - - -
        - -
        -

        {$t('content.index.toolProcessCards.data.title')}

        - - {#if !hasSomeData() && !$state.isInputConnected} -
        - -
        - {:else} -
        -
        - - g.name.trim() || g.recordings.length > 0)} - isLightTheme={false} - underlineIconText={false} - iconText={$t('content.data.data')} - titleText={$t('content.data.data')} - bodyText={$t('content.data.dataDescription')} /> -
        - -
        - {/if} -
        - g.name.trim())} /> -
        - navigate(Paths.TRAINING)} /> - -
        -
        -
        - -
        -
        -
        diff --git a/src/pages/GetStartedPage.svelte b/src/pages/GetStartedPage.svelte deleted file mode 100644 index 94f3fa11d..000000000 --- a/src/pages/GetStartedPage.svelte +++ /dev/null @@ -1,108 +0,0 @@ - - - - - -

        Get connected

        -

        - To start using the micro:bit machine learning tool, you need to connect a micro:bit to - the micro:bit machine learning tool web page on your computer. -

        -

        There are two options for connecting the micro:bit.

        -

        Option 1: Web Bluetooth

        -

        The first option uses a Web Bluetooth wireless connection.

        -

        Click on 'Start new session' and follow the instructions on screen.

        -

        Option 2: micro:bit radio

        -

        - If you can't use Bluetooth on your computer, the second option uses a micro:bit radio - connection instead. -

        -

        - Click on 'Start new session', then 'Connect using micro:bit radio instead' and follow - the instructions on screen. -

        -

        - The micro:bit radio option uses two micro:bits. The first micro:bit senses movement. - It sends movement data using radio to a second micro:bit connected to your computer - with a USB cable. -

        -

        Now you're connected

        -

        - Once the micro:bit sensing movement is connected, you'll see a happy face on its LED - display. -

        -

        - As you move the micro:bit, you see live movement data from its accelerometer sensor in - a graph at the bottom of the screen. -

        -

        - Each coloured line represents a different direction, or dimension, you're moving the - micro:bit in. -

        - -
        -

        Looking at the front of the micro:bit:

        -
          -
        • x shows movement left and right
        • -
        • y shows movement up and down
        • -
        • z shows movement from front to back.
        • -
        -
        -

        This data is how the machine learning tool understands how you move.

        -

        - There are three steps to teaching the computer how to use this data to recognise - different movements you make. -

        -

        Step 1: Add data

        - -

        - Choose at least two different movements, or actions, to train the - machine learning tool to recognise. Waving and clapping are good actions to start - with. -

        -
          -
        • Name your first action, for example 'wave'.
        • -
        • Start moving the micro:bit, making the action.
        • -
        • Click the red button to collect a sample of data.
        • -
        • Collect at least three samples of your first action.
        • -
        • Collect at least three samples of one other action, for example 'clap'.
        • -
        -

        Step 2: Train the model

        -

        - The machine learning tool analyses your samples of data and makes a set of - mathematical rules to allow it to estimate which physical action you make. These rules - make up the machine learning model. -

        - -

        Step 3: Test the model

        -

        This screen shows which action the model estimates that you're making.

        - -

        - The percentage number is how certain the model is that you're making an action. High - numbers mean the model is very sure you're making that action. Lower numbers mean it - is less sure. -

        -

        - Make each of your actions to test the model. If you see low certainty numbers, the - model may need more data samples. If it has got the estimated action wrong, you'll also need to add more data. -

        -

        - You can now go back to step 1 to collect more data to make your machine learning model - more reliable, or recognise other kinds of movement. -

        -
        diff --git a/src/pages/Homepage.svelte b/src/pages/Homepage.svelte deleted file mode 100644 index 3b8cb2c5a..000000000 --- a/src/pages/Homepage.svelte +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - {title} - - -
        -

        {$t('content.index.title')}

        -
        -
        -
        -

        micro:bit machine learning tool

        -

        - Introduce students to machine learning concepts through physical movement and - data -

        -
        -
        -
        - {#each steps as step, idx} -
        -

        - {idx + 1}. {$t(step.titleId)} -

        - -

        - {$t(step.descriptionId)} -

        -
        - {/each} -
        -
        -
        - -
        -

        Resources

        -
        - {#each resources as resource} - - - - -

        - {resource.title} -

        -
        -
        -
        - {/each} -
        -
        - - -
        -
        diff --git a/src/pages/IntroducingToolPage.svelte b/src/pages/IntroducingToolPage.svelte deleted file mode 100644 index 5016594fb..000000000 --- a/src/pages/IntroducingToolPage.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - - - - -

        - The micro:bit machine learning tool introduces students to machine learning - (ML) concepts through physical movement and data, a topic familiar from mathematics and - science. -

        -

        - It allows students to quickly teach a computer to tell the difference between - different physical movements, such as waving and clapping, using data captured by a - BBC micro:bit. -

        -

        - Students collect samples of data of different movements and label them, so the - computer knows which sets of data represent which movements. -

        -

        - The computer learns to recognise different movements by analysing the data samples, - and creating a set of rules called a model. This process of - collecting and labelling data to build a model is called training a machine - learning model. -

        -

        - Students then test and improve the model. This enables learning about the importance - of collecting enough data from diverse sources when designing and making ML systems. -

        -

        - The micro:bit machine learning tool is co-designed and tested in classrooms in Denmark - and the United Kingdom by Aarhus University and the Micro:bit Educational Foundation. -

        -

        - There are no downloads, no logins. You just need the Chrome or Edge web browser and a - micro:bit - or two micro:bits if your computer doesn’t have Bluetooth enabled. -

        -

        How it works

        -

        - The BBC micro:bit has a built-in accelerometer, a movement sensor also found in phones - and fitness trackers. It measures movement in three dimensions: x, y and z. -

        - -
        -

        Looking at the front of the micro:bit:

        -
          -
        • x shows movement left and right
        • -
        • y shows movement up and down
        • -
        • z shows movement from front to back.
        • -
        -
        -

        - Students hold or wear a micro:bit that sends movement data to the machine learning - tool. They see their physical movement captured as data in real time, as moving lines - on a graph. Making the connection between moving your body and seeing your movements - captured and visualised as the computer sees it, as data, is a powerful, immersive - experience. -

        -

        - To create an ML model, students record samples of data of different types of - movements, such as waving or clapping. We call these different movements ‘actions’. - Students label each set of actions so the computer knows which sets of data represent - which kinds of movement. -

        - -

        - Students then use the data they have collected to train and test a machine learning - model running in the web browser. -

        -

        - They’ll discover that to work well, successfully telling one action from another, the - model needs plenty of data. They’ll also find that to work for different people, who - move in different ways, the training data needs to be collected from as many different - people as possible. -

        -

        - They can then improve the model by recording more samples of movement data, training - and testing the machine learning model again. -

        -

        - Try it out for yourself, to see how quick and easy it is to get started. The video and - short written guide show you how. -

        -

        Glossary

        -

        - Artificial intelligence: the ability of computer software designed by - people to perform tasks that usually need human intelligence. -

        -

        Data: information collected for use elsewhere.

        -

        - Machine learning: the application of AI technology where computer software - is designed to perform a task quickly and reliably, having been trained by examples of - data provided by humans. This training process can be described as 'learning' and this - is why we use the term 'machine learning'. -

        -

        - Machine learning model: a set of rules developed by a machine learning - system to categorise data. -

        -

        - Training a machine learning model: providing samples of data categorised - and labelled by humans to help machine learning software to build a model. -

        -

        - Testing a machine learning model: evaluating a machine learning model - against labelled or known data to see if it is correctly categorising that labelled or - known data. If it is not correct, it may need further training data. -

        -
        diff --git a/src/pages/PlaygroundPage.svelte b/src/pages/PlaygroundPage.svelte deleted file mode 100644 index 096c1eb95..000000000 --- a/src/pages/PlaygroundPage.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - - - -
        -
        -
        -
        -

        Model store

        -

        - {JSON.stringify($model, null, 2).substring( - 2, - JSON.stringify($model, null, 2).length - 1, - )} -

        -
        -
        -

        Classifier store

        -

        - {JSON.stringify($classifier, null, 2).substring( - 2, - JSON.stringify($classifier, null, 2).length - 1, - )} -

        -
        -
        -

        Engine store

        -

        - {JSON.stringify($engine, null, 2).substring( - 2, - JSON.stringify($engine, null, 2).length - 1, - )} -

        -
        -
        -

        LiveData store

        -

        - {JSON.stringify($liveData, null, 2).substring( - 2, - JSON.stringify($liveData, null, 2).length - 1, - )} -

        -
        -
        -

        Gestures (without recordings/output)

        - {#each $gestures as { ID }} - - {/each} -
        -
        -
        -
        - - - - - -
        -
        - - -
        diff --git a/src/pages/filter/D3Plot.svelte b/src/pages/filter/D3Plot.svelte deleted file mode 100644 index 2ece79b40..000000000 --- a/src/pages/filter/D3Plot.svelte +++ /dev/null @@ -1,312 +0,0 @@ - - - - -
        -
        - {#each classList as c} - -
        highlight(null, { gestureClassID: c.id })} - on:mouseleave={doNotHighlight}> - {c.name} -
        - {/each} -
        -
        -
        diff --git a/src/pages/filter/FilterPage.svelte b/src/pages/filter/FilterPage.svelte deleted file mode 100644 index 249174f5e..000000000 --- a/src/pages/filter/FilterPage.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - { - navigate(Paths.TRAINING); - }}> - - - -{#if $gestures.length === 0} -
        -
        -

        - {$t('content.filters.NoDataHeader')} -

        -

        - {$t('content.filters.NoDataBody')} -

        -
        -
        -{:else if isFilterInspectorDialogOpen && currentFilter !== undefined} -
        - -
        -{:else} -
        - {#each Object.values(Filters) as filter} - - {/each} -
        -{/if} diff --git a/src/pages/filter/FilterToggler.svelte b/src/pages/filter/FilterToggler.svelte deleted file mode 100644 index e3ddef94e..000000000 --- a/src/pages/filter/FilterToggler.svelte +++ /dev/null @@ -1,108 +0,0 @@ - - - - - -
        -
        -
        -
        - -
        -

        - {filterText.name} -

        -
        - -
        - - - -
        { - openFilterInspector(filter, !fullScreen); - }}> - -
        - - - -
        { - toggleFilter(); - }}> - -
        -
        -
        -
        - -
        -
        -
        diff --git a/src/pages/home-page-content-tiles/NewFeaturesTile.svelte b/src/pages/home-page-content-tiles/NewFeaturesTile.svelte deleted file mode 100644 index b0d1b8bea..000000000 --- a/src/pages/home-page-content-tiles/NewFeaturesTile.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - - -

        {$t('content.index.newzHeading')}

        -

        - {$t('content.index.newzBodyMakecode')} -

        -
        - -
        -

        - {$t('content.index.newzBodyViz')} -

        -
        - -
        diff --git a/src/pages/home-page-content-tiles/WhatIsMachineLearningTile.svelte b/src/pages/home-page-content-tiles/WhatIsMachineLearningTile.svelte deleted file mode 100644 index 2a4de1ec7..000000000 --- a/src/pages/home-page-content-tiles/WhatIsMachineLearningTile.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - - - -

        {$t('content.index.MLHeading')}

        -

        - {$t('content.index.MLSubheading')} -

        -
          - {#each MLStepsContent as step} -
        1. - - -

          - {$t(step.text)} -

          -
        2. - {/each} -
        diff --git a/src/pages/model/ModelPage.svelte b/src/pages/model/ModelPage.svelte deleted file mode 100644 index 77652d9d6..000000000 --- a/src/pages/model/ModelPage.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - - - -{#if $state.modelView == ModelView.TILE} - -{:else} - -{/if} diff --git a/src/pages/model/stackview/ModelPageStackView.svelte b/src/pages/model/stackview/ModelPageStackView.svelte deleted file mode 100644 index 2f02ef568..000000000 --- a/src/pages/model/stackview/ModelPageStackView.svelte +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - {title} - - - -
        - -
        - {#if $state.isPredicting} - - {:else} - - {/if} -
        -
        diff --git a/src/pages/model/stackview/ModelPageStackViewContent.svelte b/src/pages/model/stackview/ModelPageStackViewContent.svelte deleted file mode 100644 index 00de016bd..000000000 --- a/src/pages/model/stackview/ModelPageStackViewContent.svelte +++ /dev/null @@ -1,156 +0,0 @@ - - - -

        {$t('content.index.toolProcessCards.model.title')}

        -
        - - - {$t('content.model.output.estimatedGesture.label', { - values: { - action: prediction.name, - }, - })} - -
        -

        - {$t('content.model.output.estimatedGesture.iconTitle')} -

        -

        - {prediction.name} -

        -
        -
        - {#if prediction.certainty} -

        - {prediction.certainty} -

        - {/if} - -
        -
        -
        -
        - - - {#if enableOutputGestures} - - - - {/if} -
        - - {#if !$state.isOutputConnected && !hasClosedPopup && hasInteracted} -
        -
        -
        -

        - {$t('content.model.output.popup.header')} -

        -

        - {$t('content.model.output.popup.body')} -

        -
        -
        - {$t('arrowIconDown.altText')} -
        -
        - -
        -
        -
        - {/if} -
        -
        - -
        -
        diff --git a/src/pages/model/tileview/ModelPageTileView.svelte b/src/pages/model/tileview/ModelPageTileView.svelte deleted file mode 100644 index 8a7a19329..000000000 --- a/src/pages/model/tileview/ModelPageTileView.svelte +++ /dev/null @@ -1,108 +0,0 @@ - - - - -resetPins -
        - {#if !$state.isPredicting} - - {:else} - -
        -
        -

        MakeCode

        -

        - You can create a hex file on - MakeCode - -

        -
        -
        - {/if} -
        diff --git a/src/pages/model/tileview/ModelPageTileViewTiles.svelte b/src/pages/model/tileview/ModelPageTileViewTiles.svelte deleted file mode 100644 index 9a206d988..000000000 --- a/src/pages/model/tileview/ModelPageTileViewTiles.svelte +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - {#if matches} -
        - {#each gestures.getGestures() as gesture} - - {/each} -
        - {/if} -
        - - {#if matches} -
        - {#each gestures.getGestures() as gesture} - - {/each} -
        - {/if} -
        - - {#if matches} -
        - {#each gestures.getGestures() as gesture} - - {/each} -
        - {/if} -
        diff --git a/src/pages/training/TrainingButton.svelte b/src/pages/training/TrainingButton.svelte deleted file mode 100644 index dad4155b5..000000000 --- a/src/pages/training/TrainingButton.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - - - -{$t('menu.trainer.trainModelButton')} - diff --git a/src/pages/training/TrainingPage.svelte b/src/pages/training/TrainingPage.svelte deleted file mode 100644 index 02b52c299..000000000 --- a/src/pages/training/TrainingPage.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - {title} - - - (isFailedTrainingDialogOpen = false)} - class="w-175 space-y-5"> - - {$t('content.trainer.failure.header')} - - -
        -

        - {$t('content.trainer.failure.body')} -

        -

        - {$t('content.trainer.failure.todo')} -

        -
        -
        -
        - -
        - -
        - -

        {$t('content.trainer.header')}

        -

        - {$t('content.trainer.description')} -

        -
        - {#if !sufficientData} - - {$t('menu.trainer.addDataButton')} - - {:else if sufficientData && !$state.isTraining && !$state.isPredicting && !$state.hasTrainedBefore} - - - - {:else if sufficientData && !$state.isTraining && !$state.isPredicting} - - - - {:else if $state.isTraining} - - - - {:else if $state.isPredicting} - -
        - {$t('menu.trainer.addMoreDataButton')} - {$t('menu.trainer.testModelButton')} -
        -
        - {/if} -
        -
        -
        diff --git a/src/placeholder.test.ts b/src/placeholder.test.ts new file mode 100644 index 000000000..9bfce767d --- /dev/null +++ b/src/placeholder.test.ts @@ -0,0 +1,3 @@ +test("placeholder", () => { + // So it works? +}) \ No newline at end of file diff --git a/src/router/Router.svelte b/src/router/Router.svelte deleted file mode 100644 index 1bfe9d5f5..000000000 --- a/src/router/Router.svelte +++ /dev/null @@ -1,107 +0,0 @@ - - - - - diff --git a/src/router/paths.ts b/src/router/paths.ts deleted file mode 100644 index 76b7e75da..000000000 --- a/src/router/paths.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { writable, Writable, get, derived } from 'svelte/store'; -import type { MessageFormatter } from '../i18n'; -import { appName } from '../script/environment'; -export const Paths = { - HOME: '/', - PLAYGROUND: 'playground', - INTRODUCING_TOOL: 'resources/introducing-the-microbit-machine-learning-tool', - GET_STARTED: 'resources/get-started', - DATA: 'add-data', - TRAINING: 'train-model', - MODEL: 'test-model', - FILTERS: 'training/filters', -} as const; - -export type PathType = (typeof Paths)[keyof typeof Paths]; - -export const isValidPath = (s: string): s is PathType => { - return Object.values(Paths).includes(s as PathType); -}; - -export const currentPathPrivate: Writable = writable(Paths.HOME); -export const currentPath = derived(currentPathPrivate, path => path); - -export function navigate(path: PathType) { - if (path === get(currentPath)) { - return; - } - currentPathPrivate.set(path); -} - -export const getTitle = (path: PathType, t: MessageFormatter) => { - switch (path) { - case Paths.HOME: { - return appName; - } - case Paths.DATA: { - return `${t('content.index.toolProcessCards.data.title')} | ${appName}`; - } - case Paths.TRAINING: { - return `${t('content.index.toolProcessCards.train.title')} | ${appName}`; - } - case Paths.MODEL: { - return `${t('content.index.toolProcessCards.model.title')} | ${appName}`; - } - default: - return appName; - } -}; diff --git a/src/script/ControlledStorage.ts b/src/script/ControlledStorage.ts deleted file mode 100644 index 853d86b02..000000000 --- a/src/script/ControlledStorage.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -type StoredValue = { - version: number; - value: T; -}; - -class ControlledStorage { - public static get(key: string): T { - const storedValue = this.getStoredItem(key); - const parsedValue = this.parseItem(storedValue); - return parsedValue.value; - } - - public static set(key: string, value: T): void { - const encapsulated = this.encapsulateItem(value); - const stringified = JSON.stringify(encapsulated); - localStorage.setItem(key, stringified); - } - - private static getStoredItem(key: string): string { - const value = localStorage.getItem(key); - if (!value) { - throw new Error(`Could not find item ${key}. No such element!`); - } - return value; - } - - private static parseItem(storedValue: string): StoredValue { - const parsed = JSON.parse(storedValue) as StoredValue; - if (!parsed) { - throw new Error(`Could not parse value '${storedValue}'`); - } - if (!('version' in parsed)) { - throw new Error( - `Could not parse value '${storedValue}'. It did not contain version number`, - ); - } - if (!('value' in parsed)) { - throw new Error(`Could not parse value '${storedValue}'. It did not contain value`); - } - return parsed; - } - - private static encapsulateItem(value: T): StoredValue { - return { - version: 1, // todo move this magic constant - value, - }; - } -} - -export default ControlledStorage; diff --git a/src/script/CookieManager.ts b/src/script/CookieManager.ts deleted file mode 100644 index 9c310fc43..000000000 --- a/src/script/CookieManager.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import Cookies from 'js-cookie'; - -/** - * Handles the storage and access of cookie information in a GDPR-compliant manner - */ -class CookieManager { - private static reconnectCookieName = 'reconnect_flag'; - private static featureFlagsCookieName = 'fflags'; - - public static isReconnectFlagSet(): boolean { - return Cookies.get(this.reconnectCookieName) !== undefined; - } - - public static unsetReconnectFlag(): void { - Cookies.remove(this.reconnectCookieName); - } - - public static setReconnectFlag(): void { - Cookies.set(this.reconnectCookieName, 'true'); - } - - public static hasFeatureFlag(flag: string): boolean { - const flags = Cookies.get(this.featureFlagsCookieName); - if (!flags) { - return false; - } - return flags.toLowerCase().includes(flag.toLowerCase()); - } -} - -export default CookieManager; diff --git a/src/script/TypingUtils.ts b/src/script/TypingUtils.ts deleted file mode 100644 index 418482de1..000000000 --- a/src/script/TypingUtils.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -/** - * Utility class for type annotation - */ -const TypingUtils = { - /** - * Used to denote explicitly empty functions as `() => {}` - */ - emptyFunction: () => { - /* Empty */ - }, -}; - -export default TypingUtils; diff --git a/src/script/compatibility/CompatibilityChecker.ts b/src/script/compatibility/CompatibilityChecker.ts deleted file mode 100644 index 0ca993065..000000000 --- a/src/script/compatibility/CompatibilityChecker.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import Bowser from 'bowser'; -import { nonAllowedPlatforms } from './CompatibilityList'; -import { isDevMode } from '../environment'; - -export type CompatibilityStatus = { - bluetooth: boolean; - usb: boolean; - platformAllowed: boolean; - webGL: boolean; -}; - -export function checkCompatibility(): CompatibilityStatus { - if (localStorage.getItem('isTesting')) { - return { bluetooth: true, usb: true, platformAllowed: true, webGL: true }; - } - - const canvas = document.createElement('canvas'); - // TODO: Handle webgl1 vs webgl2 in relation to threejs - const webGL = canvas.getContext('webgl') instanceof WebGLRenderingContext; - - const browser = Bowser.getParser(window.navigator.userAgent); - const browserVersion = browser.getBrowserVersion(); - if (!browserVersion) { - return { bluetooth: false, usb: false, platformAllowed: true, webGL: webGL }; - } - - let platformType = browser.getPlatform().type; - - // If platform won't report what it is, just assume desktop (ChromeOS doesnt report it) - if (platformType == undefined) { - platformType = 'desktop'; - } - const isPlatformAllowed = isDevMode || !nonAllowedPlatforms.includes(platformType); - - return { - bluetooth: !!navigator.bluetooth, - usb: !!navigator.usb, - platformAllowed: isPlatformAllowed, - webGL: webGL, - }; -} diff --git a/src/script/compatibility/CompatibilityList.ts b/src/script/compatibility/CompatibilityList.ts deleted file mode 100644 index 64e87cd25..000000000 --- a/src/script/compatibility/CompatibilityList.ts +++ /dev/null @@ -1,262 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -/// -import Bowser from 'bowser'; - -// REFERENCES FOR VALUES = https://github.com/lancedikson/bowser/blob/master/src/constants.js - -export const nonAllowedPlatforms = ['mobile', 'tablet', 'tv']; - -export interface SemVer { - majorVersion: number | string; - minorVersion: number | string; - - isGreaterThan(version: SemVer): boolean; - - isLessThan(version: SemVer): boolean; - - isEqualTo(version: SemVer): boolean; - - toString(): string; -} - -export class SemVerImpl implements SemVer { - majorVersion: number | string; - minorVersion: number | string; - - constructor(major: string | number, minor: string | number) { - this.majorVersion = major; - this.minorVersion = minor; - } - - private static compareSubVersions( - subVersion1: number | string, - subVersion2: number | string, - ): number { - let subVersionNum1: number = subVersion1 as number; - let subVersionNum2: number = subVersion2 as number; - if (typeof subVersion1 === 'string') { - if (subVersion1 === '*') return 0; - subVersionNum1 = parseInt(subVersion1); - } - if (typeof subVersion2 === 'string') { - if (subVersion2 === '*') return 0; - subVersionNum2 = parseInt(subVersion2); - } - - const comparison = Math.sign(subVersionNum1 - subVersionNum2); - console.assert(!isNaN(comparison)); - return comparison; - } - - public isGreaterThan(version: SemVer): boolean { - const compareMajor = SemVerImpl.compareSubVersions( - this.majorVersion, - version.majorVersion, - ); - const compareMinor = SemVerImpl.compareSubVersions( - this.minorVersion, - version.minorVersion, - ); - if (compareMajor < 0) return false; - if (compareMajor > 0) return true; - return compareMinor > 0; - } - - public isEqualTo(version: SemVer): boolean { - const compareMajor = SemVerImpl.compareSubVersions( - this.majorVersion, - version.majorVersion, - ); - const compareMinor = SemVerImpl.compareSubVersions( - this.minorVersion, - version.minorVersion, - ); - - return compareMajor == 0 && compareMinor == 0; - } - - public isLessThan(version: SemVer): boolean { - return !this.isGreaterThan(version) && !this.isEqualTo(version); - } - - public toString(): string { - const maj = parseInt(this.majorVersion.toString()); - const min = parseInt(this.minorVersion.toString()); - if (isNaN(min)) { - return maj.toString(); - } - return maj.toString() + '.' + min.toString(); - } -} - -type BrowserVersion = { - browser: string; - version: SemVer; -}; - -type BrowserException = { - browser: string; - version: SemVer; - os: string; -}; - -abstract class CompatibilityList { - protected static readonly minVersions: BrowserVersion[]; - protected static readonly exceptions: BrowserException[]; - - // TODO: Use or remove. Never used at the moment - public static getMinVersion(browserName: string): SemVer | undefined { - return this.minVersions.find( - version => version.browser.toLowerCase() === browserName.toLowerCase(), - )?.version; - } - - public static getSupportedBrowsers(): BrowserVersion[] { - return this.minVersions; - } - - public static isVersionExempted( - browserName: string, - version: SemVer, - operatingSystem: string, - ): boolean { - return ( - this.exceptions - .filter(value => value.browser.toLowerCase() == browserName.toLowerCase()) - .filter(value => value.os.toLowerCase() == operatingSystem.toLowerCase()) - .filter(value => value.version.isEqualTo(version)).length > 0 - ); - } - - public static isVersionAtLeastMin(browserName: string, version: SemVer): boolean { - return ( - this.minVersions - .filter(value => value.browser == browserName) - .filter( - value => value.version.isEqualTo(version) || value.version.isLessThan(version), - ).length > 0 - ); - } - - public static isBrowserSupported(browserName: string): boolean { - return ( - this.minVersions.filter( - value => value.browser.toLowerCase() === browserName.toLowerCase(), - ).length != 0 - ); - } - - public static isVersionSupported( - browserName: string, - version: SemVer, - operatingSystem: string, - ): boolean { - return ( - !this.isVersionExempted(browserName, version, operatingSystem) && - this.isVersionAtLeastMin(browserName, version) - ); - } - - public static isSupported( - browserName: string, - version: SemVer, - operatingSystem: string, - ): boolean { - throw new Error( - 'isSupported is not implemented on this instance of CompatibilityList!', - ); - } -} - -export class WebBluetoothCompatibility extends CompatibilityList { - protected static readonly exceptions = [] as BrowserException[]; - - protected static readonly minVersions = [ - { - browser: Bowser.BROWSER_MAP.chrome, - version: new SemVerImpl(56, '*'), - }, - { - browser: Bowser.BROWSER_MAP.chromium, - version: new SemVerImpl(56, '*'), - }, - { - browser: Bowser.BROWSER_MAP.edge, - version: new SemVerImpl(79, '*'), - }, - { - browser: Bowser.BROWSER_MAP.opera, - version: new SemVerImpl(43, '*'), - } /*{ - browser: Bowser.BROWSER_MAP.android, - version: new SemVerImpl(105, "*") - },*/ /*{ - browser: Bowser.BROWSER_MAP.samsung_internet, - version: new SemVerImpl(6, 2) - }, { - browser: Bowser.BROWSER_MAP.uc, - version: new SemVerImpl(13, 4) - }*/, - ] as BrowserVersion[]; - - public static isSupported( - browserName: string, - version: SemVer, - operatingSystem: string, - ): boolean { - if (!navigator.bluetooth) return false; - return this.isVersionSupported(browserName, version, operatingSystem); - } -} - -export class WebUSBCompatibility extends CompatibilityList { - protected static readonly exceptions = [ - { - browser: Bowser.BROWSER_MAP.chrome, - version: new SemVerImpl(105, '*'), - os: Bowser.OS_MAP.ChromeOS, - }, - ] as BrowserException[]; - - protected static readonly minVersions = [ - { - browser: Bowser.BROWSER_MAP.chrome, - version: new SemVerImpl(61, '*'), - }, - { - browser: Bowser.BROWSER_MAP.chromium, - version: new SemVerImpl(61, '*'), - }, - { - browser: Bowser.BROWSER_MAP.edge, - version: new SemVerImpl(79, '*'), - }, - { - browser: Bowser.BROWSER_MAP.opera, - version: new SemVerImpl(48, '*'), - } /* { - browser: Bowser.BROWSER_MAP.android, - version: new SemVerImpl(105, "*") - }, { - browser: Bowser.BROWSER_MAP.samsung_internet, - version: new SemVerImpl(8, 2) - }, { - browser: Bowser.BROWSER_MAP.uc, - version: new SemVerImpl(13, 4) - }*/, - ] as BrowserVersion[]; - - public static isSupported( - browserName: string, - version: SemVer, - operatingSystem: string, - ): boolean { - if (!navigator.usb) return false; - return this.isVersionSupported(browserName, version, operatingSystem); - } -} diff --git a/src/script/datafunctions.ts b/src/script/datafunctions.ts deleted file mode 100644 index ed3b3be5a..000000000 --- a/src/script/datafunctions.ts +++ /dev/null @@ -1,245 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { t } from '../i18n'; -import { get } from 'svelte/store'; - -export const Filters = { - MAX: 'max', - MEAN: 'mean', - MIN: 'min', - STD: 'std', - PEAKS: 'peaks', - ACC: 'acc', - ZCR: 'zcr', - RMS: 'rms', -} as const; - -export type FilterType = (typeof Filters)[keyof typeof Filters]; - -export const Axes = { - X: 'x', - Y: 'y', - Z: 'z', -} as const; - -export type AxesType = (typeof Axes)[keyof typeof Axes]; - -export function clamp(value: number, min: number, max: number): number { - return Math.min(Math.max(value, min), max); -} - -interface FilterStrategy { - computeOutput(data: number[]): number; - getText(): { name: string; description: string }; -} - -class MeanFilter implements FilterStrategy { - computeOutput(data: number[]): number { - return data.reduce((a, b) => a + b) / data.length; - } - getText() { - return { - name: get(t)('content.filters.mean.title'), - description: get(t)('content.filters.mean.description'), - }; - } -} - -class SDFilter implements FilterStrategy { - computeOutput(data: number[]): number { - const mean = data.reduce((a, b) => a + b) / data.length; - return Math.sqrt(data.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / data.length); - } - getText() { - return { - name: get(t)('content.filters.std.title'), - description: get(t)('content.filters.std.description'), - }; - } -} - -class RootMeanSquareFilter implements FilterStrategy { - computeOutput(data: number[]): number { - const res = Math.sqrt(data.reduce((a, b) => a + Math.pow(b, 2), 0) / data.length); - return res; - } - getText() { - return { - name: get(t)('content.filters.rms.title'), - description: get(t)('content.filters.rms.description'), - }; - } -} - -class ZeroCrossingRateFilter implements FilterStrategy { - computeOutput(data: number[]): number { - let count = 0; - for (let i = 1; i < data.length; i++) { - if ((data[i] >= 0 && data[i - 1] < 0) || (data[i] < 0 && data[i - 1] >= 0)) { - count++; - } - } - return count / (data.length - 1); - } - getText() { - return { - name: get(t)('content.filters.zcr.title'), - description: get(t)('content.filters.zcr.description'), - }; - } -} - -class TotalAccFilter implements FilterStrategy { - computeOutput(data: number[]): number { - return data.reduce((a, b) => a + Math.abs(b)); - } - getText() { - return { - name: get(t)('content.filters.acc.title'), - description: get(t)('content.filters.acc.description'), - }; - } -} - -class MaxFilter implements FilterStrategy { - computeOutput(data: number[]): number { - return Math.max(...data); - } - getText() { - return { - name: get(t)('content.filters.max.title'), - description: get(t)('content.filters.max.description'), - }; - } -} - -class MinFilter implements FilterStrategy { - computeOutput(data: number[]): number { - return Math.min(...data); - } - getText() { - return { - name: get(t)('content.filters.min.title'), - description: get(t)('content.filters.min.description'), - }; - } -} - -class PeaksFilter implements FilterStrategy { - computeOutput(data: number[]): number { - const lag = 5; - const threshold = 3.5; - const influence = 0.5; - - let peaksCounter = 0; - - if (data.length < lag + 2) { - throw new Error('data sample is too short'); - } - - // init variables - const signals = Array(data.length).fill(0) as number[]; - const filteredY = data.slice(0); - const lead_in = data.slice(0, lag); - - const avgFilter: number[] = []; - avgFilter[lag - 1] = mean(lead_in); - const stdFilter: number[] = []; - stdFilter[lag - 1] = stddev(lead_in); - - for (let i = lag; i < data.length; i++) { - if ( - Math.abs(data[i] - avgFilter[i - 1]) > 0.1 && - Math.abs(data[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1] - ) { - if (data[i] > avgFilter[i - 1]) { - signals[i] = +1; // positive signal - if (i - 1 > 0 && signals[i - 1] == 0) { - peaksCounter++; - } - } else { - signals[i] = -1; // negative signal - } - // make influence lower - filteredY[i] = influence * data[i] + (1 - influence) * filteredY[i - 1]; - } else { - signals[i] = 0; // no signal - filteredY[i] = data[i]; - } - - // adjust the filters - const y_lag = filteredY.slice(i - lag, i); - avgFilter[i] = mean(y_lag); - stdFilter[i] = stddev(y_lag); - } - return peaksCounter; - } - getText() { - return { - name: get(t)('content.filters.peaks.title'), - description: get(t)('content.filters.peaks.description'), - }; - } -} - -function mean(a: number[]): number { - return a.reduce((acc, val) => acc + val) / a.length; -} - -function stddev(arr: number[]): number { - const arr_mean = mean(arr); - const r = function (acc: number, val: number) { - return acc + (val - arr_mean) * (val - arr_mean); - }; - return Math.sqrt(arr.reduce(r, 0.0) / arr.length); -} - -export function determineFilter(filter: FilterType): FilterStrategy { - switch (filter) { - case Filters.MAX: - return new MaxFilter(); - case Filters.MIN: - return new MinFilter(); - case Filters.STD: - return new SDFilter(); - case Filters.PEAKS: - return new PeaksFilter(); - case Filters.ACC: - return new TotalAccFilter(); - case Filters.MEAN: - return new MeanFilter(); - case Filters.ZCR: - return new ZeroCrossingRateFilter(); - case Filters.RMS: - return new RootMeanSquareFilter(); - default: - throw new Error('Filter not found'); - } -} - -export function getFilterLimits(filter: FilterType): { min: number; max: number } { - switch (filter) { - case Filters.MAX: - return { min: -2.4, max: 2.4 }; - case Filters.MIN: - return { min: -2.4, max: 2.4 }; - case Filters.STD: - return { min: 0, max: 2.4 }; - case Filters.PEAKS: - return { min: 0, max: 10 }; - case Filters.ACC: - return { min: 0, max: 160 }; - case Filters.MEAN: - return { min: -2.4, max: 2.4 }; - case Filters.ZCR: - return { min: 0, max: 1 }; - case Filters.RMS: - return { min: 0, max: 2 }; - default: - throw new Error('Filter not found'); - } -} diff --git a/src/script/domain/Axes.ts b/src/script/domain/Axes.ts deleted file mode 100644 index 8bfd5492d..000000000 --- a/src/script/domain/Axes.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -enum Axes { - X = 'x', - Y = 'y', - Z = 'z', -} - -export default Axes; diff --git a/src/script/domain/BindableValue.ts b/src/script/domain/BindableValue.ts deleted file mode 100644 index eed73065f..000000000 --- a/src/script/domain/BindableValue.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { Readable, Subscriber, Unsubscriber, Updater, Writable, get } from 'svelte/store'; - -/** - * Bindable value is a wrapper that is useful when you want to allow binding of variables in custom stores. - * - * The following is an example of how to use it to allow binding of the name of gestures. - * - * ```ts - * public bindName(): BindableValue { - * const setter = (val: string) => this.setName(val); - * const bindable = new BindableValue( - * setter, - * derived(this.store, s => s.name), - * ); - * return bindable; - * } - * ``` - */ -class BindableValue implements Writable { - /** - * The setter is the function that will be used when the bound variable receives an update instruction. - * The subscribable is the store that will be used as data source - */ - constructor( - private setter: (newValue: T) => void, - private subscribable: Readable, - ) {} - public set(value: T): void { - this.setter(value); - } - public update(updater: Updater): void { - this.setter(updater(get(this.subscribable))); - } - public subscribe( - run: Subscriber, - invalidate?: ((value?: T | undefined) => void) | undefined, - ): Unsubscriber { - return this.subscribable.subscribe(run, invalidate); - } -} - -export default BindableValue; diff --git a/src/script/domain/Classifier.ts b/src/script/domain/Classifier.ts deleted file mode 100644 index 3fe5daf58..000000000 --- a/src/script/domain/Classifier.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Readable, Subscriber, Unsubscriber, Writable, derived, get } from 'svelte/store'; -import Model, { ModelData } from './Model'; -import AccelerometerClassifierInput from '../mlmodels/AccelerometerClassifierInput'; -import Filters from './Filters'; -import Gesture, { GestureID } from './Gesture'; -import ClassifierInput from './ClassifierInput'; - -type ClassifierData = { - model: ModelData; -}; - -class Classifier implements Readable { - constructor( - private model: Model, - private filters: Writable, - private gestures: Gesture[], - private confidenceSetter: (gestureId: GestureID, confidence: number) => void, - ) {} - - public subscribe( - run: Subscriber, - invalidate?: ((value?: ClassifierData | undefined) => void) | undefined, - ): Unsubscriber { - return derived([this.model], stores => { - const modelStore = stores[0]; - return { - model: modelStore, - }; - }).subscribe(run, invalidate); - } - - public async classify(input: ClassifierInput): Promise { - const filteredInput = input.getInput(get(this.filters)); - const predictions = await this.getModel().predict(filteredInput); - predictions.forEach((confidence, index) => { - const gesture = this.gestures[index]; - this.confidenceSetter(gesture.getId(), confidence); - }); - } - - public getModel(): Model { - return this.model; - } -} - -export default Classifier; diff --git a/src/script/domain/ClassifierFactory.ts b/src/script/domain/ClassifierFactory.ts deleted file mode 100644 index 3b6f7a1e8..000000000 --- a/src/script/domain/ClassifierFactory.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Writable } from 'svelte/store'; -import { RecordingData } from '../stores/mlStore'; -import Classifier from './Classifier'; -import Filters from './Filters'; -import Gesture, { GestureID } from './Gesture'; -import Model from './Model'; -import { TrainingData } from './ModelTrainer'; -import MLModel from './MLModel'; -import { TrainerConsumer } from '../repository/ClassifierRepository'; - -class ClassifierFactory { - public buildClassifier( - model: Writable, - trainerConsumer: TrainerConsumer, - filters: Writable, - gestures: Gesture[], - confidenceSetter: (gestureId: GestureID, confidence: number) => void, - ): Classifier { - return new Classifier( - this.buildModel(trainerConsumer, model), - filters, - gestures, - confidenceSetter, - ); - } - - public buildTrainingData(gestures: Gesture[], filters: Filters): TrainingData { - const classes = gestures.map(gesture => { - return { - samples: this.buildFilteredSamples(gesture.getRecordings(), filters), - }; - }); - - return { - classes, - }; - } - - private buildModel( - trainerConsumer: TrainerConsumer, - mlModel: Writable, - ): Model { - const model = new Model(trainerConsumer, mlModel); - return model; - } - - private buildFilteredSamples(recordings: RecordingData[], filters: Filters) { - return recordings.map(recording => { - const data = recording.data; - return { - value: [ - ...filters.compute(data.x), - ...filters.compute(data.y), - ...filters.compute(data.z), - ], - }; - }); - } -} - -export default ClassifierFactory; diff --git a/src/script/domain/Engine.ts b/src/script/domain/Engine.ts deleted file mode 100644 index 34002a0de..000000000 --- a/src/script/domain/Engine.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Readable } from 'svelte/store'; - -export type EngineData = { - isRunning: boolean; -}; - -interface Engine extends Readable { - start(): void; - stop(): void; -} - -export default Engine; diff --git a/src/script/domain/Filter.ts b/src/script/domain/Filter.ts deleted file mode 100644 index 9e1f2eb75..000000000 --- a/src/script/domain/Filter.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -interface Filter { - filter(inValues: number[]): number; -} - -export default Filter; diff --git a/src/script/domain/Filters.ts b/src/script/domain/Filters.ts deleted file mode 100644 index d6080cb7e..000000000 --- a/src/script/domain/Filters.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Readable, Subscriber, Unsubscriber, Writable, get } from 'svelte/store'; -import Filter from './Filter'; - -class Filters implements Readable { - constructor(private filters: Writable) {} - public subscribe( - run: Subscriber, - invalidate?: ((value?: Filter[] | undefined) => void) | undefined, - ): Unsubscriber { - return this.filters.subscribe(run, invalidate); - } - - public compute(values: number[]): number[] { - return get(this.filters).map(filter => { - return filter.filter(values); - }); - } - - public count(): number { - return get(this.filters).length; - } -} - -export default Filters; diff --git a/src/script/domain/Gesture.ts b/src/script/domain/Gesture.ts deleted file mode 100644 index 7eeeee91b..000000000 --- a/src/script/domain/Gesture.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Readable, Subscriber, Unsubscriber, Writable, derived, get } from 'svelte/store'; -import { GestureData, GestureOutput, RecordingData, SoundData } from '../stores/mlStore'; -import { PersistantGestureData } from './Gestures'; -import { PinTurnOnState } from '../../components/output/PinSelectorUtil'; -import MBSpecs from '../microbit-interfacing/MBSpecs'; -import BindableValue from './BindableValue'; -import GestureConfidence from './GestureConfidence'; - -export type GestureID = number; - -class Gesture implements Readable { - private store: Readable; - - constructor( - private persistedData: Writable, - private gestureConfidence: GestureConfidence, - ) { - this.store = this.deriveStore(); - } - - public subscribe( - run: Subscriber, - invalidate?: ((value?: GestureData | undefined) => void) | undefined, - ): Unsubscriber { - return this.store.subscribe(run, invalidate); - } - - public getId(): GestureID { - return get(this.store).ID; - } - - public getRecordings(): RecordingData[] { - return get(this.store).recordings; - } - - public getOutput(): GestureOutput { - return get(this.store).output; - } - - public setName(newName: string): void { - this.persistedData.update(val => { - val.name = newName; - return val; - }); - } - - public getName(): string { - return get(this.store).name; - } - - public addRecording(recording: RecordingData) { - this.persistedData.update(newVal => { - newVal.recordings = [recording, ...newVal.recordings]; - return newVal; - }); - } - - public removeRecording(recordingId: number) { - this.persistedData.update(newVal => { - newVal.recordings = newVal.recordings.filter( - recording => recording.ID !== recordingId, - ); - return newVal; - }); - } - - public setSoundOutput(sound: SoundData | undefined) { - this.persistedData.update(newVal => { - newVal.output.sound = sound; - return newVal; - }); - } - - public setIOPinOutput(pin: MBSpecs.UsableIOPin, state: PinTurnOnState, time: number) { - this.persistedData.update(newVal => { - newVal.output.outputPin = { - pin: pin, - pinState: state, - turnOnTime: time, - }; - return newVal; - }); - } - - public setLEDOutput(matrix: boolean[]) { - this.persistedData.update(newVal => { - newVal.output.matrix = matrix; - return newVal; - }); - } - - public bindName(): BindableValue { - const setter = (val: string) => this.setName(val); - const bindable = new BindableValue( - setter, - derived(this.store, s => s.name), - ); - return bindable; - } - - public getConfidence(): GestureConfidence { - return this.gestureConfidence; - } - - private deriveStore(): Readable { - return derived([this.persistedData, this.gestureConfidence], stores => { - const peristantData = stores[0]; - const confidenceData = stores[1]; - const derivedData: GestureData = { - ID: peristantData.ID, - name: peristantData.name, - recordings: peristantData.recordings, - output: peristantData.output, - confidence: { - currentConfidence: confidenceData.confidence, - requiredConfidence: confidenceData.requiredConfidence, - isConfident: confidenceData.isConfident, - }, - }; - - return derivedData; - }); - } -} - -export default Gesture; diff --git a/src/script/domain/GestureConfidence.ts b/src/script/domain/GestureConfidence.ts deleted file mode 100644 index c79262472..000000000 --- a/src/script/domain/GestureConfidence.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { - Readable, - Subscriber, - Unsubscriber, - Writable, - derived, - get, - writable, -} from 'svelte/store'; - -type ConfidenceData = { - confidence: number; - requiredConfidence: number; - isConfident: boolean; -}; - -class GestureConfidence implements Readable { - private requiredConfidence: Writable; - - private store: Readable; - - constructor( - requiredConfidence: number, - private confidence: Readable, - ) { - /**/ - this.requiredConfidence = writable(requiredConfidence); - this.store = this.deriveStore(); - } - - public subscribe( - run: Subscriber, - invalidate?: ((value?: ConfidenceData | undefined) => void) | undefined, - ): Unsubscriber { - return this.store.subscribe(run, invalidate); - } - - public getCurrentConfidence(): number { - return get(this.confidence); - } - - public setRequiredConfidence(value: number) { - if (value < 0 || value > 1) { - throw new Error( - 'Could not set required confidence. Cannot go outside the 0.0-1.0 range', - ); - } - this.requiredConfidence.set(value); - } - - public getRequiredConfidence(): number { - return get(this.requiredConfidence); - } - - public isConfident(): boolean { - return this.getCurrentConfidence() > this.getRequiredConfidence(); - } - - private deriveStore(): Readable { - return derived([this.confidence, this.requiredConfidence], stores => { - return { - confidence: stores[0], - requiredConfidence: stores[1], - isConfident: stores[0] > stores[1], - }; - }); - } -} - -export default GestureConfidence; diff --git a/src/script/domain/Gestures.ts b/src/script/domain/Gestures.ts deleted file mode 100644 index bc33831d9..000000000 --- a/src/script/domain/Gestures.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { - Readable, - Subscriber, - Unsubscriber, - Writable, - derived, - get, - writable, -} from 'svelte/store'; -import { GestureData, GestureOutput, RecordingData } from '../stores/mlStore'; -import Gesture, { GestureID } from './Gesture'; -import GestureRepository from '../repository/GestureRepository'; - -export type PersistantGestureData = { - name: string; - ID: GestureID; - recordings: RecordingData[]; - output: GestureOutput; -}; - -class Gestures implements Readable { - private static subscribableGestures: Writable; - private repository: GestureRepository; - - constructor(repository: GestureRepository) { - this.repository = repository; - Gestures.subscribableGestures = writable(); - this.repository.subscribe(storeArray => { - Gestures.subscribableGestures.set(storeArray); - }); - } - - public subscribe( - run: Subscriber, - invalidate?: ((value?: GestureData[] | undefined) => void) | undefined, - ): Unsubscriber { - return derived(Gestures.subscribableGestures, gestures => - gestures.map(gesture => this.gestureToGestureData(gesture)), - ).subscribe(run, invalidate); - } - - public clearGestures() { - this.repository.clearGestures(); - } - - public getGesture(gestureID: number): Gesture { - return this.repository.getGesture(gestureID); - } - - public getGestures(): Gesture[] { - return get(Gestures.subscribableGestures); - } - - public createGesture(name = ''): Gesture { - const newId = Date.now(); - return this.addGestureFromPersistedData({ - ID: newId, - recordings: [], - output: {}, //TODO: ADD DEFAULT VALUES HERE - name: name, - }); - } - - public removeGesture(gestureId: number): void { - this.repository.removeGesture(gestureId); - } - - public importFrom(gestureData: PersistantGestureData[]) { - this.clearGestures(); - gestureData.forEach(data => this.addGestureFromPersistedData(data)); - } - - public getNumberOfGestures(): number { - return get(Gestures.subscribableGestures).length; - } - - private addGestureFromPersistedData(gestureData: PersistantGestureData): Gesture { - return this.repository.addGesture(gestureData); - } - - private gestureToGestureData(gesture: Gesture): GestureData { - return { - ID: gesture.getId(), - name: gesture.getName(), - recordings: gesture.getRecordings(), - output: gesture.getOutput(), - confidence: { - currentConfidence: gesture.getConfidence().getCurrentConfidence(), - requiredConfidence: gesture.getConfidence().getRequiredConfidence(), - isConfident: gesture.getConfidence().isConfident(), - }, - }; - } -} - -export default Gestures; diff --git a/src/script/domain/LiveData.ts b/src/script/domain/LiveData.ts deleted file mode 100644 index 11a2b1c09..000000000 --- a/src/script/domain/LiveData.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Readable } from 'svelte/store'; -import LiveDataBuffer from './LiveDataBuffer'; - -interface LiveData extends Readable { - put(data: T): void; - getBuffer(): LiveDataBuffer; -} - -export default LiveData; diff --git a/src/script/domain/LiveDataBuffer.ts b/src/script/domain/LiveDataBuffer.ts deleted file mode 100644 index b6fb701ea..000000000 --- a/src/script/domain/LiveDataBuffer.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -export type TimestampedData = { - value: T; - timestamp: number; -}; -class LiveDataBuffer { - private buffer: TimestampedData[]; - private bufferPtr = 0; - private bufferUtilization = 0; - constructor(private maxLen: number) { - this.buffer = new Array>(maxLen).fill(null!); - } - - public addValue(value: T) { - const bufferIndex = this.getBufferIndex(); - this.bufferPtr++; - this.buffer[bufferIndex] = { - timestamp: Date.now(), - value: value, - }; - } - - public getSeries(time: number, noOfElements: number) { - let searchPointer = this.bufferPtr; - this.bufferUtilization = 0; // for debugging - // Search for elements that fit the time frame - const series = []; - const dateStart = Date.now(); - let i = 0; - while (i < this.maxLen) { - const element = this.buffer[this.getBufferIndexFrom(searchPointer - 1)]; - if (!element) { - console.warn('Found nulll element'); - break; - } - const timeElapsed = dateStart - element.timestamp; - if (timeElapsed > time) { - break; - } - series.push(element); - searchPointer--; - i++; - } - - this.bufferUtilization = Math.max( - series.length / this.maxLen, - noOfElements / this.maxLen, - ); - - // Now the series array is filled with elements within the timeframe. - // We should now find `noOfElements` number of items to return - if (series.length < noOfElements) { - console.error( - 'Insufficient buffer data! Try increasing the polling rate or decrease the number of elements requested', - ); - } - - if (series.length > 10 * noOfElements) { - console.warn( - 'The number of values in LiveDataBuffer, that fit the timeframe is greater than 1000%! Maybe decrease the polling frequency or increase the number of elements fetched from buffer to improve performance', - ); - } - - // We will spread out the values evenly and return the result - const resultSeries = []; - for (let i = 0; i < noOfElements; i++) { - const index = Math.floor(series.length / noOfElements) * i; - resultSeries.push(series[index]); - } - return resultSeries; - } - - public getBufferUtilization() { - return this.bufferUtilization; - } - - private getBufferIndex(): number { - return this.getBufferIndexFrom(this.bufferPtr); - } - - private getBufferIndexFrom(index: number): number { - return index % this.maxLen; - } -} - -export default LiveDataBuffer; diff --git a/src/script/domain/MLModel.ts b/src/script/domain/MLModel.ts deleted file mode 100644 index ef3cf1cee..000000000 --- a/src/script/domain/MLModel.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -interface MLModel { - predict(filteredData: number[]): Promise; -} - -export default MLModel; diff --git a/src/script/domain/MLModelFactory.ts b/src/script/domain/MLModelFactory.ts deleted file mode 100644 index fcc2c1af8..000000000 --- a/src/script/domain/MLModelFactory.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import Axes from './Axes'; -import Filters from './Filters'; -import MLModel from './MLModel'; - -export type MLModelSettings = { - axes: Axes[]; - filters: Filters; -}; - -interface MLModelFactory { - buildModel(settings: MLModelSettings): T; -} - -export default MLModelFactory; diff --git a/src/script/domain/Model.ts b/src/script/domain/Model.ts deleted file mode 100644 index 474d0263e..000000000 --- a/src/script/domain/Model.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { - Readable, - Subscriber, - Unsubscriber, - Writable, - get, - writable, -} from 'svelte/store'; -import { TrainerConsumer } from '../repository/ClassifierRepository'; -import MLModel from './MLModel'; -import ModelTrainer from './ModelTrainer'; - -export enum TrainingStatus { - Untrained, - InProgress, - Success, - Failure, -} - -export enum ModelType { - LAYERS, -} - -export type ModelData = { - trainingStatus: TrainingStatus; -}; - -class Model implements Readable { - private modelData: Writable; - - constructor( - private trainerConsumer: TrainerConsumer, - private mlModel: Readable, - ) { - this.modelData = writable({ - trainingStatus: TrainingStatus.Untrained, - }); - } - - public async train(modelTrainer: ModelTrainer): Promise { - this.modelData.update(state => { - state.trainingStatus = TrainingStatus.InProgress; - return state; - }); - try { - await this.trainerConsumer(modelTrainer); - this.modelData.update(state => { - state.trainingStatus = TrainingStatus.Success; - return state; - }); - } catch (err) { - this.modelData.update(state => { - state.trainingStatus = TrainingStatus.Failure; - return state; - }); - console.error(err); - } - } - - public isTrained(): boolean { - return get(this.modelData).trainingStatus === TrainingStatus.Success; - } - - public async predict(inputData: number[]): Promise { - return await get(this.mlModel).predict(inputData); - } - - subscribe( - run: Subscriber, - invalidate?: ((value?: ModelData | undefined) => void) | undefined, - ): Unsubscriber { - return this.modelData.subscribe(run, invalidate); - } -} - -export default Model; diff --git a/src/script/domain/ModelTrainer.ts b/src/script/domain/ModelTrainer.ts deleted file mode 100644 index ae177599c..000000000 --- a/src/script/domain/ModelTrainer.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import MLModel from './MLModel'; - -export type TrainingData = { - classes: { - samples: { - value: number[]; - }[]; - }[]; -}; - -interface ModelTrainer { - trainModel(trainingData: TrainingData): Promise; -} - -export default ModelTrainer; diff --git a/src/script/engine/PollingPredictorEngine.ts b/src/script/engine/PollingPredictorEngine.ts deleted file mode 100644 index 9e0b6c7ac..000000000 --- a/src/script/engine/PollingPredictorEngine.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Subscriber, Unsubscriber, Writable, derived, get, writable } from 'svelte/store'; -import Classifier from '../domain/Classifier'; -import Engine, { EngineData } from '../domain/Engine'; -import LiveData from '../domain/LiveData'; -import AccelerometerClassifierInput, { - AccelerometerRecording, -} from '../mlmodels/AccelerometerClassifierInput'; -import { MicrobitAccelerometerData } from '../livedata/MicrobitAccelerometerData'; - -class PollingPredictorEngine implements Engine { - private pollingInterval: ReturnType | undefined; - private pollingIntervalTime = 100; - private isRunning: Writable; - - constructor( - private classifier: Classifier, - private liveData: LiveData, - ) { - this.isRunning = writable(true); - this.startPolling(); - } - public subscribe( - run: Subscriber, - invalidate?: ((value?: EngineData | undefined) => void) | undefined, - ): Unsubscriber { - return derived([this.isRunning], stores => { - const isRunning = stores[0]; - return { - isRunning: isRunning, - }; - }).subscribe(run, invalidate); - } - - public start(): void { - this.isRunning.set(true); - } - - public stop(): void { - this.isRunning.set(false); - } - - private stopPolling() { - clearInterval(this.pollingInterval); - } - - private startPolling() { - this.pollingInterval = setInterval(() => { - void this.predict(); - }, this.pollingIntervalTime); - } - - private predict() { - if (this.classifier.getModel().isTrained() && get(this.isRunning)) { - void this.classifier.classify(this.bufferToInput()); - } - } - - private bufferToInput(): AccelerometerClassifierInput { - const bufferedData = this.liveData.getBuffer().getSeries(1800, 80); // Todo: Replace these values with appropriate sources of truth - const input: AccelerometerRecording = { - x: bufferedData.map(data => data.value.accelX), - y: bufferedData.map(data => data.value.accelY), - z: bufferedData.map(data => data.value.accelZ), - }; - return new AccelerometerClassifierInput(input); - } -} - -export default PollingPredictorEngine; diff --git a/src/script/environment.ts b/src/script/environment.ts deleted file mode 100644 index 01981d9e0..000000000 --- a/src/script/environment.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -export const appName = 'micro:bit machine learning tool'; - -// See CI & package.json scripts. -export const version = import.meta.env.VITE_VERSION || 'local'; -export type Stage = 'local' | 'review' | 'staging' | 'production'; -export const stage: Stage = (() => { - const value = (import.meta.env.VITE_STAGE || 'local').toLowerCase(); - if (['local', 'review', 'staging', 'production'].indexOf(value) === -1) { - throw new Error(`Unknown stage: ${value}`); - } - return value as Stage; -})(); -export const isDevMode = stage === 'local'; diff --git a/src/script/filters/FilterWithMaths.ts b/src/script/filters/FilterWithMaths.ts deleted file mode 100644 index 42669fe34..000000000 --- a/src/script/filters/FilterWithMaths.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import Filter from '../domain/Filter'; - -abstract class FilterWithMaths implements Filter { - abstract filter(inValues: number[]): number; - - protected stddev(arr: number[]): number { - const arr_mean = this.mean(arr); - const r = (acc: number, val: number) => { - return acc + (val - arr_mean) * (val - arr_mean); - }; - return Math.sqrt(arr.reduce(r, 0.0) / arr.length); - } - - protected mean(inValues: number[]): number { - return inValues.reduce((acc, val) => acc + val) / inValues.length; - } -} - -export default FilterWithMaths; diff --git a/src/script/filters/MaxFilter.ts b/src/script/filters/MaxFilter.ts deleted file mode 100644 index e3c547b54..000000000 --- a/src/script/filters/MaxFilter.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import Filter from '../domain/Filter'; - -class MaxFilter implements Filter { - public filter(inValues: number[]): number { - return Math.max(...inValues); - } -} -export default MaxFilter; diff --git a/src/script/filters/MeanFilter.ts b/src/script/filters/MeanFilter.ts deleted file mode 100644 index a19053f82..000000000 --- a/src/script/filters/MeanFilter.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import FilterWithMaths from './FilterWithMaths'; - -class MeanFilter extends FilterWithMaths { - public filter(inValues: number[]): number { - return this.mean(inValues); - } -} - -export default MeanFilter; diff --git a/src/script/filters/MinFilter.ts b/src/script/filters/MinFilter.ts deleted file mode 100644 index 5ecbdd705..000000000 --- a/src/script/filters/MinFilter.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import Filter from '../domain/Filter'; - -class MinFilter implements Filter { - public filter(inValues: number[]): number { - return Math.min(...inValues); - } -} - -export default MinFilter; diff --git a/src/script/filters/PeaksFilter.ts b/src/script/filters/PeaksFilter.ts deleted file mode 100644 index 9b99c3547..000000000 --- a/src/script/filters/PeaksFilter.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import FilterWithMaths from './FilterWithMaths'; - -class PeaksFilter extends FilterWithMaths { - public filter(inValues: number[]): number { - const lag = 5; - const threshold = 3.5; - const influence = 0.5; - - let peaksCounter = 0; - - if (inValues.length < lag + 2) { - throw new Error('data sample is too short'); - } - - // init variables - const signals = Array(inValues.length).fill(0) as number[]; - const filteredY = inValues.slice(0); - const lead_in = inValues.slice(0, lag); - - const avgFilter: number[] = []; - avgFilter[lag - 1] = this.mean(lead_in); - const stdFilter: number[] = []; - stdFilter[lag - 1] = this.stddev(lead_in); - - for (let i = lag; i < inValues.length; i++) { - if ( - Math.abs(inValues[i] - avgFilter[i - 1]) > 0.1 && - Math.abs(inValues[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1] - ) { - if (inValues[i] > avgFilter[i - 1]) { - signals[i] = +1; // positive signal - if (i - 1 > 0 && signals[i - 1] == 0) { - peaksCounter++; - } - } else { - signals[i] = -1; // negative signal - } - // make influence lower - filteredY[i] = influence * inValues[i] + (1 - influence) * filteredY[i - 1]; - } else { - signals[i] = 0; // no signal - filteredY[i] = inValues[i]; - } - - // adjust the filters - const y_lag = filteredY.slice(i - lag, i); - avgFilter[i] = this.mean(y_lag); - stdFilter[i] = this.stddev(y_lag); - } - return peaksCounter; - } -} - -export default PeaksFilter; diff --git a/src/script/filters/RootMeanSquareFilter.ts b/src/script/filters/RootMeanSquareFilter.ts deleted file mode 100644 index 83d2c4ae9..000000000 --- a/src/script/filters/RootMeanSquareFilter.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import Filter from '../domain/Filter'; - -class RootMeanSquareFilter implements Filter { - public filter(inValues: number[]): number { - return Math.sqrt(inValues.reduce((a, b) => a + Math.pow(b, 2), 0) / inValues.length); - } -} - -export default RootMeanSquareFilter; diff --git a/src/script/filters/StandardDeviationFilter.ts b/src/script/filters/StandardDeviationFilter.ts deleted file mode 100644 index bf504af48..000000000 --- a/src/script/filters/StandardDeviationFilter.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import FilterWithMaths from './FilterWithMaths'; - -class StandardDeviationFilter extends FilterWithMaths { - public filter(inValues: number[]): number { - const mean = this.mean(inValues); - return Math.sqrt( - inValues.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / inValues.length, - ); - } -} - -export default StandardDeviationFilter; diff --git a/src/script/filters/TotalAccFilter.ts b/src/script/filters/TotalAccFilter.ts deleted file mode 100644 index 41449c9b5..000000000 --- a/src/script/filters/TotalAccFilter.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import Filter from '../domain/Filter'; - -class TotalAccFilter implements Filter { - public filter(inValues: number[]): number { - return inValues.reduce((a, b) => a + Math.abs(b)); - } -} - -export default TotalAccFilter; diff --git a/src/script/filters/ZeroCrossingRateFilter.ts b/src/script/filters/ZeroCrossingRateFilter.ts deleted file mode 100644 index b44d1714a..000000000 --- a/src/script/filters/ZeroCrossingRateFilter.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import Filter from '../domain/Filter'; - -class ZeroCrossingRateFilter implements Filter { - public filter(inValues: number[]): number { - let count = 0; - for (let i = 1; i < inValues.length; i++) { - if ( - (inValues[i] >= 0 && inValues[i - 1] < 0) || - (inValues[i] < 0 && inValues[i - 1] >= 0) - ) { - count++; - } - } - return count / (inValues.length - 1); - } -} - -export default ZeroCrossingRateFilter; diff --git a/src/script/flags.ts b/src/script/flags.ts deleted file mode 100644 index 35f00016e..000000000 --- a/src/script/flags.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -/** - * Features disabled here even in preview are not ready for feedback. - * - * Preview features are not ready for general use. - */ - -import { Stage, stage as stageFromEnvironment } from './environment'; - -/** - * A union of the flag names (alphabetical order). - */ -export type Flag = - /** - * Changes radio bridge hexes to a bridge hex that uses local data rather than communicating with a remote. - * There's no need to flash micro:bit one in this scenario (as they're the same). - */ - | 'radioLocal' - /** - * Changes the remote radio bridge hex to one that uses button A to stop sending data. - */ - | 'radioRemoteDev'; - -interface FlagMetadata { - defaultOnStages: Stage[]; - name: Flag; -} - -const allFlags: FlagMetadata[] = [ - // Alphabetical order. - { name: 'radioLocal', defaultOnStages: [] }, - { name: 'radioRemoteDev', defaultOnStages: [] }, -]; - -type Flags = Record; - -// Exposed for testing. -export const flagsForParams = (stage: Stage, params: URLSearchParams) => { - const enableFlags = new Set(params.getAll('flag')); - const allFlagsDefault = enableFlags.has('none') - ? false - : enableFlags.has('*') - ? true - : undefined; - return Object.fromEntries( - allFlags.map(f => [ - f.name, - isEnabled(f, stage, allFlagsDefault, enableFlags.has(f.name)), - ]), - ) as Flags; -}; - -const isEnabled = ( - f: FlagMetadata, - stage: Stage, - allFlagsDefault: boolean | undefined, - thisFlagOn: boolean, -): boolean => { - if (thisFlagOn) { - return true; - } - if (allFlagsDefault !== undefined) { - return allFlagsDefault; - } - return f.defaultOnStages.includes(stage); -}; - -export const flags: Flags = (() => { - const params = new URLSearchParams(window.location.search); - return flagsForParams(stageFromEnvironment, params); -})(); diff --git a/src/script/getPrediction.ts b/src/script/getPrediction.ts deleted file mode 100644 index 54c57012f..000000000 --- a/src/script/getPrediction.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { GestureData } from './stores/mlStore'; - -export const getPrediction = (gestures: GestureData[]): GestureData | undefined => { - const confidentGestures = gestures.filter(g => g.confidence.isConfident); - if (confidentGestures.length === 0) { - return undefined; - } - return confidentGestures.reduce((prevPredictedGesture, gesture) => { - return prevPredictedGesture.confidence.currentConfidence < - gesture.confidence.currentConfidence - ? gesture - : prevPredictedGesture; - }); -}; diff --git a/src/script/livedata/MicrobitAccelerometerData.ts b/src/script/livedata/MicrobitAccelerometerData.ts deleted file mode 100644 index 5d9a75141..000000000 --- a/src/script/livedata/MicrobitAccelerometerData.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Subscriber, Unsubscriber, Writable, writable } from 'svelte/store'; -import LiveData from '../domain/LiveData'; -import LiveDataBuffer from '../domain/LiveDataBuffer'; - -export type MicrobitAccelerometerData = { - accelX: number; - accelY: number; - accelZ: number; - smoothedAccelX: number; - smoothedAccelY: number; - smoothedAccelZ: number; -}; - -class MicrobitAccelerometerLiveData implements LiveData { - private store: Writable; - constructor(private dataBuffer: LiveDataBuffer) { - this.store = writable({ - accelX: 0, - accelY: 0, - accelZ: 0, - smoothedAccelX: 0, - smoothedAccelY: 0, - smoothedAccelZ: 0, - }); - } - - public getBuffer(): LiveDataBuffer { - return this.dataBuffer; - } - - public put(data: MicrobitAccelerometerData): void { - this.store.set(data); - this.dataBuffer.addValue(data); - } - - public subscribe( - run: Subscriber, - invalidate?: ((value?: MicrobitAccelerometerData | undefined) => void) | undefined, - ): Unsubscriber { - return this.store.subscribe(run, invalidate); - } -} - -export default MicrobitAccelerometerLiveData; diff --git a/src/script/microbit-interfacing/MBSpecs.ts b/src/script/microbit-interfacing/MBSpecs.ts deleted file mode 100644 index 8064918e2..000000000 --- a/src/script/microbit-interfacing/MBSpecs.ts +++ /dev/null @@ -1,353 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -/* eslint-disable @typescript-eslint/no-namespace */ -/** - * References to the Bluetooth Profile UUIDs. - */ - -namespace MBSpecs { - /** - * The UUIDs of the services available on the micro:bit. - */ - export namespace Services { - /** - * The UUID of the micro:bit's UART service. - */ - export const UART_SERVICE = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'; - /** - * The micro:bits accelerometer service. - */ - export const ACCEL_SERVICE = 'e95d0753-251d-470a-a062-fa1922dfa9a8'; - /** - * The device information service. Exposes information about manufacturer, vendor, and firmware version. - */ - export const DEVICE_INFO_SERVICE = '0000180a-0000-1000-8000-00805f9b34fb'; - /** - * Used for controlling the LEDs on the micro:bit. - */ - export const LED_SERVICE = 'e95dd91d-251d-470a-a062-fa1922dfa9a8'; - /** - * The UUID of the micro:bit's IO service. - */ - export const IO_SERVICE = 'e95d127b-251d-470a-a062-fa1922dfa9a8'; - /** - * Service for buttons on the micro:bit. - */ - export const BUTTON_SERVICE = 'e95d9882-251d-470a-a062-fa1922dfa9a8'; - } - - /** - * The UUIDs of the characteristics available on the micro:bit. - */ - export namespace Characteristics { - /** - * Characteristic for the A button. - */ - export const BUTTON_A = 'e95dda90-251d-470a-a062-fa1922dfa9a8'; - /** - * Characteristic for the B button. - */ - export const BUTTON_B = 'e95dda91-251d-470a-a062-fa1922dfa9a8'; - /** - * The accelerometer data characteristic. - */ - export const ACCEL_DATA = 'e95dca4b-251d-470a-a062-fa1922dfa9a8'; - /** - * IO data characteristic. Used for controlling IO pins on the micro:bit. - */ - export const IO_DATA = 'e95d8d00-251d-470a-a062-fa1922dfa9a8'; - /** - * Allows the state of any|all LEDs in the 5x5 grid to be set to on or off with a single GATT operation. - * - * Octet 0, LED Row 1: bit4 bit3 bit2 bit1 bit0 - * - * Octet 1, LED Row 2: bit4 bit3 bit2 bit1 bit0 - * - * Octet 2, LED Row 3: bit4 bit3 bit2 bit1 bit0 - * - * Octet 3, LED Row 4: bit4 bit3 bit2 bit1 bit0 - * - * Octet 4, LED Row 5: bit4 bit3 bit2 bit1 bit0 - */ - export const LED_MATRIX_STATE = 'e95d7b77-251d-470a-a062-fa1922dfa9a8'; - - /** - * The model number of the micro:bit as a string. - */ - export const MODEL_NUMBER = '00002a24-0000-1000-8000-00805f9b34fb'; - /** - * The UUID of the micro:bit's UART TX characteristic. - * Used to listen for data from the micro:bit. - */ - export const UART_DATA_TX = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'; - /** - * The UUID of the micro:bit's UART RX characteristic. - * Used for sending data to the micro:bit. - */ - export const UART_DATA_RX = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'; - } - - export namespace USBSpecs { - export const PRODUCT_ID = 516; - export const VENDOR_ID = 3368; - export const FICR = 0x10000000; - export const DEVICE_ID_1 = 0x064; - export const MICROBIT_NAME_LENGTH = 5; - export const MICROBIT_NAME_CODE_LETTERS = 5; - } - - /** - * The buttons on the micro:bit. - */ - export type Button = 'A' | 'B'; - - export type MBVersion = 1 | 2; - - /** - * The state of the buttons on the micro:bit. Becomes LongPressed when the button is held for more than ~2 second. - */ - export type ButtonState = - | ButtonStates.Released - | ButtonStates.Pressed - | ButtonStates.LongPressed; - - /** - * The state of the buttons on the micro:bit. Becomes LongPressed when the button is held for more than ~2 second. - */ - export enum ButtonStates { - Released, - Pressed, - LongPressed, - } - - /** - * Ordered list of all IO pins. Such as 0, 2, '3V', 'GND', etc. - */ - export const IO_PIN_LAYOUT: IOPin[] = [ - 3, - 0, - 4, - 5, - 6, - 7, - 1, - 8, - 9, - 10, - 11, - 12, - 2, - 13, - 14, - 15, - 16, - 17, - '3V', - 18, - 19, - 20, - 21, - 'GND', - 24, - ]; - - export type IOPin = - | 3 - | 0 - | 4 - | 5 - | 6 - | 7 - | 1 - | 8 - | 9 - | 10 - | 11 - | 12 - | 2 - | 13 - | 14 - | 15 - | 16 - | 17 - | '3V' - | 18 - | 19 - | 20 - | 21 - | 'GND' - | 24; - - export type UsableIOPin = 0 | 1 | 2; - - /** - * Utilities for working with the micro:bit's Bluetooth Profile. - */ - export class Utility { - private static CODEBOOK_USB = [ - ['z', 'v', 'g', 'p', 't'], - ['u', 'o', 'i', 'e', 'a'], - ['z', 'v', 'g', 'p', 't'], - ['u', 'o', 'i', 'e', 'a'], - ['z', 'v', 'g', 'p', 't'], - ]; - - /** - * This is a version of the microbit codebook, where the original codebook is transposed - * and the rows are flipped. This gives an easier to use version for the bluetooth pattern - * connection. - * This could be done programatically, but having it typed out hopefully helps - * with the understanding of pattern <-> friendly name conversion - */ - private static CODEBOOK_BLUETOOTH: string[][] = [ - ['t', 'a', 't', 'a', 't'], - ['p', 'e', 'p', 'e', 'p'], - ['g', 'i', 'g', 'i', 'g'], - ['v', 'o', 'v', 'o', 'v'], - ['z', 'u', 'z', 'u', 'z'], - ]; - - /** - * Converts a micro:bit serial number to it's corresponding friendly name - * @param {number} serialNo The serial number of the micro:bit - * @returns {string} the name of the micro:bit. - */ - public static serialNumberToName(serialNo: number): string { - let d = USBSpecs.MICROBIT_NAME_CODE_LETTERS; - let ld = 1; - let name = ''; - - for (let i = 0; i < USBSpecs.MICROBIT_NAME_LENGTH; i++) { - const h = Math.floor((serialNo % d) / ld); - serialNo -= h; - d *= USBSpecs.MICROBIT_NAME_CODE_LETTERS; - ld *= USBSpecs.MICROBIT_NAME_CODE_LETTERS; - name = Utility.CODEBOOK_USB[i][h] + name; - } - return name; - } - - public static messageToDataview(message: string, delimiter = '#'): DataView { - if (delimiter.length != 1) { - throw new Error('The delimiter must be 1 character long'); - } - const fullMessage = `${message}${delimiter}`; - const view = new DataView(new ArrayBuffer(fullMessage.length)); - for (let i = 0; i < fullMessage.length; i++) { - view.setUint8(i, fullMessage.charCodeAt(i)); - } - return view; - } - - /** - * Converts a pairing pattern to a name. - * See guide on microbit names to understand how a pattern is turned into a name - * https://support.microbit.org/support/solutions/articles/19000067679-how-to-find-the-name-of-your-micro-bit - * @param {boolean[]} pattern The pattern to convert. - * @returns {string} The name of the micro:bit. - */ - public static patternToName(pattern: boolean[]): string { - const code: string[] = [' ', ' ', ' ', ' ', ' ']; - - for (let col = 0; col < USBSpecs.MICROBIT_NAME_LENGTH; col++) { - for (let row = 0; row < USBSpecs.MICROBIT_NAME_LENGTH; row++) { - if (pattern[row * USBSpecs.MICROBIT_NAME_LENGTH + col]) { - // Find the first vertical on/true in each column - code[col] = this.CODEBOOK_BLUETOOTH[row][col]; // Use code-book to find char - break; // Rest of column is irrelevant - } - // If we get to here the pattern is not legal, and the returned name - // will not match any microbit. - } - } - - return code.join(''); - } - - public static isPairingPattermValid(pattern: boolean[]): boolean { - for (let col = 0; col < USBSpecs.MICROBIT_NAME_LENGTH; col++) { - let isAnyHighlighted = false; - for (let row = 0; row < USBSpecs.MICROBIT_NAME_LENGTH; row++) { - if (pattern[row * USBSpecs.MICROBIT_NAME_LENGTH + col]) { - isAnyHighlighted = true; - } - } - if (!isAnyHighlighted) { - return false; - } - } - return true; - } - - /** - * Converts a name to a pairing pattern. - * IMPORTANT: Assumes correct microbit name. Not enough error handling for - * incorrect names. - * @param {string} name The name of the micro:bit - * @returns {boolean[]} The pairing pattern - */ - public static nameToPattern(name: string): boolean[] { - const pattern: boolean[] = new Array(25).fill(true); - - // if wrong name length, return empty pattern - if (name.length != USBSpecs.MICROBIT_NAME_LENGTH) { - return pattern.map(() => false); - } - - for (let column = 0; column < USBSpecs.MICROBIT_NAME_LENGTH; column++) { - for (let row = 0; row < USBSpecs.MICROBIT_NAME_LENGTH; row++) { - if (this.CODEBOOK_BLUETOOTH[row][column] === name.charAt(column)) { - break; - } - pattern[5 * row + column] = false; - } - } - - return pattern; - } - - /** - * Converts a binary number represented as an array of numbers into an octet. - * @param {number[]} array Bitmap array to convert. - * @returns {number} The octet. - */ - public static arrayToOctet(array: number[]): number; - - /** - * Converts a binary number represented as an array of boolean into an octet. - * @param {boolean[]} array Bitmap array to convert. - * @returns {number} The octet. - */ - public static arrayToOctet(array: boolean[]): number; - - /** - * Converts a binary number represented as an array of numbers into an octet. - * - * @param array Bitmap array to convert. - * @returns {number} The octet. - */ - public static arrayToOctet(array: unknown[]): number { - let numArray: number[] = array as number[]; - if (typeof array[0] === 'boolean') { - const typedArray = array as boolean[]; - numArray = typedArray.map((value: boolean) => (value ? 1 : 0)); - } - let sum = 0; - // We track the bit pos because the value of the octet, will just be the decimal value of the bit representation. - let bitPos = 0; - // Walk in reverse order to match the order of the micro:bit. - for (let i = numArray.length - 1; i >= 0; i--) { - // Just calculate the decimal value of the bit position and add it to the sum. - sum += numArray[i] * Math.pow(2, bitPos); - bitPos++; - } - return sum; - } - } -} - -export default MBSpecs; diff --git a/src/script/microbit-interfacing/MicrobitBluetooth.ts b/src/script/microbit-interfacing/MicrobitBluetooth.ts deleted file mode 100644 index 9aee838a2..000000000 --- a/src/script/microbit-interfacing/MicrobitBluetooth.ts +++ /dev/null @@ -1,565 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import Bowser from 'bowser'; -import StaticConfiguration from '../../StaticConfiguration'; -import { outputting } from '../stores/uiStore'; -import { logError, logEvent, logMessage } from '../utils/logging'; -import MBSpecs from './MBSpecs'; -import MicrobitConnection, { DeviceRequestStates } from './MicrobitConnection'; -import { UARTMessageType } from './Microbits'; -import { - onAccelerometerChange, - onButtonChange, - onUARTDataReceived, -} from './change-listeners'; -import { - stateOnAssigned, - stateOnConnected, - stateOnDisconnected, - stateOnReady, - stateOnReconnectionAttempt, -} from './state-updaters'; -import { btSelectMicrobitDialogOnLoad } from '../stores/connectionStore'; - -const browser = Bowser.getParser(window.navigator.userAgent); -const isWindowsOS = browser.getOSName() === 'Windows'; - -/** - * UART data target. For fixing type compatibility issues. - */ -export type CharacteristicDataTarget = EventTarget & { - value: DataView; -}; - -type OutputCharacteristics = { - io: BluetoothRemoteGATTCharacteristic; - matrix: BluetoothRemoteGATTCharacteristic; - uart: BluetoothRemoteGATTCharacteristic; -}; - -export class MicrobitBluetooth implements MicrobitConnection { - inUseAs: Set = new Set(); - - private outputCharacteristics: OutputCharacteristics | undefined; - - // Used to avoid automatic reconnection during user triggered connect/disconnect - // or reconnection itself. - private duringExplicitConnectDisconnect: number = 0; - - // On ChromeOS and Mac there's no timeout and no clear way to abort - // device.gatt.connect(), so we accept that sometimes we'll still - // be trying to connect when we'd rather not be. If it succeeds when - // we no longer intend to be connected then we disconnect at that - // point. If we try to connect when a previous connection attempt is - // still around then we wait for it for our timeout period. - // - // On Windows it times out after 7s. - // https://bugs.chromium.org/p/chromium/issues/detail?id=684073 - private gattConnectPromise: Promise | undefined; - private disconnectPromise: Promise | undefined; - private connecting = false; - private isReconnect = false; - private reconnectReadyPromise: Promise | undefined; - // Whether this is the final reconnection attempt. - private finalAttempt = false; - - private outputWriteQueue: { - busy: boolean; - queue: Array<(outputCharacteristics: OutputCharacteristics) => Promise>; - } = { - busy: false, - queue: [], - }; - - constructor( - public readonly name: string, - public readonly device: BluetoothDevice, - ) { - device.addEventListener('gattserverdisconnected', this.handleDisconnectEvent); - } - - async connect(...states: DeviceRequestStates[]): Promise { - logEvent({ - type: this.isReconnect ? 'Reconnect' : 'Connect', - action: 'Bluetooth connect start', - states, - }); - if (this.duringExplicitConnectDisconnect) { - logMessage('Skipping connect attempt when one is already in progress'); - // Wait for the gattConnectPromise while showing a "connecting" dialog. - // If the user clicks disconnect while the automatic reconnect is in progress, - // then clicks reconnect, we need to wait rather than return immediately. - await this.gattConnectPromise; - return; - } - this.duringExplicitConnectDisconnect++; - if (this.device.gatt === undefined) { - throw new Error('BluetoothRemoteGATTServer for micro:bit device is undefined'); - } - try { - // A previous connect might have completed in the background as a device was replugged etc. - await this.disconnectPromise; - this.gattConnectPromise = - this.gattConnectPromise ?? - this.device.gatt - .connect() - .then(async () => { - // We always do this even if we might immediately disconnect as disconnecting - // without using services causes getPrimaryService calls to hang on subsequent - // reconnect - probably a device-side issue. - const modelNumber = await this.getModelNumber(); - // This connection could be arbitrarily later when our manual timeout may have passed. - // Do we still want to be connected? - if (!this.connecting) { - logMessage( - 'Bluetooth GATT server connect after timeout, triggering disconnect', - ); - this.disconnectPromise = (async () => { - await this.disconnectInternal(false, false); - this.disconnectPromise = undefined; - })(); - } else { - logMessage('Bluetooth GATT server connected when connecting'); - } - return modelNumber; - }) - .catch(e => { - if (this.connecting) { - // Error will be logged by main connect error handling. - throw e; - } else { - logError('Bluetooth GATT server connect error after our timeout', e); - return undefined; - } - }) - .finally(() => { - logMessage('Bluetooth GATT server promise field cleared'); - this.gattConnectPromise = undefined; - }); - - this.connecting = true; - let microbitVersion: MBSpecs.MBVersion | undefined; - try { - const gattConnectResult = await Promise.race([ - this.gattConnectPromise, - new Promise<'timeout'>(resolve => - setTimeout( - () => resolve('timeout'), - StaticConfiguration.connectTimeoutDuration, - ), - ), - ]); - if (gattConnectResult === 'timeout') { - logMessage('Bluetooth GATT server connect timeout'); - throw new Error('Bluetooth GATT server connect timeout'); - } - microbitVersion = gattConnectResult; - } finally { - this.connecting = false; - } - - logMessage('Bluetooth GATT server connected', this.device.gatt.connected); - - states.forEach(stateOnConnected); - if (states.includes(DeviceRequestStates.INPUT)) { - await this.listenToInputServices(); - } - if (states.includes(DeviceRequestStates.OUTPUT)) { - await this.listenToOutputServices(); - } - states.forEach(s => this.inUseAs.add(s)); - states.forEach(s => stateOnAssigned(s, microbitVersion!)); - states.forEach(s => stateOnReady(s)); - logEvent({ - type: this.isReconnect ? 'Reconnect' : 'Connect', - action: 'Bluetooth connect success', - states, - }); - } catch (e) { - logError('Bluetooth connect error', e); - logEvent({ - type: this.isReconnect ? 'Reconnect' : 'Connect', - action: 'Bluetooth connect failed', - states, - }); - await this.disconnectInternal(false); - throw new Error('Failed to establish a connection!'); - } finally { - this.finalAttempt = false; - this.duringExplicitConnectDisconnect--; - } - } - - async disconnect(): Promise { - return this.disconnectInternal(true); - } - - private async disconnectInternal( - userTriggered: boolean, - updateState: boolean = true, - ): Promise { - logMessage( - `Bluetooth disconnect ${userTriggered ? '(user triggered)' : '(programmatic)'}`, - ); - this.duringExplicitConnectDisconnect++; - try { - if (this.device.gatt?.connected) { - this.device.gatt?.disconnect(); - } - } catch (e) { - logError('Bluetooth GATT disconnect error (ignored)', e); - // We might have already lost the connection. - } finally { - this.duringExplicitConnectDisconnect--; - } - this.reconnectReadyPromise = new Promise(resolve => setTimeout(resolve, 3_500)); - if (updateState) { - this.inUseAs.forEach(value => - stateOnDisconnected( - value, - userTriggered || this.finalAttempt - ? false - : this.isReconnect - ? 'autoReconnect' - : 'connect', - 'bluetooth', - ), - ); - } - } - - async reconnect(finalAttempt: boolean = false): Promise { - this.finalAttempt = finalAttempt; - logMessage('Bluetooth reconnect'); - this.isReconnect = true; - const as = Array.from(this.inUseAs); - if (isWindowsOS) { - // On Windows, the micro:bit can take around 3 seconds to respond to gatt.disconnect(). - // Attempting to reconnect before the micro:bit has responded results in another - // gattserverdisconnected event being fired. We then fail to get primaryService on a - // disconnected GATT server. - await this.reconnectReadyPromise; - } - await this.connect(...as); - } - - handleDisconnectEvent = async (): Promise => { - this.outputWriteQueue = { busy: false, queue: [] }; - - try { - if (!this.duringExplicitConnectDisconnect) { - logMessage('Bluetooth GATT disconnected... automatically trying reconnect'); - stateOnReconnectionAttempt(); - await this.reconnect(); - } else { - logMessage('Bluetooth GATT disconnect ignored during explicit disconnect'); - } - } catch (e) { - logError('Bluetooth connect triggered by disconnect listener failed', e); - this.inUseAs.forEach(s => stateOnDisconnected(s, 'autoReconnect', 'bluetooth')); - } - }; - - private assertGattServer(): BluetoothRemoteGATTServer { - if (!this.device.gatt?.connected) { - throw new Error('Could not listen to services, no microbit connected!'); - } - return this.device.gatt; - } - - private async listenToInputServices(): Promise { - await this.listenToAccelerometer(); - await this.listenToButton('A'); - await this.listenToButton('B'); - - await this.listenToUART(DeviceRequestStates.INPUT); - } - - private async listenToButton(buttonToListenFor: MBSpecs.Button): Promise { - const gattServer = this.assertGattServer(); - const buttonService = await gattServer.getPrimaryService( - MBSpecs.Services.BUTTON_SERVICE, - ); - - // Select the correct characteristic to listen to. - const uuid = - buttonToListenFor === 'A' - ? MBSpecs.Characteristics.BUTTON_A - : MBSpecs.Characteristics.BUTTON_B; - const buttonCharacteristic = await buttonService.getCharacteristic(uuid); - - await buttonCharacteristic.startNotifications(); - - buttonCharacteristic.addEventListener( - 'characteristicvaluechanged', - (event: Event) => { - const target = event.target as CharacteristicDataTarget; - const stateId = target.value.getUint8(0); - let state = MBSpecs.ButtonStates.Released; - if (stateId === 1) { - state = MBSpecs.ButtonStates.Pressed; - } - if (stateId === 2) { - state = MBSpecs.ButtonStates.LongPressed; - } - onButtonChange(state, buttonToListenFor); - }, - ); - } - - private async listenToAccelerometer(): Promise { - const gattServer = this.assertGattServer(); - const accelerometerService = await gattServer.getPrimaryService( - MBSpecs.Services.ACCEL_SERVICE, - ); - const accelerometerCharacteristic = await accelerometerService.getCharacteristic( - MBSpecs.Characteristics.ACCEL_DATA, - ); - await accelerometerCharacteristic.startNotifications(); - accelerometerCharacteristic.addEventListener( - 'characteristicvaluechanged', - (event: Event) => { - const target = event.target as CharacteristicDataTarget; - const x = target.value.getInt16(0, true); - const y = target.value.getInt16(2, true); - const z = target.value.getInt16(4, true); - onAccelerometerChange(x, y, z); - }, - ); - } - - private async listenToUART(state: DeviceRequestStates): Promise { - const gattServer = this.assertGattServer(); - const uartService = await gattServer.getPrimaryService(MBSpecs.Services.UART_SERVICE); - const uartTXCharacteristic = await uartService.getCharacteristic( - MBSpecs.Characteristics.UART_DATA_TX, - ); - await uartTXCharacteristic.startNotifications(); - uartTXCharacteristic.addEventListener( - 'characteristicvaluechanged', - (event: Event) => { - // Convert the data to a string. - const receivedData: number[] = []; - const target = event.target as CharacteristicDataTarget; - for (let i = 0; i < target.value.byteLength; i += 1) { - receivedData[i] = target.value.getUint8(i); - } - const receivedString = String.fromCharCode.apply(null, receivedData); - onUARTDataReceived(state, receivedString); - }, - ); - } - - private async listenToOutputServices(): Promise { - const gattServer = this.assertGattServer(); - if (!gattServer.connected) { - throw new Error('Could not listen to services, no microbit connected!'); - } - const ioService = await gattServer.getPrimaryService(MBSpecs.Services.IO_SERVICE); - const io = await ioService.getCharacteristic(MBSpecs.Characteristics.IO_DATA); - const ledService = await gattServer.getPrimaryService(MBSpecs.Services.LED_SERVICE); - const matrix = await ledService.getCharacteristic( - MBSpecs.Characteristics.LED_MATRIX_STATE, - ); - const uartService = await gattServer.getPrimaryService(MBSpecs.Services.UART_SERVICE); - const uart = await uartService.getCharacteristic( - MBSpecs.Characteristics.UART_DATA_RX, - ); - this.outputCharacteristics = { - io, - matrix, - uart, - }; - await this.listenToUART(DeviceRequestStates.OUTPUT); - } - - private setPinInternal = (pin: MBSpecs.UsableIOPin, on: boolean): void => { - this.queueAction(outputCharacteristics => { - const dataView = new DataView(new ArrayBuffer(2)); - dataView.setInt8(0, pin); - dataView.setInt8(1, on ? 1 : 0); - outputting.set({ text: `Turn pin ${pin} ${on ? 'on' : 'off'}` }); - return outputCharacteristics.io.writeValue(dataView); - }); - }; - - // Counter tracking the pin state. Incremented when we turn it on - // and decremented when we turn it off. This avoids us turning off - // pins that others have turned on (i.e. we bias towards enabling - // pins). - private pinStateCounters = new Map(); - - setPin(pin: MBSpecs.UsableIOPin, on: boolean): void { - let stateCounter = this.pinStateCounters.get(pin) ?? 0; - stateCounter = stateCounter + (on ? 1 : -1); - // Has it transitioned to off or on? - const changed = stateCounter === 0 || stateCounter === 1; - this.pinStateCounters.set(pin, Math.max(0, stateCounter)); - if (changed) { - this.setPinInternal(pin, on); - } - } - - resetPins = () => { - this.pinStateCounters = new Map(); - StaticConfiguration.supportedPins.forEach(value => { - this.setPinInternal(value, false); - }); - }; - - setLeds = (matrix: boolean[]): void => { - this.queueAction(outputCharacteristics => { - const dataView = new DataView(new ArrayBuffer(5)); - for (let i = 0; i < 5; i++) { - dataView.setUint8( - i, - matrix - .slice(i * 5, 5 + i * 5) - .reduce((byte, bool) => (byte << 1) | (bool ? 1 : 0), 0), - ); - } - return outputCharacteristics.matrix.writeValue(dataView); - }); - }; - - /** - * Sends a message through UART - * @param type The type of UART message, i.e 'g' for gesture and 's' for sound - * @param value The message - */ - sendToOutputUart = (type: UARTMessageType, value: string): void => { - this.queueAction(outputCharacteristics => { - const view = MBSpecs.Utility.messageToDataview(`${type}_${value}`); - return outputCharacteristics.uart.writeValue(view); - }); - }; - - queueAction = ( - action: (outputCharacteristics: OutputCharacteristics) => Promise, - ) => { - this.outputWriteQueue.queue.push(action); - this.processActionQueue(); - }; - - processActionQueue = () => { - if (!this.outputCharacteristics) { - // We've become disconnected before processing all actions. - this.outputWriteQueue = { - busy: false, - queue: [], - }; - return; - } - if (this.outputWriteQueue.busy) { - return; - } - const action = this.outputWriteQueue.queue.shift(); - if (action) { - this.outputWriteQueue.busy = true; - action(this.outputCharacteristics) - .then(() => { - this.outputWriteQueue.busy = false; - this.processActionQueue(); - }) - .catch(e => { - logError('Error processing action queue', e); - // Do we want to keep going if we hit errors? - // What did it do previously? - this.outputWriteQueue.busy = false; - this.processActionQueue(); - }); - } - }; - - /** - * Fetches the model number of the micro:bit. - * @param {BluetoothRemoteGATTServer} gattServer The GATT server to read from. - * @return {Promise} The model number of the micro:bit. 1 for the original, 2 for the new. - */ - private async getModelNumber(): Promise { - this.assertGattServer(); - try { - const deviceInfo = await this.assertGattServer().getPrimaryService( - MBSpecs.Services.DEVICE_INFO_SERVICE, - ); - const modelNumber = await deviceInfo.getCharacteristic( - MBSpecs.Characteristics.MODEL_NUMBER, - ); - // Read the value and convert it to UTF-8 (as specified in the Bluetooth specification). - const modelNumberValue = await modelNumber.readValue(); - const decodedModelNumber = new TextDecoder().decode(modelNumberValue); - // The model number either reads "BBC micro:bit" or "BBC micro:bit V2.0". Still unsure if those are the only cases. - if (decodedModelNumber.toLowerCase() === 'BBC micro:bit'.toLowerCase()) { - return 1; - } - if (decodedModelNumber.toLowerCase().includes('BBC micro:bit v2'.toLowerCase())) { - return 2; - } - throw new Error(`Unexpected model number ${decodedModelNumber}`); - } catch (e) { - logError('Could not read model number', e); - throw new Error('Could not read model number'); - } - } -} - -const deviceIdToConnection: Map = new Map(); - -export const startBluetoothConnection = async ( - name: string, - requestState: DeviceRequestStates, -): Promise => { - const device = await requestDevice(name); - if (!device) { - return undefined; - } - try { - // Reuse our connection objects for the same device as they - // track the GATT connect promise that never resolves. - const bluetooth = - deviceIdToConnection.get(device.id) ?? new MicrobitBluetooth(name, device); - deviceIdToConnection.set(device.id, bluetooth); - await bluetooth.connect(requestState); - return bluetooth; - } catch (e) { - return undefined; - } -}; - -const requestDevice = async (name: string): Promise => { - try { - // In some situations the Chrome device prompt simply doesn't appear so we time this out after 30 seconds and reload the page - const result = await Promise.race([ - navigator.bluetooth.requestDevice({ - filters: [{ namePrefix: `BBC micro:bit [${name}]` }], - optionalServices: [ - MBSpecs.Services.UART_SERVICE, - MBSpecs.Services.ACCEL_SERVICE, - MBSpecs.Services.DEVICE_INFO_SERVICE, - MBSpecs.Services.LED_SERVICE, - MBSpecs.Services.IO_SERVICE, - MBSpecs.Services.BUTTON_SERVICE, - ], - }), - new Promise<'timeout'>(resolve => - setTimeout( - () => resolve('timeout'), - StaticConfiguration.requestDeviceTimeoutDuration, - ), - ), - ]); - if (result === 'timeout') { - btSelectMicrobitDialogOnLoad.set(true); - window.location.reload(); - return undefined; - } - return result; - } catch (e) { - logError('Bluetooth request device failed/cancelled', e); - return undefined; - } -}; diff --git a/src/script/microbit-interfacing/MicrobitConnection.ts b/src/script/microbit-interfacing/MicrobitConnection.ts deleted file mode 100644 index 9b8b161aa..000000000 --- a/src/script/microbit-interfacing/MicrobitConnection.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -export enum DeviceRequestStates { - NONE, - INPUT, - OUTPUT, -} - -/** - * A connection to a micro:bit. - */ -interface MicrobitConnection { - connect(...states: DeviceRequestStates[]): Promise; - - reconnect(finalAttempt: boolean): Promise; - - disconnect(): Promise; -} - -export default MicrobitConnection; diff --git a/src/script/microbit-interfacing/MicrobitSerial.ts b/src/script/microbit-interfacing/MicrobitSerial.ts deleted file mode 100644 index d574c4885..000000000 --- a/src/script/microbit-interfacing/MicrobitSerial.ts +++ /dev/null @@ -1,324 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { logError, logEvent, logMessage } from '../utils/logging'; -import MicrobitConnection, { DeviceRequestStates } from './MicrobitConnection'; -import MicrobitUSB from './MicrobitUSB'; -import { onAccelerometerChange, onButtonChange } from './change-listeners'; -import * as protocol from './serialProtocol'; -import { - stateOnAssigned, - stateOnConnected, - stateOnDisconnected, - stateOnReady, - stateOnReconnected, - stateOnReconnectionAttempt, -} from './state-updaters'; -import StaticConfiguration from '../../StaticConfiguration'; -import { ConnectionType } from '../stores/uiStore'; - -class BridgeError extends Error {} -class RemoteError extends Error {} - -export class MicrobitSerial implements MicrobitConnection { - private responseMap = new Map< - number, - (value: protocol.MessageResponse | PromiseLike) => void - >(); - - // To avoid concurrent connect attempts - private isConnecting: boolean = false; - - private connectionCheckIntervalId: ReturnType | undefined; - private lastReceivedMessageTimestamp: number | undefined; - private isReconnect: boolean = false; - // Whether this is the final reconnection attempt. - private finalAttempt = false; - - constructor( - private usb: MicrobitUSB, - private remoteDeviceId: number, - ) {} - - async connect(...states: DeviceRequestStates[]): Promise { - logEvent({ - type: this.isReconnect ? 'Reconnect' : 'Connect', - action: 'Serial connect start', - states, - }); - if (this.isConnecting) { - logMessage('Skipping connect attempt when one is already in progress'); - return; - } - this.isConnecting = true; - let unprocessedData = ''; - let previousButtonState = { A: 0, B: 0 }; - let onPeriodicMessageRecieved: (() => void) | undefined; - - const handleError = (e: unknown) => { - logError('Serial error', e); - void this.disconnectInternal(false, 'bridge'); - }; - const processMessage = (data: string) => { - const messages = protocol.splitMessages(unprocessedData + data); - unprocessedData = messages.remainingInput; - messages.messages.forEach(async msg => { - this.lastReceivedMessageTimestamp = Date.now(); - - // Messages are either periodic sensor data or command/response - const sensorData = protocol.processPeriodicMessage(msg); - if (sensorData) { - stateOnReconnected(); - if (onPeriodicMessageRecieved) { - onPeriodicMessageRecieved(); - onPeriodicMessageRecieved = undefined; - } - onAccelerometerChange( - sensorData.accelerometerX, - sensorData.accelerometerY, - sensorData.accelerometerZ, - ); - if (sensorData.buttonA !== previousButtonState.A) { - previousButtonState.A = sensorData.buttonA; - onButtonChange(sensorData.buttonA, 'A'); - } - if (sensorData.buttonB !== previousButtonState.B) { - previousButtonState.B = sensorData.buttonB; - onButtonChange(sensorData.buttonB, 'B'); - } - } else { - const messageResponse = protocol.processResponseMessage(msg); - if (!messageResponse) { - return; - } - const responseResolve = this.responseMap.get(messageResponse.messageId); - if (responseResolve) { - this.responseMap.delete(messageResponse.messageId); - responseResolve(messageResponse); - } - } - }); - }; - try { - await this.usb.startSerial(processMessage, handleError); - await this.handshake(); - stateOnConnected(DeviceRequestStates.INPUT); - - // Check for connection lost - if (this.connectionCheckIntervalId === undefined) { - this.connectionCheckIntervalId = setInterval(async () => { - if ( - this.lastReceivedMessageTimestamp && - Date.now() - this.lastReceivedMessageTimestamp > 1_000 - ) { - stateOnReconnectionAttempt(); - } - if ( - this.lastReceivedMessageTimestamp && - Date.now() - this.lastReceivedMessageTimestamp > - StaticConfiguration.connectTimeoutDuration - ) { - await this.handleReconnect(); - } - }, 1000); - } - - logMessage(`Serial: using remote device id ${this.remoteDeviceId}`); - const remoteMbIdCommand = protocol.generateCmdRemoteMbId(this.remoteDeviceId); - const remoteMbIdResponse = await this.sendCmdWaitResponse(remoteMbIdCommand); - if ( - remoteMbIdResponse.type === protocol.ResponseTypes.Error || - remoteMbIdResponse.value !== this.remoteDeviceId - ) { - throw new BridgeError( - `Failed to set remote micro:bit ID. Expected ${this.remoteDeviceId}, got ${remoteMbIdResponse.value}`, - ); - } - - // For now we only support input state. - if (states.includes(DeviceRequestStates.INPUT)) { - // Request the micro:bit to start sending the periodic messages - const startCmd = protocol.generateCmdStart({ - accelerometer: true, - buttons: true, - }); - const periodicMessagePromise = new Promise((resolve, reject) => { - onPeriodicMessageRecieved = resolve; - setTimeout(() => { - onPeriodicMessageRecieved = undefined; - reject(new Error('Failed to receive data from remote micro:bit')); - }, 500); - }); - - const startCmdResponse = await this.sendCmdWaitResponse(startCmd); - if (startCmdResponse.type === protocol.ResponseTypes.Error) { - throw new RemoteError( - `Failed to start streaming sensors data. Error response received: ${startCmdResponse.message}`, - ); - } - - if (this.isReconnect) { - await periodicMessagePromise; - } else { - periodicMessagePromise.catch(async e => { - logError('Failed to initialise serial protocol', e); - await this.disconnectInternal(false, 'remote'); - this.isConnecting = false; - }); - } - } - - stateOnAssigned(DeviceRequestStates.INPUT, this.usb.getModelNumber()); - stateOnReady(DeviceRequestStates.INPUT); - logEvent({ - type: this.isReconnect ? 'Reconnect' : 'Connect', - action: 'Serial connect success', - states, - }); - } catch (e) { - logError('Failed to initialise serial protocol', e); - logEvent({ - type: this.isReconnect ? 'Reconnect' : 'Connect', - action: 'Serial connect failed', - states, - }); - const reconnectHelp = e instanceof BridgeError ? 'bridge' : 'remote'; - await this.disconnectInternal(false, reconnectHelp); - throw e; - } finally { - this.finalAttempt = false; - this.isConnecting = false; - } - } - - async disconnect(): Promise { - return this.disconnectInternal(true, 'bridge'); - } - - private stopConnectionCheck() { - clearInterval(this.connectionCheckIntervalId); - this.connectionCheckIntervalId = undefined; - this.lastReceivedMessageTimestamp = undefined; - } - - private async disconnectInternal( - userDisconnect: boolean, - reconnectHelp: ConnectionType, - ): Promise { - this.stopConnectionCheck(); - try { - await this.sendCmdWaitResponse(protocol.generateCmdStop()); - } catch (e) { - // If this fails the remote micro:bit has already gone away. - } - this.responseMap.clear(); - await this.usb.stopSerial(); - stateOnDisconnected( - DeviceRequestStates.INPUT, - userDisconnect || this.finalAttempt - ? false - : this.isReconnect - ? 'autoReconnect' - : 'connect', - reconnectHelp, - ); - } - - async handleReconnect(): Promise { - if (this.isConnecting) { - logMessage('Serial disconnect ignored... reconnect already in progress'); - return; - } - try { - this.stopConnectionCheck(); - logMessage('Serial disconnected... automatically trying to reconnect'); - this.responseMap.clear(); - await this.usb.stopSerial(); - await this.usb.softwareReset(); - await this.reconnect(); - } catch (e) { - logError('Serial connect triggered by disconnect listener failed', e); - } finally { - this.isConnecting = false; - } - } - - async reconnect(finalAttempt: boolean = false): Promise { - this.finalAttempt = finalAttempt; - logMessage('Serial reconnect'); - this.isReconnect = true; - await this.connect(DeviceRequestStates.INPUT); - } - - private async sendCmdWaitResponse( - cmd: protocol.MessageCmd, - ): Promise { - const responsePromise = new Promise((resolve, reject) => { - this.responseMap.set(cmd.messageId, resolve); - setTimeout(() => { - this.responseMap.delete(cmd.messageId); - reject(new Error(`Timeout waiting for response ${cmd.messageId}`)); - }, 1_000); - }); - await this.usb.serialWrite(cmd.message); - return responsePromise; - } - - private async handshake(): Promise { - // There is an issue where we cannot read data out from the micro:bit serial - // buffer until the buffer has been filled. - // As a workaround we can spam the micro:bit with handshake messages until - // enough responses have been queued in the buffer to fill it and the data - // starts to flow. - logMessage('Serial handshake'); - const handshakeResult = await new Promise( - async (resolve, reject) => { - const attempts = 20; - let attemptCounter = 0; - let failureCounter = 0; - let resolved = false; - while (attemptCounter < 20 && !resolved) { - attemptCounter++; - this.sendCmdWaitResponse(protocol.generateCmdHandshake()) - .then(value => { - if (!resolved) { - resolved = true; - resolve(value); - } - }) - .catch(() => { - // We expect some to time out, likely well after the handshake is completed. - if (!resolved) { - if (++failureCounter === attempts) { - reject(new BridgeError('Handshake not completed')); - } - } - }); - await new Promise(resolve => setTimeout(resolve, 100)); - } - }, - ); - if (handshakeResult.value !== protocol.version) { - throw new BridgeError( - `Handshake failed. Unexpected protocol version ${protocol.version}`, - ); - } - } -} - -export const startSerialConnection = async ( - usb: MicrobitUSB, - requestState: DeviceRequestStates, - remoteDeviceId: number, -): Promise => { - try { - const serial = new MicrobitSerial(usb, remoteDeviceId); - await serial.connect(requestState); - return serial; - } catch (e) { - return undefined; - } -}; diff --git a/src/script/microbit-interfacing/MicrobitUSB.ts b/src/script/microbit-interfacing/MicrobitUSB.ts deleted file mode 100644 index 68f322b6a..000000000 --- a/src/script/microbit-interfacing/MicrobitUSB.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { CortexM, DAPLink, WebUSB } from 'dapjs'; -import { logError } from '../utils/logging'; -import MBSpecs from './MBSpecs'; -import { CortexSpecialReg } from './constants'; - -const baudRate = 115200; -const serialDelay = 5; - -/** - * A USB connection to a micro:bit. - */ -class MicrobitUSB { - private readonly transport: WebUSB; - private serialPromise: Promise | undefined; - private serialDAPLink: DAPLink | undefined; - private serialCallbacks: - | { - data: (data: string) => void; - error: (e: unknown) => void; - } - | undefined; - - /** - * Creates a new MicrobitUSB object. - * - * Use MicrobitUSB.requestConnection() or MicrobitUSB.createWithoutRequest() to create a new MicrobitUSB object. - * @param usbDevice The USB device to connect to. - * @protected constructor for internal use. - */ - protected constructor(protected usbDevice: USBDevice) { - this.transport = new WebUSB(usbDevice); - } - - /** - * Open prompt for USB connection. - * @returns {Promise} A promise that resolves to a new MicrobitUSB object. - */ - public static async requestConnection(): Promise { - const requestOptions: USBDeviceRequestOptions = { - filters: [ - { - vendorId: MBSpecs.USBSpecs.VENDOR_ID, - productId: MBSpecs.USBSpecs.PRODUCT_ID, - }, - ], - }; - - try { - const device: USBDevice = await navigator.usb.requestDevice(requestOptions); - return new MicrobitUSB(device); - } catch (e) { - logError('USB request device failed/cancelled', e); - throw e; - } - } - - /** - * Uses the serial number from dapjs to determine the model number of the board. - * Read more: https://support.microbit.org/support/solutions/articles/19000035697-what-are-the-usb-vid-pid-numbers-for-micro-bit - * @returns The hardware model of the micro:bit. Either 1 or 2. - */ - public getModelNumber(): MBSpecs.MBVersion { - const sernoPrefix: string = this.usbDevice.serialNumber!.toString().substring(0, 4); - if (parseInt(sernoPrefix) < 9903) return 1; - else return 2; - } - - /** - * @returns {string} The device ID of the micro:bit from FICR. - */ - public async getDeviceId(): Promise { - const debug = new CortexM(this.transport); - try { - await debug.connect(); - // Microbit only uses MSB of serial number - return await debug.readMem32(MBSpecs.USBSpecs.FICR + MBSpecs.USBSpecs.DEVICE_ID_1); - } catch (e: unknown) { - logError('USB failed to read name', e); - throw new Error('Failed to read name: ' + e); - } finally { - await debug.disconnect(); - } - } - - /** - * Resets the micro:bit in software by writing to NVIC_AIRCR. - * Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L347 - */ - public async softwareReset(): Promise { - const cortexM = new CortexM(this.transport); - await cortexM.connect(); - await cortexM.writeMem32( - CortexSpecialReg.NVIC_AIRCR, - CortexSpecialReg.NVIC_AIRCR_VECTKEY | CortexSpecialReg.NVIC_AIRCR_SYSRESETREQ, - ); - - // wait for the system to come out of reset - let dhcsr = await cortexM.readMem32(CortexSpecialReg.DHCSR); - - while ((dhcsr & CortexSpecialReg.S_RESET_ST) !== 0) { - dhcsr = await cortexM.readMem32(CortexSpecialReg.DHCSR); - } - await cortexM.disconnect(); - } - - /** - * Flashes a .hex file to the micro:bit. - * @param {string} url The hex file to flash. (As a link) - * @param {(progress: number) => void} progressCallback A callback for progress. - */ - public async flashHex( - url: string, - progressCallback: (progress: number) => void, - ): Promise { - const hexFile = await fetch(url); - const buffer = await hexFile.arrayBuffer(); - const target = new DAPLink(this.transport); - - target.on(DAPLink.EVENT_PROGRESS, (progress: number) => { - progressCallback(progress); - }); - - try { - await target.connect(); - await target.flash(buffer); - await target.disconnect(); - } catch (e) { - logError('Failed to flash hex', e); - throw e; - } - } - - public async startSerial( - dataCallback: (data: string) => void, - errorCallback: (e: unknown) => void, - ) { - await this.transport.open(); - - this.serialCallbacks = { data: dataCallback, error: errorCallback }; - this.serialDAPLink = new DAPLink(this.transport); - const initialBaudRate = await this.serialDAPLink.getSerialBaudrate(); - this.serialDAPLink.addListener(DAPLink.EVENT_SERIAL_DATA, dataCallback); - await this.serialDAPLink.connect(); - if (initialBaudRate !== baudRate) { - await this.serialDAPLink.setSerialBaudrate(baudRate); - } - this.serialPromise = this.serialDAPLink - .startSerialRead(serialDelay, false) - .catch(e => { - // Indirect so we can remove the callback as part of disconnect - this.serialCallbacks?.error(e); - }); - } - - public async serialWrite(data: string): Promise { - return this.serialDAPLink?.serialWrite(data); - } - - isSerialConnected(): boolean { - return !!this.serialPromise; - } - - public async stopSerial() { - if (this.serialDAPLink) { - if (this.serialCallbacks) { - this.serialDAPLink.removeListener( - DAPLink.EVENT_SERIAL_DATA, - this.serialCallbacks.data, - ); - this.serialCallbacks = undefined; - } - this.serialDAPLink.stopSerialRead(); - try { - await this.serialPromise; - } catch (e) { - // Errors from here will be handled by the error callback - // in normal operation or can be ignored during disconnect. - } - this.serialPromise = undefined; - try { - await this.serialDAPLink.disconnect(); - } catch (e) { - // If the micro:bit has gone away then this will fail. - } - this.serialDAPLink = undefined; - } - } -} - -export default MicrobitUSB; diff --git a/src/script/microbit-interfacing/Microbits.ts b/src/script/microbit-interfacing/Microbits.ts deleted file mode 100644 index 45b88febe..000000000 --- a/src/script/microbit-interfacing/Microbits.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import MicrobitConnection, { DeviceRequestStates } from './MicrobitConnection'; -import MicrobitUSB from './MicrobitUSB'; -import { MicrobitBluetooth, startBluetoothConnection } from './MicrobitBluetooth'; -import { startSerialConnection } from './MicrobitSerial'; - -export type FlashStage = 'bluetooth' | 'radio-remote' | 'radio-bridge'; -export type HexType = - | 'bluetooth' - | 'radio-remote' - | 'radio-bridge' - | 'radio-local' - | 'radio-remote-dev'; - -export type UARTMessageType = 'g' | 's'; - -export const getHexFileUrl = ( - version: 1 | 2 | 'universal', - type: HexType, -): string | undefined => { - if (type === 'bluetooth') { - return { - 1: 'firmware/ml-microbit-cpp-version-combined.hex', - 2: 'firmware/MICROBIT.hex', - universal: 'firmware/universal-hex.hex', - }[version]; - } - if (version !== 2) { - return undefined; - } - return { - 'radio-remote-dev': 'firmware/radio-remote-v0.2.1-dev.hex', - 'radio-remote': 'firmware/radio-remote-v0.2.1.hex', - 'radio-bridge': 'firmware/radio-bridge-v0.2.1.hex', - 'radio-local': 'firmware/local-sensors-v0.2.1.hex', - }[type]; -}; - -class Microbits { - private static inputMicrobit: MicrobitConnection | undefined = undefined; - private static outputMicrobit: MicrobitBluetooth | undefined = undefined; - - public static getInputMicrobit(): MicrobitConnection | undefined { - return this.inputMicrobit; - } - - public static getOutputMicrobit(): MicrobitBluetooth | undefined { - return this.outputMicrobit; - } - - public static async assignBluetoothInput(name: string): Promise { - this.inputMicrobit = await startBluetoothConnection(name, DeviceRequestStates.INPUT); - return !!this.inputMicrobit; - } - - public static async assignSerialInput( - usb: MicrobitUSB, - remoteDeviceId: number, - ): Promise { - this.inputMicrobit = await startSerialConnection( - usb, - DeviceRequestStates.INPUT, - remoteDeviceId, - ); - return !!this.inputMicrobit; - } - - public static async assignBluetoothOutput(name: string): Promise { - // If it's the input micro:bit then grab the input micro:bit reference - // use it as the output micro:bit and connect it in that mode too. - if ( - this.inputMicrobit instanceof MicrobitBluetooth && - this.inputMicrobit.name === name - ) { - await this.inputMicrobit.connect(DeviceRequestStates.OUTPUT); - this.outputMicrobit = this.inputMicrobit; - return true; - } else { - this.outputMicrobit = await startBluetoothConnection( - name, - DeviceRequestStates.OUTPUT, - ); - return !!this.outputMicrobit; - } - } - - public static async reconnect( - requestState: DeviceRequestStates.INPUT | DeviceRequestStates.OUTPUT, - finalAttempt: boolean = false, - ) { - return this.getMicrobit(requestState)?.reconnect(finalAttempt); - } - - public static async disconnect( - requestState: DeviceRequestStates.INPUT | DeviceRequestStates.OUTPUT, - ) { - // For now disconnect disconnects as input and output if the same device - // is both. - return this.getMicrobit(requestState)?.disconnect(); - } - - private static getMicrobit( - state: DeviceRequestStates.INPUT | DeviceRequestStates.OUTPUT, - ): MicrobitConnection | undefined { - return state === DeviceRequestStates.INPUT ? this.inputMicrobit : this.outputMicrobit; - } - - public static async disconnectInputAndOutput() { - await this.disconnect(DeviceRequestStates.INPUT); - await this.disconnect(DeviceRequestStates.OUTPUT); - } - - public static hasDeviceReference(requestState: DeviceRequestStates) { - if (requestState === DeviceRequestStates.INPUT) { - return !!this.inputMicrobit; - } - return !!this.outputMicrobit; - } - - public static async dispose( - requestState: DeviceRequestStates.INPUT | DeviceRequestStates.OUTPUT, - ) { - if (requestState === DeviceRequestStates.INPUT) { - this.inputMicrobit = undefined; - } else { - this.outputMicrobit = undefined; - } - } -} - -export default Microbits; diff --git a/src/script/microbit-interfacing/change-listeners.ts b/src/script/microbit-interfacing/change-listeners.ts deleted file mode 100644 index 40a6375ae..000000000 --- a/src/script/microbit-interfacing/change-listeners.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { livedata } from '../stores/mlStore'; -import { buttonPressed } from '../stores/uiStore'; -import MBSpecs from './MBSpecs'; -import { DeviceRequestStates } from './MicrobitConnection'; -import { - stateOnIdentifiedAsMakecode, - stateOnIdentifiedAsProprietary, - stateOnVersionIdentified, -} from './state-updaters'; - -let smoothedAccelX = 0; -let smoothedAccelY = 0; -let smoothedAccelZ = 0; - -export const onAccelerometerChange = (x: number, y: number, z: number): void => { - const accelX = x / 1000.0; - const accelY = y / 1000.0; - const accelZ = z / 1000.0; - smoothedAccelX = accelX * 0.25 + smoothedAccelX * 0.75; - smoothedAccelY = accelY * 0.25 + smoothedAccelY * 0.75; - smoothedAccelZ = accelZ * 0.25 + smoothedAccelZ * 0.75; - - const data = { - accelX, - accelY, - accelZ, - smoothedAccelX, - smoothedAccelY, - smoothedAccelZ, - }; - - livedata.set(data); // This is the old livedata store -}; - -export const onButtonChange = ( - buttonState: MBSpecs.ButtonState, - button: MBSpecs.Button, -): void => { - if (buttonState === MBSpecs.ButtonStates.Released) { - return; - } - if (button === 'A') { - buttonPressed.update(obj => { - obj.buttonA = 1; - obj.buttonB = 0; - return obj; - }); - } else { - buttonPressed.update(obj => { - obj.buttonA = 0; - obj.buttonB = 1; - return obj; - }); - } -}; - -export const onUARTDataReceived = ( - requestState: DeviceRequestStates, - data: string, -): void => { - if (data === 'id_mkcd') { - stateOnIdentifiedAsMakecode(requestState); - } - if (data === 'id_prop') { - stateOnIdentifiedAsProprietary(requestState); - } - if (data.includes('vi_')) { - const version = parseInt(data.substring(3)); - stateOnVersionIdentified(requestState, version); - // TODO: Use this to show outdated program dialog? - - // this.inputBuildVersion = version; - // if (this.isInputOutputTheSame()) { - // clearTimeout(this.outputVersionIdentificationTimeout); - // } - // clearTimeout(this.inputVersionIdentificationTimeout); - // connectionBehaviour.onVersionIdentified(version); - // const isOutdated = StaticConfiguration.isMicrobitOutdated(this.inputOrigin, version); - // if (isOutdated) { - // connectionBehaviour.onIdentifiedAsOutdated(); - // } - } -}; diff --git a/src/script/microbit-interfacing/constants.ts b/src/script/microbit-interfacing/constants.ts deleted file mode 100644 index 2f20968a1..000000000 --- a/src/script/microbit-interfacing/constants.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -export const CortexSpecialReg = { - // Debug Halting Control and Status Register - DHCSR: 0xe000edf0, - S_RESET_ST: 1 << 25, - - NVIC_AIRCR: 0xe000ed0c, - NVIC_AIRCR_VECTKEY: 0x5fa << 16, - NVIC_AIRCR_SYSRESETREQ: 1 << 2, - - // Many more. -}; diff --git a/src/script/microbit-interfacing/serialProtocol.ts b/src/script/microbit-interfacing/serialProtocol.ts deleted file mode 100644 index 7b947750a..000000000 --- a/src/script/microbit-interfacing/serialProtocol.ts +++ /dev/null @@ -1,228 +0,0 @@ -/** - * (c) 2024, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ - -export type SplittedMessages = { - messages: string[]; - remainingInput: string; -}; - -enum MessageTypes { - Command = 'C', - Response = 'R', - Periodic = 'P', -} - -export enum CommandTypes { - Handshake = 'HS', - RadioFrequency = 'RF', - RemoteMbId = 'RMBID', - SoftwareVersion = 'SWVER', - HardwareVersion = 'HWVER', - Zstart = 'ZSTART', - Stop = 'STOP', -} - -enum ResponseExtraTypes { - Error = 'ERROR', -} - -export type ResponseTypes = CommandTypes | ResponseExtraTypes; -export const ResponseTypes = { ...CommandTypes, ...ResponseExtraTypes }; - -export type MessageCmd = { - message: string; - messageId: number; - type: CommandTypes; - value: number | string; -}; - -export type MessageResponse = { - message: string; - messageId: number; - type: ResponseTypes; - value: number | string; -}; - -// More sensors are available in the protocol, but we only support these two -export type MicrobitSensors = { - accelerometer: boolean; - buttons: boolean; -}; - -export type MicrobitSensorState = { - accelerometerX: number; - accelerometerY: number; - accelerometerZ: number; - buttonA: number; - buttonB: number; -}; - -// Currently implemented protocol version -export const version = 1; - -export const splitMessages = (message: string): SplittedMessages => { - if (!message) { - return { - messages: [], - remainingInput: '', - }; - } - let messages = message.split('\n'); - let remainingInput = messages.pop() || ''; - - // Throw away any empty messages and messages that don't start with a valid type - messages = messages.filter( - (msg: string) => - msg.length > 0 && Object.values(MessageTypes).includes(msg[0] as MessageTypes), - ); - - // Any remaining input will be the start of the next message, so if it doesn't start - // with a valid type throw it away as it'll be prepended to the next serial string - if ( - remainingInput.length > 0 && - !Object.values(MessageTypes).includes(remainingInput[0] as MessageTypes) - ) { - remainingInput = ''; - } - - return { - messages, - remainingInput, - }; -}; - -export const processResponseMessage = (message: string): MessageResponse | undefined => { - // Regex for a message response with 3 groups: - // id -> The message ID, 1-8 hex characters - // cmd -> The command type, a string, only capital letters, matching CommandTypes - // value -> The response value, empty string or a word, number, - // or version (e.g 1.2.3) depending on the command type - const responseMatch = - /^R\[(?[0-9A-Fa-f]{1,8})\](?[A-Z]+)\[(?-?[\w.]*)\]$/.exec(message); - if (!responseMatch || !responseMatch.groups) { - return undefined; - } - const messageId = parseInt(responseMatch.groups['id'], 16); - if (isNaN(messageId)) { - return undefined; - } - const responseType = responseMatch.groups['cmd'] as ResponseTypes; - if (!Object.values(ResponseTypes).includes(responseType)) { - return undefined; - } - let value: string | number = responseMatch.groups['value']; - switch (responseType) { - // Commands with numeric values - case ResponseTypes.Handshake: - case ResponseTypes.RadioFrequency: - case ResponseTypes.RemoteMbId: - case ResponseTypes.HardwareVersion: - case ResponseTypes.Error: - value = Number(value); - if (isNaN(value) || value < 0 || value > 0xffffffff) { - return undefined; - } - break; - // Commands without values - case ResponseTypes.Zstart: - case ResponseTypes.Stop: - if (value !== '') { - return undefined; - } - break; - // Semver-ish values (valid range 00.00.00 to 99.99.99) - case ResponseTypes.SoftwareVersion: - if (!/^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}$/.test(value)) { - return undefined; - } - break; - } - return { - message, - messageId, - type: responseType, - value, - }; -}; - -export const processPeriodicMessage = ( - message: string, -): MicrobitSensorState | undefined => { - // Basic checks to match the message being a compact periodic message - if (message.length !== 13 || message[0] !== MessageTypes.Periodic) { - return undefined; - } - // All characters except the first one should be hex - if (!/^[0-9A-Fa-f]+$/.test(message.substring(1))) { - return undefined; - } - - // Only the two Least Significant Bits from the buttons are used - const buttons = parseInt(message[12], 16); - if (buttons > 3) { - return undefined; - } - - return { - // The accelerometer data has been clamped to -2048 to 2047, and an offset - // added to make the values positive, so that needs to be reversed - accelerometerX: parseInt(message.substring(3, 6), 16) - 2048, - accelerometerY: parseInt(message.substring(6, 9), 16) - 2048, - accelerometerZ: parseInt(message.substring(9, 12), 16) - 2048, - // Button A is the LSB, button B is the next bit - buttonA: buttons & 1, - buttonB: (buttons >> 1) & 1, - }; -}; - -const generateCommand = (cmdType: CommandTypes, cmdData: string = ''): MessageCmd => { - // Generate an random (enough) ID with max value of 8 hex digits - const msgID = Math.floor(Math.random() * 0xffffffff); - return { - message: `C[${msgID.toString(16).toUpperCase()}]${cmdType}[${cmdData}]\n`, - messageId: msgID, - type: cmdType, - value: cmdData, - }; -}; - -export const generateCmdHandshake = (): MessageCmd => { - return generateCommand(CommandTypes.Handshake); -}; - -export const generateCmdStart = (sensors: MicrobitSensors): MessageCmd => { - let cmdData = ''; - if (sensors.accelerometer) { - cmdData += 'A'; - } - if (sensors.buttons) { - cmdData += 'B'; - } - return generateCommand(CommandTypes.Zstart, cmdData); -}; - -export const generateCmdStop = (): MessageCmd => { - return generateCommand(CommandTypes.Stop); -}; - -export const generateCmdRadioFrequency = (frequency: number): MessageCmd => { - if (frequency < 0 || frequency > 83) { - throw new Error('Radio frequency out of range'); - } - return generateCommand(CommandTypes.RadioFrequency, frequency.toString()); -}; - -export const generateCmdRemoteMbId = (remoteMicrobitId: number): MessageCmd => { - if (remoteMicrobitId < 0 || remoteMicrobitId > 0xffffffff) { - throw new Error('Remote micro:bit ID out of range'); - } - return generateCommand(CommandTypes.RemoteMbId, remoteMicrobitId.toString()); -}; - -export const generateRandomRadioFrequency = (): number => { - // The value range for radio frequencies is 0 to 83 - return Math.floor(Math.random() * 84); -}; diff --git a/src/script/microbit-interfacing/state-updaters.ts b/src/script/microbit-interfacing/state-updaters.ts deleted file mode 100644 index 1e99c1d9b..000000000 --- a/src/script/microbit-interfacing/state-updaters.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { get } from 'svelte/store'; -import { ConnectHelp, ConnectionType, ModelView, state } from '../stores/uiStore'; -import { Paths, currentPath, navigate } from '../../router/paths'; -import MBSpecs from './MBSpecs'; -import { HexOrigin } from '../../StaticConfiguration'; -import Microbits from './Microbits'; -import { DeviceRequestStates } from './MicrobitConnection'; - -export const stateOnConnected = (requestState: DeviceRequestStates) => { - state.update(s => { - requestState === DeviceRequestStates.INPUT - ? (s.isInputConnected = true) - : (s.isOutputConnected = true); - s.showConnectHelp = false; - s.reconnectState = { - ...s.reconnectState, - // This is set on disconnect. - connectionType: 'none', - reconnectFailed: false, - reconnecting: false, - }; - return s; - }); -}; - -export const stateOnIdentifiedAsMakecode = (requestState: DeviceRequestStates): void => { - if (requestState === DeviceRequestStates.INPUT) { - state.update(s => { - s.inputOrigin = HexOrigin.MAKECODE; - s.modelView = ModelView.TILE; - return s; - }); - } else { - state.update(s => { - s.outputOrigin = HexOrigin.MAKECODE; - s.modelView = ModelView.TILE; - return s; - }); - } -}; - -export const stateOnIdentifiedAsProprietary = ( - requestState: DeviceRequestStates, -): void => { - if (requestState === DeviceRequestStates.INPUT) { - state.update(s => { - s.inputOrigin = HexOrigin.PROPRIETARY; - s.modelView = ModelView.STACK; - return s; - }); - } else { - state.update(s => { - s.outputOrigin = HexOrigin.PROPRIETARY; - s.modelView = ModelView.STACK; - return s; - }); - } -}; - -export const stateOnReady = (requestState: DeviceRequestStates) => { - if (requestState === DeviceRequestStates.INPUT) { - state.update(s => { - s.isInputReady = true; - return s; - }); - if (get(currentPath) === Paths.HOME) { - navigate(Paths.DATA); - } - } else { - Microbits.getOutputMicrobit()?.resetPins(); - state.update(s => { - s.isOutputReady = true; - return s; - }); - } -}; - -export const stateOnAssigned = ( - requestState: DeviceRequestStates, - microbitVersion: MBSpecs.MBVersion, -) => { - if (requestState === DeviceRequestStates.INPUT) { - state.update(s => { - s.inputMicrobitVersion = microbitVersion; - return s; - }); - } else { - state.update(s => { - s.inputMicrobitVersion = microbitVersion; - return s; - }); - } - if (get(currentPath) === Paths.HOME) { - navigate(Paths.DATA); - } -}; - -export const stateOnDisconnected = ( - requestState: DeviceRequestStates, - connectHelp: ConnectHelp, - connectionType: ConnectionType, -): void => { - if (requestState === DeviceRequestStates.INPUT) { - state.update(s => { - s.isInputConnected = false; - s.isInputReady = false; - s.showConnectHelp = connectHelp; - s.reconnectState = { - reconnecting: false, - reconnectFailed: false, - connectionType: connectionType, - inUseAs: s.reconnectState.inUseAs.add(DeviceRequestStates.INPUT), - }; - s.isInputOutdated = false; - return s; - }); - } else { - state.update(s => { - s.isOutputConnected = false; - s.showConnectHelp = connectHelp; - s.isOutputReady = false; - s.isOutputOutdated = false; - s.reconnectState = { - reconnecting: false, - reconnectFailed: false, - connectionType: connectionType, - inUseAs: s.reconnectState.inUseAs.add(DeviceRequestStates.OUTPUT), - }; - return s; - }); - } -}; - -export const stateOnFailedToConnect = (requestState: DeviceRequestStates) => { - if (requestState === DeviceRequestStates.INPUT) { - state.update(s => { - s.isInputConnected = false; - s.isInputReady = false; - s.showConnectHelp = false; - s.reconnectState = { - ...s.reconnectState, - reconnecting: false, - reconnectFailed: true, - inUseAs: new Set(), - }; - s.isInputOutdated = false; - return s; - }); - } else { - state.update(s => { - s.isOutputConnected = false; - s.showConnectHelp = false; - s.isOutputReady = false; - s.reconnectState = { - ...s.reconnectState, - reconnecting: false, - reconnectFailed: true, - inUseAs: new Set(), - }; - s.isOutputOutdated = false; - return s; - }); - } -}; - -export const stateOnShowConnectHelp = (userTriggered: boolean = false) => { - state.update(s => { - s.showConnectHelp = userTriggered ? 'userReconnect' : 'autoReconnect'; - return s; - }); -}; - -export const stateOnHideConnectHelp = () => { - state.update(s => { - s.showConnectHelp = false; - return s; - }); -}; - -export const stateOnVersionIdentified = ( - requestState: DeviceRequestStates, - value: number, -) => { - if (requestState === DeviceRequestStates.INPUT) { - state.update(s => { - s.inputHexVersion = value; - return s; - }); - } else { - state.update(s => { - s.outputHexVersion = value; - return s; - }); - } -}; - -export const stateOnReconnectionAttempt = () => { - state.update(s => { - s.showConnectHelp = false; - s.reconnectState = { - ...s.reconnectState, - reconnecting: true, - }; - return s; - }); -}; - -export const stateOnReconnected = () => { - state.update(s => { - s.reconnectState = { - ...s.reconnectState, - reconnecting: false, - }; - return s; - }); -}; diff --git a/src/script/ml.ts b/src/script/ml.ts deleted file mode 100644 index 04e0672ea..000000000 --- a/src/script/ml.ts +++ /dev/null @@ -1,349 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { alertUser, state } from './stores/uiStore'; -import { - bestPrediction, - gestureConfidences, - type GestureData, - getPrevData, - model, - settings, - trainingStatus, -} from './stores/mlStore'; -import { FilterType, Axes, determineFilter, AxesType } from './datafunctions'; -import { get, type Unsubscriber } from 'svelte/store'; -import { t } from '../i18n'; -import * as tf from '@tensorflow/tfjs'; -import { LayersModel, SymbolicTensor, Tensor } from '@tensorflow/tfjs'; -import { gestures } from './stores/Stores'; -import Repositories from './repository/Repositories'; -import { getPrediction } from './getPrediction'; -import { TrainingStatus } from './domain/Model'; -import { logEvent } from './utils/logging'; - -let text: (key: string, vars?: object) => string; -t.subscribe(t => (text = t)); - -// Whenever model is trained, the settings at the time is saved in this variable -// Such that prediction continues on with the same settings as during training -let modelSettings: { axes: AxesType[]; filters: Set }; - -// Add parameter to allow unsubscribing from store, when predicting ends. -// Prevents memory leak. -let unsubscribeFromSettings: Unsubscriber | undefined = undefined; - -// Variable for accessing the predictionInterval -let predictionInterval: NodeJS.Timeout | undefined = undefined; - -// Exported for testing. -export function createModel(): LayersModel { - const gestureData = get(gestures); - const numberOfClasses: number = gestureData.length; - const inputShape = [ - get(settings).includedFilters.size * get(settings).includedAxes.length, - ]; - - const input = tf.input({ shape: inputShape }); - const normalizer = tf.layers.batchNormalization().apply(input); - const dense = tf.layers.dense({ units: 16, activation: 'relu' }).apply(normalizer); - const softmax = tf.layers - .dense({ units: numberOfClasses, activation: 'softmax' }) - .apply(dense) as SymbolicTensor; - const model = tf.model({ inputs: input, outputs: softmax }); - - model.compile({ - loss: 'categoricalCrossentropy', - optimizer: tf.train.sgd(0.5), - metrics: ['accuracy'], - }); - - return model; -} - -export async function trainModel(): Promise { - state.update(obj => { - obj.isTraining = true; - return obj; - }); - if (!isTrainingAllowed()) { - state.update(obj => { - obj.isTraining = false; - return obj; - }); - return; - } - - // Freeze modelSetting untill next training - modelSettings = { - axes: get(settings).includedAxes, - filters: get(settings).includedFilters, - }; - - // Fetch data - const gestureData = get(gestures); - const features: Array = []; - const labels: Array = []; - const numberofClasses: number = gestureData.length; - - gestureData.forEach((MLClass, index) => { - MLClass.recordings.forEach(recording => { - // prepare features - const inputs: number[] = makeInputs(recording.data); - features.push(inputs); - - // Prepare labels - const label: number[] = new Array(numberofClasses) as number[]; - label.fill(0, 0, numberofClasses); - label[index] = 1; - labels.push(label); - }); - }); - - const tensorFeatures = tf.tensor(features); - const tensorLabels = tf.tensor(labels); - const nn: LayersModel = createModel(); - const totalNumEpochs = get(settings).numEpochs; - - try { - await nn.fit(tensorFeatures, tensorLabels, { - epochs: totalNumEpochs, - batchSize: 16, - validationSplit: 0.1, - callbacks: { - onTrainEnd, - onEpochEnd: (epoch: number) => { - // Epochs indexed at 0 - updateTrainingProgress(epoch / (totalNumEpochs - 1)); - }, - }, - }); - model.set(nn); - } catch (err) { - trainingStatus.set(TrainingStatus.Failure); - console.error('tensorflow training process failed:', err); - } - return nn; -} - -function getNumberOfActionsAndRecordings() { - const gestureData = get(gestures); - let numRecordings = 0; - gestureData.forEach(g => { - numRecordings += g.recordings.length; - }); - return { - numActions: gestureData.length, - numRecordings, - }; -} - -export function isParametersLegal(): boolean { - const s = get(settings); - return s.includedAxes.length > 0 && s.includedFilters.size > 0; -} - -// Assess whether -function isTrainingAllowed(messageUser = true) { - const gestureData = get(gestures); - - // If less than two gestures - if (!gestureData || gestureData.length < 2) { - if (messageUser) { - alertUser(text('alert.twoGestures')); - } - return false; - } - - // If parameters aren't legal - if (!isParametersLegal()) { - if (messageUser) { - alertUser(text('alert.oneDataRepresentation')); - } - return false; - } - - // If gestures have less than three recordings per gesture. - if (!sufficientGestureData(gestureData, messageUser)) { - return false; - } - - return true; -} - -// Assess whether each gesture has sufficient data. (Limited by three) -export function sufficientGestureData(gestureData: GestureData[], messageUser: boolean) { - let sufficientData = true; - gestureData.forEach(gesture => { - if (gesture.recordings.length < 3) { - if (messageUser) { - alertUser(text('alert.recordingsPerGesture')); - } - sufficientData = false; - } - }); - return sufficientData; -} - -function updateTrainingProgress(progress: number) { - state.update(obj => { - obj.trainingProgress = progress; - return obj; - }); -} - -function onTrainEnd() { - // Set state to not-Training and initiate prediction. - state.update(obj => { - obj.isTraining = false; - obj.hasTrainedBefore = true; - obj.trainingProgress = 0; - return obj; - }); - trainingStatus.set(TrainingStatus.Success); - logEvent({ type: 'Data', action: 'Train model', ...getNumberOfActionsAndRecordings() }); - setupPredictionInterval(); -} - -// makeInput reduces array of x, y and z inputs to a single number array with values. -// Depending on user settings. There will be anywhere between 1-24 parameters in - -// Exported for testing. -export function makeInputs(sample: { x: number[]; y: number[]; z: number[] }): number[] { - const dataRep: number[] = []; - - if (!modelSettings) { - throw new Error('Model settings not found'); - } - - // We use modelSettings to determine which filters to use. In this way the classify function - // will be called with the same filters and axes untill the next training - modelSettings.filters.forEach(filter => { - const filterOutput = determineFilter(filter); - if (modelSettings.axes.includes(Axes.X)) - dataRep.push(filterOutput.computeOutput(sample.x)); - if (modelSettings.axes.includes(Axes.Y)) - dataRep.push(filterOutput.computeOutput(sample.y)); - if (modelSettings.axes.includes(Axes.Z)) - dataRep.push(filterOutput.computeOutput(sample.z)); - }); - - return dataRep; -} - -// Set the global state. Telling components, that the program is predicting -function setIsPredicting(isPredicting: boolean): void { - state.update(s => { - s.isPredicting = isPredicting; - return s; - }); -} - -// Setup prediction. Listens for user-settings (Updates pr second). -// Whenever this changes, the updatesPrSecond also changes. -function setupPredictionInterval(): void { - // Set state and fetch updatesPrSecond. - setIsPredicting(true); - const updatesPrSecond = get(settings).updatesPrSecond; - - const classifyAutomatically = get(settings).automaticClassification; - - if (classifyAutomatically) { - predictionInterval = setInterval(classify, 1000 / updatesPrSecond); - } - - // When user changes settings - unsubscribeFromSettings = settings.subscribe(update => { - // Only if the updatesPrSecond changed or buttons changed - // TODO: Change to early exit structure - if ( - update.updatesPrSecond !== updatesPrSecond || - update.automaticClassification !== classifyAutomatically - ) { - if (predictionInterval !== undefined) { - clearInterval(predictionInterval); - } - predictionInterval = undefined; - setupPredictionInterval(); - } - }); -} - -let previouslyConnected = false; -// Classify data -export function classify() { - // Get currentState to check whether the prediction has been interrupted by other processes - const currentState = get(state); - const currentTrainingStatus = get(trainingStatus); - const hasBeenInterrupted = - !currentState.isPredicting || - currentState.isRecording || - currentState.isTraining || - currentTrainingStatus !== TrainingStatus.Success; - - if (hasBeenInterrupted) { - if (predictionInterval !== undefined) { - clearInterval(predictionInterval); - } - predictionInterval = undefined; - setIsPredicting(false); - unsubscribeFromSettings?.(); - // if (unsubscribeFromSettings) unsubscribeFromSettings(); - return; - } - - if (currentState.isInputConnected) { - // Get formatted version of previous data - const data = getPrevData(); - if (data === undefined) - if (previouslyConnected) { - throw new Error('Insufficient amount of data to make prediction'); - } else { - // If we have connected a micro:bit while on the test model page - // insufficient data is expected. - return; - } - const input: number[] = makeInputs(data); - const inputTensor = tf.tensor([input]); - const prediction: Tensor = get(model).predict(inputTensor) as Tensor; - prediction - .data() - .then(data => { - tfHandlePrediction(data as Float32Array); - }) - .catch(err => console.error('Prediction error:', err)); - } - previouslyConnected = currentState.isInputConnected; -} - -function tfHandlePrediction(result: Float32Array) { - const gestureData = get(gestures); - - gestureData.forEach(({ ID }, index) => { - Repositories.getInstance() - .getModelRepository() - .setGestureConfidence(ID, result[index]); - gestureConfidences.update(confidenceMap => { - confidenceMap[ID] = result[index]; - return confidenceMap; - }); - }); - - bestPrediction.set(getPrediction(get(gestures))); -} - -state.subscribe(({ isInputConnected }) => { - if (!isInputConnected) { - const gestureData = get(gestures); - gestureData.forEach(({ ID }) => { - Repositories.getInstance().getModelRepository().setGestureConfidence(ID, 0); - gestureConfidences.update(confidenceMap => { - confidenceMap[ID] = 0; - return confidenceMap; - }); - bestPrediction.set(undefined); - }); - } -}); diff --git a/src/script/mlmodels/AccelerometerClassifierInput.ts b/src/script/mlmodels/AccelerometerClassifierInput.ts deleted file mode 100644 index 60f710fb1..000000000 --- a/src/script/mlmodels/AccelerometerClassifierInput.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import ClassifierInput from '../domain/ClassifierInput'; -import Filters from '../domain/Filters'; - -export type AccelerometerRecording = { - x: number[]; - y: number[]; - z: number[]; -}; - -class AccelerometerClassifierInput implements ClassifierInput { - constructor(private input: AccelerometerRecording) {} - - getInput(filters: Filters): number[] { - return [ - ...filters.compute(this.input.x), - ...filters.compute(this.input.y), - ...filters.compute(this.input.z), - ]; - } -} - -export default AccelerometerClassifierInput; diff --git a/src/script/mlmodels/LayersMLModel.ts b/src/script/mlmodels/LayersMLModel.ts deleted file mode 100644 index 454116797..000000000 --- a/src/script/mlmodels/LayersMLModel.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { LayersModel } from '@tensorflow/tfjs'; -import MLModel from '../domain/MLModel'; -import * as tf from '@tensorflow/tfjs'; - -class LayersMLModel implements MLModel { - constructor(private neuralNet: LayersModel) {} - - public async predict(filteredData: number[]): Promise { - const inputTensor = tf.tensor([filteredData]); - const prediction: tf.Tensor = this.neuralNet.predict(inputTensor) as tf.Tensor; - try { - const predictionOutput = (await prediction.data()) as Float32Array; - return Array.from(predictionOutput); - } catch (err) { - console.error('Prediction error:', err); - return Promise.reject(err); - } - } -} - -export default LayersMLModel; diff --git a/src/script/mlmodels/LayersModelTrainer.ts b/src/script/mlmodels/LayersModelTrainer.ts deleted file mode 100644 index 8130a5d15..000000000 --- a/src/script/mlmodels/LayersModelTrainer.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import ModelTrainer, { TrainingData } from '../domain/ModelTrainer'; -import LayersMLModel from './LayersMLModel'; -import * as tf from '@tensorflow/tfjs'; -type LayersModelTrainingSettings = { noOfEpochs: number }; -class LayersModelTrainer implements ModelTrainer { - constructor(private settings: LayersModelTrainingSettings) {} - public async trainModel(trainingData: TrainingData): Promise { - // Fetch data - const features: Array = []; - const labels: Array = []; - const numberOfClasses = trainingData.classes.length; - - trainingData.classes.forEach((gestureClass, index) => { - gestureClass.samples.forEach(sample => { - features.push(sample.value); - - const label: number[] = new Array(numberOfClasses) as number[]; - label.fill(0, 0, numberOfClasses); - label[index] = 1; - labels.push(label); - }); - }); - - const tensorFeatures = tf.tensor(features); - const tensorLabels = tf.tensor(labels); - - // Find the shape by looking at the first data point - const inputShape = [trainingData.classes[0].samples[0].value.length]; - - const input = tf.input({ shape: inputShape }); - const normalizer = tf.layers.batchNormalization().apply(input); - const dense = tf.layers.dense({ units: 16, activation: 'relu' }).apply(normalizer); - const softmax = tf.layers - .dense({ units: numberOfClasses, activation: 'softmax' }) - .apply(dense) as tf.SymbolicTensor; - - const model = tf.model({ inputs: input, outputs: softmax }); - - model.compile({ - loss: 'categoricalCrossentropy', - optimizer: tf.train.sgd(0.5), - metrics: ['accuracy'], - }); - - await model - .fit(tensorFeatures, tensorLabels, { - epochs: this.settings.noOfEpochs, - batchSize: 16, - validationSplit: 0.1, - }) - .catch(err => { - console.error('tensorflow training process failed:', err); - return Promise.reject(err); - }); - return Promise.resolve(new LayersMLModel(model)); - } -} - -export default LayersModelTrainer; diff --git a/src/script/mlmodels/NoneMLModel.ts b/src/script/mlmodels/NoneMLModel.ts deleted file mode 100644 index 683b74111..000000000 --- a/src/script/mlmodels/NoneMLModel.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import MLModel from '../domain/MLModel'; - -class NoneMLModel implements MLModel { - predict(filteredData: number[]): Promise { - throw new Error('No model have been assigned. Make a new model!'); - } -} - -export default NoneMLModel; diff --git a/src/script/navigation/Menus.ts b/src/script/navigation/Menus.ts deleted file mode 100644 index e4116d41e..000000000 --- a/src/script/navigation/Menus.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { writable } from 'svelte/store'; -import { Paths, PathType } from '../../router/paths'; - -export type MenuProperties = { - title: string; - navigationPath: PathType; -}; - -class Menus { - private static menuStore = writable([ - { - title: 'menu.data.helpHeading', - navigationPath: Paths.DATA, - }, - { - title: 'menu.trainer.helpHeading', - navigationPath: Paths.TRAINING, - }, - { - title: 'menu.model.helpHeading', - navigationPath: Paths.MODEL, - }, - ]); - - public static getMenuStore() { - return this.menuStore; - } -} - -export default Menus; diff --git a/src/script/patternMatrixTransforms.ts b/src/script/patternMatrixTransforms.ts deleted file mode 100644 index 41c18e615..000000000 --- a/src/script/patternMatrixTransforms.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -export const transformMatrixToColumns = (m: T[], matrixDimension: number): T[][] => { - const cols = []; - for (let colId = 1; colId <= matrixDimension; colId++) { - const remainder = colId === matrixDimension ? 0 : colId; - cols.push(m.filter((_c, i) => (i + 1) % matrixDimension === remainder)); - } - return cols; -}; - -export const transformColumnsToMatrix = (cols: T[][]): T[] => { - let matrix: T[] = []; - for (let i = 0; i < cols.length; i++) { - matrix = [...matrix, ...cols.map(c => c[i])]; - } - return matrix; -}; - -export const generateMatrix = (dimension: number, fillValue: T) => { - return new Array(dimension).fill(new Array(dimension).fill(fillValue)); -}; - -export type MatrixColumns = boolean[][]; - -export interface CellPosition { - colIdx: number; - rowIdx: number; -} - -export const getHighlightedColumns = ( - matrixColumns: MatrixColumns, - cellPos: CellPosition, -) => { - const col = matrixColumns[cellPos.colIdx]; - const highlightedCol = col.map( - (isOn, idx) => (!isOn && cellPos.rowIdx <= idx) || (isOn && cellPos.rowIdx > idx), - ); - const highlightedColumns = generateMatrix(matrixColumns.length, false); - highlightedColumns[cellPos.colIdx] = highlightedCol; - return highlightedColumns; -}; - -export const updateMatrixColumns = ( - matrixColumns: MatrixColumns, - cellPos: CellPosition, -) => { - const newCol = Array(matrixColumns.length).fill(false).fill(true, cellPos.rowIdx); - return [ - ...matrixColumns.slice(0, cellPos.colIdx), - newCol, - ...matrixColumns.slice(cellPos.colIdx + 1), - ]; -}; diff --git a/src/script/repository/ClassifierRepository.ts b/src/script/repository/ClassifierRepository.ts deleted file mode 100644 index aa80a16f3..000000000 --- a/src/script/repository/ClassifierRepository.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Readable, Writable, derived, get, writable } from 'svelte/store'; -import StaticConfiguration from '../../StaticConfiguration'; -import GestureConfidence from '../domain/GestureConfidence'; -import NoneMLModel from '../mlmodels/NoneMLModel'; -import MLModel from '../domain/MLModel'; -import ModelTrainer from '../domain/ModelTrainer'; -import ClassifierFactory from '../domain/ClassifierFactory'; -import Repositories from './Repositories'; -import Filters from '../domain/Filters'; -import Classifier from '../domain/Classifier'; -import Filter from '../domain/Filter'; -import MaxFilter from '../filters/MaxFilter'; -import MinFilter from '../filters/MinFilter'; -import PeaksFilter from '../filters/PeaksFilter'; -import { GestureID } from '../domain/Gesture'; -import MeanFilter from '../filters/MeanFilter'; -import RootMeanSquareFilter from '../filters/RootMeanSquareFilter'; -import StandardDeviationFilter from '../filters/StandardDeviationFilter'; -import TotalAccFilter from '../filters/TotalAccFilter'; -import ZeroCrossingRateFilter from '../filters/ZeroCrossingRateFilter'; - -export type TrainerConsumer = ( - trainer: ModelTrainer, -) => Promise; - -class ClassifierRepository { - private static confidences: Writable>; - private static mlModel: Writable; - private static filters: Writable; - private static filterArray: Writable; - private classifierFactory: ClassifierFactory; - - constructor() { - const initialConfidence = new Map(); - ClassifierRepository.confidences = writable(initialConfidence); - ClassifierRepository.mlModel = writable(new NoneMLModel()); - ClassifierRepository.filterArray = writable([]); - ClassifierRepository.filters = writable( - new Filters(ClassifierRepository.filterArray), - ); - this.classifierFactory = new ClassifierFactory(); - this.addAllFilters(); - } - - public getMLModel(): Readable { - return { - subscribe: ClassifierRepository.mlModel.subscribe, - }; - } - - public getClassifier(): Classifier { - return this.classifierFactory.buildClassifier( - ClassifierRepository.mlModel, - this.getTrainerConsumer(), - ClassifierRepository.filters, - get(Repositories.getInstance().getGestureRepository()), - (gestureId: GestureID, confidence: number) => { - this.setGestureConfidence(gestureId, confidence); - }, - ); - } - - private async trainModel(trainer: ModelTrainer): Promise { - const gestureRepository = Repositories.getInstance().getGestureRepository(); - const trainingData = this.classifierFactory.buildTrainingData( - get(gestureRepository), - get(ClassifierRepository.filters), - ); - const model = await trainer.trainModel(trainingData); - ClassifierRepository.mlModel.set(model); - } - - private getTrainerConsumer(): TrainerConsumer { - return (trainer: ModelTrainer) => this.trainModel(trainer); - } - - private addAllFilters(): void { - // todo to be removed - ClassifierRepository.filters.set( - new Filters( - writable([ - new MaxFilter(), - new MeanFilter(), - new MinFilter(), - new PeaksFilter(), - new RootMeanSquareFilter(), - new StandardDeviationFilter(), - new TotalAccFilter(), - new ZeroCrossingRateFilter(), - ]), - ), - ); - } - - /* TODO: Should be private, but currently ml.ts is depending on it. That should change by removing the functionality from ml.ts and using classifier instead */ - public setGestureConfidence(gestureId: GestureID, confidence: number) { - if (confidence < 0 || confidence > 1) { - throw new Error('Cannot set gesture confidence. Must be in the range 0.0-1.0'); - } - const newConfidences = get(ClassifierRepository.confidences); - newConfidences.set(gestureId, confidence); - ClassifierRepository.confidences.set(newConfidences); - } - - public getGestureConfidence(gestureId: number): GestureConfidence { - const derivedConfidence = derived([ClassifierRepository.confidences], stores => { - const confidenceStore = stores[0]; - if (confidenceStore.has(gestureId)) { - return confidenceStore.get(gestureId) as number; - } - return 0; - }); - return new GestureConfidence( - StaticConfiguration.defaultRequiredConfidence, - derivedConfidence, - ); - } -} - -export default ClassifierRepository; diff --git a/src/script/repository/GestureRepository.ts b/src/script/repository/GestureRepository.ts deleted file mode 100644 index 14938da20..000000000 --- a/src/script/repository/GestureRepository.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { PersistantGestureData } from '../domain/Gestures'; -import Gesture from '../domain/Gesture'; -import ControlledStorage from '../ControlledStorage'; -import { - Readable, - Subscriber, - Unsubscriber, - Writable, - get, - writable, -} from 'svelte/store'; -import ClassifierRepository from './ClassifierRepository'; - -class GestureRepository implements Readable { - private readonly LOCAL_STORAGE_KEY = 'gestureData'; - private static gestureStore: Writable; - constructor(private modelRepository: ClassifierRepository) { - GestureRepository.gestureStore = writable([]); - GestureRepository.gestureStore.set(this.getPersistedGestures()); - } - - public getGesture(gestureId: number) { - const gestures = get(GestureRepository.gestureStore); - const gestureIndex = gestures.findIndex(gesture => gesture.getId() === gestureId); - if (gestureIndex === -1) { - throw new Error(`Could not find gesture with id '${gestureId}'`); - } - return gestures[gestureIndex]; - } - - public subscribe( - run: Subscriber, - invalidate?: ((value?: Gesture[] | undefined) => void) | undefined, - ): Unsubscriber { - return GestureRepository.gestureStore.subscribe(run, invalidate); - } - - public clearGestures(): void { - GestureRepository.gestureStore.set([]); - this.saveCurrentGestures(); - } - - public addGesture(gestureData: PersistantGestureData): Gesture { - const gesture = this.buildGesture(gestureData); - GestureRepository.gestureStore.update(arr => { - arr.push(gesture); - return arr; - }); - this.saveCurrentGestures(); - return gesture; - } - - public removeGesture(gestureId: number) { - GestureRepository.gestureStore.update(arr => { - return arr.filter(gesture => gesture.getId() !== gestureId); - }); - this.saveCurrentGestures(); - } - - private buildPersistedGestureStore( - gestureData: PersistantGestureData, - ): Writable { - const store = writable(gestureData); - - return { - subscribe: store.subscribe, - set: val => { - store.set(val); - GestureRepository.gestureStore.update(val => val); - this.saveCurrentGestures(); - }, - update: updater => { - store.update(updater); - GestureRepository.gestureStore.update(val => val); - this.saveCurrentGestures(); - }, - }; - } - - private saveCurrentGestures() { - const gestures = get(GestureRepository.gestureStore); - const data = gestures.map(gesture => this.getPersistantValues(gesture)); - ControlledStorage.set(this.LOCAL_STORAGE_KEY, data); - } - - private getPersistantValues(gesture: Gesture): PersistantGestureData { - return { - ID: gesture.getId(), - name: gesture.getName(), - recordings: gesture.getRecordings(), - output: gesture.getOutput(), - }; - } - - private getPersistedGestures() { - const resultFromFetch: PersistantGestureData[] = this.getPersistedData(); - return resultFromFetch.map(persistedData => this.buildGesture(persistedData)); - } - - private buildGesture(persistedData: PersistantGestureData) { - const store = this.buildPersistedGestureStore(persistedData); - - return new Gesture(store, this.modelRepository.getGestureConfidence(get(store).ID)); - } - - private getPersistedData(): PersistantGestureData[] { - const result = localStorage.getItem(this.LOCAL_STORAGE_KEY); - if (!result) { - return []; - } - return ControlledStorage.get(this.LOCAL_STORAGE_KEY); - } -} - -export default GestureRepository; diff --git a/src/script/repository/Repositories.ts b/src/script/repository/Repositories.ts deleted file mode 100644 index 083cb19bf..000000000 --- a/src/script/repository/Repositories.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import GestureRepository from './GestureRepository'; -import ClassifierRepository from './ClassifierRepository'; - -class Repositories { - private gestureRepository: GestureRepository; - - private classifierRepository: ClassifierRepository; - - private static instance: Repositories; - - constructor() { - if (Repositories.instance) { - // Singleton - throw new Error('Could not instantiate repository. It is already instantiated!'); - } - Repositories.instance = this; - this.classifierRepository = new ClassifierRepository(); - this.gestureRepository = new GestureRepository(this.classifierRepository); - } - - public static getInstance() { - return this.instance; - } - - public getGestureRepository() { - return this.gestureRepository; - } - - public getModelRepository() { - return this.classifierRepository; - } -} - -export default Repositories; diff --git a/src/script/smoothenXYZData.ts b/src/script/smoothenXYZData.ts deleted file mode 100644 index 4ba0e67df..000000000 --- a/src/script/smoothenXYZData.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -interface XYZData { - x: number[]; - y: number[]; - z: number[]; -} - -const smoothen = (d: number[]): number[] => { - if (d.length === 0) { - return d; - } - const newData: number[] = []; - let prevValue = d[0]; - d.forEach(v => { - const newValue = v * 0.25 + prevValue * 0.75; - newData.push(newValue); - prevValue = newValue; - }); - return newData; -}; - -// Smoothen data -export function smoothenXYZData(d: XYZData): XYZData { - return { - x: smoothen(d.x), - y: smoothen(d.y), - z: smoothen(d.z), - }; -} diff --git a/src/script/stores/Stores.ts b/src/script/stores/Stores.ts deleted file mode 100644 index 5b24b0002..000000000 --- a/src/script/stores/Stores.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import Repositories from '../repository/Repositories'; -import Gestures from '../domain/Gestures'; -import Classifier from '../domain/Classifier'; -import PollingPredictorEngine from '../engine/PollingPredictorEngine'; -import LiveData from '../domain/LiveData'; -import MicrobitAccelerometerLiveData, { - MicrobitAccelerometerData, -} from '../livedata/MicrobitAccelerometerData'; -import LiveDataBuffer from '../domain/LiveDataBuffer'; - -const repositories = new Repositories(); - -export const gestures: Gestures = new Gestures(repositories.getGestureRepository()); -export const classifier: Classifier = repositories.getModelRepository().getClassifier(); -const liveDataBuffer = new LiveDataBuffer(400); -export const liveData: LiveData = - new MicrobitAccelerometerLiveData(liveDataBuffer); -export const engine: PollingPredictorEngine = new PollingPredictorEngine( - classifier, - liveData, -); diff --git a/src/script/stores/complianceStore.ts b/src/script/stores/complianceStore.ts deleted file mode 100644 index 292287c43..000000000 --- a/src/script/stores/complianceStore.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { writable } from 'svelte/store'; - -// Integrates the Micro:bit Educational Foundation common cookie consent dialog/analytics. -// Not suitable for other deployments. - -export interface CookieConsent { - analytics: boolean; - functional: boolean; -} - -const domain = window.location.hostname; -const config = { - ga: - import.meta.env.VITE_STAGE === 'PRODUCTION' || - import.meta.env.VITE_STAGE === 'STAGING' - ? {} - : undefined, - custom: [ - { - type: 'local', - domain, - category: 'essential', - name: 'gestureData', - purpose: 'Stores the training data and other settings for each gesture', - }, - { - type: 'local', - domain, - category: 'essential', - name: 'btPatternInput', - purpose: 'Stores the pairing pattern for the most recent input micro:bit', - }, - { - type: 'local', - domain, - category: 'essential', - name: 'btPatternOutput', - purpose: 'Stores the pairing pattern for the most recent output micro:bit', - }, - { - type: 'local', - domain, - category: 'essential', - name: 'MLSettings', - purpose: 'Stores settings for the machine learning model', - }, - { - type: 'local', - domain, - category: 'essential', - name: 'lang', - purpose: 'Stores your chosen language', - }, - { - type: 'local', - domain, - category: 'essential', - name: 'hasSeenSignUpDialog', - purpose: - 'Used to ensure that the testing community sign up dialog is only shown once', - }, - { - type: 'local', - domain, - category: 'essential', - name: 'hasSeenAppVersionRedirectDialog', - purpose: - 'Used to ensure that the UK primary school teacher dialog is only shown once', - }, - // Some of the Svelte stores use local storage, this needs investigating - ], -}; - -function showConsent( - { userTriggered }: { userTriggered: boolean } = { userTriggered: false }, -) { - const w = window as any; - w.commonConsent?.show({ userTriggered, config }); -} - -export function manageCookies() { - showConsent({ userTriggered: true }); -} - -export const consent = writable(undefined); - -const w = window as any; -const updateListener = (event: CustomEvent) => { - consent.set(event.detail); -}; -w.addEventListener('consentchange', updateListener); -if (w.commonConsent) { - showConsent(); -} else { - w.addEventListener('consentinit', showConsent); -} diff --git a/src/script/stores/connectDialogStore.ts b/src/script/stores/connectDialogStore.ts deleted file mode 100644 index df59bf563..000000000 --- a/src/script/stores/connectDialogStore.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { get, writable } from 'svelte/store'; -import { compatibility, state } from './uiStore'; -import { DeviceRequestStates } from '../microbit-interfacing/MicrobitConnection'; - -export enum ConnectDialogStates { - NONE, // No connection in progress -> Dialog box closed - START_RADIO, // Initial box. Main prompt is to connect via radio however includes choice to connect via bluetooth - CONNECTING_MICROBITS, // Micro:bits connecting prompt - START_BLUETOOTH, // Initial box to begin the bluetooth connection flow - START_OUTPUT, // Initial box if input microbit is already connected. Choice between same and other microbit for output - BAD_FIRMWARE, // We detected an issue with the firmware of the micro:bit trying to transfer program. - CONNECT_CABLE, // Instructions how to connect micro:bit via usb - CONNECT_TUTORIAL_USB, // Instructions how to select micro:bit on popup when connected by usb - USB_DOWNLOADING, // Downloading usb program status bar prompt - CONNECT_BATTERY, // Instructions to connect micro:bit to battery - BLUETOOTH, // Bluetooth connect prompt, with pattern drawing - CONNECT_TUTORIAL_BLUETOOTH, // Instructions on how to connect micro:bit when connecting to bluetooth - BLUETOOTH_CONNECTING, // Downloading BlueTooth prompt - CONNECT_TUTORIAL_SERIAL, // Instructions how to connect the micro:bit using a serial connection - MANUAL_TUTORIAL, // Prompt with tutorial gif for manual installation (and downloading of program) - USB_TRY_AGAIN, // Prompt user to try connecting via WebUSB again - BLUETOOTH_TRY_AGAIN, // Prompt user to try connecting via WebBluetooth again - MICROBIT_UNSUPPORTED, // Warn user that micro:bit V1 is not supported - BROWSER_DIALOG, // Awaiting user interaction with browser dialog -} - -export const connectionDialogState = writable<{ - connectionState: ConnectDialogStates; - deviceState: DeviceRequestStates; -}>({ - connectionState: ConnectDialogStates.NONE, - deviceState: DeviceRequestStates.NONE, -}); - -export const startConnectionProcess = (): void => { - const { bluetooth } = get(compatibility); - const { isInputConnected, reconnectState } = get(state); - // Updating the state will cause a popup to appear, from where the connection process will take place - let initialInputDialogState = ConnectDialogStates.START_BLUETOOTH; - if (reconnectState.connectionType === 'none' && !bluetooth) { - initialInputDialogState = ConnectDialogStates.START_RADIO; - } else if ( - reconnectState.connectionType === 'bridge' || - reconnectState.connectionType === 'remote' - ) { - initialInputDialogState = ConnectDialogStates.START_RADIO; - } - connectionDialogState.update(s => { - s.connectionState = isInputConnected - ? ConnectDialogStates.START_OUTPUT - : initialInputDialogState; - s.deviceState = isInputConnected - ? DeviceRequestStates.OUTPUT - : DeviceRequestStates.INPUT; - return s; - }); -}; diff --git a/src/script/stores/connectionStore.ts b/src/script/stores/connectionStore.ts deleted file mode 100644 index 7a75c8d6b..000000000 --- a/src/script/stores/connectionStore.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { get, type Writable } from 'svelte/store'; -import { persistantWritable } from './storeUtil'; -import MBSpecs from '../microbit-interfacing/MBSpecs'; - -// Todo: Rename file to a more appropriate name -// Pattern for connecting to input microbit -export const btPatternInput = persistantWritable( - 'btPatternInput', - Array(25).fill(false), -); - -// Pattern for connecting to output microbit -export const btPatternOutput = persistantWritable( - 'btPatternOutput', - Array(25).fill(false), -); - -export const radioBridgeRemoteDeviceId = persistantWritable( - 'radioBridgeRemoteDeviceId', - -1, -); - -// Show the select micro:bit dialog for Bluetooth pairing on page load. -// The previous attempt to requestDevice failed. -export const btSelectMicrobitDialogOnLoad = persistantWritable( - 'btSelectMicrobitDialogOnLoad', - false, -); - -export const isInputPatternValid = () => { - const pattern = get(btPatternInput); - return MBSpecs.Utility.isPairingPattermValid(pattern); -}; diff --git a/src/script/stores/mlStore.ts b/src/script/stores/mlStore.ts deleted file mode 100644 index 2f3283746..000000000 --- a/src/script/stores/mlStore.ts +++ /dev/null @@ -1,276 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { persistantWritable } from './storeUtil'; -import { get, writable } from 'svelte/store'; -import { LayersModel } from '@tensorflow/tfjs-layers'; -import { state } from './uiStore'; -import { AxesType, FilterType, Axes, Filters } from '../datafunctions'; -import { PinTurnOnState } from '../../components/output/PinSelectorUtil'; -import MBSpecs from '../microbit-interfacing/MBSpecs'; -import { PersistantGestureData } from '../domain/Gestures'; -import Gesture, { GestureID } from '../domain/Gesture'; -import { gestures } from './Stores'; -import { TrainingStatus } from '../domain/Model'; - -export type RecordingData = { - ID: number; - data: { - x: number[]; - y: number[]; - z: number[]; - }; -}; - -export function loadDatasetFromFile(file: File) { - const reader = new FileReader(); - reader.onload = function (e: ProgressEvent) { - if (!e.target) { - return; - } - const contents = e.target.result; - if (typeof contents === 'string') { - // TODO: fix the following really unsafe parsing and casting - const gestureData: PersistantGestureData[] = JSON.parse( - contents, - ) as PersistantGestureData[]; - updateToUntrainedState(); - gestures.importFrom(gestureData); - } - }; - reader.readAsText(file as Blob); -} - -export function downloadDataset() { - const element = document.createElement('a'); - element.setAttribute( - 'href', - 'data:application/json;charset=utf-8,' + - encodeURIComponent(JSON.stringify(get(gestures), null, 2)), - ); - element.setAttribute('download', 'dataset'); - - element.style.display = 'none'; - document.body.appendChild(element); - - element.click(); - document.body.removeChild(element); -} - -// Delete this function! -export function clearGestures() { - gestures.clearGestures(); -} - -export type GestureData = { - name: string; - ID: GestureID; - recordings: RecordingData[]; - output: GestureOutput; - confidence: { - currentConfidence: number; - requiredConfidence: number; - isConfident: boolean; - }; -}; - -export type GestureOutput = { - matrix?: boolean[]; - sound?: SoundData; - outputPin?: { pin: MBSpecs.UsableIOPin; pinState: PinTurnOnState; turnOnTime: number }; -}; - -export type SoundData = { - name: string; - id: string; - path: string; -}; - -export type LiveData = { - //Todo remove this - accelX: number; - accelY: number; - accelZ: number; - smoothedAccelX: number; - smoothedAccelY: number; - smoothedAccelZ: number; -}; - -type MlSettings = { - duration: number; // Duration of recording - numSamples: number; // number of samples in one recording (when recording samples) - minSamples: number; // minimum number of samples for reliable detection (when detecting gestures) - automaticClassification: boolean; // If true, automatically classify gestures - updatesPrSecond: number; // Times algorithm predicts data pr second - numEpochs: number; // Number of epochs for ML - learningRate: number; - includedAxes: AxesType[]; - includedFilters: Set; -}; - -const initialMLSettings: MlSettings = { - duration: 1800, - numSamples: 80, - minSamples: 80, - automaticClassification: true, - updatesPrSecond: 4, - numEpochs: 80, - learningRate: 0.5, - includedAxes: [Axes.X, Axes.Y, Axes.Z], - includedFilters: new Set([ - Filters.MAX, - Filters.MEAN, - Filters.MIN, - Filters.STD, - Filters.PEAKS, - Filters.ACC, - Filters.ZCR, - Filters.RMS, - ]), -}; - -// Store with ML-Algorithm settings -export const settings = persistantWritable('MLSettings', initialMLSettings); - -export const livedata = writable({ - accelX: 0, - accelY: 0, - accelZ: 0, - smoothedAccelX: 0, - smoothedAccelY: 0, - smoothedAccelZ: 0, -}); - -export const currentData = writable<{ x: number; y: number; z: number }>({ - x: 0, - y: 0, - z: 0, -}); - -livedata.subscribe(data => { - currentData.set({ - x: data.smoothedAccelX, - y: data.smoothedAccelY, - z: data.smoothedAccelZ, - }); -}); - -// Store for current gestures -export const chosenGesture = writable(null); - -function updateToUntrainedState() { - state.update(s => { - s.isPredicting = false; - return s; - }); - trainingStatus.set(TrainingStatus.Untrained); -} - -// Delete this, maybe? updateToUntrainedState -export function addGesture(name: string): void { - updateToUntrainedState(); - gestures.createGesture(name); -} - -// Delete this, maybe? updateToUntrainedState -export function removeGesture(gesture: GestureData) { - updateToUntrainedState(); - gestures.removeGesture(gesture.ID); -} - -// Delete this, maybe? updateToUntrainedState -export function addRecording(gestureID: number, recording: RecordingData) { - updateToUntrainedState(); - gestures.getGesture(gestureID).addRecording(recording); -} - -// Delete this, maybe? updateToUntrainedState -export function removeRecording(gestureID: number, recordingID: number) { - updateToUntrainedState(); - gestures.getGesture(gestureID).removeRecording(recordingID); -} - -// Delete this, maybe? updateToUntrainedState -export function updateGestureSoundOutput( - gestureID: number, - sound: SoundData | undefined, -) { - gestures.getGesture(gestureID).setSoundOutput(sound); -} - -export function updateGesturePinOutput( - gestureID: number, - pin: MBSpecs.UsableIOPin, - state: PinTurnOnState, - time: number, -) { - gestures.getGesture(gestureID).setIOPinOutput(pin, state, time); -} - -export function updateGestureLEDOutput(gestureID: number, matrix: boolean[]) { - gestures.getGesture(gestureID).setLEDOutput(matrix); -} - -export const gestureConfidences = writable<{ [id: string]: number }>({}); - -// TODO: This is only used one place. Remove store and compute best prediction at said component? -export const bestPrediction = writable(undefined); - -// Store for components to assess model status -export const model = writable(undefined); - -export const trainingStatus = writable(TrainingStatus.Untrained); - -// Stores and manages previous data-elements. Used for classifying current gesture -// TODO: Only used for 'getPrevData' (which is only used for ml.ts). Do we even want this as global state? -export const prevData = writable(new Array(get(settings).minSamples)); - -let liveDataIndex = 0; -livedata.subscribe(data => { - prevData.update((prevDataArray: LiveData[]) => { - prevDataArray[liveDataIndex] = data; - return prevDataArray; - }); - liveDataIndex++; - if (liveDataIndex >= get(settings).minSamples) { - liveDataIndex = 0; - } -}); - -// Store for training state. Used to radiate current epoch state (not done presently). -// TODO: Not used for anything presently (only ever updated). Use or delete -export const trainingState = writable({ - percentage: 0, - loss: 0, - epochs: 0, -}); - -// TODO: Only used at one location (ml.ts). Move to ml.ts? -export function getPrevData(): { x: number[]; y: number[]; z: number[] } | undefined { - const data: LiveData[] = get(prevData); - const dataLength: number = data.length; - // Returns undefined if there has not being collected minSamples data yet - if (Object.values(data).length !== data.length) { - return undefined; - } - const x: number[] = new Array(dataLength); - const y: number[] = new Array(dataLength); - const z: number[] = new Array(dataLength); - - for (let i = 0; i < dataLength; i++) { - const oldDataIndex = (i + liveDataIndex) % dataLength; - x[i] = data[oldDataIndex].accelX; - y[i] = data[oldDataIndex].accelY; - z[i] = data[oldDataIndex].accelZ; - } - - return { x, y, z }; -} - -// // Never used? -// export const lossGraphStore = writable(undefined); -// // Never used? -// export const classificationStore = writable({ lastRecording: undefined, recordingTime: undefined }); diff --git a/src/script/stores/storeUtil.ts b/src/script/stores/storeUtil.ts deleted file mode 100644 index 6417d8088..000000000 --- a/src/script/stores/storeUtil.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { writable, type Writable } from 'svelte/store'; - -// Version of saved information. -// Increment if stored information types are changed -// or if a localstorage data needs to be wiped -// (incrementing it, will overwrite all persistantWritable data stored in localstorage) -const persistVersion = 1; - -// Creates a svelte store which automatically loads from localstorage, and -// keeps localstorage up to date -export function persistantWritable(key: string, initValue: T): Writable { - if (initValue === null || initValue === undefined) { - throw new Error( - "Do not use 'null' or 'undefined' values as initial value. Later checking for changes, will result in stored changes being deleted", - ); - } - const storedJson: string | null = localStorage.getItem(key); - - let storedObject: unknown; - try { - const parseSets = (key: string | number, value: unknown) => { - if (value instanceof Array && key === 'includedFilters') { - const setWithFilters = new Set(value); - return setWithFilters; - } - return value; - }; - storedObject = storedJson != null ? JSON.parse(storedJson, parseSets) : null; - } catch (error) { - console.warn(error); - storedObject = null; - } - - const useStored = shouldUseStored(storedObject, initValue); - initValue = useStored - ? (storedObject as { version: number; value: T }).value - : initValue; - const store: Writable = writable(initValue); - store.subscribe(val => - localStorage.setItem( - key, - JSON.stringify( - { version: persistVersion, value: val }, - (key: string | number, value: unknown) => { - if (value instanceof Set && key === 'includedFilters') - return [...value] as unknown[]; - return value; - }, - ), - ), - ); - - return store; -} - -function shouldUseStored(storedObject: unknown, initValue: T): boolean { - if (storedObject == null) { - return false; - } - if (typeof storedObject !== 'object') { - return false; - } - if (!('value' in storedObject) || !('version' in storedObject)) { - return false; - } - const storedPersistObject = storedObject as { version: unknown; value: unknown }; // This should not be needed, but the typescript rollup plugin complains if we keep working directly on 'storedObject' - if (storedPersistObject.version !== persistVersion) { - return false; - } - - return areMatchingTypes(initValue, storedPersistObject.value); -} - -// Recursively checks if to variables of unknown type matches each -// other type and structure wise -// Due to javascript being a dumb language without proper typing, this cannot check -// properly if something in the initial values are null or undefined or if something gets set to null -// or undefined -function areMatchingTypes(value1: unknown, value2: unknown): boolean { - if (value1 === value2) { - return true; - } - if (value1 === null) { - // This makes the type-checking incomplete, but otherwise we need to completely ban 'null' values in pesistant storage - return true; - } - const type1 = typeof value1; - const type2 = typeof value2; - if (type1 !== type2) { - return false; - } - - if (['boolean', 'number', 'string', 'null'].includes(type1)) { - // value is a primitive - return true; // We know from previous check that type1 === type2 - } - if (type1 === 'undefined') { - throw new Error('You should never use "undefined" as values for persistant storage'); - } - if (type1 !== 'object') { - throw new Error('Unrecognised type: ' + type1); - } - - const isType1Array = Array.isArray(value1); - const isType2Array = Array.isArray(value2); - - if (isType1Array !== isType2Array) { - return false; - } - - if (isType1Array) { - const array1 = value1 as unknown[]; - const array2 = value2 as unknown[]; - if (array1.length === 0 || array2.length === 0) { - return true; // Either array is empty -> no type conflict - } - for (const item of array2) { - const isValidItem = areMatchingTypes(array1[0], item); - if (!isValidItem) { - return false; - } - } - return true; - } - - // Both parameters are 'object's and not arrays - - const obj1 = value1 as object; - const obj2 = value2 as object; - - const obj1Properties = Object.entries(obj1); - - for (const [key, value] of obj1Properties) { - if (!(key in obj2) && value != null) { - return false; // A property existing on one object does not exist on the other - } - } - - // The two objects have the same properties - - for (const [key, value] of obj1Properties) { - const obj2Property: unknown = obj2[key as keyof object]; // A bit hacky, but we have already checked to two objects have the same keys - const isMatch = areMatchingTypes(value as unknown, obj2Property); - if (!isMatch) { - return false; - } - } - return true; -} diff --git a/src/script/stores/uiStore.ts b/src/script/stores/uiStore.ts deleted file mode 100644 index f302d8490..000000000 --- a/src/script/stores/uiStore.ts +++ /dev/null @@ -1,190 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { get, writable } from 'svelte/store'; -import { t } from '../../i18n'; -import { - checkCompatibility, - type CompatibilityStatus, -} from '../compatibility/CompatibilityChecker'; -import MBSpecs from '../microbit-interfacing/MBSpecs'; -import { gestures } from './Stores'; -import { HexOrigin } from '../../StaticConfiguration'; -import { DeviceRequestStates } from '../microbit-interfacing/MicrobitConnection'; -import { persistantWritable } from './storeUtil'; -import { logError, logEvent } from '../utils/logging'; - -// TODO: Rename? Split up further? - -let text: (key: string, vars?: object) => string; -t.subscribe(t => (text = t)); - -const compatibilityResult = checkCompatibility(); -export const compatibility = writable(compatibilityResult); -if (compatibilityResult.bluetooth) { - navigator.bluetooth - ?.getAvailability() - .then(bluetoothAvailable => { - compatibility.update(s => { - logEvent({ - type: 'Device', - action: 'Bluetooth available', - value: s.bluetooth && bluetoothAvailable ? 1 : 0, - }); - s.bluetooth = s.bluetooth && bluetoothAvailable; - return s; - }); - }) - .catch(e => logError('Failed to get Bluetooth availability', e)); -} - -export const isCompatibilityWarningDialogOpen = writable(false); - -export const hasSeenAppVersionRedirectDialog = persistantWritable( - 'hasSeenAppVersionRedirectDialog', - false, -); - -export const hasSeenSignUpDialog = persistantWritable( - 'hasSeenSignUpDialog', - false, -); - -export enum ModelView { - TILE, - STACK, -} - -export type ConnectHelp = 'autoReconnect' | 'userReconnect' | 'connect' | false; - -export type ConnectionType = 'none' | 'bluetooth' | 'bridge' | 'remote'; - -interface ReconnectState { - connectionType: ConnectionType; - inUseAs: Set; - reconnecting: boolean; - reconnectFailed: boolean; -} - -// Store current state to prevent error prone actions -export const state = writable<{ - isTesting: boolean; - isRecording: boolean; - isTraining: boolean; - trainingProgress: number; // where 1 is 100% complete - isInputConnected: boolean; - isOutputConnected: boolean; - hasTrainedBefore: boolean; - isPredicting: boolean; - showConnectHelp: ConnectHelp; - reconnectState: ReconnectState; - isInputReady: boolean; - inputHexVersion: number; - inputMicrobitVersion: MBSpecs.MBVersion | -1; - inputOrigin: HexOrigin; - isOutputReady: boolean; - outputHexVersion: number; - outputMicrobitVersion: MBSpecs.MBVersion | -1; - outputOrigin: HexOrigin; - modelView: ModelView; - isInputOutdated: boolean; - isOutputOutdated: boolean; -}>({ - isTesting: false, - isRecording: false, - isTraining: false, - trainingProgress: 0, - isInputConnected: false, - isOutputConnected: false, - hasTrainedBefore: false, - isPredicting: false, - showConnectHelp: false, - reconnectState: { - connectionType: 'none', - inUseAs: new Set(), - reconnecting: false, - reconnectFailed: false, - }, - isInputReady: false, - inputHexVersion: -1, - inputMicrobitVersion: -1, - inputOrigin: HexOrigin.UNKNOWN, - isOutputReady: false, - outputHexVersion: -1, - outputMicrobitVersion: -1, - outputOrigin: HexOrigin.UNKNOWN, - modelView: ModelView.STACK, - isInputOutdated: false, - isOutputOutdated: false, -}); - -// Message store to propagate allow all components to inform users. -export const message = writable<{ warning: boolean; text: string }>({ - warning: false, - text: '', -}); - -// Message store to propagate allow all components to inform users. -export const outputting = writable<{ text: string }>({ text: '' }); - -// Alert user sets current message to text and hightlights it. -export function alertUser(text: string): void { - message.set({ - warning: true, - text: text, - }); -} - -// Assess whether an action is allowed. Alert user if not -export function areActionsAllowed(actionAllowed = true, alertIfNotReady = true): boolean { - const status = assessStateStatus(actionAllowed); - - if (!status.isReady && alertIfNotReady) { - alertUser(status.msg); - } - - return status.isReady; -} - -// Assess status and return message to alert user. -function assessStateStatus(actionAllowed = true): { isReady: boolean; msg: string } { - const currentState = get(state); - - if (currentState.isRecording) return { isReady: false, msg: text('alert.isRecording') }; - if (currentState.isTesting) return { isReady: false, msg: text('alert.isTesting') }; - if (currentState.isTraining) return { isReady: false, msg: text('alert.isTraining') }; - if (!currentState.isInputConnected && actionAllowed) - return { isReady: false, msg: text('alert.isNotConnected') }; - - return { isReady: true, msg: '' }; -} - -export const hasSufficientData = (): boolean => { - if (!gestures) { - return false; - } - if (gestures.getNumberOfGestures() < 2) { - return false; - } - return !gestures.getGestures().some(gesture => gesture.getRecordings().length < 3); -}; - -export const buttonPressed = writable<{ buttonA: 0 | 1; buttonB: 0 | 1 }>({ - buttonA: 0, - buttonB: 0, -}); - -export enum MicrobitInteractions { - A, - B, - AB, -} - -const initialMicrobitInteraction: MicrobitInteractions = MicrobitInteractions.B; - -export const microbitInteraction = writable( - initialMicrobitInteraction, -); diff --git a/src/script/transitions.ts b/src/script/transitions.ts deleted file mode 100644 index 9f0bd0b95..000000000 --- a/src/script/transitions.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { cubicOut } from 'svelte/easing'; - -export function horizontalSlide( - element: any, - { delay = 0, duration = 400, easing = cubicOut, axis = 'x' } = {}, -) { - const style = getComputedStyle(element); - const opacity = +style.opacity; - const primary_property = axis === 'y' ? 'height' : 'width'; - const primary_property_value = parseFloat(style[primary_property]); - const secondary_properties = axis === 'y' ? ['top', 'bottom'] : ['left', 'right']; - const capitalized_secondary_properties = secondary_properties.map( - e => `${e[0].toUpperCase()}${e.slice(1)}`, - ); - - const padding_start_value = parseFloat( - // @ts-ignore - style[`padding${capitalized_secondary_properties[0]}`], - ); - const padding_end_value = parseFloat( - // @ts-ignore - style[`padding${capitalized_secondary_properties[1]}`], - ); - const margin_start_value = parseFloat( - // @ts-ignore - style[`margin${capitalized_secondary_properties[0]}`], - ); - const margin_end_value = parseFloat( - // @ts-ignore - style[`margin${capitalized_secondary_properties[1]}`], - ); - const border_width_start_value = parseFloat( - // @ts-ignore - style[`border${capitalized_secondary_properties[0]}Width`], - ); - const border_width_end_value = parseFloat( - // @ts-ignore - style[`border${capitalized_secondary_properties[1]}Width`], - ); - return { - delay, - duration, - easing, - css: (t: number) => - 'overflow: hidden;' + - `opacity: ${Math.min(t * 20, 1) * opacity};` + - `${primary_property}: ${t * primary_property_value}px;` + - `padding-${secondary_properties[0]}: ${t * padding_start_value}px;` + - `padding-${secondary_properties[1]}: ${t * padding_end_value}px;` + - `margin-${secondary_properties[0]}: ${t * margin_start_value}px;` + - `margin-${secondary_properties[1]}: ${t * margin_end_value}px;` + - `border-${secondary_properties[0]}-width: ${t * border_width_start_value}px;` + - `border-${secondary_properties[1]}-width: ${t * border_width_end_value}px;`, - }; -} diff --git a/src/script/utils/Smoother.ts b/src/script/utils/Smoother.ts deleted file mode 100644 index 0bc28656e..000000000 --- a/src/script/utils/Smoother.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -class Smoother { - private sum: number; - private smoothingPercentage: number; - - /** - * - * @param smoothingPercentage number between 0 and 1 dictating the percentage a number influences the smoothed number. The closer to 1, the stronger smoothing is applied. If number is outside of bounds, things will break. - * @param initial initial value. Is not necessary. - */ - constructor(smoothingPercentage: number, initial = 0) { - if (1 <= smoothingPercentage || smoothingPercentage <= 0) { - console.warn( - `SmoothingPercentage cannot be set outside of bounds 0 to 1 (Including bounds). - Setting default bounds`, - ); - } - this.smoothingPercentage = Math.min(Math.max(smoothingPercentage, 0.001), 0.999); - this.sum = initial; - } - - /** - * @return latest smoothed number - */ - get latest(): number { - return this.sum; - } - - /** - * - * @param newNumber adds the new number. Applies smoothing in accordance to previous numbers - * @returns the current number after smoothing - */ - process(newNumber: number): number { - this.sum = - this.sum * this.smoothingPercentage + newNumber * (1 - this.smoothingPercentage); - return this.latest; - } -} - -export default Smoother; diff --git a/src/script/utils/api.ts b/src/script/utils/api.ts deleted file mode 100644 index 972cd4d78..000000000 --- a/src/script/utils/api.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { logError } from './logging'; - -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -interface BrowserInfo { - country?: string; - region?: string; -} - -const isBrowserInfo = (v: unknown): v is BrowserInfo => { - return typeof v === 'object' && v !== null; -}; - -/** - * Best effort attempt to fetch browser info. - * On error it returns empty browser info. - */ -export const fetchBrowserInfo = async (): Promise => { - try { - // Note this API is not available if you're running locally without configuring API_PROXY in .env - const response = await fetch('/api/v1/browser/info'); - if (!response.ok) { - return {}; - } - const json = await response.json(); - if (isBrowserInfo(json)) { - return json; - } - } catch (e) { - logError('Failed to fetch browser info', e); - } - return {}; -}; - -export const signUp = async (email: string): Promise => { - try { - // Note this API is not available if you're running locally without configuring API_PROXY in .env - const response = await fetch('/api/v1/newsletters/ml-prototype/subscribe', { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify({ - email, - }), - }); - if (response.ok) { - return true; - } - } catch (e) { - logError('Failed to sign up', e); - } - return false; -}; diff --git a/src/script/utils/logging.ts b/src/script/utils/logging.ts deleted file mode 100644 index 378ef95dd..000000000 --- a/src/script/utils/logging.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import * as Sentry from '@sentry/svelte'; -import { appName, isDevMode, stage, version } from '../environment'; - -export interface Event extends Record { - type: string; - action: string; -} - -const sentryDsn: string | undefined = - stage !== 'production' ? undefined : import.meta.env.VITE_SENTRY_DSN; - -try { - Sentry.init({ - dsn: sentryDsn, - release: `machine-learning-tool-v${version}`, - environment: stage, - enabled: stage === 'production', - }); -} catch (e) { - // Ensure failures here don't impact app use. -} - -export const logMessage = (message: string, ...optionalParams: any[]) => { - if (isDevMode) { - console.log(message, ...optionalParams); - } - if (stage === 'production' && sentryDsn) { - try { - Sentry.addBreadcrumb({ - category: 'Log message', - message, - data: optionalParams, - level: 'info', - }); - } catch (e) { - // Ensure failures here don't impact app use. - } - } -}; - -export const logError = (context: string, error: any) => { - if (isDevMode) { - console.error(context); - console.error(error); - } - if (stage === 'production' && sentryDsn) { - try { - Sentry.captureException(error, { - extra: { - context, - }, - }); - } catch (e) { - // Ensure failures here don't impact app use. - } - } -}; - -export const logEvent = (event: Event) => { - if (isDevMode) { - console.log(event); - } - const gtag = (window as any).gtag; - if (stage === 'production' && gtag) { - try { - const { type, action, ...rest } = event; - gtag('event', type, { - app_name: appName, - action, - ...rest, - }); - } catch (e) { - // Ensure failures here don't impact app use. - } - } - if (stage === 'production' && sentryDsn) { - try { - Sentry.addBreadcrumb({ - category: 'Event', - data: event, - level: 'info', - }); - } catch (e) { - // Ensure failures here don't impact app use. - } - } -}; diff --git a/src/script/utils/reconnect.ts b/src/script/utils/reconnect.ts deleted file mode 100644 index 240e0e00b..000000000 --- a/src/script/utils/reconnect.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { get } from 'svelte/store'; -import { - connectionDialogState, - ConnectDialogStates, - startConnectionProcess, -} from '../stores/connectDialogStore'; -import { state } from '../stores/uiStore'; -import Microbits from '../microbit-interfacing/Microbits'; -import { - stateOnFailedToConnect, - stateOnReconnectionAttempt, - stateOnShowConnectHelp, -} from '../microbit-interfacing/state-updaters'; - -export const reconnect = async (finalAttempt: boolean = false) => { - stateOnReconnectionAttempt(); - const { reconnectState } = get(state); - if (reconnectState.connectionType === 'bluetooth') { - connectionDialogState.update(s => { - s.connectionState = ConnectDialogStates.BLUETOOTH_CONNECTING; - return s; - }); - } else { - connectionDialogState.update(s => { - s.connectionState = ConnectDialogStates.CONNECTING_MICROBITS; - return s; - }); - } - try { - for (const inUseAs of reconnectState.inUseAs.values()) { - await Microbits.reconnect(inUseAs, finalAttempt); - } - connectionDialogState.update(s => { - s.connectionState = ConnectDialogStates.NONE; - return s; - }); - } catch (e) { - if (finalAttempt) { - reconnectState.inUseAs.forEach(s => Microbits.dispose(s)); - reconnectState.inUseAs.forEach(s => stateOnFailedToConnect(s)); - startConnectionProcess(); - } else { - connectionDialogState.update(s => { - s.connectionState = ConnectDialogStates.NONE; - return s; - }); - stateOnShowConnectHelp(true); - } - } finally { - state.update(s => { - s.reconnectState = { - ...s.reconnectState, - reconnecting: false, - }; - return s; - }); - } -}; diff --git a/src/setup_tests.ts b/src/setup_tests.ts deleted file mode 100644 index 02759f77a..000000000 --- a/src/setup_tests.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import 'vitest-dom/extend-expect'; - -// browser mocks -const setLang = (lang: string) => { - const localStorageMock = (function () { - let store = { - isTesting: true, - lang: JSON.stringify(lang), - }; - return { - getItem: function (key: 'isTesting' | 'lang') { - return store[key] || null; - }, - setItem: function (key: 'isTesting' | 'lang', value: any) { - // @ts-ignore - store[key] = value.toString(); - }, - removeItem: function (key: 'isTesting' | 'lang') { - delete store[key]; - }, - clear: function () { - store = { - isTesting: true, - lang: JSON.stringify(lang), - }; - }, - }; - })(); - Object.defineProperty(window, 'localStorage', { - value: localStorageMock, - }); -}; - -localStorage.setItem('isTesting', 'true'); -setLang('da'); - -export default setLang; diff --git a/src/svelte-skeleton.d.ts b/src/svelte-skeleton.d.ts deleted file mode 100644 index b20f913c1..000000000 --- a/src/svelte-skeleton.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -declare module 'svelte-skeleton/Skeleton.svelte'; diff --git a/src/views/IncompatiblePlatformView.svelte b/src/views/IncompatiblePlatformView.svelte deleted file mode 100644 index 9148cb21d..000000000 --- a/src/views/IncompatiblePlatformView.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - - -
        -

        - {appName} -

        -

        - {$t('compatibility.platform.notSupported')} -

        -

        {$t('compatibility.platform.notSupported.joinDesktop')}

        -
        diff --git a/src/views/OverlayView.svelte b/src/views/OverlayView.svelte deleted file mode 100644 index b4056c16a..000000000 --- a/src/views/OverlayView.svelte +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - -
        - {#if showLatestMessage} -
        -
        -

        - {latestMessage} -

        -
        -
        - {/if} - -
        diff --git a/src/views/PageContentView.svelte b/src/views/PageContentView.svelte deleted file mode 100644 index abf2de8c7..000000000 --- a/src/views/PageContentView.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/views/TabView.svelte b/src/views/TabView.svelte deleted file mode 100644 index e986a654f..000000000 --- a/src/views/TabView.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - - diff --git a/src/views/currentComponentStore.ts b/src/views/currentComponentStore.ts deleted file mode 100644 index 0cb0cf07f..000000000 --- a/src/views/currentComponentStore.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { SvelteComponent } from 'svelte'; -import { Writable, writable } from 'svelte/store'; -import Homepage from '../pages/Homepage.svelte'; - -export const currentPageComponent: Writable> = - writable(Homepage); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 58c65bd6d..9faaeeaea 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -4,6 +4,4 @@ * SPDX-License-Identifier: MIT */ -/// /// -/// diff --git a/staticwebapp.config.json b/staticwebapp.config.json deleted file mode 100644 index 2b6c269da..000000000 --- a/staticwebapp.config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "navigationFallback": { - "rewrite": "/index.html", - "exclude": ["*.{png,jpg,gif,css,js,icosvg}"] - } -} \ No newline at end of file diff --git a/svelte.config.js b/svelte.config.js deleted file mode 100644 index cf967bd04..000000000 --- a/svelte.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ -import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' - -export default { - // Consult https://svelte.dev/docs#compile-time-svelte-preprocess - // for more information about preprocessors - preprocess: vitePreprocess() -}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 338c2dde7..a7fc6fbf2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,46 +1,25 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ { - "extends": "@tsconfig/svelte/tsconfig.json", - "include": [ - "src/**/*", - "src/node_modules/**/*" - ], - "exclude": [ - "node_modules/*", - "__sapper__/*", - "public/*", - ], "compilerOptions": { - "allowJs": true, + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", - "lib": [ - "DOM", - "ESNext", - "WebWorker" - ], - "moduleResolution": "node", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, "resolveJsonModule": true, - "noImplicitAny": true, + "isolatedModules": true, "noEmit": true, - "esModuleInterop": true, - "allowImportingTsExtensions": true, - "importsNotUsedAsValues": "remove", + "jsx": "react-jsx", + + /* Linting */ "strict": true, - "verbatimModuleSyntax": false, - "ignoreDeprecations": "5.0", - "noFallthroughCasesInSwitch": true, - "types": [ - "node", - "svelte", - "vitest/globals", - "w3c-web-serial" - ], + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true }, - "files": [ - "./windi.config.js" - ] + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 000000000..42872c59f --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts index 07776024f..5a916f566 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,11 +5,7 @@ * SPDX-License-Identifier: MIT */ import { defineConfig, loadEnv } from 'vite'; -import { svelte } from '@sveltejs/vite-plugin-svelte'; -import WindiCSS from 'vite-plugin-windicss'; -import { preprocessMeltUI, sequence } from '@melt-ui/pp'; -import { sveltePreprocess } from 'svelte-preprocess/dist/autoProcess'; -import Icons from 'unplugin-icons/vite'; +import react from "@vitejs/plugin-react"; export default defineConfig(({ mode }) => { const commonEnv = loadEnv(mode, process.cwd(), ''); @@ -17,21 +13,7 @@ export default defineConfig(({ mode }) => { return { base: process.env.BASE_URL ?? '/', plugins: [ - svelte({ - preprocess: sequence([ - sveltePreprocess({ typescript: true }), - preprocessMeltUI(), - ]), - - onwarn(warning, defaultHandler) { - if (warning.code.includes('a11y')) return; // Ignores the a11y warnings when compiling. This does not apply to the editor, see comment at bottom for vscode instructions - - // handle all other warnings normally - defaultHandler!(warning); - }, - }), - WindiCSS(), - Icons({ compiler: 'svelte' }), + react() ], define: { 'import.meta.env.VITE_APP_VERSION': JSON.stringify(process.env.npm_package_version), @@ -55,7 +37,6 @@ export default defineConfig(({ mode }) => { : undefined, test: { globals: true, - setupFiles: ['./src/setup_tests.ts'], poolOptions: { threads: { // threads disabled for now due to https://github.com/vitest-dev/vitest/issues/1982 @@ -64,39 +45,4 @@ export default defineConfig(({ mode }) => { }, }, }; -}); - -/** -To disable a11y warnings in vscode: - -create file .vscode/settings.json and place - -{ - "svelte.plugin.svelte.compilerWarnings": { - "a11y-aria-attributes": "ignore", - "a11y-incorrect-aria-attribute-type": "ignore", - "a11y-unknown-aria-attribute": "ignore", - "a11y-hidden": "ignore", - "a11y-misplaced-role": "ignore", - "a11y-unknown-role": "ignore", - "a11y-no-abstract-role": "ignore", - "a11y-no-redundant-roles": "ignore", - "a11y-role-has-required-aria-props": "ignore", - "a11y-accesskey": "ignore", - "a11y-autofocus": "ignore", - "a11y-misplaced-scope": "ignore", - "a11y-positive-tabindex": "ignore", - "a11y-invalid-attribute": "ignore", - "a11y-missing-attribute": "ignore", - "a11y-img-redundant-alt": "ignore", - "a11y-label-has-associated-control": "ignore", - "a11y-media-has-caption": "ignore", - "a11y-distracting-elements": "ignore", - "a11y-structure": "ignore", - "a11y-click-events-have-key-events": "ignore", - "a11y-missing-content": "ignore", - } - } - - Add configuration values as needed. - */ +}); \ No newline at end of file diff --git a/windi.config.js b/windi.config.js deleted file mode 100644 index af271c603..000000000 --- a/windi.config.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -//Ml-Machine colors - -export default { - theme: { - extend: { - fontFamily: { - sans: ['Helvetica', 'Arial', 'sans-serif'], - serif: ['serif'], - }, - colors: { - brand: { - // Produced with using the hue from the brand blue at 400 but adjusting 500 - // to be an acceptable button background color with white text at all sizes - // (WCGA AA 4.5) - // https://huetone.ardov.me/ - 50: '#edf7ff', - 100: '#c1e1fb', - 200: '#94ccf6', - 300: '#62b3ed', - // Brand colour, but too light for white - 400: '#2a94d6', - // 4.5 contrast with white - 500: '#007dbc', - 600: '#0071aa', - 700: '#16567e', - 800: '#1d4662', - 900: '#023a5a', - }, - primary: '#2a94d6', - primarytext: '#000000', - secondary: '#00a000', - secondarytext: '#FFFFFF', - info: '#98A2B3', - backgrounddark: '#F5F5F5', - backgroundlight: '#ffffff', - infolight: '#93c5fd', - link: '#6c4bc1', - disabled: '#8892A3', - primaryborder: '#E5E7EB', - infobglight: '#E7E5E4', - infobgdark: '#57534E', - infoiconlight: '#FFFFFF7F', - infoicondark: '#787878', - infotextlight: '#ffffff', - infotextdark: '#787878', - ring: 'rgba(66, 153, 255, 0.6)', - ringBright: 'rgb(66, 153, 255)', - }, - }, - }, -}; From 5805cce9fdd0db0c82aa48b6d453edd1a249708a Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Tue, 25 Jun 2024 14:44:42 +0100 Subject: [PATCH 002/172] Adapt strings to React format --- bin/tidy-lang.cjs | 67 ++ lang/ui.en.json | 978 ++++++++++++++++++++ src/messages/ui.en.json | 1933 +++++++++++++++++++++++++++++++++------ 3 files changed, 2676 insertions(+), 302 deletions(-) create mode 100644 bin/tidy-lang.cjs create mode 100644 lang/ui.en.json diff --git a/bin/tidy-lang.cjs b/bin/tidy-lang.cjs new file mode 100644 index 000000000..37751f279 --- /dev/null +++ b/bin/tidy-lang.cjs @@ -0,0 +1,67 @@ +/** + * Sorts the strings in the lang/*.json source files so we + * don't have to care about where we add new strings. + */ +const fs = require("fs"); +const path = require("path"); + +// This is just a best effort check that variables haven't been changed. +const areTranslationsValid = (file, enJson, translatedJson) => { + let valid = true; + const keys = Object.keys(enJson); + for (const k of keys) { + const en = enJson[k].defaultMessage; + const translated = translatedJson[k].defaultMessage; + if (en.match(/, plural/)) { + // Skip ICU strings as we don't understand them. + continue; + } + const variablesEn = new Set(en.match(variableRegExp) ?? []); + const variablesTranslated = new Set(translated.match(variableRegExp) ?? []); + const areSetsEqual = (a, b) => + a.size === b.size && Array.from(a).every((value) => b.has(value)); + if (!areSetsEqual(variablesEn, variablesTranslated)) { + if (valid) { + console.error(file); + valid = false; + } + console.error(` ${en}`); + console.error(` ${translated}`); + console.error(` Differing variables!`); + console.error(); + } + } + return valid; +}; + +const variableRegExp = /({[a-zA-Z0-9]+})/g; + +const valid = fs + .readdirSync("lang") + .filter((f) => f.endsWith(".json")) + .map((messages) => { + const file = path.join("lang", messages); + const enFile = file.replace(/\.[a-z-]+\.json/, ".en.json"); + const en = JSON.parse(fs.readFileSync(enFile)); + const validKeys = new Set(Object.keys(en)); + const data = { + // Ensure we fallback to English even if we haven't roundtripped via Crowdin yet. + ...en, + ...JSON.parse(fs.readFileSync(file)), + }; + Object.keys(data).forEach((k) => { + if (!validKeys.has(k)) { + delete data[k]; + } + }); + const sortedKeys = Object.keys(data).sort(); + const result = Object.create(null); + sortedKeys.forEach((k) => (result[k] = data[k])); + fs.writeFileSync(file, JSON.stringify(result, null, 2)); + return areTranslationsValid(file, en, result); + }) + .reduce((prev, curr) => prev && curr, true); + +const okExitStatus = 0; +const seriousTroubleExistStatus = 2; +process.exit(valid ? okExitStatus : seriousTroubleExistStatus); diff --git a/lang/ui.en.json b/lang/ui.en.json new file mode 100644 index 000000000..1abd09004 --- /dev/null +++ b/lang/ui.en.json @@ -0,0 +1,978 @@ +{ + "about.developedInPartnership": { + "defaultMessage": "Developed in partnership with Center for Computational Thinking and Design, Aarhus University", + "description": "" + }, + "about.microbitHeartImageAlt": { + "defaultMessage": "micro:bit board with the 5 by 5 LED grid showing a heart", + "description": "" + }, + "about.softwareVersions": { + "defaultMessage": "Software versions", + "description": "" + }, + "actions.cancel": { + "defaultMessage": "Cancel", + "description": "" + }, + "actions.close": { + "defaultMessage": "Close", + "description": "" + }, + "actions.reconnect": { + "defaultMessage": "Reconnect", + "description": "" + }, + "alert.data.classNameLengthAlert": { + "defaultMessage": "Action names cannot be longer than {maxLen} characters.", + "description": "" + }, + "alert.deleteGestureConfirm": { + "defaultMessage": "Are you sure you want to delete the action \"{action}\"?", + "description": "" + }, + "alert.isNotConnected": { + "defaultMessage": "Your micro:bit should be connected!", + "description": "" + }, + "alert.isRecording": { + "defaultMessage": "You are currently recording!", + "description": "" + }, + "alert.isTesting": { + "defaultMessage": "You are currently recording!", + "description": "" + }, + "alert.isTraining": { + "defaultMessage": "You are currently training a model!", + "description": "" + }, + "alert.oneDataRepresentation": { + "defaultMessage": "You need at least one data representation", + "description": "" + }, + "alert.recording.disconnectedDuringRecording": { + "defaultMessage": "micro:bit disconnected during recording", + "description": "" + }, + "alert.recordingsPerGesture": { + "defaultMessage": "You need at least three examples per action", + "description": "" + }, + "alert.twoGestures": { + "defaultMessage": "You need at least two actions", + "description": "" + }, + "arrowIconDown.altText": { + "defaultMessage": "arrow pointing down", + "description": "" + }, + "arrowIconRight.altText": { + "defaultMessage": "arrow pointing right", + "description": "" + }, + "compatibility.platform.notSupported": { + "defaultMessage": "The tool is not supported on your current platform.", + "description": "" + }, + "compatibility.platform.notSupported.joinDesktop": { + "defaultMessage": "Please use Google Chrome or Microsoft Edge on a computer.", + "description": "" + }, + "compatibility.webgl.notSupported": { + "defaultMessage": "WebGL not available. Enable WebGL to see 3D data view.", + "description": "" + }, + "connectFailed.bluetooth1": { + "defaultMessage": "The connection to the micro:bit could not be established.", + "description": "" + }, + "connectFailed.bluetoothHeading": { + "defaultMessage": "Failed to connect to micro:bit", + "description": "" + }, + "connectFailed.bridge1": { + "defaultMessage": "The connection to the micro:bit connected to your computer could not be established.", + "description": "" + }, + "connectFailed.bridgeHeading": { + "defaultMessage": "Failed to connect to micro:bit 2", + "description": "" + }, + "connectFailed.remote1": { + "defaultMessage": "The connection to the wireless micro:bit could not be established.", + "description": "" + }, + "connectFailed.remoteHeading": { + "defaultMessage": "Failed to connect to micro:bit 1", + "description": "" + }, + "connectMB.backButton": { + "defaultMessage": "Back", + "description": "" + }, + "connectMB.bluetooth.cancelledConnection": { + "defaultMessage": "You didn't choose a micro:bit. Do you want to try again?", + "description": "" + }, + "connectMB.bluetooth.heading": { + "defaultMessage": "Connect using Web Bluetooth", + "description": "" + }, + "connectMB.bluetooth.invalidPattern": { + "defaultMessage": "The pattern you have drawn is invalid.", + "description": "" + }, + "connectMB.bluetoothStart.heading": { + "defaultMessage": "What you need to connect using Web Bluetooth", + "description": "" + }, + "connectMB.bluetoothStart.requirements1": { + "defaultMessage": "1 micro:bit", + "description": "" + }, + "connectMB.bluetoothStart.requirements2": { + "defaultMessage": "Computer", + "description": "" + }, + "connectMB.bluetoothStart.requirements2.subtitle": { + "defaultMessage": "with Internet, a USB port & Web Bluetooth", + "description": "" + }, + "connectMB.bluetoothStart.requirements3": { + "defaultMessage": "Micro USB cable", + "description": "" + }, + "connectMB.bluetoothStart.requirements4": { + "defaultMessage": "Battery holder", + "description": "" + }, + "connectMB.bluetoothStart.requirements4.subtitle": { + "defaultMessage": "with batteries", + "description": "" + }, + "connectMB.bluetoothStart.switchRadio": { + "defaultMessage": "Connect using micro:bit radio instead", + "description": "" + }, + "connectMB.connectBattery.heading": { + "defaultMessage": "Connect battery pack and disconnect USB", + "description": "" + }, + "connectMB.connectBattery.link": { + "defaultMessage": "You can attach the micro:bit to your wrist or an object", + "description": "" + }, + "connectMB.connectBattery.subtitle": { + "defaultMessage": "Disconnect the micro:bit from the computer and connect the battery pack.", + "description": "" + }, + "connectMB.connectCable.altText": { + "defaultMessage": "animation showing a USB cable being connected to the top of a micro:bit", + "description": "" + }, + "connectMB.connectCable.heading": { + "defaultMessage": "Connect USB cable to micro:bit", + "description": "" + }, + "connectMB.connectCable.skip": { + "defaultMessage": "Skip: program already downloaded?", + "description": "" + }, + "connectMB.connectCable.subtitle": { + "defaultMessage": "Connect the micro:bit to this computer with a USB cable so that the machine learning tool program can be downloaded to it.", + "description": "" + }, + "connectMB.connectCableMB1.heading": { + "defaultMessage": "Connect USB cable to micro:bit 1", + "description": "" + }, + "connectMB.connectCableMB1.subtitle": { + "defaultMessage": "Connect micro:bit 1 to this computer with a USB cable so that the machine learning tool program can be downloaded to it. This is the micro:bit you will use to collect data samples.", + "description": "" + }, + "connectMB.connectCableMB2.heading": { + "defaultMessage": "Connect USB cable to micro:bit 2", + "description": "" + }, + "connectMB.connectCableMB2.subtitle": { + "defaultMessage": "Connect a second micro:bit to this computer with a USB cable.", + "description": "" + }, + "connectMB.connecting": { + "defaultMessage": "Connecting…", + "description": "" + }, + "connectMB.nextButton": { + "defaultMessage": "Next", + "description": "" + }, + "connectMB.output.header": { + "defaultMessage": "A micro:bit is already connected", + "description": "" + }, + "connectMB.outputMB.different": { + "defaultMessage": "Connect another micro:bit", + "description": "" + }, + "connectMB.outputMB.otherButton": { + "defaultMessage": "Other", + "description": "" + }, + "connectMB.outputMB.same": { + "defaultMessage": "Use the same micro:bit", + "description": "" + }, + "connectMB.outputMB.sameButton": { + "defaultMessage": "Same", + "description": "" + }, + "connectMB.pattern.heading": { + "defaultMessage": "Copy pattern", + "description": "" + }, + "connectMB.pattern.inputLabel": { + "defaultMessage": "Number of LEDs lit in column {colNum} on the micro:bit display", + "description": "" + }, + "connectMB.pattern.subtitle": { + "defaultMessage": "Copy the pattern displayed on the micro:bit.", + "description": "" + }, + "connectMB.radio.heading": { + "defaultMessage": "Connecting micro:bits", + "description": "" + }, + "connectMB.radioStart.heading": { + "defaultMessage": "What you need to connect using micro:bit radio", + "description": "" + }, + "connectMB.radioStart.requirements1": { + "defaultMessage": "2 micro:bits", + "description": "" + }, + "connectMB.radioStart.requirements2": { + "defaultMessage": "Computer", + "description": "" + }, + "connectMB.radioStart.requirements2.subtitle": { + "defaultMessage": "with Internet & a USB port", + "description": "" + }, + "connectMB.radioStart.requirements3": { + "defaultMessage": "Micro USB cable", + "description": "" + }, + "connectMB.radioStart.requirements4": { + "defaultMessage": "Battery holder", + "description": "" + }, + "connectMB.radioStart.requirements4.subtitle": { + "defaultMessage": "with batteries", + "description": "" + }, + "connectMB.radioStart.switchBluetooth": { + "defaultMessage": "Connect using Web Bluetooth instead", + "description": "" + }, + "connectMB.reconnecting": { + "defaultMessage": "Reconnecting…", + "description": "" + }, + "connectMB.transferHex.altText": { + "defaultMessage": "animation of dragging a hex file from the downloads folder onto the micro:bit drive or device listed in the file explorer", + "description": "" + }, + "connectMB.transferHex.heading": { + "defaultMessage": "Transfer saved hex file to micro:bit", + "description": "" + }, + "connectMB.transferHex.manualDownload": { + "defaultMessage": "If the file did not automatically download then please download the file here.", + "description": "" + }, + "connectMB.transferHex.message": { + "defaultMessage": "Drag the hex file from your Downloads folder to the MICROBIT drive.", + "description": "" + }, + "connectMB.troubleshoot": { + "defaultMessage": "Troubleshoot problems with connecting to your micro:bit", + "description": "" + }, + "connectMB.troubleshooting": { + "defaultMessage": "Troubleshooting", + "description": "" + }, + "connectMB.tryAgain": { + "defaultMessage": "Try again", + "description": "" + }, + "connectMB.unsupportedMicrobit.ctaWithBluetooth": { + "defaultMessage": "Connect with Web Bluetooth", + "description": "" + }, + "connectMB.unsupportedMicrobit.explain": { + "defaultMessage": "Unfortunately, we only support using micro:bit V2 when connecting two micro:bits. You have connected a micro:bit V1.", + "description": "" + }, + "connectMB.unsupportedMicrobit.header": { + "defaultMessage": "micro:bit V1 is not supported for connecting two micro:bits", + "description": "" + }, + "connectMB.unsupportedMicrobit.withBluetooth": { + "defaultMessage": "Please use Web Bluetooth to connect to your micro:bit V1 instead.", + "description": "" + }, + "connectMB.unsupportedMicrobit.withoutBluetooth": { + "defaultMessage": "You can connect to micro:bit V1 using Web Bluetooth, however, this browser or device does not have Bluetooth enabled. How to enable Bluetooth.", + "description": "" + }, + "connectMB.usb.firmwareBroken.content1": { + "defaultMessage": "Connecting to the micro:bit failed because the firmware on your micro:bit is too old.", + "description": "" + }, + "connectMB.usb.firmwareBroken.content2": { + "defaultMessage": "You must update your firmware before you can connect to this micro:bit.", + "description": "" + }, + "connectMB.usb.firmwareBroken.content3": { + "defaultMessage": "Troubleshoot problems with connecting to your micro:bit", + "description": "" + }, + "connectMB.usb.firmwareBroken.heading": { + "defaultMessage": "Firmware update required", + "description": "" + }, + "connectMB.usb.firmwareBroken.skip": { + "defaultMessage": "Skip and transfer manually", + "description": "" + }, + "connectMB.usbDownloading.header": { + "defaultMessage": "Downloading program to micro:bit", + "description": "" + }, + "connectMB.usbDownloading.subtitle": { + "defaultMessage": "Please wait. Downloading program to micro:bit.", + "description": "" + }, + "connectMB.usbDownloadingMB1.header": { + "defaultMessage": "Downloading program to micro:bit 1", + "description": "" + }, + "connectMB.usbDownloadingMB2.header": { + "defaultMessage": "Downloading program to micro:bit 2", + "description": "" + }, + "connectMB.usbTryAgain.closeTabs1": { + "defaultMessage": "Another process is connected to this device.", + "description": "" + }, + "connectMB.usbTryAgain.closeTabs2": { + "defaultMessage": "Close any other tabs that may be using WebUSB (e.g. MakeCode, Python Editor, machine learning tool), or unplug and replug the micro:bit before trying again.", + "description": "" + }, + "connectMB.usbTryAgain.heading": { + "defaultMessage": "Connect using WebUSB", + "description": "" + }, + "connectMB.usbTryAgain.replugMicrobit1": { + "defaultMessage": "A WebUSB error occurred.", + "description": "" + }, + "connectMB.usbTryAgain.replugMicrobit2": { + "defaultMessage": "Please:", + "description": "" + }, + "connectMB.usbTryAgain.replugMicrobit3": { + "defaultMessage": "check that this micro:bit does not have a battery pack connected", + "description": "" + }, + "connectMB.usbTryAgain.replugMicrobit4": { + "defaultMessage": "unplug and replug the USB cable", + "description": "" + }, + "connectMB.usbTryAgain.selectMicrobit": { + "defaultMessage": "You didn't select a micro:bit. Do you want to try again?", + "description": "" + }, + "connectMB.webPopup": { + "defaultMessage": "Select micro:bit", + "description": "" + }, + "connectMB.webPopup.instruction.heading": { + "defaultMessage": "In the next popup", + "description": "" + }, + "connectMB.webPopup.instruction1": { + "defaultMessage": "Choose your micro:bit", + "description": "" + }, + "connectMB.webPopup.webBluetooth.altText": { + "defaultMessage": "Web Bluetooth connection dialog with BBC micro:bit entry labelled 1 and Pair button labelled 2", + "description": "" + }, + "connectMB.webPopup.webBluetooth.instruction2": { + "defaultMessage": "Select 'Pair'", + "description": "" + }, + "connectMB.webPopup.webUsb.altText": { + "defaultMessage": "WebUSB connection dialog with BBC micro:bit entry labelled 1 and Connect button labelled 2", + "description": "" + }, + "connectMB.webPopup.webUsb.instruction2": { + "defaultMessage": "Select 'Connect'", + "description": "" + }, + "content.data.addAction": { + "defaultMessage": "Add action", + "description": "" + }, + "content.data.addAction.inputLabel": { + "defaultMessage": "Name of action field", + "description": "" + }, + "content.data.addActionWalkThrough": { + "defaultMessage": "Name an action you want the micro:bit to recognise", + "description": "" + }, + "content.data.addRecordingWalkThrough": { + "defaultMessage": "Press to record a data sample or press button B on your micro:bit.", + "description": "" + }, + "content.data.classHelpBody": { + "defaultMessage": "The type of movement you want the machine learning tool to recognise e.g. 'wave' or 'clap'. ", + "description": "" + }, + "content.data.classHelpHeader": { + "defaultMessage": "Action", + "description": "" + }, + "content.data.classPlaceholderNewClass": { + "defaultMessage": "Name of action", + "description": "" + }, + "content.data.classification": { + "defaultMessage": "Action", + "description": "" + }, + "content.data.controlbar.button.clearData": { + "defaultMessage": "Delete all data samples", + "description": "" + }, + "content.data.controlbar.button.clearData.confirm": { + "defaultMessage": "Are you sure you wish to delete all data samples?\nThis cannot be undone.", + "description": "" + }, + "content.data.controlbar.button.downloadData": { + "defaultMessage": "Download all data samples", + "description": "" + }, + "content.data.controlbar.button.menu": { + "defaultMessage": "Data actions", + "description": "" + }, + "content.data.controlbar.button.uploadData": { + "defaultMessage": "Import data samples", + "description": "" + }, + "content.data.data": { + "defaultMessage": "Data samples", + "description": "" + }, + "content.data.dataDescription": { + "defaultMessage": "Multiple samples of movement data collected in approximately 2 second bursts.", + "description": "" + }, + "content.data.deleteAction": { + "defaultMessage": "Delete action \"{action}\"", + "description": "" + }, + "content.data.deleteRecording": { + "defaultMessage": "Delete recording", + "description": "" + }, + "content.data.recordAction": { + "defaultMessage": "Record data for action \"{action}\"", + "description": "" + }, + "content.data.recording.button.cancel": { + "defaultMessage": "Cancel recording", + "description": "" + }, + "content.data.recordingDialog.go": { + "defaultMessage": "Go", + "description": "" + }, + "content.data.recordingDialog.recording": { + "defaultMessage": "Recording", + "description": "" + }, + "content.data.recordingDialog.title": { + "defaultMessage": "Record data for action \"{action}\"", + "description": "" + }, + "content.data.selectAndRecordAction": { + "defaultMessage": "Select action \"{action}\" and record data", + "description": "" + }, + "content.index.dataWarning.subtitleOne": { + "defaultMessage": "You have existing data that will be lost when you start a new session.", + "description": "" + }, + "content.index.dataWarning.subtitleTwo": { + "defaultMessage": "To save your data, download all data samples before continuing.", + "description": "" + }, + "content.index.dataWarning.title": { + "defaultMessage": "Existing data samples will be lost", + "description": "" + }, + "content.index.title": { + "defaultMessage": "machine learning tool", + "description": "" + }, + "content.index.toolProcessCards.data.description": { + "defaultMessage": "Add samples of the actions you would like your model to recognise (e.g. waving and clapping).", + "description": "" + }, + "content.index.toolProcessCards.data.title": { + "defaultMessage": "Add data", + "description": "" + }, + "content.index.toolProcessCards.model.description": { + "defaultMessage": "Find out if it correctly recognises each action. Add more data to improve the model.", + "description": "" + }, + "content.index.toolProcessCards.model.title": { + "defaultMessage": "Test model", + "description": "" + }, + "content.index.toolProcessCards.train.description": { + "defaultMessage": "Ask the computer to use your training samples to train the machine learning model to recognise different actions.", + "description": "" + }, + "content.index.toolProcessCards.train.title": { + "defaultMessage": "Train model", + "description": "" + }, + "content.model.addData": { + "defaultMessage": "Add data", + "description": "" + }, + "content.model.notEnoughDataInfoBody1": { + "defaultMessage": "You cannot test the model until it has been trained.", + "description": "" + }, + "content.model.notEnoughDataInfoBody2": { + "defaultMessage": "You need at least 3 data samples for 2 actions to train a model.", + "description": "" + }, + "content.model.output.action.descriptionBody": { + "defaultMessage": "The type of movement you want the machine learning tool to recognise e.g. ‘wave’ or ‘clap’.", + "description": "" + }, + "content.model.output.action.descriptionTitle": { + "defaultMessage": "Action", + "description": "" + }, + "content.model.output.action.iconTitle": { + "defaultMessage": "Action", + "description": "" + }, + "content.model.output.certainty.descriptionBody": { + "defaultMessage": "How confident the model is that you are currently doing each action.", + "description": "" + }, + "content.model.output.certainty.descriptionTitle": { + "defaultMessage": "Certainty", + "description": "" + }, + "content.model.output.certainty.iconTitle": { + "defaultMessage": "Certainty", + "description": "" + }, + "content.model.output.estimatedGesture.descriptionBody": { + "defaultMessage": "This is the action the model thinks you are currently doing.", + "description": "" + }, + "content.model.output.estimatedGesture.descriptionTitle": { + "defaultMessage": "Estimated action", + "description": "" + }, + "content.model.output.estimatedGesture.iconTitle": { + "defaultMessage": "Estimated action:", + "description": "" + }, + "content.model.output.estimatedGesture.label": { + "defaultMessage": "Estimated action: \"{action}\"", + "description": "" + }, + "content.model.output.estimatedGesture.none": { + "defaultMessage": "None", + "description": "" + }, + "content.model.output.recognitionPoint": { + "defaultMessage": "Recognition Point:", + "description": "" + }, + "content.model.retrainModelBody": { + "defaultMessage": "You need to train your model with the current set of data samples before you can test it.", + "description": "" + }, + "content.model.trainModelBody": { + "defaultMessage": "You need to train your model before you can test it.", + "description": "" + }, + "content.model.trainModelFirstHeading": { + "defaultMessage": "Test model", + "description": "" + }, + "content.trainer.description": { + "defaultMessage": "The computer program spots patterns or differences in your data samples, and uses these to build a mathematical model that allows the micro:bit machine learning tool to recognise different actions when you move your micro:bit.", + "description": "" + }, + "content.trainer.enoughdata.title": { + "defaultMessage": "Status: You've collected enough data to train the model.", + "description": "" + }, + "content.trainer.failure.body": { + "defaultMessage": "The training did not result in a usable model. The reason for this is most likely the data used for training. If the data samples for different actions are too similar, this can result in issues in the training process.", + "description": "" + }, + "content.trainer.failure.header": { + "defaultMessage": "Training failed", + "description": "" + }, + "content.trainer.failure.todo": { + "defaultMessage": "Return to the Add data page and change your data.", + "description": "" + }, + "content.trainer.header": { + "defaultMessage": "Training a model", + "description": "" + }, + "content.trainer.retrain.title": { + "defaultMessage": "Status: Your data samples have changed. You can retrain the model.", + "description": "" + }, + "content.trainer.training.title": { + "defaultMessage": "Status: Training the model in progress.", + "description": "" + }, + "disconnectedWarning.bluetooth1": { + "defaultMessage": "The connection to the micro:bit has been lost.", + "description": "" + }, + "disconnectedWarning.bluetooth2": { + "defaultMessage": "Please check that the micro:bit:", + "description": "" + }, + "disconnectedWarning.bluetooth3": { + "defaultMessage": "is powered on (the red light on the back of the micro:bit should be on)", + "description": "" + }, + "disconnectedWarning.bluetooth4": { + "defaultMessage": "is close to the computer", + "description": "" + }, + "disconnectedWarning.bluetoothHeading": { + "defaultMessage": "micro:bit connection lost", + "description": "" + }, + "disconnectedWarning.bridge1": { + "defaultMessage": "The connection to the micro:bit connected to your computer has been lost.", + "description": "" + }, + "disconnectedWarning.bridgeHeading": { + "defaultMessage": "micro:bit 2 connection lost", + "description": "" + }, + "disconnectedWarning.remote1": { + "defaultMessage": "The connection to the wireless micro:bit has been lost.", + "description": "" + }, + "disconnectedWarning.remoteHeading": { + "defaultMessage": "micro:bit 1 connection lost", + "description": "" + }, + "footer.connectButton": { + "defaultMessage": "Connect", + "description": "" + }, + "footer.disconnectButton": { + "defaultMessage": "Disconnect", + "description": "" + }, + "footer.helpContent": { + "defaultMessage": "This graph shows movement data from the microbit’s accelerometer in real time. Try moving your connected micro:bit to see the X, Y and Z axes change.", + "description": "" + }, + "footer.helpHeader": { + "defaultMessage": "Live graph", + "description": "" + }, + "footer.resume": { + "defaultMessage": "Resume session", + "description": "" + }, + "footer.start": { + "defaultMessage": "Start new session", + "description": "" + }, + "helpMenu.about": { + "defaultMessage": "About", + "description": "" + }, + "helpMenu.cookies": { + "defaultMessage": "Cookies", + "description": "" + }, + "helpMenu.helpAndSupport": { + "defaultMessage": "Help & support", + "description": "" + }, + "helpMenu.label": { + "defaultMessage": "Help", + "description": "" + }, + "helpMenu.privacyPolicy": { + "defaultMessage": "Privacy policy", + "description": "" + }, + "helpMenu.termsOfUse": { + "defaultMessage": "Terms of use", + "description": "" + }, + "homepage.Link": { + "defaultMessage": "Home page", + "description": "" + }, + "info.label": { + "defaultMessage": "More information about \"{item}\"", + "description": "" + }, + "languageDialog.title": { + "defaultMessage": "Language", + "description": "" + }, + "loading": { + "defaultMessage": "loading", + "description": "" + }, + "menu.data.helpHeading": { + "defaultMessage": "1. Add data", + "description": "" + }, + "menu.model.connectInputMicrobit": { + "defaultMessage": "Connect micro:bit", + "description": "" + }, + "menu.model.disconnect": { + "defaultMessage": "Disconnect output micro:bit", + "description": "" + }, + "menu.model.helpHeading": { + "defaultMessage": "3. Test model", + "description": "" + }, + "menu.model.noModel": { + "defaultMessage": "No model", + "description": "" + }, + "menu.trainer.TrainingFinished": { + "defaultMessage": "Status: Your model is trained.", + "description": "" + }, + "menu.trainer.addDataButton": { + "defaultMessage": "Add data", + "description": "" + }, + "menu.trainer.addMoreDataButton": { + "defaultMessage": "Add more data", + "description": "" + }, + "menu.trainer.helpHeading": { + "defaultMessage": "2. Train model", + "description": "" + }, + "menu.trainer.notConnected1": { + "defaultMessage": "You do not have a micro:bit connected", + "description": "" + }, + "menu.trainer.notConnected2": { + "defaultMessage": " Select the ‘Connect’ button to connect a micro:bit.", + "description": "" + }, + "menu.trainer.notEnoughDataHeader1": { + "defaultMessage": "Status: You don't have enough data.", + "description": "" + }, + "menu.trainer.notEnoughDataInfoBody": { + "defaultMessage": "You need at least 3 data samples for 2 actions to train the model.", + "description": "" + }, + "menu.trainer.testModelButton": { + "defaultMessage": "Test model", + "description": "" + }, + "menu.trainer.trainModelButton": { + "defaultMessage": "Train model", + "description": "" + }, + "performanceWarning.content1": { + "defaultMessage": "The quality of data being received from your micro:bit is low. This could be due to a connection issue.", + "description": "" + }, + "performanceWarning.content2": { + "defaultMessage": "Do you want to restart the connection process to try to improve performance?", + "description": "" + }, + "performanceWarning.heading": { + "defaultMessage": "Low quality connection", + "description": "" + }, + "performanceWarning.ignore": { + "defaultMessage": "Don't show this again", + "description": "" + }, + "performanceWarning.nextButton": { + "defaultMessage": "Next", + "description": "" + }, + "performanceWarning.troubleshoot": { + "defaultMessage": "Troubleshoot problems with connection quality", + "description": "" + }, + "popup.appVersionRedirect.button.redirect": { + "defaultMessage": "I'm a UK primary school teacher", + "description": "" + }, + "popup.appVersionRedirect.button.stay": { + "defaultMessage": "I'm not a UK primary school teacher", + "description": "" + }, + "popup.appVersionRedirect.explain": { + "defaultMessage": "If you are a UK* primary school teacher, you will be redirected to the version of this tool designed for the UK's BBC micro:bit playground survey.", + "description": "" + }, + "popup.appVersionRedirect.header": { + "defaultMessage": "Are you a UK* primary school teacher?", + "description": "" + }, + "popup.appVersionRedirect.uk": { + "defaultMessage": "*includes crown dependencies Jersey, Guernsey and the Isle of Man", + "description": "" + }, + "popup.compatibility.advice": { + "defaultMessage": "We recommend Google Chrome or Microsoft Edge so you can connect directly to your micro:bit.", + "description": "" + }, + "popup.compatibility.explain": { + "defaultMessage": "Unfortunately, WebUSB and Web Bluetooth are not supported by this browser. This means you cannot connect to a micro:bit to record the data samples needed to train a machine learning model.", + "description": "" + }, + "popup.compatibility.header": { + "defaultMessage": "This browser is not supported", + "description": "" + }, + "popup.outdatedmicrobit.button.later": { + "defaultMessage": "Later", + "description": "" + }, + "popup.outdatedmicrobit.button.update": { + "defaultMessage": "Update now", + "description": "" + }, + "popup.outdatedmicrobit.button.update.mkcd": { + "defaultMessage": "Open MakeCode", + "description": "" + }, + "popup.outdatedmicrobit.header": { + "defaultMessage": "Your micro:bit has an outdated machine learning tool program", + "description": "" + }, + "popup.outdatedmicrobit.text": { + "defaultMessage": "We strongly recommend that you update it now, as some features may not work as expected.", + "description": "" + }, + "popup.outdatedmicrobit.text.mkcd": { + "defaultMessage": "Open the newest MakeCode template to use the updated extension.", + "description": "" + }, + "prototype.warning": { + "defaultMessage": "This is a prototype version and is subject to change without notice", + "description": "" + }, + "reconnectFailed.bluetooth1": { + "defaultMessage": "The connection to the micro:bit could not be re-established.", + "description": "" + }, + "reconnectFailed.bluetoothHeading": { + "defaultMessage": "Failed to reconnect to micro:bit", + "description": "" + }, + "reconnectFailed.bridge1": { + "defaultMessage": "The connection to the micro:bit connected to your computer could not be re-established.", + "description": "" + }, + "reconnectFailed.bridgeHeading": { + "defaultMessage": "Failed to reconnect to micro:bit 2", + "description": "" + }, + "reconnectFailed.radioHeading": { + "defaultMessage": "Failed to reconnect to micro:bits", + "description": "" + }, + "reconnectFailed.remote1": { + "defaultMessage": "The connection to the wireless micro:bit could not be re-established.", + "description": "" + }, + "reconnectFailed.remoteHeading": { + "defaultMessage": "Failed to reconnect to micro:bit 1", + "description": "" + }, + "reconnectFailed.subtitle": { + "defaultMessage": "Follow these instructions to restart the connection process.", + "description": "" + }, + "resources.getStarted.video": { + "defaultMessage": "get started video", + "description": "" + }, + "resources.introduction.video": { + "defaultMessage": "introduction video", + "description": "" + }, + "settings.label": { + "defaultMessage": "Settings", + "description": "" + }, + "sign-up.content": { + "defaultMessage": "Welcome to our prototype of the micro:bit machine learning tool. We’d love you to try it out and give us your feedback. Please enter your email so we can keep you updated as it evolves.", + "description": "" + }, + "sign-up.email-label": { + "defaultMessage": "Email", + "description": "" + }, + "sign-up.error": { + "defaultMessage": "Something went wrong. Please try again or contact us.", + "description": "" + }, + "sign-up.invalid": { + "defaultMessage": "Please enter a valid email address", + "description": "" + }, + "sign-up.sign-up-action": { + "defaultMessage": "Sign up", + "description": "" + }, + "sign-up.skip-action": { + "defaultMessage": "Skip", + "description": "" + }, + "sign-up.title": { + "defaultMessage": "Join our testing community", + "description": "" + } +} \ No newline at end of file diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 7f83d0656..77b87c84d 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -1,303 +1,1632 @@ { - "about.developedInPartnership": "Developed in partnership with Center for Computational Thinking and Design, Aarhus University", - "about.softwareVersions": "Software versions", - "about.microbitHeartImageAlt": "micro:bit board with the 5 by 5 LED grid showing a heart", - - "actions.close": "Close", - "actions.cancel": "Cancel", - "actions.reconnect": "Reconnect", - - "alert.data.classNameLengthAlert": "Action names cannot be longer than {maxLen} characters.", - "alert.recording.disconnectedDuringRecording": "micro:bit disconnected during recording", - - "alert.isRecording": "You are currently recording!", - "alert.isTesting": "You are currently recording!", - "alert.isTraining": "You are currently training a model!", - "alert.isNotConnected": "Your micro:bit should be connected!", - "alert.deleteGestureConfirm": "Are you sure you want to delete the action \"{action}\"?", - - "alert.twoGestures": "You need at least two actions", - "alert.oneDataRepresentation": "You need at least one data representation", - "alert.recordingsPerGesture": "You need at least three examples per action", - - "content.index.title": "machine learning tool", - "content.index.toolProcessCards.data.title": "Add data", - "content.index.toolProcessCards.data.description": "Add samples of the actions you would like your model to recognise (e.g. waving and clapping).", - "content.index.toolProcessCards.train.title": "Train model", - "content.index.toolProcessCards.train.description": "Ask the computer to use your training samples to train the machine learning model to recognise different actions.", - "content.index.toolProcessCards.model.title": "Test model", - "content.index.toolProcessCards.model.description": "Find out if it correctly recognises each action. Add more data to improve the model.", - "content.index.dataWarning.title": "Existing data samples will be lost", - "content.index.dataWarning.subtitleOne": "You have existing data that will be lost when you start a new session.", - "content.index.dataWarning.subtitleTwo": "To save your data, download all data samples before continuing.", - - "content.data.classPlaceholderNewClass": "Name of action", - "content.data.classHelpHeader": "Action", - "content.data.classHelpBody": "The type of movement you want the machine learning tool to recognise e.g. 'wave' or 'clap'. ", - "content.data.classification": "Action", - "content.data.addAction.inputLabel": "Name of action field", - "content.data.data": "Data samples", - "content.data.addAction": "Add action", - "content.data.addActionWalkThrough": "Name an action you want the micro:bit to recognise", - "content.data.addRecordingWalkThrough": "Press to record a data sample or press button B on your micro:bit.", - "content.data.dataDescription": "Multiple samples of movement data collected in approximately 2 second bursts.", - "content.data.selectAndRecordAction": "Select action \"{action}\" and record data", - "content.data.recordAction": "Record data for action \"{action}\"", - "content.data.deleteAction": "Delete action \"{action}\"", - "content.data.deleteRecording": "Delete recording", - "content.data.recordingDialog.title": "Record data for action \"{action}\"", - "content.data.recordingDialog.go": "Go", - "content.data.recordingDialog.recording": "Recording", - - "content.data.controlbar.button.menu": "Data actions", - "content.data.controlbar.button.clearData": "Delete all data samples", - "content.data.controlbar.button.clearData.confirm": "Are you sure you wish to delete all data samples?\nThis cannot be undone.", - "content.data.controlbar.button.downloadData": "Download all data samples", - "content.data.controlbar.button.uploadData": "Import data samples", - - "content.data.recording.button.cancel": "Cancel recording", - - "content.trainer.header": "Training a model", - "content.trainer.description": "The computer program spots patterns or differences in your data samples, and uses these to build a mathematical model that allows the micro:bit machine learning tool to recognise different actions when you move your micro:bit.", - "content.trainer.enoughdata.title": "Status: You've collected enough data to train the model.", - "content.trainer.training.title": "Status: Training the model in progress.", - "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 samples for different actions are too similar, this can result in issues in the training process.", - "content.trainer.failure.todo": "Return to the Add data page and change your data.", - "content.trainer.retrain.title": "Status: Your data samples have changed. You can retrain the model.", - - "content.model.trainModelFirstHeading": "Test model", - "content.model.notEnoughDataInfoBody1": "You cannot test the model until it has been trained.", - "content.model.notEnoughDataInfoBody2": "You need at least 3 data samples for 2 actions to train a model.", - "content.model.trainModelBody": "You need to train your model before you can test it.", - "content.model.retrainModelBody": "You need to train your model with the current set of data samples before you can test it.", - "content.model.addData": "Add data", - - "content.model.output.estimatedGesture.iconTitle": "Estimated action:", - "content.model.output.estimatedGesture.label": "Estimated action: \"{action}\"", - "content.model.output.estimatedGesture.descriptionTitle": "Estimated action", - "content.model.output.estimatedGesture.descriptionBody": "This is the action the model thinks you are currently doing.", - "content.model.output.estimatedGesture.none": "None", - - "content.model.output.action.iconTitle": "Action", - "content.model.output.action.descriptionTitle": "Action", - "content.model.output.action.descriptionBody": "The type of movement you want the machine learning tool to recognise e.g. ‘wave’ or ‘clap’.", - - "content.model.output.certainty.iconTitle": "Certainty", - "content.model.output.certainty.descriptionTitle": "Certainty", - "content.model.output.certainty.descriptionBody": "How confident the model is that you are currently doing each action.", - - "content.model.output.recognitionPoint": "Recognition Point:", - - "footer.start": "Start new session", - "footer.resume": "Resume session", - "footer.disconnectButton": "Disconnect", - "footer.connectButton": "Connect", - "footer.helpHeader": "Live graph", - "footer.helpContent": "This graph shows movement data from the microbit’s accelerometer in real time. Try moving your connected micro:bit to see the X, Y and Z axes change.", - - "helpMenu.label": "Help", - "helpMenu.helpAndSupport": "Help & support", - "helpMenu.privacyPolicy": "Privacy policy", - "helpMenu.termsOfUse": "Terms of use", - "helpMenu.cookies": "Cookies", - "helpMenu.about": "About", - - "homepage.Link": "Home page", - - "resources.introduction.video": "introduction video", - "resources.getStarted.video": "get started video", - - "menu.data.helpHeading": "1. Add data", - - "menu.trainer.helpHeading": "2. Train model", - "menu.trainer.notConnected1": "You do not have a micro:bit connected", - "menu.trainer.notConnected2": " Select the ‘Connect’ button to connect a micro:bit.", - "menu.trainer.notEnoughDataHeader1": "Status: You don't have enough data.", - "menu.trainer.notEnoughDataInfoBody": "You need at least 3 data samples for 2 actions to train the model.", - "menu.trainer.TrainingFinished": "Status: Your model is trained.", - - "menu.trainer.trainModelButton": "Train model", - "menu.trainer.addDataButton": "Add data", - "menu.trainer.addMoreDataButton": "Add more data", - "menu.trainer.testModelButton": "Test model", - - "menu.model.helpHeading": "3. Test model", - "menu.model.noModel": "No model", - "menu.model.disconnect": "Disconnect output micro:bit", - "menu.model.connectInputMicrobit": "Connect micro:bit", - - "connectMB.nextButton": "Next", - "connectMB.backButton": "Back", - "connectMB.tryAgain": "Try again", - - "connectMB.usbTryAgain.heading": "Connect using WebUSB", - "connectMB.usbTryAgain.selectMicrobit": "You didn't select a micro:bit. Do you want to try again?", - "connectMB.usbTryAgain.closeTabs1": "Another process is connected to this device.", - "connectMB.usbTryAgain.closeTabs2": "Close any other tabs that may be using WebUSB (e.g. MakeCode, Python Editor, machine learning tool), or unplug and replug the micro:bit before trying again.", - "connectMB.usbTryAgain.replugMicrobit1": "A WebUSB error occurred.", - "connectMB.usbTryAgain.replugMicrobit2": "Please:", - "connectMB.usbTryAgain.replugMicrobit3": "check that this micro:bit does not have a battery pack connected", - "connectMB.usbTryAgain.replugMicrobit4": "unplug and replug the USB cable", - - "connectMB.unsupportedMicrobit.header": "micro:bit V1 is not supported for connecting two micro:bits", - "connectMB.unsupportedMicrobit.explain": "Unfortunately, we only support using micro:bit V2 when connecting two micro:bits. You have connected a micro:bit V1.", - "connectMB.unsupportedMicrobit.withBluetooth": "Please use Web Bluetooth to connect to your micro:bit V1 instead.", - "connectMB.unsupportedMicrobit.ctaWithBluetooth": "Connect with Web Bluetooth", - "connectMB.unsupportedMicrobit.withoutBluetooth": "You can connect to micro:bit V1 using Web Bluetooth, however, this browser or device does not have Bluetooth enabled. How to enable Bluetooth.", - - "connectMB.radioStart.heading": "What you need to connect using micro:bit radio", - "connectMB.radioStart.requirements1": "2 micro:bits", - "connectMB.radioStart.requirements2": "Computer", - "connectMB.radioStart.requirements2.subtitle": "with Internet & a USB port", - "connectMB.radioStart.requirements3": "Micro USB cable", - "connectMB.radioStart.requirements4": "Battery holder", - "connectMB.radioStart.requirements4.subtitle": "with batteries", - "connectMB.radioStart.switchBluetooth": "Connect using Web Bluetooth instead", - - "connectMB.bluetoothStart.heading": "What you need to connect using Web Bluetooth", - "connectMB.bluetoothStart.requirements1": "1 micro:bit", - "connectMB.bluetoothStart.requirements2": "Computer", - "connectMB.bluetoothStart.requirements2.subtitle": "with Internet, a USB port & Web Bluetooth", - "connectMB.bluetoothStart.requirements3": "Micro USB cable", - "connectMB.bluetoothStart.requirements4": "Battery holder", - "connectMB.bluetoothStart.requirements4.subtitle": "with batteries", - "connectMB.bluetoothStart.switchRadio": "Connect using micro:bit radio instead", - - "connectMB.connectCableMB1.heading": "Connect USB cable to micro:bit 1", - "connectMB.connectCableMB1.subtitle": "Connect micro:bit 1 to this computer with a USB cable so that the machine learning tool program can be downloaded to it. This is the micro:bit you will use to collect data samples.", - - "connectMB.connectCableMB2.heading": "Connect USB cable to micro:bit 2", - "connectMB.connectCableMB2.subtitle": "Connect a second micro:bit to this computer with a USB cable.", - - "connectMB.connectCable.heading": "Connect USB cable to micro:bit", - "connectMB.connectCable.subtitle": "Connect the micro:bit to this computer with a USB cable so that the machine learning tool program can be downloaded to it.", - - "connectMB.connectCable.altText": "animation showing a USB cable being connected to the top of a micro:bit", - "connectMB.connectCable.skip": "Skip: program already downloaded?", - - "connectMB.webPopup": "Select micro:bit", - "connectMB.webPopup.instruction.heading": "In the next popup", - "connectMB.webPopup.instruction1": "Choose your micro:bit", - "connectMB.webPopup.webUsb.instruction2": "Select 'Connect'", - "connectMB.webPopup.webBluetooth.instruction2": "Select 'Pair'", - "connectMB.webPopup.webUsb.altText": "WebUSB connection dialog with BBC micro:bit entry labelled 1 and Connect button labelled 2", - "connectMB.webPopup.webBluetooth.altText": "Web Bluetooth connection dialog with BBC micro:bit entry labelled 1 and Pair button labelled 2", - - "connectMB.usbDownloadingMB1.header": "Downloading program to micro:bit 1", - "connectMB.usbDownloadingMB2.header": "Downloading program to micro:bit 2", - "connectMB.usbDownloading.header": "Downloading program to micro:bit", - "connectMB.usbDownloading.subtitle": "Please wait. Downloading program to micro:bit.", - - "connectMB.connectBattery.heading": "Connect battery pack and disconnect USB", - "connectMB.connectBattery.subtitle": "Disconnect the micro:bit from the computer and connect the battery pack.", - "connectMB.connectBattery.link": "You can attach the micro:bit to your wrist or an object", - - "connectMB.pattern.heading": "Copy pattern", - "connectMB.pattern.subtitle": "Copy the pattern displayed on the micro:bit.", - "connectMB.pattern.inputLabel": "Number of LEDs lit in column {colNum} on the micro:bit display", - - "connectMB.bluetooth.heading": "Connect using Web Bluetooth", - "connectMB.bluetooth.cancelledConnection": "You didn't choose a micro:bit. Do you want to try again?", - - "connectMB.radio.heading": "Connecting micro:bits", - "connectMB.connecting": "Connecting…", - "connectMB.reconnecting": "Reconnecting…", - "connectMB.bluetooth.invalidPattern": "The pattern you have drawn is invalid.", - - "connectFailed.bluetoothHeading": "Failed to connect to micro:bit", - "connectFailed.bluetooth1": "The connection to the micro:bit could not be established.", - "reconnectFailed.radioHeading": "Failed to reconnect to micro:bits", - "reconnectFailed.bluetoothHeading": "Failed to reconnect to micro:bit", - "reconnectFailed.bluetooth1": "The connection to the micro:bit could not be re-established.", - "connectFailed.remoteHeading": "Failed to connect to micro:bit 1", - "connectFailed.remote1": "The connection to the wireless micro:bit could not be established.", - "reconnectFailed.remoteHeading": "Failed to reconnect to micro:bit 1", - "reconnectFailed.remote1": "The connection to the wireless micro:bit could not be re-established.", - "connectFailed.bridgeHeading": "Failed to connect to micro:bit 2", - "connectFailed.bridge1": "The connection to the micro:bit connected to your computer could not be established.", - "reconnectFailed.bridgeHeading": "Failed to reconnect to micro:bit 2", - "reconnectFailed.bridge1": "The connection to the micro:bit connected to your computer could not be re-established.", - "reconnectFailed.subtitle": "Follow these instructions to restart the connection process.", - - "disconnectedWarning.bluetoothHeading": "micro:bit connection lost", - "disconnectedWarning.bluetooth1": "The connection to the micro:bit has been lost.", - "disconnectedWarning.bluetooth2": "Please check that the micro:bit:", - "disconnectedWarning.bluetooth3": "is powered on (the red light on the back of the micro:bit should be on)", - "disconnectedWarning.bluetooth4": "is close to the computer", - - "disconnectedWarning.remoteHeading": "micro:bit 1 connection lost", - "disconnectedWarning.remote1": "The connection to the wireless micro:bit has been lost.", - - "disconnectedWarning.bridgeHeading": "micro:bit 2 connection lost", - "disconnectedWarning.bridge1": "The connection to the micro:bit connected to your computer has been lost.", - - "connectMB.output.header": "A micro:bit is already connected", - - "performanceWarning.heading": "Low quality connection", - "performanceWarning.content1": "The quality of data being received from your micro:bit is low. This could be due to a connection issue.", - "performanceWarning.content2": "Do you want to restart the connection process to try to improve performance?", - "performanceWarning.troubleshoot": "Troubleshoot problems with connection quality", - "performanceWarning.ignore": "Don't show this again", - "performanceWarning.nextButton": "Next", - - "connectMB.troubleshoot": "Troubleshoot problems with connecting to your micro:bit", - "connectMB.troubleshooting": "Troubleshooting", - - "connectMB.usb.firmwareBroken.heading": "Firmware update required", - "connectMB.usb.firmwareBroken.content1": "Connecting to the micro:bit failed because the firmware on your micro:bit is too old.", - "connectMB.usb.firmwareBroken.content2": "You must update your firmware before you can connect to this micro:bit.", - "connectMB.usb.firmwareBroken.content3": "Troubleshoot problems with connecting to your micro:bit", - "connectMB.usb.firmwareBroken.skip": "Skip and transfer manually", - - "connectMB.outputMB.same": "Use the same micro:bit", - "connectMB.outputMB.different": "Connect another micro:bit", - "connectMB.outputMB.sameButton": "Same", - "connectMB.outputMB.otherButton": "Other", - - "info.label": "More information about \"{item}\"", - - "languageDialog.title": "Language", - - "popup.compatibility.header": "This browser is not supported", - "popup.compatibility.explain": "Unfortunately, WebUSB and Web Bluetooth are not supported by this browser. This means you cannot connect to a micro:bit to record the data samples needed to train a machine learning model.", - "popup.compatibility.advice": "We recommend Google Chrome or Microsoft Edge so you can connect directly to your micro:bit.", - - "connectMB.transferHex.heading": "Transfer saved hex file to micro:bit", - "connectMB.transferHex.manualDownload": "If the file did not automatically download then please download the file here.", - "connectMB.transferHex.message": "Drag the hex file from your Downloads folder to the MICROBIT drive.", - "connectMB.transferHex.altText": "animation of dragging a hex file from the downloads folder onto the micro:bit drive or device listed in the file explorer", - - "compatibility.platform.notSupported": "The tool is not supported on your current platform.", - "compatibility.platform.notSupported.joinDesktop": "Please use Google Chrome or Microsoft Edge on a computer.", - "compatibility.webgl.notSupported": "WebGL not available. Enable WebGL to see 3D data view.", - - "loading": "loading", - - "popup.outdatedmicrobit.header": "Your micro:bit has an outdated machine learning tool program", - "popup.outdatedmicrobit.text": "We strongly recommend that you update it now, as some features may not work as expected.", - "popup.outdatedmicrobit.text.mkcd": "Open the newest MakeCode template to use the updated extension.", - "popup.outdatedmicrobit.button.later": "Later", - "popup.outdatedmicrobit.button.update": "Update now", - "popup.outdatedmicrobit.button.update.mkcd": "Open MakeCode", - - "popup.appVersionRedirect.header": "Are you a UK* primary school teacher?", - "popup.appVersionRedirect.explain": "If you are a UK* primary school teacher, you will be redirected to the version of this tool designed for the UK's BBC micro:bit playground survey.", - "popup.appVersionRedirect.button.redirect": "I'm a UK primary school teacher", - "popup.appVersionRedirect.button.stay": "I'm not a UK primary school teacher", - "popup.appVersionRedirect.uk": "*includes crown dependencies Jersey, Guernsey and the Isle of Man", - - "arrowIconRight.altText": "arrow pointing right", - "arrowIconDown.altText": "arrow pointing down", - - "settings.label": "Settings", - "prototype.warning": "This is a prototype version and is subject to change without notice", - - "sign-up.title": "Join our testing community", - "sign-up.content": "Welcome to our prototype of the micro:bit machine learning tool. We’d love you to try it out and give us your feedback. Please enter your email so we can keep you updated as it evolves.", - "sign-up.email-label": "Email", - "sign-up.error": "Something went wrong. Please try again or contact us.", - "sign-up.invalid": "Please enter a valid email address", - "sign-up.sign-up-action": "Sign up", - "sign-up.skip-action": "Skip" -} + "about.developedInPartnership": [ + { + "type": 0, + "value": "Developed in partnership with " + }, + { + "children": [ + { + "type": 0, + "value": "Center for Computational Thinking and Design, Aarhus University" + } + ], + "type": 8, + "value": "link" + } + ], + "about.microbitHeartImageAlt": [ + { + "type": 0, + "value": "micro:bit board with the 5 by 5 LED grid showing a heart" + } + ], + "about.softwareVersions": [ + { + "type": 0, + "value": "Software versions" + } + ], + "actions.cancel": [ + { + "type": 0, + "value": "Cancel" + } + ], + "actions.close": [ + { + "type": 0, + "value": "Close" + } + ], + "actions.reconnect": [ + { + "type": 0, + "value": "Reconnect" + } + ], + "alert.data.classNameLengthAlert": [ + { + "type": 0, + "value": "Action names cannot be longer than " + }, + { + "type": 1, + "value": "maxLen" + }, + { + "type": 0, + "value": " characters." + } + ], + "alert.deleteGestureConfirm": [ + { + "type": 0, + "value": "Are you sure you want to delete the action \"" + }, + { + "type": 1, + "value": "action" + }, + { + "type": 0, + "value": "\"?" + } + ], + "alert.isNotConnected": [ + { + "type": 0, + "value": "Your micro:bit should be connected!" + } + ], + "alert.isRecording": [ + { + "type": 0, + "value": "You are currently recording!" + } + ], + "alert.isTesting": [ + { + "type": 0, + "value": "You are currently recording!" + } + ], + "alert.isTraining": [ + { + "type": 0, + "value": "You are currently training a model!" + } + ], + "alert.oneDataRepresentation": [ + { + "type": 0, + "value": "You need at least one data representation" + } + ], + "alert.recording.disconnectedDuringRecording": [ + { + "type": 0, + "value": "micro:bit disconnected during recording" + } + ], + "alert.recordingsPerGesture": [ + { + "type": 0, + "value": "You need at least three examples per action" + } + ], + "alert.twoGestures": [ + { + "type": 0, + "value": "You need at least two actions" + } + ], + "arrowIconDown.altText": [ + { + "type": 0, + "value": "arrow pointing down" + } + ], + "arrowIconRight.altText": [ + { + "type": 0, + "value": "arrow pointing right" + } + ], + "compatibility.platform.notSupported": [ + { + "type": 0, + "value": "The tool is not supported on your current platform." + } + ], + "compatibility.platform.notSupported.joinDesktop": [ + { + "type": 0, + "value": "Please use Google Chrome or Microsoft Edge on a computer." + } + ], + "compatibility.webgl.notSupported": [ + { + "type": 0, + "value": "WebGL not available. Enable WebGL to see 3D data view." + } + ], + "connectFailed.bluetooth1": [ + { + "type": 0, + "value": "The connection to the micro:bit could not be established." + } + ], + "connectFailed.bluetoothHeading": [ + { + "type": 0, + "value": "Failed to connect to micro:bit" + } + ], + "connectFailed.bridge1": [ + { + "type": 0, + "value": "The connection to the micro:bit connected to your computer could not be established." + } + ], + "connectFailed.bridgeHeading": [ + { + "type": 0, + "value": "Failed to connect to micro:bit 2" + } + ], + "connectFailed.remote1": [ + { + "type": 0, + "value": "The connection to the wireless micro:bit could not be established." + } + ], + "connectFailed.remoteHeading": [ + { + "type": 0, + "value": "Failed to connect to micro:bit 1" + } + ], + "connectMB.backButton": [ + { + "type": 0, + "value": "Back" + } + ], + "connectMB.bluetooth.cancelledConnection": [ + { + "type": 0, + "value": "You didn't choose a micro:bit. Do you want to try again?" + } + ], + "connectMB.bluetooth.heading": [ + { + "type": 0, + "value": "Connect using Web Bluetooth" + } + ], + "connectMB.bluetooth.invalidPattern": [ + { + "type": 0, + "value": "The pattern you have drawn is invalid." + } + ], + "connectMB.bluetoothStart.heading": [ + { + "type": 0, + "value": "What you need to connect using Web Bluetooth" + } + ], + "connectMB.bluetoothStart.requirements1": [ + { + "type": 0, + "value": "1 micro:bit" + } + ], + "connectMB.bluetoothStart.requirements2": [ + { + "type": 0, + "value": "Computer" + } + ], + "connectMB.bluetoothStart.requirements2.subtitle": [ + { + "type": 0, + "value": "with Internet, a USB port & Web Bluetooth" + } + ], + "connectMB.bluetoothStart.requirements3": [ + { + "type": 0, + "value": "Micro USB cable" + } + ], + "connectMB.bluetoothStart.requirements4": [ + { + "type": 0, + "value": "Battery holder" + } + ], + "connectMB.bluetoothStart.requirements4.subtitle": [ + { + "type": 0, + "value": "with batteries" + } + ], + "connectMB.bluetoothStart.switchRadio": [ + { + "type": 0, + "value": "Connect using micro:bit radio instead" + } + ], + "connectMB.connectBattery.heading": [ + { + "type": 0, + "value": "Connect battery pack and disconnect USB" + } + ], + "connectMB.connectBattery.link": [ + { + "type": 0, + "value": "You can attach the micro:bit to your wrist or an object" + } + ], + "connectMB.connectBattery.subtitle": [ + { + "type": 0, + "value": "Disconnect the micro:bit from the computer and connect the battery pack." + } + ], + "connectMB.connectCable.altText": [ + { + "type": 0, + "value": "animation showing a USB cable being connected to the top of a micro:bit" + } + ], + "connectMB.connectCable.heading": [ + { + "type": 0, + "value": "Connect USB cable to micro:bit" + } + ], + "connectMB.connectCable.skip": [ + { + "type": 0, + "value": "Skip: program already downloaded?" + } + ], + "connectMB.connectCable.subtitle": [ + { + "type": 0, + "value": "Connect the micro:bit to this computer with a USB cable so that the machine learning tool program can be downloaded to it." + } + ], + "connectMB.connectCableMB1.heading": [ + { + "type": 0, + "value": "Connect USB cable to micro:bit 1" + } + ], + "connectMB.connectCableMB1.subtitle": [ + { + "type": 0, + "value": "Connect micro:bit 1 to this computer with a USB cable so that the machine learning tool program can be downloaded to it. This is the micro:bit you will use to collect data samples." + } + ], + "connectMB.connectCableMB2.heading": [ + { + "type": 0, + "value": "Connect USB cable to micro:bit 2" + } + ], + "connectMB.connectCableMB2.subtitle": [ + { + "type": 0, + "value": "Connect a second micro:bit to this computer with a USB cable." + } + ], + "connectMB.connecting": [ + { + "type": 0, + "value": "Connecting…" + } + ], + "connectMB.nextButton": [ + { + "type": 0, + "value": "Next" + } + ], + "connectMB.output.header": [ + { + "type": 0, + "value": "A micro:bit is already connected" + } + ], + "connectMB.outputMB.different": [ + { + "type": 0, + "value": "Connect another micro:bit" + } + ], + "connectMB.outputMB.otherButton": [ + { + "type": 0, + "value": "Other" + } + ], + "connectMB.outputMB.same": [ + { + "type": 0, + "value": "Use the same micro:bit" + } + ], + "connectMB.outputMB.sameButton": [ + { + "type": 0, + "value": "Same" + } + ], + "connectMB.pattern.heading": [ + { + "type": 0, + "value": "Copy pattern" + } + ], + "connectMB.pattern.inputLabel": [ + { + "type": 0, + "value": "Number of LEDs lit in column " + }, + { + "type": 1, + "value": "colNum" + }, + { + "type": 0, + "value": " on the micro:bit display" + } + ], + "connectMB.pattern.subtitle": [ + { + "type": 0, + "value": "Copy the pattern displayed on the micro:bit." + } + ], + "connectMB.radio.heading": [ + { + "type": 0, + "value": "Connecting micro:bits" + } + ], + "connectMB.radioStart.heading": [ + { + "type": 0, + "value": "What you need to connect using micro:bit radio" + } + ], + "connectMB.radioStart.requirements1": [ + { + "type": 0, + "value": "2 micro:bits" + } + ], + "connectMB.radioStart.requirements2": [ + { + "type": 0, + "value": "Computer" + } + ], + "connectMB.radioStart.requirements2.subtitle": [ + { + "type": 0, + "value": "with Internet & a USB port" + } + ], + "connectMB.radioStart.requirements3": [ + { + "type": 0, + "value": "Micro USB cable" + } + ], + "connectMB.radioStart.requirements4": [ + { + "type": 0, + "value": "Battery holder" + } + ], + "connectMB.radioStart.requirements4.subtitle": [ + { + "type": 0, + "value": "with batteries" + } + ], + "connectMB.radioStart.switchBluetooth": [ + { + "type": 0, + "value": "Connect using Web Bluetooth instead" + } + ], + "connectMB.reconnecting": [ + { + "type": 0, + "value": "Reconnecting…" + } + ], + "connectMB.transferHex.altText": [ + { + "type": 0, + "value": "animation of dragging a hex file from the downloads folder onto the micro:bit drive or device listed in the file explorer" + } + ], + "connectMB.transferHex.heading": [ + { + "type": 0, + "value": "Transfer saved hex file to micro:bit" + } + ], + "connectMB.transferHex.manualDownload": [ + { + "type": 0, + "value": "If the file did not automatically download then " + }, + { + "children": [ + { + "type": 0, + "value": "please download the file here" + } + ], + "type": 8, + "value": "link" + }, + { + "type": 0, + "value": "." + } + ], + "connectMB.transferHex.message": [ + { + "type": 0, + "value": "Drag the hex file from your Downloads folder to the MICROBIT drive." + } + ], + "connectMB.troubleshoot": [ + { + "type": 0, + "value": "Troubleshoot problems with connecting to your micro:bit" + } + ], + "connectMB.troubleshooting": [ + { + "type": 0, + "value": "Troubleshooting" + } + ], + "connectMB.tryAgain": [ + { + "type": 0, + "value": "Try again" + } + ], + "connectMB.unsupportedMicrobit.ctaWithBluetooth": [ + { + "type": 0, + "value": "Connect with Web Bluetooth" + } + ], + "connectMB.unsupportedMicrobit.explain": [ + { + "type": 0, + "value": "Unfortunately, we only support using micro:bit V2 when connecting two micro:bits. You have connected a " + }, + { + "children": [ + { + "type": 0, + "value": "micro:bit V1" + } + ], + "type": 8, + "value": "link" + }, + { + "type": 0, + "value": "." + } + ], + "connectMB.unsupportedMicrobit.header": [ + { + "type": 0, + "value": "micro:bit V1 is not supported for connecting two micro:bits" + } + ], + "connectMB.unsupportedMicrobit.withBluetooth": [ + { + "type": 0, + "value": "Please use Web Bluetooth to connect to your micro:bit V1 instead." + } + ], + "connectMB.unsupportedMicrobit.withoutBluetooth": [ + { + "type": 0, + "value": "You can connect to micro:bit V1 using Web Bluetooth, however, this browser or device does not have Bluetooth enabled. " + }, + { + "children": [ + { + "type": 0, + "value": "How to enable Bluetooth" + } + ], + "type": 8, + "value": "link" + }, + { + "type": 0, + "value": "." + } + ], + "connectMB.usb.firmwareBroken.content1": [ + { + "type": 0, + "value": "Connecting to the micro:bit failed because the firmware on your micro:bit is too old." + } + ], + "connectMB.usb.firmwareBroken.content2": [ + { + "type": 0, + "value": "You must " + }, + { + "children": [ + { + "type": 0, + "value": "update your firmware" + } + ], + "type": 8, + "value": "link" + }, + { + "type": 0, + "value": " before you can connect to this micro:bit." + } + ], + "connectMB.usb.firmwareBroken.content3": [ + { + "type": 0, + "value": "Troubleshoot problems with connecting to your micro:bit" + } + ], + "connectMB.usb.firmwareBroken.heading": [ + { + "type": 0, + "value": "Firmware update required" + } + ], + "connectMB.usb.firmwareBroken.skip": [ + { + "type": 0, + "value": "Skip and transfer manually" + } + ], + "connectMB.usbDownloading.header": [ + { + "type": 0, + "value": "Downloading program to micro:bit" + } + ], + "connectMB.usbDownloading.subtitle": [ + { + "type": 0, + "value": "Please wait. Downloading program to micro:bit." + } + ], + "connectMB.usbDownloadingMB1.header": [ + { + "type": 0, + "value": "Downloading program to micro:bit 1" + } + ], + "connectMB.usbDownloadingMB2.header": [ + { + "type": 0, + "value": "Downloading program to micro:bit 2" + } + ], + "connectMB.usbTryAgain.closeTabs1": [ + { + "type": 0, + "value": "Another process is connected to this device." + } + ], + "connectMB.usbTryAgain.closeTabs2": [ + { + "type": 0, + "value": "Close any other tabs that may be using WebUSB (e.g. MakeCode, Python Editor, machine learning tool), or unplug and replug the micro:bit before trying again." + } + ], + "connectMB.usbTryAgain.heading": [ + { + "type": 0, + "value": "Connect using WebUSB" + } + ], + "connectMB.usbTryAgain.replugMicrobit1": [ + { + "type": 0, + "value": "A WebUSB error occurred." + } + ], + "connectMB.usbTryAgain.replugMicrobit2": [ + { + "type": 0, + "value": "Please:" + } + ], + "connectMB.usbTryAgain.replugMicrobit3": [ + { + "type": 0, + "value": "check that this micro:bit does not have a battery pack connected" + } + ], + "connectMB.usbTryAgain.replugMicrobit4": [ + { + "type": 0, + "value": "unplug and replug the USB cable" + } + ], + "connectMB.usbTryAgain.selectMicrobit": [ + { + "type": 0, + "value": "You didn't select a micro:bit. Do you want to try again?" + } + ], + "connectMB.webPopup": [ + { + "type": 0, + "value": "Select micro:bit" + } + ], + "connectMB.webPopup.instruction.heading": [ + { + "type": 0, + "value": "In the next popup" + } + ], + "connectMB.webPopup.instruction1": [ + { + "type": 0, + "value": "Choose your micro:bit" + } + ], + "connectMB.webPopup.webBluetooth.altText": [ + { + "type": 0, + "value": "Web Bluetooth connection dialog with BBC micro:bit entry labelled 1 and Pair button labelled 2" + } + ], + "connectMB.webPopup.webBluetooth.instruction2": [ + { + "type": 0, + "value": "Select 'Pair'" + } + ], + "connectMB.webPopup.webUsb.altText": [ + { + "type": 0, + "value": "WebUSB connection dialog with BBC micro:bit entry labelled 1 and Connect button labelled 2" + } + ], + "connectMB.webPopup.webUsb.instruction2": [ + { + "type": 0, + "value": "Select 'Connect'" + } + ], + "content.data.addAction": [ + { + "type": 0, + "value": "Add action" + } + ], + "content.data.addAction.inputLabel": [ + { + "type": 0, + "value": "Name of action field" + } + ], + "content.data.addActionWalkThrough": [ + { + "type": 0, + "value": "Name an action you want the micro:bit to recognise" + } + ], + "content.data.addRecordingWalkThrough": [ + { + "type": 0, + "value": "Press to record a data sample or press button B on your micro:bit." + } + ], + "content.data.classHelpBody": [ + { + "type": 0, + "value": "The type of movement you want the machine learning tool to recognise e.g. 'wave' or 'clap'. " + } + ], + "content.data.classHelpHeader": [ + { + "type": 0, + "value": "Action" + } + ], + "content.data.classPlaceholderNewClass": [ + { + "type": 0, + "value": "Name of action" + } + ], + "content.data.classification": [ + { + "type": 0, + "value": "Action" + } + ], + "content.data.controlbar.button.clearData": [ + { + "type": 0, + "value": "Delete all data samples" + } + ], + "content.data.controlbar.button.clearData.confirm": [ + { + "type": 0, + "value": "Are you sure you wish to delete all data samples?\nThis cannot be undone." + } + ], + "content.data.controlbar.button.downloadData": [ + { + "type": 0, + "value": "Download all data samples" + } + ], + "content.data.controlbar.button.menu": [ + { + "type": 0, + "value": "Data actions" + } + ], + "content.data.controlbar.button.uploadData": [ + { + "type": 0, + "value": "Import data samples" + } + ], + "content.data.data": [ + { + "type": 0, + "value": "Data samples" + } + ], + "content.data.dataDescription": [ + { + "type": 0, + "value": "Multiple samples of movement data collected in approximately 2 second bursts." + } + ], + "content.data.deleteAction": [ + { + "type": 0, + "value": "Delete action \"" + }, + { + "type": 1, + "value": "action" + }, + { + "type": 0, + "value": "\"" + } + ], + "content.data.deleteRecording": [ + { + "type": 0, + "value": "Delete recording" + } + ], + "content.data.recordAction": [ + { + "type": 0, + "value": "Record data for action \"" + }, + { + "type": 1, + "value": "action" + }, + { + "type": 0, + "value": "\"" + } + ], + "content.data.recording.button.cancel": [ + { + "type": 0, + "value": "Cancel recording" + } + ], + "content.data.recordingDialog.go": [ + { + "type": 0, + "value": "Go" + } + ], + "content.data.recordingDialog.recording": [ + { + "type": 0, + "value": "Recording" + } + ], + "content.data.recordingDialog.title": [ + { + "type": 0, + "value": "Record data for action \"" + }, + { + "type": 1, + "value": "action" + }, + { + "type": 0, + "value": "\"" + } + ], + "content.data.selectAndRecordAction": [ + { + "type": 0, + "value": "Select action \"" + }, + { + "type": 1, + "value": "action" + }, + { + "type": 0, + "value": "\" and record data" + } + ], + "content.index.dataWarning.subtitleOne": [ + { + "type": 0, + "value": "You have existing data that will be lost when you start a new session." + } + ], + "content.index.dataWarning.subtitleTwo": [ + { + "type": 0, + "value": "To save your data, " + }, + { + "children": [ + { + "type": 0, + "value": "download all data samples" + } + ], + "type": 8, + "value": "link" + }, + { + "type": 0, + "value": " before continuing." + } + ], + "content.index.dataWarning.title": [ + { + "type": 0, + "value": "Existing data samples will be lost" + } + ], + "content.index.title": [ + { + "type": 0, + "value": "machine learning tool" + } + ], + "content.index.toolProcessCards.data.description": [ + { + "type": 0, + "value": "Add samples of the actions you would like your model to recognise (e.g. waving and clapping)." + } + ], + "content.index.toolProcessCards.data.title": [ + { + "type": 0, + "value": "Add data" + } + ], + "content.index.toolProcessCards.model.description": [ + { + "type": 0, + "value": "Find out if it correctly recognises each action. Add more data to improve the model." + } + ], + "content.index.toolProcessCards.model.title": [ + { + "type": 0, + "value": "Test model" + } + ], + "content.index.toolProcessCards.train.description": [ + { + "type": 0, + "value": "Ask the computer to use your training samples to train the machine learning model to recognise different actions." + } + ], + "content.index.toolProcessCards.train.title": [ + { + "type": 0, + "value": "Train model" + } + ], + "content.model.addData": [ + { + "type": 0, + "value": "Add data" + } + ], + "content.model.notEnoughDataInfoBody1": [ + { + "type": 0, + "value": "You cannot test the model until it has been trained." + } + ], + "content.model.notEnoughDataInfoBody2": [ + { + "type": 0, + "value": "You need at least 3 data samples for 2 actions to train a model." + } + ], + "content.model.output.action.descriptionBody": [ + { + "type": 0, + "value": "The type of movement you want the machine learning tool to recognise e.g. ‘wave’ or ‘clap’." + } + ], + "content.model.output.action.descriptionTitle": [ + { + "type": 0, + "value": "Action" + } + ], + "content.model.output.action.iconTitle": [ + { + "type": 0, + "value": "Action" + } + ], + "content.model.output.certainty.descriptionBody": [ + { + "type": 0, + "value": "How confident the model is that you are currently doing each action." + } + ], + "content.model.output.certainty.descriptionTitle": [ + { + "type": 0, + "value": "Certainty" + } + ], + "content.model.output.certainty.iconTitle": [ + { + "type": 0, + "value": "Certainty" + } + ], + "content.model.output.estimatedGesture.descriptionBody": [ + { + "type": 0, + "value": "This is the action the model thinks you are currently doing." + } + ], + "content.model.output.estimatedGesture.descriptionTitle": [ + { + "type": 0, + "value": "Estimated action" + } + ], + "content.model.output.estimatedGesture.iconTitle": [ + { + "type": 0, + "value": "Estimated action:" + } + ], + "content.model.output.estimatedGesture.label": [ + { + "type": 0, + "value": "Estimated action: \"" + }, + { + "type": 1, + "value": "action" + }, + { + "type": 0, + "value": "\"" + } + ], + "content.model.output.estimatedGesture.none": [ + { + "type": 0, + "value": "None" + } + ], + "content.model.output.recognitionPoint": [ + { + "type": 0, + "value": "Recognition Point:" + } + ], + "content.model.retrainModelBody": [ + { + "type": 0, + "value": "You need to train your model with the current set of data samples before you can test it." + } + ], + "content.model.trainModelBody": [ + { + "type": 0, + "value": "You need to train your model before you can test it." + } + ], + "content.model.trainModelFirstHeading": [ + { + "type": 0, + "value": "Test model" + } + ], + "content.trainer.description": [ + { + "type": 0, + "value": "The computer program spots patterns or differences in your data samples, and uses these to build a mathematical model that allows the micro:bit machine learning tool to recognise different actions when you move your micro:bit." + } + ], + "content.trainer.enoughdata.title": [ + { + "type": 0, + "value": "Status: You've collected enough data to train the model." + } + ], + "content.trainer.failure.body": [ + { + "type": 0, + "value": "The training did not result in a usable model. The reason for this is most likely the data used for training. If the data samples for different actions are too similar, this can result in issues in the training process." + } + ], + "content.trainer.failure.header": [ + { + "type": 0, + "value": "Training failed" + } + ], + "content.trainer.failure.todo": [ + { + "type": 0, + "value": "Return to the Add data page and change your data." + } + ], + "content.trainer.header": [ + { + "type": 0, + "value": "Training a model" + } + ], + "content.trainer.retrain.title": [ + { + "type": 0, + "value": "Status: Your data samples have changed. You can retrain the model." + } + ], + "content.trainer.training.title": [ + { + "type": 0, + "value": "Status: Training the model in progress." + } + ], + "disconnectedWarning.bluetooth1": [ + { + "type": 0, + "value": "The connection to the micro:bit has been lost." + } + ], + "disconnectedWarning.bluetooth2": [ + { + "type": 0, + "value": "Please check that the micro:bit:" + } + ], + "disconnectedWarning.bluetooth3": [ + { + "type": 0, + "value": "is powered on (the red light on the back of the micro:bit should be on)" + } + ], + "disconnectedWarning.bluetooth4": [ + { + "type": 0, + "value": "is close to the computer" + } + ], + "disconnectedWarning.bluetoothHeading": [ + { + "type": 0, + "value": "micro:bit connection lost" + } + ], + "disconnectedWarning.bridge1": [ + { + "type": 0, + "value": "The connection to the micro:bit connected to your computer has been lost." + } + ], + "disconnectedWarning.bridgeHeading": [ + { + "type": 0, + "value": "micro:bit 2 connection lost" + } + ], + "disconnectedWarning.remote1": [ + { + "type": 0, + "value": "The connection to the wireless micro:bit has been lost." + } + ], + "disconnectedWarning.remoteHeading": [ + { + "type": 0, + "value": "micro:bit 1 connection lost" + } + ], + "footer.connectButton": [ + { + "type": 0, + "value": "Connect" + } + ], + "footer.disconnectButton": [ + { + "type": 0, + "value": "Disconnect" + } + ], + "footer.helpContent": [ + { + "type": 0, + "value": "This graph shows movement data from the microbit’s accelerometer in real time. Try moving your connected micro:bit to see the X, Y and Z axes change." + } + ], + "footer.helpHeader": [ + { + "type": 0, + "value": "Live graph" + } + ], + "footer.resume": [ + { + "type": 0, + "value": "Resume session" + } + ], + "footer.start": [ + { + "type": 0, + "value": "Start new session" + } + ], + "helpMenu.about": [ + { + "type": 0, + "value": "About" + } + ], + "helpMenu.cookies": [ + { + "type": 0, + "value": "Cookies" + } + ], + "helpMenu.helpAndSupport": [ + { + "type": 0, + "value": "Help & support" + } + ], + "helpMenu.label": [ + { + "type": 0, + "value": "Help" + } + ], + "helpMenu.privacyPolicy": [ + { + "type": 0, + "value": "Privacy policy" + } + ], + "helpMenu.termsOfUse": [ + { + "type": 0, + "value": "Terms of use" + } + ], + "homepage.Link": [ + { + "type": 0, + "value": "Home page" + } + ], + "info.label": [ + { + "type": 0, + "value": "More information about \"" + }, + { + "type": 1, + "value": "item" + }, + { + "type": 0, + "value": "\"" + } + ], + "languageDialog.title": [ + { + "type": 0, + "value": "Language" + } + ], + "loading": [ + { + "type": 0, + "value": "loading" + } + ], + "menu.data.helpHeading": [ + { + "type": 0, + "value": "1. Add data" + } + ], + "menu.model.connectInputMicrobit": [ + { + "type": 0, + "value": "Connect micro:bit" + } + ], + "menu.model.disconnect": [ + { + "type": 0, + "value": "Disconnect output micro:bit" + } + ], + "menu.model.helpHeading": [ + { + "type": 0, + "value": "3. Test model" + } + ], + "menu.model.noModel": [ + { + "type": 0, + "value": "No model" + } + ], + "menu.trainer.TrainingFinished": [ + { + "type": 0, + "value": "Status: Your model is trained." + } + ], + "menu.trainer.addDataButton": [ + { + "type": 0, + "value": "Add data" + } + ], + "menu.trainer.addMoreDataButton": [ + { + "type": 0, + "value": "Add more data" + } + ], + "menu.trainer.helpHeading": [ + { + "type": 0, + "value": "2. Train model" + } + ], + "menu.trainer.notConnected1": [ + { + "type": 0, + "value": "You do not have a micro:bit connected" + } + ], + "menu.trainer.notConnected2": [ + { + "type": 0, + "value": " Select the ‘Connect’ button to connect a micro:bit." + } + ], + "menu.trainer.notEnoughDataHeader1": [ + { + "type": 0, + "value": "Status: You don't have enough data." + } + ], + "menu.trainer.notEnoughDataInfoBody": [ + { + "type": 0, + "value": "You need at least 3 data samples for 2 actions to train the model." + } + ], + "menu.trainer.testModelButton": [ + { + "type": 0, + "value": "Test model" + } + ], + "menu.trainer.trainModelButton": [ + { + "type": 0, + "value": "Train model" + } + ], + "performanceWarning.content1": [ + { + "type": 0, + "value": "The quality of data being received from your micro:bit is low. This could be due to a connection issue." + } + ], + "performanceWarning.content2": [ + { + "type": 0, + "value": "Do you want to restart the connection process to try to improve performance?" + } + ], + "performanceWarning.heading": [ + { + "type": 0, + "value": "Low quality connection" + } + ], + "performanceWarning.ignore": [ + { + "type": 0, + "value": "Don't show this again" + } + ], + "performanceWarning.nextButton": [ + { + "type": 0, + "value": "Next" + } + ], + "performanceWarning.troubleshoot": [ + { + "type": 0, + "value": "Troubleshoot problems with connection quality" + } + ], + "popup.appVersionRedirect.button.redirect": [ + { + "type": 0, + "value": "I'm a UK primary school teacher" + } + ], + "popup.appVersionRedirect.button.stay": [ + { + "type": 0, + "value": "I'm not a UK primary school teacher" + } + ], + "popup.appVersionRedirect.explain": [ + { + "type": 0, + "value": "If you are a UK* primary school teacher, you will be redirected to the version of this tool designed for the UK's BBC micro:bit playground survey." + } + ], + "popup.appVersionRedirect.header": [ + { + "type": 0, + "value": "Are you a UK* primary school teacher?" + } + ], + "popup.appVersionRedirect.uk": [ + { + "type": 0, + "value": "*includes crown dependencies Jersey, Guernsey and the Isle of Man" + } + ], + "popup.compatibility.advice": [ + { + "type": 0, + "value": "We recommend Google Chrome or Microsoft Edge so you can connect directly to your micro:bit." + } + ], + "popup.compatibility.explain": [ + { + "type": 0, + "value": "Unfortunately, WebUSB and Web Bluetooth are not supported by this browser. This means you cannot connect to a micro:bit to record the data samples needed to train a machine learning model." + } + ], + "popup.compatibility.header": [ + { + "type": 0, + "value": "This browser is not supported" + } + ], + "popup.outdatedmicrobit.button.later": [ + { + "type": 0, + "value": "Later" + } + ], + "popup.outdatedmicrobit.button.update": [ + { + "type": 0, + "value": "Update now" + } + ], + "popup.outdatedmicrobit.button.update.mkcd": [ + { + "type": 0, + "value": "Open MakeCode" + } + ], + "popup.outdatedmicrobit.header": [ + { + "type": 0, + "value": "Your micro:bit has an outdated machine learning tool program" + } + ], + "popup.outdatedmicrobit.text": [ + { + "type": 0, + "value": "We strongly recommend that you update it now, as some features may not work as expected." + } + ], + "popup.outdatedmicrobit.text.mkcd": [ + { + "type": 0, + "value": "Open the newest MakeCode template to use the updated extension." + } + ], + "prototype.warning": [ + { + "type": 0, + "value": "This is a prototype version and is subject to change without notice" + } + ], + "reconnectFailed.bluetooth1": [ + { + "type": 0, + "value": "The connection to the micro:bit could not be re-established." + } + ], + "reconnectFailed.bluetoothHeading": [ + { + "type": 0, + "value": "Failed to reconnect to micro:bit" + } + ], + "reconnectFailed.bridge1": [ + { + "type": 0, + "value": "The connection to the micro:bit connected to your computer could not be re-established." + } + ], + "reconnectFailed.bridgeHeading": [ + { + "type": 0, + "value": "Failed to reconnect to micro:bit 2" + } + ], + "reconnectFailed.radioHeading": [ + { + "type": 0, + "value": "Failed to reconnect to micro:bits" + } + ], + "reconnectFailed.remote1": [ + { + "type": 0, + "value": "The connection to the wireless micro:bit could not be re-established." + } + ], + "reconnectFailed.remoteHeading": [ + { + "type": 0, + "value": "Failed to reconnect to micro:bit 1" + } + ], + "reconnectFailed.subtitle": [ + { + "type": 0, + "value": "Follow these instructions to restart the connection process." + } + ], + "resources.getStarted.video": [ + { + "type": 0, + "value": "get started video" + } + ], + "resources.introduction.video": [ + { + "type": 0, + "value": "introduction video" + } + ], + "settings.label": [ + { + "type": 0, + "value": "Settings" + } + ], + "sign-up.content": [ + { + "type": 0, + "value": "Welcome to our prototype of the micro:bit machine learning tool. We’d love you to try it out and give us your feedback. Please enter your email so we can keep you updated as it evolves." + } + ], + "sign-up.email-label": [ + { + "type": 0, + "value": "Email" + } + ], + "sign-up.error": [ + { + "type": 0, + "value": "Something went wrong. Please try again or " + }, + { + "children": [ + { + "type": 0, + "value": "contact us" + } + ], + "type": 8, + "value": "link" + }, + { + "type": 0, + "value": "." + } + ], + "sign-up.invalid": [ + { + "type": 0, + "value": "Please enter a valid email address" + } + ], + "sign-up.sign-up-action": [ + { + "type": 0, + "value": "Sign up" + } + ], + "sign-up.skip-action": [ + { + "type": 0, + "value": "Skip" + } + ], + "sign-up.title": [ + { + "type": 0, + "value": "Join our testing community" + } + ] +} \ No newline at end of file From 3d1be37fedcbd665f7ff45c3a8c675e776ff0a5d Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Tue, 25 Jun 2024 14:50:03 +0100 Subject: [PATCH 003/172] Prettier, CI fixes --- .github/workflows/build.yml | 5 +---- .prettierrc | 1 + ci.sh | 15 --------------- package.json | 2 +- src/App.tsx | 4 ++-- src/main.tsx | 6 ++---- src/messages/ui.en.json | 2 +- src/placeholder.test.ts | 2 +- 8 files changed, 9 insertions(+), 28 deletions(-) create mode 100644 .prettierrc delete mode 100755 ci.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bafb55774..3571fe8bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,10 +39,7 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: node ./bin/print-ci-env.cjs >> $GITHUB_ENV - - run: npm run test - - run: npm run check - - run: npx prettier --check src - - run: npm run build + - run: npm run ci - run: npx website-deploy-aws if: github.repository_owner == 'microbit-foundation' && (env.STAGE == 'REVIEW' || success()) env: diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/ci.sh b/ci.sh deleted file mode 100755 index 2140b2e8a..000000000 --- a/ci.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -# This script is used to run the CloudFlare pages CI build. -# CloudFlare will automatically install the NPM dependencies. - -set -euxo pipefail - -# This lint step fails so disabled for now. Does it work upstream? -npm run check || echo "Temporarily allowed to fail" - -# This is very close to passing. Two files need fixing to enable it. -npx prettier --check src || echo "Temporarily allowed to fail" - -npm run test -npm run build \ No newline at end of file diff --git a/package.json b/package.json index 37a324467..d5510935c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "license": "MIT", "scripts": { "build": "tsc && cross-env VITE_VERSION=$npm_package_version vite build", - "ci": "npm run test && npm run lint && npm run build", + "ci": "npm run test && npm run lint && npm run build && npx prettier --check src", "deploy": "website-deploy-aws", "dev": "cross-env VITE_VERSION=$npm_package_version vite", "invalidate": "invalidate-cloudfront-distribution", diff --git a/src/App.tsx b/src/App.tsx index ff4f3b2cc..e8b1f012a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,3 @@ -const App = () =>

        Hi!

        +const App = () =>

        Hi!

        ; -export default App; \ No newline at end of file +export default App; diff --git a/src/main.tsx b/src/main.tsx index 8cc546496..c8db8d6c9 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,9 +4,7 @@ * SPDX-License-Identifier: MIT */ -import App from './App'; +import App from "./App"; import ReactDOM from "react-dom/client"; -ReactDOM.createRoot(document.getElementById("root")!).render( - -); \ No newline at end of file +ReactDOM.createRoot(document.getElementById("root")!).render(); diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 77b87c84d..45ef4be91 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -1629,4 +1629,4 @@ "value": "Join our testing community" } ] -} \ No newline at end of file +} diff --git a/src/placeholder.test.ts b/src/placeholder.test.ts index 9bfce767d..42fc4a5ff 100644 --- a/src/placeholder.test.ts +++ b/src/placeholder.test.ts @@ -1,3 +1,3 @@ test("placeholder", () => { // So it works? -}) \ No newline at end of file +}); From bd9074bcf5f93e58afe5cc88f7ec0be7cac2735b Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Tue, 25 Jun 2024 15:28:15 +0100 Subject: [PATCH 004/172] Basic skeleton UI Missing lots of strings --- package-lock.json | 420 +++++++++++------- package.json | 2 + src/App.tsx | 84 +++- src/compliance.tsx | 89 ++++ src/components/AboutDialog.tsx | 149 +++++++ src/components/ActionBar.tsx | 48 ++ src/components/AppLogo.tsx | 43 ++ src/components/BackArrow.tsx | 16 + src/components/DefaultPageLayout.tsx | 94 ++++ src/components/ErrorBoundary.tsx | 39 ++ src/components/ErrorHandlerErrorView.tsx | 38 ++ src/components/ErrorPage.tsx | 22 + src/components/HelpMenu.tsx | 125 ++++++ src/components/LanguageDialog.tsx | 117 +++++ src/components/LanguageMenuItem.tsx | 30 ++ src/components/Link.tsx | 26 ++ src/components/MicrobitLogo.tsx | 29 ++ src/components/NotFound.tsx | 21 + src/components/SettingsMenu.tsx | 55 +++ src/constants.ts | 1 + src/environment.ts | 10 + src/flags.test.ts | 55 +++ src/flags.ts | 64 +++ src/hooks/use-storage.ts | 81 ++++ src/icons.ts | 4 + src/{imgs => images}/ML_predict.svg | 0 src/{imgs => images}/ML_train.svg | 0 src/{imgs => images}/Makecode_integration.png | Bin src/{imgs => images}/add_data.png | Bin src/{imgs => images}/add_data.svg | 0 src/{imgs => images}/app-name.svg | 0 src/{imgs => images}/aulogo_uk_var2_blue.png | Bin src/{imgs => images}/blank_microbit.svg | 0 src/{imgs => images}/connect-cable.gif | Bin src/{imgs => images}/curve-arrow-up.svg | 0 .../data-trainer-thumpnail.png | Bin src/{imgs => images}/data_recorded.png | Bin src/{imgs => images}/data_rep_viz.png | Bin src/{imgs => images}/data_representation.svg | 0 src/{imgs => images}/down_arrow.svg | 0 .../greeting-emoji-with-arrow.svg | 0 src/{imgs => images}/led_off.svg | 0 src/{imgs => images}/led_on.svg | 0 src/{imgs => images}/microbit-heart.png | Bin src/{imgs => images}/microbit-logo-black.svg | 0 src/{imgs => images}/microbit-logo.svg | 0 src/{imgs => images}/microbit2_blank.svg | 0 src/{imgs => images}/microbitV1.svg | 0 src/{imgs => images}/microbitV2.svg | 0 src/{imgs => images}/microbit_guide.svg | 0 src/{imgs => images}/microbit_icon.png | Bin src/{imgs => images}/microbit_icon_dual.png | Bin .../microbit_record_guide.svg | 0 src/{imgs => images}/microbit_unaltered.svg | 0 src/{imgs => images}/microbit_xyz_arrows.png | Bin src/{imgs => images}/model.svg | 0 src/{imgs => images}/model_2.svg | 0 src/{imgs => images}/model_3.svg | 0 src/{imgs => images}/model_blue.svg | 0 src/{imgs => images}/model_green.svg | 0 src/{imgs => images}/model_old.svg | 0 src/{imgs => images}/red_arrows.svg | 0 src/{imgs => images}/resource-get-started.jpg | Bin .../resource-introducing-tool.jpg | Bin src/{imgs => images}/right_arrow.svg | 0 src/{imgs => images}/right_arrow_blue.svg | 0 src/{imgs => images}/search_for_patterns.svg | 0 .../select-microbit-bluetooth.png | Bin .../select-microbit-web-usb.png | Bin src/{imgs => images}/sidebar_background.svg | 0 .../stylised-battery-pack.svg | 0 .../stylised-microbit-black.svg | 0 .../stylised-microbit-connected.svg | 0 .../stylised-two-microbits-black.svg | 0 src/{imgs => images}/stylised-usb-cable.svg | 0 src/{imgs => images}/stylised_computer.svg | 0 .../stylised_computer_w_bluetooth.svg | 0 src/{imgs => images}/test_model.png | Bin src/{imgs => images}/test_model_black.svg | 0 src/{imgs => images}/test_model_blue.svg | 0 src/{imgs => images}/train_model.png | Bin src/{imgs => images}/train_model_black.svg | 0 src/{imgs => images}/train_model_blue.svg | 0 .../transfer_program_chromeos.gif | Bin .../transfer_program_macos.gif | Bin .../transfer_program_windows.gif | Bin src/{imgs => images}/v1.svg | 0 src/{imgs => images}/v2.svg | 0 src/messages/TranslationProvider.tsx | 37 ++ src/messages/chunk-util.test.ts | 31 ++ src/messages/chunk-util.ts | 24 + src/pages/HomePage.tsx | 11 + src/settings.tsx | 85 ++++ src/theme/colors.ts | 94 ++++ src/theme/components/alert.ts | 26 ++ src/theme/components/avatar.ts | 21 + src/theme/components/button.ts | 112 +++++ src/theme/components/heading.ts | 15 + src/theme/components/text.ts | 15 + src/theme/components/tooltip.ts | 9 + src/theme/constants/colors.ts | 5 + src/theme/constants/dimensions.ts | 1 + src/theme/default-graph.ts | 14 + src/theme/fonts.ts | 6 + src/theme/graph.ts | 17 + src/theme/radii.ts | 6 + src/theme/shadows.ts | 6 + src/theme/theme.ts | 35 ++ src/urls.ts | 7 + src/vite-env.d.ts | 16 +- vite.config.ts | 23 +- 111 files changed, 2101 insertions(+), 177 deletions(-) create mode 100644 src/compliance.tsx create mode 100644 src/components/AboutDialog.tsx create mode 100644 src/components/ActionBar.tsx create mode 100644 src/components/AppLogo.tsx create mode 100644 src/components/BackArrow.tsx create mode 100644 src/components/DefaultPageLayout.tsx create mode 100644 src/components/ErrorBoundary.tsx create mode 100644 src/components/ErrorHandlerErrorView.tsx create mode 100644 src/components/ErrorPage.tsx create mode 100644 src/components/HelpMenu.tsx create mode 100644 src/components/LanguageDialog.tsx create mode 100644 src/components/LanguageMenuItem.tsx create mode 100644 src/components/Link.tsx create mode 100644 src/components/MicrobitLogo.tsx create mode 100644 src/components/NotFound.tsx create mode 100644 src/components/SettingsMenu.tsx create mode 100644 src/constants.ts create mode 100644 src/environment.ts create mode 100644 src/flags.test.ts create mode 100644 src/flags.ts create mode 100644 src/hooks/use-storage.ts create mode 100644 src/icons.ts rename src/{imgs => images}/ML_predict.svg (100%) rename src/{imgs => images}/ML_train.svg (100%) rename src/{imgs => images}/Makecode_integration.png (100%) rename src/{imgs => images}/add_data.png (100%) rename src/{imgs => images}/add_data.svg (100%) rename src/{imgs => images}/app-name.svg (100%) rename src/{imgs => images}/aulogo_uk_var2_blue.png (100%) rename src/{imgs => images}/blank_microbit.svg (100%) rename src/{imgs => images}/connect-cable.gif (100%) rename src/{imgs => images}/curve-arrow-up.svg (100%) rename src/{imgs => images}/data-trainer-thumpnail.png (100%) rename src/{imgs => images}/data_recorded.png (100%) rename src/{imgs => images}/data_rep_viz.png (100%) rename src/{imgs => images}/data_representation.svg (100%) rename src/{imgs => images}/down_arrow.svg (100%) rename src/{imgs => images}/greeting-emoji-with-arrow.svg (100%) rename src/{imgs => images}/led_off.svg (100%) rename src/{imgs => images}/led_on.svg (100%) rename src/{imgs => images}/microbit-heart.png (100%) rename src/{imgs => images}/microbit-logo-black.svg (100%) rename src/{imgs => images}/microbit-logo.svg (100%) rename src/{imgs => images}/microbit2_blank.svg (100%) rename src/{imgs => images}/microbitV1.svg (100%) rename src/{imgs => images}/microbitV2.svg (100%) rename src/{imgs => images}/microbit_guide.svg (100%) rename src/{imgs => images}/microbit_icon.png (100%) rename src/{imgs => images}/microbit_icon_dual.png (100%) rename src/{imgs => images}/microbit_record_guide.svg (100%) rename src/{imgs => images}/microbit_unaltered.svg (100%) rename src/{imgs => images}/microbit_xyz_arrows.png (100%) rename src/{imgs => images}/model.svg (100%) rename src/{imgs => images}/model_2.svg (100%) rename src/{imgs => images}/model_3.svg (100%) rename src/{imgs => images}/model_blue.svg (100%) rename src/{imgs => images}/model_green.svg (100%) rename src/{imgs => images}/model_old.svg (100%) rename src/{imgs => images}/red_arrows.svg (100%) rename src/{imgs => images}/resource-get-started.jpg (100%) rename src/{imgs => images}/resource-introducing-tool.jpg (100%) rename src/{imgs => images}/right_arrow.svg (100%) rename src/{imgs => images}/right_arrow_blue.svg (100%) rename src/{imgs => images}/search_for_patterns.svg (100%) rename src/{imgs => images}/select-microbit-bluetooth.png (100%) rename src/{imgs => images}/select-microbit-web-usb.png (100%) rename src/{imgs => images}/sidebar_background.svg (100%) rename src/{imgs => images}/stylised-battery-pack.svg (100%) rename src/{imgs => images}/stylised-microbit-black.svg (100%) rename src/{imgs => images}/stylised-microbit-connected.svg (100%) rename src/{imgs => images}/stylised-two-microbits-black.svg (100%) rename src/{imgs => images}/stylised-usb-cable.svg (100%) rename src/{imgs => images}/stylised_computer.svg (100%) rename src/{imgs => images}/stylised_computer_w_bluetooth.svg (100%) rename src/{imgs => images}/test_model.png (100%) rename src/{imgs => images}/test_model_black.svg (100%) rename src/{imgs => images}/test_model_blue.svg (100%) rename src/{imgs => images}/train_model.png (100%) rename src/{imgs => images}/train_model_black.svg (100%) rename src/{imgs => images}/train_model_blue.svg (100%) rename src/{imgs => images}/transfer_program_chromeos.gif (100%) rename src/{imgs => images}/transfer_program_macos.gif (100%) rename src/{imgs => images}/transfer_program_windows.gif (100%) rename src/{imgs => images}/v1.svg (100%) rename src/{imgs => images}/v2.svg (100%) create mode 100644 src/messages/TranslationProvider.tsx create mode 100644 src/messages/chunk-util.test.ts create mode 100644 src/messages/chunk-util.ts create mode 100644 src/pages/HomePage.tsx create mode 100644 src/settings.tsx create mode 100644 src/theme/colors.ts create mode 100644 src/theme/components/alert.ts create mode 100644 src/theme/components/avatar.ts create mode 100644 src/theme/components/button.ts create mode 100644 src/theme/components/heading.ts create mode 100644 src/theme/components/text.ts create mode 100644 src/theme/components/tooltip.ts create mode 100644 src/theme/constants/colors.ts create mode 100644 src/theme/constants/dimensions.ts create mode 100644 src/theme/default-graph.ts create mode 100644 src/theme/fonts.ts create mode 100644 src/theme/graph.ts create mode 100644 src/theme/radii.ts create mode 100644 src/theme/shadows.ts create mode 100644 src/theme/theme.ts create mode 100644 src/urls.ts diff --git a/package-lock.json b/package-lock.json index 63f66fe99..e4dc6963b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,8 @@ "react-dom": "^18.3.1", "react-icons": "^4.12.0", "react-intl": "^6.6.8", + "react-router": "^6.24.0", + "react-router-dom": "^6.24.0", "smoothie": "^1.36.1", "three": "^0.152.2", "uuid4": "^2.0.3" @@ -3521,10 +3523,26 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", - "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -3538,9 +3556,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", - "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -3554,9 +3572,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", - "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -3570,9 +3588,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", - "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -3586,9 +3604,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", - "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -3602,9 +3620,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", - "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -3618,9 +3636,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", - "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -3634,9 +3652,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", - "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -3650,9 +3668,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", - "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -3666,9 +3684,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", - "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -3682,9 +3700,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", - "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -3698,9 +3716,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", - "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -3714,9 +3732,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", - "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -3730,9 +3748,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", - "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -3746,9 +3764,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", - "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -3762,9 +3780,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", - "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -3778,9 +3796,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", - "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -3794,9 +3812,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", - "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -3810,9 +3828,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", - "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -3826,9 +3844,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", - "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -3842,9 +3860,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", - "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -3858,9 +3876,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", - "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -4397,6 +4415,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.0.tgz", + "integrity": "sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-node-resolve": { "version": "15.2.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", @@ -4467,9 +4493,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.0.tgz", - "integrity": "sha512-+1ge/xmaJpm1KVBuIH38Z94zj9fBD+hp+/5WLaHgyY8XLq1ibxk/zj6dTXaqM2cAbYKq8jYlhHd6k05If1W5xA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", "cpu": [ "arm" ], @@ -4480,9 +4506,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.0.tgz", - "integrity": "sha512-im6hUEyQ7ZfoZdNvtwgEJvBWZYauC9KVKq1w58LG2Zfz6zMd8gRrbN+xCVoqA2hv/v6fm9lp5LFGJ3za8EQH3A==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", "cpu": [ "arm64" ], @@ -4493,9 +4519,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.0.tgz", - "integrity": "sha512-u7aTMskN6Dmg1lCT0QJ+tINRt+ntUrvVkhbPfFz4bCwRZvjItx2nJtwJnJRlKMMaQCHRjrNqHRDYvE4mBm3DlQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", "cpu": [ "arm64" ], @@ -4506,9 +4532,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.0.tgz", - "integrity": "sha512-8FvEl3w2ExmpcOmX5RJD0yqXcVSOqAJJUJ29Lca29Ik+3zPS1yFimr2fr5JSZ4Z5gt8/d7WqycpgkX9nocijSw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", "cpu": [ "x64" ], @@ -4519,9 +4545,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.0.tgz", - "integrity": "sha512-lHoKYaRwd4gge+IpqJHCY+8Vc3hhdJfU6ukFnnrJasEBUvVlydP8PuwndbWfGkdgSvZhHfSEw6urrlBj0TSSfg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", "cpu": [ "arm" ], @@ -4532,9 +4571,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.0.tgz", - "integrity": "sha512-JbEPfhndYeWHfOSeh4DOFvNXrj7ls9S/2omijVsao+LBPTPayT1uKcK3dHW3MwDJ7KO11t9m2cVTqXnTKpeaiw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", "cpu": [ "arm64" ], @@ -4545,9 +4584,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.0.tgz", - "integrity": "sha512-ahqcSXLlcV2XUBM3/f/C6cRoh7NxYA/W7Yzuv4bDU1YscTFw7ay4LmD7l6OS8EMhTNvcrWGkEettL1Bhjf+B+w==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", "cpu": [ "arm64" ], @@ -4557,10 +4596,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.0.tgz", - "integrity": "sha512-uwvOYNtLw8gVtrExKhdFsYHA/kotURUmZYlinH2VcQxNCQJeJXnkmWgw2hI9Xgzhgu7J9QvWiq9TtTVwWMDa+w==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", "cpu": [ "riscv64" ], @@ -4570,10 +4622,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.0.tgz", - "integrity": "sha512-m6pkSwcZZD2LCFHZX/zW2aLIISyzWLU3hrLLzQKMI12+OLEzgruTovAxY5sCZJkipklaZqPy/2bEEBNjp+Y7xg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", "cpu": [ "x64" ], @@ -4584,9 +4649,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.0.tgz", - "integrity": "sha512-VFAC1RDRSbU3iOF98X42KaVicAfKf0m0OvIu8dbnqhTe26Kh6Ym9JrDulz7Hbk7/9zGc41JkV02g+p3BivOdAg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", "cpu": [ "x64" ], @@ -4597,9 +4662,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.0.tgz", - "integrity": "sha512-9jPgMvTKXARz4inw6jezMLA2ihDBvgIU9Ml01hjdVpOcMKyxFBJrn83KVQINnbeqDv0+HdO1c09hgZ8N0s820Q==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", "cpu": [ "arm64" ], @@ -4610,9 +4675,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.0.tgz", - "integrity": "sha512-WE4pT2kTXQN2bAv40Uog0AsV7/s9nT9HBWXAou8+++MBCnY51QS02KYtm6dQxxosKi1VIz/wZIrTQO5UP2EW+Q==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", "cpu": [ "ia32" ], @@ -4623,9 +4688,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.0.tgz", - "integrity": "sha512-aPP5Q5AqNGuT0tnuEkK/g4mnt3ZhheiXrDIiSVIHN9mcN21OyXDVbEMqmXPE7e2OplNLDkcvV+ZoGJa2ZImFgw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], @@ -8268,9 +8333,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", - "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -8280,28 +8345,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.9", - "@esbuild/android-arm64": "0.19.9", - "@esbuild/android-x64": "0.19.9", - "@esbuild/darwin-arm64": "0.19.9", - "@esbuild/darwin-x64": "0.19.9", - "@esbuild/freebsd-arm64": "0.19.9", - "@esbuild/freebsd-x64": "0.19.9", - "@esbuild/linux-arm": "0.19.9", - "@esbuild/linux-arm64": "0.19.9", - "@esbuild/linux-ia32": "0.19.9", - "@esbuild/linux-loong64": "0.19.9", - "@esbuild/linux-mips64el": "0.19.9", - "@esbuild/linux-ppc64": "0.19.9", - "@esbuild/linux-riscv64": "0.19.9", - "@esbuild/linux-s390x": "0.19.9", - "@esbuild/linux-x64": "0.19.9", - "@esbuild/netbsd-x64": "0.19.9", - "@esbuild/openbsd-x64": "0.19.9", - "@esbuild/sunos-x64": "0.19.9", - "@esbuild/win32-arm64": "0.19.9", - "@esbuild/win32-ia32": "0.19.9", - "@esbuild/win32-x64": "0.19.9" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -11504,9 +11570,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -11524,7 +11590,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -11809,6 +11875,36 @@ } } }, + "node_modules/react-router": { + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz", + "integrity": "sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==", + "dependencies": { + "@remix-run/router": "1.17.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.0.tgz", + "integrity": "sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==", + "dependencies": { + "@remix-run/router": "1.17.0", + "react-router": "6.24.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -12062,10 +12158,13 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.0.tgz", - "integrity": "sha512-bUHW/9N21z64gw8s6tP4c88P382Bq/L5uZDowHlHx6s/QWpjJXivIAbEw6LZthgSvlEizZBfLC4OAvWe7aoF7A==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, @@ -12074,19 +12173,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.0", - "@rollup/rollup-android-arm64": "4.9.0", - "@rollup/rollup-darwin-arm64": "4.9.0", - "@rollup/rollup-darwin-x64": "4.9.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.0", - "@rollup/rollup-linux-arm64-gnu": "4.9.0", - "@rollup/rollup-linux-arm64-musl": "4.9.0", - "@rollup/rollup-linux-riscv64-gnu": "4.9.0", - "@rollup/rollup-linux-x64-gnu": "4.9.0", - "@rollup/rollup-linux-x64-musl": "4.9.0", - "@rollup/rollup-win32-arm64-msvc": "4.9.0", - "@rollup/rollup-win32-ia32-msvc": "4.9.0", - "@rollup/rollup-win32-x64-msvc": "4.9.0", + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", "fsevents": "~2.3.2" } }, @@ -12354,9 +12456,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -13144,14 +13246,14 @@ "integrity": "sha512-CTpAkEVXMNJl2ojgtpLXHgz23dh8z81u6/HEPiQFOvBc/c2pde6TVHmH4uwY0d/GLF3tb7+VDAj4+2eJaQSdZQ==" }, "node_modules/vite": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", - "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", + "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" diff --git a/package.json b/package.json index d5510935c..0f6da351a 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,8 @@ "react-dom": "^18.3.1", "react-icons": "^4.12.0", "react-intl": "^6.6.8", + "react-router": "^6.24.0", + "react-router-dom": "^6.24.0", "smoothie": "^1.36.1", "three": "^0.152.2", "uuid4": "^2.0.3" diff --git a/src/App.tsx b/src/App.tsx index e8b1f012a..028d7b9ba 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,85 @@ -const App = () =>

        Hi!

        ; +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +import { ChakraProvider } from "@chakra-ui/react"; +import React, { ReactNode, useMemo } from "react"; +import { + Outlet, + RouterProvider, + ScrollRestoration, + createBrowserRouter, +} from "react-router-dom"; +import { ConsentProvider } from "./compliance"; +import ErrorBoundary from "./components/ErrorBoundary"; +import ErrorHandlerErrorView from "./components/ErrorHandlerErrorView"; +import NotFound from "./components/NotFound"; +import TranslationProvider from "./messages/TranslationProvider"; +import SettingsProvider from "./settings"; +import theme from "./theme/theme"; +import HomePage from "./pages/HomePage"; +import { createHomePageUrl } from "./urls"; + +export interface ProviderLayoutProps { + children: ReactNode; +} + +const Providers = ({ children }: ProviderLayoutProps) => { + return ( + + + + + + {children} + + + + + + ); +}; + +const Layout = () => { + return ( + // We use this even though we have errorElement as this does logging. + + + + + ); +}; + +const createRouter = () => { + return createBrowserRouter([ + { + id: "root", + path: "", + element: , + // 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. + errorElement: , + children: [ + { + path: createHomePageUrl(), + element: , + }, + { + path: "*", + element: , + }, + ], + }, + ]); +}; + +const App = () => { + const router = useMemo(createRouter, []); + return ( + + + + ); +}; export default App; diff --git a/src/compliance.tsx b/src/compliance.tsx new file mode 100644 index 000000000..1866c2863 --- /dev/null +++ b/src/compliance.tsx @@ -0,0 +1,89 @@ +// We use the shared-assets consent API on window, which currently lacks types. +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { + ReactNode, + createContext, + useContext, + useEffect, + useState, +} from "react"; + +export interface CookieConsent { + analytics: boolean; + functional: boolean; +} + +export const consentContext = createContext( + undefined +); + +const config = { + ga: + import.meta.env.VITE_STAGE === "PRODUCTION" || + import.meta.env.VITE_STAGE === "STAGING" + ? {} + : undefined, + custom: [ + { + type: "cookie", + category: "essential", + name: "pgs-auth-jwt", + domain: "data.microbit.org", + purpose: + "Session information so we know what class code you are signed in as", + }, + { + type: "local", + category: "essential", + name: "settings", + domain: "data.microbit.org", + purpose: + "Used to store your settings and remember which dialogs you've opted not to be shown in future", + }, + ], +}; + +const showConsent = ( + { userTriggered }: { userTriggered: boolean } = { userTriggered: false } +) => { + const w = window as any; + w.commonConsent?.show({ userTriggered, config }); +}; + +const hideConsent = () => { + const w = window as any; + w.commonConsent?.hide(); +}; + +export const manageCookies = () => showConsent({ userTriggered: true }); + +export const ConsentProvider = ({ children }: { children: ReactNode }) => { + const [value, setValue] = useState(undefined); + useEffect(() => { + const w = window as any; + const updateListener = (event: CustomEvent) => { + setValue(event.detail); + }; + w.addEventListener("consentchange", updateListener); + if (w.commonConsent) { + showConsent(); + } else { + w.addEventListener("consentinit", showConsent); + } + return () => { + w.removeEventListener("consentchange", updateListener); + w.removeEventListener("consentinit", showConsent); + hideConsent(); + }; + }, []); + + return ( + {children} + ); +}; +export const useConsent = () => { + return useContext(consentContext); +}; diff --git a/src/components/AboutDialog.tsx b/src/components/AboutDialog.tsx new file mode 100644 index 000000000..1a730a836 --- /dev/null +++ b/src/components/AboutDialog.tsx @@ -0,0 +1,149 @@ +import { + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalOverlay, +} from "@chakra-ui/modal"; +import { + AspectRatio, + Box, + Button, + Center, + Image, + Link, + ModalCloseButton, + SimpleGrid, + Table, + TableCaption, + Tbody, + Td, + Text, + Tr, + useClipboard, + VStack, +} from "@chakra-ui/react"; +import { RiFileCopy2Line } from "react-icons/ri"; +import AppLogo from "./AppLogo"; +import microbitHeartImage from "../images/microbit-heart.png"; +import { FormattedMessage, useIntl } from "react-intl"; +import { ReactNode } from "react"; + +interface AboutDialogProps { + appName: string; + isOpen: boolean; + onClose: () => void; + finalFocusRef?: React.RefObject; +} + +/** + * An about dialog with version information. + */ +const AboutDialog = ({ + isOpen, + onClose, + appName, + finalFocusRef, +}: AboutDialogProps) => { + const versionInfo = [ + { + name: `micro:bit ${appName}`, + value: import.meta.env.VITE_VERSION, + }, + ]; + + const clipboardVersion = versionInfo + .map((x) => `${x.name} ${x.value}`) + .join("\n"); + + const { hasCopied, onCopy } = useClipboard(clipboardVersion); + const intl = useIntl(); + return ( + + + + + + +
        + +
        + + ( + + {chunks} + + ), + }} + /> + + + + + {intl.formatMessage({ + + + + + + {versionInfo.map((v) => ( + + + + + ))} + + + + +
        {v.name}{v.value}
        + +
        +
        +
        +
        + + + +
        +
        +
        + ); +}; + +export default AboutDialog; diff --git a/src/components/ActionBar.tsx b/src/components/ActionBar.tsx new file mode 100644 index 000000000..d84c7d8be --- /dev/null +++ b/src/components/ActionBar.tsx @@ -0,0 +1,48 @@ +import { BoxProps, HStack } from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { headerHeight } from "../theme/constants/dimensions"; +import { brandGrey } from "../theme/constants/colors"; + +export type ToolbarVariant = "full-screen" | "primary"; + +const styles = { + primary: { bgColor: "green.500", h: headerHeight, minH: headerHeight }, + "full-screen": { bgColor: brandGrey, h: "4rem" }, +}; + +export interface ActionBarProps extends BoxProps { + itemsLeft?: ReactNode; + itemsCenter?: ReactNode; + itemsRight?: ReactNode; + variant?: ToolbarVariant; +} + +const ActionBar = ({ + itemsLeft, + itemsCenter, + itemsRight, + variant = "primary", + ...rest +}: ActionBarProps) => { + return ( + <> + + + {itemsLeft} + + {itemsCenter && {itemsCenter}} + + {itemsRight} + + + + ); +}; + +export default ActionBar; diff --git a/src/components/AppLogo.tsx b/src/components/AppLogo.tsx new file mode 100644 index 000000000..8f97c03bf --- /dev/null +++ b/src/components/AppLogo.tsx @@ -0,0 +1,43 @@ +import { As, Divider, Heading, HStack } from "@chakra-ui/react"; +import MicrobitLogo from "./MicrobitLogo"; + +const AppLogo = ({ + color = "#FFF", + name, + as, +}: { + color?: string; + beta?: boolean; + as?: As; + name: string; +}) => ( + + + + + + {name} + + + +); + +export default AppLogo; diff --git a/src/components/BackArrow.tsx b/src/components/BackArrow.tsx new file mode 100644 index 000000000..052acd01c --- /dev/null +++ b/src/components/BackArrow.tsx @@ -0,0 +1,16 @@ +const BackArrow = () => ( + + + +); + +export default BackArrow; diff --git a/src/components/DefaultPageLayout.tsx b/src/components/DefaultPageLayout.tsx new file mode 100644 index 000000000..c9056929f --- /dev/null +++ b/src/components/DefaultPageLayout.tsx @@ -0,0 +1,94 @@ +import { + Box, + Flex, + HStack, + IconButton, + Menu, + MenuButton, + MenuDivider, + MenuList, + VStack, +} from "@chakra-ui/react"; +import { ReactNode, useEffect, useRef } from "react"; +import { RiMenuLine } from "react-icons/ri"; +import { useIntl } from "react-intl"; +import { APP_NAME } from "../constants"; +import ActionBar from "./ActionBar"; +import AppLogo from "./AppLogo"; +import HelpMenu from "./HelpMenu"; +import LanguageMenuItem from "./LanguageMenuItem"; +import SettingsMenu from "./SettingsMenu"; + +interface DefaultPageLayoutProps { + titleId: string; + children: ReactNode; + toolbarItemsRight?: ReactNode; + toolbarItemsRightMenu?: ReactNode; + layoutBreakpointOverride?: string; +} + +const DefaultPageLayout = ({ + titleId, + children, + toolbarItemsRight, + toolbarItemsRightMenu, + layoutBreakpointOverride, +}: DefaultPageLayoutProps) => { + const toolbarHamburgerRef = useRef(null); + const intl = useIntl(); + + useEffect(() => { + document.title = intl.formatMessage({ id: titleId }); + }, [intl, titleId]); + + return ( + <> + + } + itemsRight={ + <> + + {toolbarItemsRight} + + + + + + + + } + variant="plain" + size="lg" + fontSize="xl" + /> + + {toolbarItemsRightMenu} + {toolbarItemsRightMenu && } + + + + + + + } + /> + + {children} + + + + ); +}; + +export default DefaultPageLayout; diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx new file mode 100644 index 000000000..9079a286e --- /dev/null +++ b/src/components/ErrorBoundary.tsx @@ -0,0 +1,39 @@ +import React, { ReactNode } from "react"; +import ErrorHandlerErrorView from "./ErrorHandlerErrorView"; + +interface ErrorBoundaryState { + hasError: boolean; +} + +interface ErrorBoundaryProps { + children?: ReactNode; +} + +// Error boundary used for MyData apps and initialization errors. +// Otherwise we use React Router's errorElement. +class ErrorBoundary extends React.Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } + + componentDidCatch() { + // TODO: reinstate logging! + } + + static getDerivedStateFromError() { + return { hasError: true }; + } + + render() { + if (this.state.hasError) { + return ; + } + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/components/ErrorHandlerErrorView.tsx b/src/components/ErrorHandlerErrorView.tsx new file mode 100644 index 000000000..d1d3c7ab7 --- /dev/null +++ b/src/components/ErrorHandlerErrorView.tsx @@ -0,0 +1,38 @@ +import { Button, Text, VStack } from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { FormattedMessage } from "react-intl"; +import ErrorPage from "./ErrorPage"; +import Link from "./Link"; + +const ErrorHandlerErrorView = () => { + return ( + + + + ( + + {chunks} + + ), + }} + /> + + + + + + + ); +}; + +export default ErrorHandlerErrorView; diff --git a/src/components/ErrorPage.tsx b/src/components/ErrorPage.tsx new file mode 100644 index 000000000..d54e51138 --- /dev/null +++ b/src/components/ErrorPage.tsx @@ -0,0 +1,22 @@ +import { Heading, VStack } from "@chakra-ui/react"; +import { ReactNode } from "react"; + +type Props = { title: string; children: ReactNode }; + +const ErrorPage = ({ title, children }: Props) => ( + + + {title} + + {children} + +); + +export default ErrorPage; diff --git a/src/components/HelpMenu.tsx b/src/components/HelpMenu.tsx new file mode 100644 index 000000000..8507551f5 --- /dev/null +++ b/src/components/HelpMenu.tsx @@ -0,0 +1,125 @@ +import { + Box, + IconButton, + Menu, + MenuButton, + MenuDivider, + MenuItem, + MenuList, + Portal, + useDisclosure, +} from "@chakra-ui/react"; +import { + RiExternalLinkLine, + RiInformationLine, + RiQuestionLine, +} from "react-icons/ri"; +import { MdOutlineCookie } from "react-icons/md"; +import AboutDialog from "./AboutDialog"; +import { FormattedMessage, useIntl } from "react-intl"; +import { useRef } from "react"; +import { manageCookies } from "../compliance"; + +interface HelpMenuProps { + isMobile?: boolean; + appName: string; + mode: "default" | "nextgen"; + cookies?: boolean; +} + +/** + * A help button that triggers a drop-down menu with actions. + */ +const HelpMenu = ({ + isMobile, + appName, + mode, + cookies, + ...rest +}: HelpMenuProps) => { + const aboutDialogDisclosure = useDisclosure(); + const intl = useIntl(); + const MenuButtonRef = useRef(null); + + return ( + + + + } + variant="plain" + isRound + _focusVisible={{ + boxShadow: "outlineDark", + }} + /> + + + } + > + + + + } + > + + + } + > + + + {cookies && ( + } + > + + + )} + + } + onClick={aboutDialogDisclosure.onOpen} + > + + + + + + + ); +}; + +export default HelpMenu; diff --git a/src/components/LanguageDialog.tsx b/src/components/LanguageDialog.tsx new file mode 100644 index 000000000..ff2807000 --- /dev/null +++ b/src/components/LanguageDialog.tsx @@ -0,0 +1,117 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { Button } from "@chakra-ui/button"; +import { + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from "@chakra-ui/modal"; +import { HStack, SimpleGrid, Text, VStack } from "@chakra-ui/react"; +import { useCallback } from "react"; +import { FormattedMessage } from "react-intl"; +import { Language, supportedLanguages, useSettings } from "../settings"; + +interface LanguageDialogProps { + isOpen: boolean; + onClose: () => void; + finalFocusRef: React.RefObject; +} + +/** + * Language setting dialog. + */ +export const LanguageDialog = ({ + isOpen, + onClose, + finalFocusRef, +}: LanguageDialogProps) => { + const [settings, setSettings] = useSettings(); + const handleChooseLanguage = useCallback( + (languageId: string) => { + setSettings({ + ...settings, + languageId, + }); + onClose(); + }, + [settings, setSettings, onClose] + ); + const hasPreviewLanguages = supportedLanguages.some((l) => l.preview); + return ( + + + + + + + + + + {supportedLanguages.map((language) => ( + + ))} + + + {hasPreviewLanguages && ( + + * These languages are an early preview of in-progress + translations. + + )} + + + + + + + + ); +}; + +interface LanguageCardProps { + language: Language; + onChooseLanguage: (languageId: string) => void; +} + +const LanguageCard = ({ language, onChooseLanguage }: LanguageCardProps) => { + return ( + + ); +}; diff --git a/src/components/LanguageMenuItem.tsx b/src/components/LanguageMenuItem.tsx new file mode 100644 index 000000000..36c221dbb --- /dev/null +++ b/src/components/LanguageMenuItem.tsx @@ -0,0 +1,30 @@ +import { Icon, MenuItem, useDisclosure } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import { LanguageIcon } from "../icons"; +import { LanguageDialog } from "./LanguageDialog"; + +interface LanguageMenuItemProps { + finalFocusRef: React.RefObject; +} + +const LanguageMenuItem = ({ finalFocusRef }: LanguageMenuItemProps) => { + const languageDisclosure = useDisclosure(); + + return ( + <> + + } + onClick={languageDisclosure.onOpen} + > + + + + ); +}; + +export default LanguageMenuItem; diff --git a/src/components/Link.tsx b/src/components/Link.tsx new file mode 100644 index 000000000..82c5af35b --- /dev/null +++ b/src/components/Link.tsx @@ -0,0 +1,26 @@ +import { + Link as RouterLink, + type LinkProps as RouterLinkProps, +} from "react-router-dom"; +import { chakra } from "@chakra-ui/react"; +import { Ref, forwardRef } from "react"; + +// Adapter to deal with to vs href. +type RouterLinkAdaptedProps = Omit & { href: string }; + +const RouterLinkAdapted = forwardRef(function RouterLinkAdaptedInner( + { href, ...props }: RouterLinkAdaptedProps, + ref: Ref | undefined +) { + return ; +}); + +// https://chakra-ui.com/docs/components/link +const Link = chakra( + RouterLinkAdapted, + { + shouldForwardProp: (prop) => ["href", "target", "children"].includes(prop), + } +); + +export default Link; diff --git a/src/components/MicrobitLogo.tsx b/src/components/MicrobitLogo.tsx new file mode 100644 index 000000000..fb71054da --- /dev/null +++ b/src/components/MicrobitLogo.tsx @@ -0,0 +1,29 @@ +const MicrobitLogo = ({ + alt, + fill = "#fff" +}: { + alt: string; + fill?: string; +}) => ( + + {alt} + + + + + + + +); +export default MicrobitLogo; diff --git a/src/components/NotFound.tsx b/src/components/NotFound.tsx new file mode 100644 index 000000000..7d988429a --- /dev/null +++ b/src/components/NotFound.tsx @@ -0,0 +1,21 @@ +import { createHomePageUrl } from "../urls"; +import ErrorPage from "./ErrorPage"; +import { FormattedMessage, useIntl } from "react-intl"; +import Link from "./Link"; + +interface NotFoundProps { + href?: string; +} + +const NotFound = ({ href }: NotFoundProps) => { + const intl = useIntl(); + return ( + + + + + + ); +}; + +export default NotFound; diff --git a/src/components/SettingsMenu.tsx b/src/components/SettingsMenu.tsx new file mode 100644 index 000000000..4238a060a --- /dev/null +++ b/src/components/SettingsMenu.tsx @@ -0,0 +1,55 @@ +import { + IconButton, + Menu, + MenuButton, + MenuList, + ThemeTypings, +} from "@chakra-ui/react"; +import { RiSettings2Line } from "react-icons/ri"; +import { useIntl } from "react-intl"; +import LanguageMenuItem from "./LanguageMenuItem"; +import { useRef } from "react"; + +interface SettingsMenuProps { + variant?: ThemeTypings["components"]["Menu"]["variants"]; +} + +/** + * A settings button that triggers a drop-down menu with actions. + */ +const SettingsMenu = ({ variant = "plain", ...rest }: SettingsMenuProps) => { + const intl = useIntl(); + const settingsMenuRef = useRef(null); + + return ( + <> + + + } + variant={variant} + isRound + h={12} + w={12} + _focusVisible={{ + boxShadow: variant === "secondary" ? "outline" : "outlineDark", + }} + /> + + + + + + ); +}; + +export default SettingsMenu; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 000000000..67a7a19be --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const APP_NAME = "micro:bit machine learning"; diff --git a/src/environment.ts b/src/environment.ts new file mode 100644 index 000000000..1916de220 --- /dev/null +++ b/src/environment.ts @@ -0,0 +1,10 @@ +// See CI & package.json scripts. +export const version = import.meta.env.VITE_VERSION || "local"; +export type Stage = "local" | "review" | "staging" | "production"; +export const stage: Stage = (() => { + const value = (import.meta.env.VITE_STAGE || "LOCAL").toLowerCase(); + if (["local", "review", "staging", "production"].indexOf(value) === -1) { + throw new Error(`Unknown stage: ${value}`); + } + return value as Stage; +})(); diff --git a/src/flags.test.ts b/src/flags.test.ts new file mode 100644 index 000000000..851d363b2 --- /dev/null +++ b/src/flags.test.ts @@ -0,0 +1,55 @@ +import { flagsForParams } from "./flags"; + +describe("flags", () => { + it("enables nothing in production", () => { + const params = new URLSearchParams([]); + + const flags = flagsForParams("production", params); + + expect(Object.values(flags).every(x => !x)).toEqual(true); + }); + + it("enables by stage", () => { + const params = new URLSearchParams([]); + + const flags = flagsForParams("staging", params); + + expect(flags.exampleOptInA).toEqual(true); + }); + + it("enable specific flag", () => { + const params = new URLSearchParams([["flag", "exampleOptInA"]]); + + const flags = flagsForParams("production", params); + + expect( + Object.entries(flags).every( + ([flag, status]) => (flag === "exampleOptInA") === status + ) + ).toEqual(true); + }); + + it("enable everything", () => { + const params = new URLSearchParams([["flag", "*"]]); + const flags = flagsForParams("production", params); + expect(Object.values(flags).every(x => x)).toEqual(true); + }); + + it("enable nothing", () => { + const params = new URLSearchParams([["flag", "none"]]); + const flags = flagsForParams("review", params); + expect(Object.values(flags).every(x => !x)).toEqual(true); + }); + + it("can combine none with specific enabled flags in review", () => { + const params = new URLSearchParams([ + ["flag", "none"], + ["flag", "exampleOptInB"] + ]); + + const flags = flagsForParams("review", params); + + expect(flags.exampleOptInA).toBe(false); + expect(flags.exampleOptInB).toBe(true); + }); +}); diff --git a/src/flags.ts b/src/flags.ts new file mode 100644 index 000000000..2b50c1c50 --- /dev/null +++ b/src/flags.ts @@ -0,0 +1,64 @@ +/** + * Simple feature flags. + * + * Features disabled here even in preview are not ready for feedback. + * + * Preview features are not ready for general use. + */ +import { Stage, stage as stageFromEnvironment } from "./environment"; + +/** + * A union of the flag names (alphabetical order). + */ +export type Flag = + // Example flags used for testing. + "exampleOptInA" | "exampleOptInB"; + +interface FlagMetadata { + defaultOnStages: Stage[]; + name: Flag; +} + +const allFlags: FlagMetadata[] = [ + // Alphabetical order. + { name: "exampleOptInA", defaultOnStages: ["review", "staging"] }, + { name: "exampleOptInB", defaultOnStages: [] }, +]; + +type Flags = Record; + +// Exposed for testing. +export const flagsForParams = (stage: Stage, params: URLSearchParams) => { + const enableFlags = new Set(params.getAll("flag")); + const allFlagsDefault = enableFlags.has("none") + ? false + : enableFlags.has("*") + ? true + : undefined; + return Object.fromEntries( + allFlags.map((f) => [ + f.name, + isEnabled(f, stage, allFlagsDefault, enableFlags.has(f.name)), + ]) + ) as Flags; +}; + +const isEnabled = ( + f: FlagMetadata, + stage: Stage, + allFlagsDefault: boolean | undefined, + thisFlagOn: boolean +): boolean => { + if (thisFlagOn) { + return true; + } + if (allFlagsDefault !== undefined) { + return allFlagsDefault; + } + return f.defaultOnStages.includes(stage); +}; + +export const flags: Flags = (() => { + const params = new URLSearchParams(window.location.search); + return flagsForParams(stageFromEnvironment, params); +})(); diff --git a/src/hooks/use-storage.ts b/src/hooks/use-storage.ts new file mode 100644 index 000000000..2326d3fea --- /dev/null +++ b/src/hooks/use-storage.ts @@ -0,0 +1,81 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { useCallback, useState } from "react"; + +type storageType = "local" | "session"; + +/** + * Local and session storage-backed state (via JSON serialization). + */ +export function useStorage( + storageType: storageType, + key: string, + defaultValue: T, + validate?: (x: unknown) => x is T +): [T, (value: T) => void] { + const [state, setState] = useState(() => { + const storage = + storageType === "local" + ? localStorageIfPossible() + : sessionStorageIfPossible(); + const value = storage ? storage.getItem(key) : null; + if (value !== null) { + try { + let parsed = JSON.parse(value); + // Ensure we get new top-level defaults. + parsed = { + ...defaultValue, + ...parsed, + }; + // Remove any top-level keys that aren't present in defaults. + const unknownKeys = new Set(Object.keys(parsed)); + Object.keys(defaultValue).forEach((k) => unknownKeys.delete(k)); + unknownKeys.forEach((k) => delete parsed[k]); + + if (validate && !validate(parsed)) { + throw new Error(`Invalid data stored in ${storageType} storage`); + } + + return parsed; + } catch (e) { + // Better than exploding forever. + return defaultValue; + } + } + return defaultValue; + }); + const setAndSaveState = useCallback( + (value: T) => { + const storage = + storageType === "local" + ? localStorageIfPossible() + : sessionStorageIfPossible(); + if (storage) { + storage.setItem(key, JSON.stringify(value)); + } + setState(value); + }, + [setState, key, storageType] + ); + return [state, setAndSaveState]; +} + +const localStorageIfPossible = () => { + try { + return window.localStorage; + } catch (e) { + // Handle possible SecurityError, absent window. + return undefined; + } +}; + +const sessionStorageIfPossible = () => { + try { + return window.sessionStorage; + } catch (e) { + // Handle possible SecurityError, absent window. + return undefined; + } +}; diff --git a/src/icons.ts b/src/icons.ts new file mode 100644 index 000000000..1fbc5b244 --- /dev/null +++ b/src/icons.ts @@ -0,0 +1,4 @@ +import { IconType } from "react-icons"; +import { IoMdGlobe } from "react-icons/io"; + +export const LanguageIcon = IoMdGlobe as IconType; diff --git a/src/imgs/ML_predict.svg b/src/images/ML_predict.svg similarity index 100% rename from src/imgs/ML_predict.svg rename to src/images/ML_predict.svg diff --git a/src/imgs/ML_train.svg b/src/images/ML_train.svg similarity index 100% rename from src/imgs/ML_train.svg rename to src/images/ML_train.svg diff --git a/src/imgs/Makecode_integration.png b/src/images/Makecode_integration.png similarity index 100% rename from src/imgs/Makecode_integration.png rename to src/images/Makecode_integration.png diff --git a/src/imgs/add_data.png b/src/images/add_data.png similarity index 100% rename from src/imgs/add_data.png rename to src/images/add_data.png diff --git a/src/imgs/add_data.svg b/src/images/add_data.svg similarity index 100% rename from src/imgs/add_data.svg rename to src/images/add_data.svg diff --git a/src/imgs/app-name.svg b/src/images/app-name.svg similarity index 100% rename from src/imgs/app-name.svg rename to src/images/app-name.svg diff --git a/src/imgs/aulogo_uk_var2_blue.png b/src/images/aulogo_uk_var2_blue.png similarity index 100% rename from src/imgs/aulogo_uk_var2_blue.png rename to src/images/aulogo_uk_var2_blue.png diff --git a/src/imgs/blank_microbit.svg b/src/images/blank_microbit.svg similarity index 100% rename from src/imgs/blank_microbit.svg rename to src/images/blank_microbit.svg diff --git a/src/imgs/connect-cable.gif b/src/images/connect-cable.gif similarity index 100% rename from src/imgs/connect-cable.gif rename to src/images/connect-cable.gif diff --git a/src/imgs/curve-arrow-up.svg b/src/images/curve-arrow-up.svg similarity index 100% rename from src/imgs/curve-arrow-up.svg rename to src/images/curve-arrow-up.svg diff --git a/src/imgs/data-trainer-thumpnail.png b/src/images/data-trainer-thumpnail.png similarity index 100% rename from src/imgs/data-trainer-thumpnail.png rename to src/images/data-trainer-thumpnail.png diff --git a/src/imgs/data_recorded.png b/src/images/data_recorded.png similarity index 100% rename from src/imgs/data_recorded.png rename to src/images/data_recorded.png diff --git a/src/imgs/data_rep_viz.png b/src/images/data_rep_viz.png similarity index 100% rename from src/imgs/data_rep_viz.png rename to src/images/data_rep_viz.png diff --git a/src/imgs/data_representation.svg b/src/images/data_representation.svg similarity index 100% rename from src/imgs/data_representation.svg rename to src/images/data_representation.svg diff --git a/src/imgs/down_arrow.svg b/src/images/down_arrow.svg similarity index 100% rename from src/imgs/down_arrow.svg rename to src/images/down_arrow.svg diff --git a/src/imgs/greeting-emoji-with-arrow.svg b/src/images/greeting-emoji-with-arrow.svg similarity index 100% rename from src/imgs/greeting-emoji-with-arrow.svg rename to src/images/greeting-emoji-with-arrow.svg diff --git a/src/imgs/led_off.svg b/src/images/led_off.svg similarity index 100% rename from src/imgs/led_off.svg rename to src/images/led_off.svg diff --git a/src/imgs/led_on.svg b/src/images/led_on.svg similarity index 100% rename from src/imgs/led_on.svg rename to src/images/led_on.svg diff --git a/src/imgs/microbit-heart.png b/src/images/microbit-heart.png similarity index 100% rename from src/imgs/microbit-heart.png rename to src/images/microbit-heart.png diff --git a/src/imgs/microbit-logo-black.svg b/src/images/microbit-logo-black.svg similarity index 100% rename from src/imgs/microbit-logo-black.svg rename to src/images/microbit-logo-black.svg diff --git a/src/imgs/microbit-logo.svg b/src/images/microbit-logo.svg similarity index 100% rename from src/imgs/microbit-logo.svg rename to src/images/microbit-logo.svg diff --git a/src/imgs/microbit2_blank.svg b/src/images/microbit2_blank.svg similarity index 100% rename from src/imgs/microbit2_blank.svg rename to src/images/microbit2_blank.svg diff --git a/src/imgs/microbitV1.svg b/src/images/microbitV1.svg similarity index 100% rename from src/imgs/microbitV1.svg rename to src/images/microbitV1.svg diff --git a/src/imgs/microbitV2.svg b/src/images/microbitV2.svg similarity index 100% rename from src/imgs/microbitV2.svg rename to src/images/microbitV2.svg diff --git a/src/imgs/microbit_guide.svg b/src/images/microbit_guide.svg similarity index 100% rename from src/imgs/microbit_guide.svg rename to src/images/microbit_guide.svg diff --git a/src/imgs/microbit_icon.png b/src/images/microbit_icon.png similarity index 100% rename from src/imgs/microbit_icon.png rename to src/images/microbit_icon.png diff --git a/src/imgs/microbit_icon_dual.png b/src/images/microbit_icon_dual.png similarity index 100% rename from src/imgs/microbit_icon_dual.png rename to src/images/microbit_icon_dual.png diff --git a/src/imgs/microbit_record_guide.svg b/src/images/microbit_record_guide.svg similarity index 100% rename from src/imgs/microbit_record_guide.svg rename to src/images/microbit_record_guide.svg diff --git a/src/imgs/microbit_unaltered.svg b/src/images/microbit_unaltered.svg similarity index 100% rename from src/imgs/microbit_unaltered.svg rename to src/images/microbit_unaltered.svg diff --git a/src/imgs/microbit_xyz_arrows.png b/src/images/microbit_xyz_arrows.png similarity index 100% rename from src/imgs/microbit_xyz_arrows.png rename to src/images/microbit_xyz_arrows.png diff --git a/src/imgs/model.svg b/src/images/model.svg similarity index 100% rename from src/imgs/model.svg rename to src/images/model.svg diff --git a/src/imgs/model_2.svg b/src/images/model_2.svg similarity index 100% rename from src/imgs/model_2.svg rename to src/images/model_2.svg diff --git a/src/imgs/model_3.svg b/src/images/model_3.svg similarity index 100% rename from src/imgs/model_3.svg rename to src/images/model_3.svg diff --git a/src/imgs/model_blue.svg b/src/images/model_blue.svg similarity index 100% rename from src/imgs/model_blue.svg rename to src/images/model_blue.svg diff --git a/src/imgs/model_green.svg b/src/images/model_green.svg similarity index 100% rename from src/imgs/model_green.svg rename to src/images/model_green.svg diff --git a/src/imgs/model_old.svg b/src/images/model_old.svg similarity index 100% rename from src/imgs/model_old.svg rename to src/images/model_old.svg diff --git a/src/imgs/red_arrows.svg b/src/images/red_arrows.svg similarity index 100% rename from src/imgs/red_arrows.svg rename to src/images/red_arrows.svg diff --git a/src/imgs/resource-get-started.jpg b/src/images/resource-get-started.jpg similarity index 100% rename from src/imgs/resource-get-started.jpg rename to src/images/resource-get-started.jpg diff --git a/src/imgs/resource-introducing-tool.jpg b/src/images/resource-introducing-tool.jpg similarity index 100% rename from src/imgs/resource-introducing-tool.jpg rename to src/images/resource-introducing-tool.jpg diff --git a/src/imgs/right_arrow.svg b/src/images/right_arrow.svg similarity index 100% rename from src/imgs/right_arrow.svg rename to src/images/right_arrow.svg diff --git a/src/imgs/right_arrow_blue.svg b/src/images/right_arrow_blue.svg similarity index 100% rename from src/imgs/right_arrow_blue.svg rename to src/images/right_arrow_blue.svg diff --git a/src/imgs/search_for_patterns.svg b/src/images/search_for_patterns.svg similarity index 100% rename from src/imgs/search_for_patterns.svg rename to src/images/search_for_patterns.svg diff --git a/src/imgs/select-microbit-bluetooth.png b/src/images/select-microbit-bluetooth.png similarity index 100% rename from src/imgs/select-microbit-bluetooth.png rename to src/images/select-microbit-bluetooth.png diff --git a/src/imgs/select-microbit-web-usb.png b/src/images/select-microbit-web-usb.png similarity index 100% rename from src/imgs/select-microbit-web-usb.png rename to src/images/select-microbit-web-usb.png diff --git a/src/imgs/sidebar_background.svg b/src/images/sidebar_background.svg similarity index 100% rename from src/imgs/sidebar_background.svg rename to src/images/sidebar_background.svg diff --git a/src/imgs/stylised-battery-pack.svg b/src/images/stylised-battery-pack.svg similarity index 100% rename from src/imgs/stylised-battery-pack.svg rename to src/images/stylised-battery-pack.svg diff --git a/src/imgs/stylised-microbit-black.svg b/src/images/stylised-microbit-black.svg similarity index 100% rename from src/imgs/stylised-microbit-black.svg rename to src/images/stylised-microbit-black.svg diff --git a/src/imgs/stylised-microbit-connected.svg b/src/images/stylised-microbit-connected.svg similarity index 100% rename from src/imgs/stylised-microbit-connected.svg rename to src/images/stylised-microbit-connected.svg diff --git a/src/imgs/stylised-two-microbits-black.svg b/src/images/stylised-two-microbits-black.svg similarity index 100% rename from src/imgs/stylised-two-microbits-black.svg rename to src/images/stylised-two-microbits-black.svg diff --git a/src/imgs/stylised-usb-cable.svg b/src/images/stylised-usb-cable.svg similarity index 100% rename from src/imgs/stylised-usb-cable.svg rename to src/images/stylised-usb-cable.svg diff --git a/src/imgs/stylised_computer.svg b/src/images/stylised_computer.svg similarity index 100% rename from src/imgs/stylised_computer.svg rename to src/images/stylised_computer.svg diff --git a/src/imgs/stylised_computer_w_bluetooth.svg b/src/images/stylised_computer_w_bluetooth.svg similarity index 100% rename from src/imgs/stylised_computer_w_bluetooth.svg rename to src/images/stylised_computer_w_bluetooth.svg diff --git a/src/imgs/test_model.png b/src/images/test_model.png similarity index 100% rename from src/imgs/test_model.png rename to src/images/test_model.png diff --git a/src/imgs/test_model_black.svg b/src/images/test_model_black.svg similarity index 100% rename from src/imgs/test_model_black.svg rename to src/images/test_model_black.svg diff --git a/src/imgs/test_model_blue.svg b/src/images/test_model_blue.svg similarity index 100% rename from src/imgs/test_model_blue.svg rename to src/images/test_model_blue.svg diff --git a/src/imgs/train_model.png b/src/images/train_model.png similarity index 100% rename from src/imgs/train_model.png rename to src/images/train_model.png diff --git a/src/imgs/train_model_black.svg b/src/images/train_model_black.svg similarity index 100% rename from src/imgs/train_model_black.svg rename to src/images/train_model_black.svg diff --git a/src/imgs/train_model_blue.svg b/src/images/train_model_blue.svg similarity index 100% rename from src/imgs/train_model_blue.svg rename to src/images/train_model_blue.svg diff --git a/src/imgs/transfer_program_chromeos.gif b/src/images/transfer_program_chromeos.gif similarity index 100% rename from src/imgs/transfer_program_chromeos.gif rename to src/images/transfer_program_chromeos.gif diff --git a/src/imgs/transfer_program_macos.gif b/src/images/transfer_program_macos.gif similarity index 100% rename from src/imgs/transfer_program_macos.gif rename to src/images/transfer_program_macos.gif diff --git a/src/imgs/transfer_program_windows.gif b/src/images/transfer_program_windows.gif similarity index 100% rename from src/imgs/transfer_program_windows.gif rename to src/images/transfer_program_windows.gif diff --git a/src/imgs/v1.svg b/src/images/v1.svg similarity index 100% rename from src/imgs/v1.svg rename to src/images/v1.svg diff --git a/src/imgs/v2.svg b/src/images/v2.svg similarity index 100% rename from src/imgs/v2.svg rename to src/images/v2.svg diff --git a/src/messages/TranslationProvider.tsx b/src/messages/TranslationProvider.tsx new file mode 100644 index 000000000..d80e2ad69 --- /dev/null +++ b/src/messages/TranslationProvider.tsx @@ -0,0 +1,37 @@ +import { useSettings } from "../settings"; +import { IntlProvider, MessageFormatElement } from "react-intl"; +import { ReactNode, useEffect, useState } from "react"; +import { retryAsyncLoad } from "./chunk-util"; + +async function loadLocaleData(locale: string) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return (await import(`./ui.${locale}.json`)).default as Messages; +} + +type Messages = Record | Record; + +interface TranslationProviderProps { + children: ReactNode; +} + +/** + * Provides translation support to the app via react-intl. + */ +const TranslationProvider = ({ children }: TranslationProviderProps) => { + const [{ languageId }] = useSettings(); + // If the messages are for a different language (or missing) then reload them + const [messages, setMessages] = useState(); + useEffect(() => { + const load = async () => { + setMessages(await retryAsyncLoad(() => loadLocaleData(languageId))); + }; + void load(); + }, [languageId]); + return messages ? ( + + {children} + + ) : null; +}; + +export default TranslationProvider; diff --git a/src/messages/chunk-util.test.ts b/src/messages/chunk-util.test.ts new file mode 100644 index 000000000..322ae0ebe --- /dev/null +++ b/src/messages/chunk-util.test.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/require-await */ +import { retryAsyncLoad } from "./chunk-util"; + +describe("retryAsyncLoad", () => { + it("retry, fail", async () => { + const waitTimes: number[] = []; + const waiter = (waitTime: number) => { + waitTimes.push(waitTime); + return Promise.resolve(); + }; + await expect(() => + retryAsyncLoad(async () => { + throw new Error("oops"); + }, waiter) + ).rejects.toThrow("oops"); + expect(waitTimes).toEqual([250, 750, 2250, 6750]); + }); + it("retry, pass", async () => { + const waiter = () => Promise.resolve(); + let i = 0; + expect( + await retryAsyncLoad(async () => { + if (i === 4) { + return "yay"; + } + i++; + throw new Error("oops"); + }, waiter) + ).toEqual("yay"); + }); +}); diff --git a/src/messages/chunk-util.ts b/src/messages/chunk-util.ts new file mode 100644 index 000000000..ac7582607 --- /dev/null +++ b/src/messages/chunk-util.ts @@ -0,0 +1,24 @@ +const defaultWaiter = (waitTime: number): Promise => + new Promise(resolve => setTimeout(resolve, waitTime)); + +export const retryAsyncLoad = async ( + load: () => Promise, + waiter: (waitTime: number) => Promise = defaultWaiter +): Promise => { + let waitTime = 250; + let attempts = 0; + // eslint-disable-next-line no-constant-condition + while (true) { + try { + // Must await here! + return await load(); + } catch (e) { + if (attempts === 4) { + throw e; + } + await waiter(waitTime); + attempts++; + waitTime *= 3; + } + } +}; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx new file mode 100644 index 000000000..277d5bc3e --- /dev/null +++ b/src/pages/HomePage.tsx @@ -0,0 +1,11 @@ +import DefaultPageLayout from "../components/DefaultPageLayout"; + +const HomePage = () => { + return ( + +

        Hi!

        +
        + ); +}; + +export default HomePage; diff --git a/src/settings.tsx b/src/settings.tsx new file mode 100644 index 000000000..9b3ab79d7 --- /dev/null +++ b/src/settings.tsx @@ -0,0 +1,85 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { createContext, ReactNode, useContext } from "react"; +import { useStorage } from "./hooks/use-storage"; +import { stage } from "./environment"; + +export interface Language { + id: string; + name: string; + enName: string; + preview?: boolean; +} + +// Tag new languages with `preview: true` to enable for beta only. +export const allLanguages: Language[] = [ + { + id: "en", + name: "English", + enName: "English", + }, +]; + +export const supportedLanguages: Language[] = allLanguages.filter( + (l) => stage !== "production" || !l.preview +); + +export const getLanguageFromQuery = (): string => { + const searchParams = new URLSearchParams(window.location.search); + const l = searchParams.get("l"); + const supportedLanguage = supportedLanguages.find((x) => x.id === l); + return supportedLanguage?.id || supportedLanguages[0].id; +}; + +export const defaultSettings: Settings = { + languageId: getLanguageFromQuery(), +}; + +export const isValidSettingsObject = (value: unknown): value is Settings => { + if (typeof value !== "object") { + return false; + } + const object = value as any; + if ( + object.languageId && + !supportedLanguages.find((x) => x.id === object.languageId) + ) { + return false; + } + return true; +}; + +export interface Settings { + languageId: string; +} + +type SettingsContextValue = [Settings, (settings: Settings) => void]; + +const SettingsContext = createContext( + undefined +); + +export const useSettings = (): SettingsContextValue => { + const settings = useContext(SettingsContext); + if (!settings) { + throw new Error("Missing provider"); + } + return settings; +}; + +const SettingsProvider = ({ children }: { children: ReactNode }) => { + const settings = useStorage( + "local", + "settings", + defaultSettings, + isValidSettingsObject + ); + return ( + + {children} + + ); +}; + +export default SettingsProvider; diff --git a/src/theme/colors.ts b/src/theme/colors.ts new file mode 100644 index 000000000..db415e837 --- /dev/null +++ b/src/theme/colors.ts @@ -0,0 +1,94 @@ +const colors = { + // Brand guidelines say: + // "Each of the main primary colours can be tinted by 80%, 50%, 30%, 20% and 10% if needed." + // We've assumed this means tints and shades. + // Colours created via e.g. + // https://maketintsandshades.com/#6C4BC1 + purple: { + 50: "#e2dbf3", // 80% tint + 100: "#b6a5e0", // 50% tint + 200: "#9881d4", // 30% tint + 300: "#896fcd", // 20% tint + 400: "#7b5dc7", // 10% tint + // Brand color + 500: "#6c4bc1", + // Unused for now: '#6144ae', // 10% shade + 600: "#563c9a", // 20% shade + 700: "#4c3587", // 30% shade + 800: "#362661", // 50% shade + 900: "#160f27", // 80% shade + }, + teal: { + 50: "#e5f5f3", // 80% tint + 100: "#bde6e1", // 50% tint + 200: "#a3dcd4", // 30% tint + 300: "#95d7ce", // 20% tint + 400: "#88d2c8", // 10% tint + // Brand color + 500: "#7bcdc2", // note this is 400 in the other scale + // Unused for now: '#6fb9af', // 10% shade + 600: "#62a49b", // 20% shade + 700: "#569088", // 30% shade + 800: "#3e6761", // 50% shade + 900: "#192927", // 80% shade + }, + blue: { + 50: "#d4eaf7", // 80% tint + 100: "#95caeb", // 50% tint + 200: "#6ab4e2", // 30% tint + 300: "#55a9de", // 20% tint + 400: "#3f9fda", // 10% tint + // Brand color + 500: "#2a94d6", + // Unused for now: '#2685c1', // 10% shade + 600: "#2276ab", // 20% shade + 700: "#1d6896", // 30% shade + 800: "#154a6b", // 50% shade + 900: "#081e2b", // 80% shade + }, + green: { + 50: "#cceccc", // 80% tint + 100: "#80d080", // 50% tint + 200: "#4dbd4d", // 30% tint + 300: "#33b333", // 20% tint + 400: "#1aaa1a", // 10% tint + // Brand color + 500: "#00a000", + // Unused for now: '#009000', // 10% shade + 600: "#008000", // 20% shade + 700: "#007000", // 30% shade + 800: "#005000", // 50% shade + 900: "#002000", // 80% shade + }, + // Not calling this red as we use Chakra's reds for form errors etc. + pink: { + 50: "#f5cde0", // 80% tint + 100: "#e681b2", // 50% tint + 200: "#dc4f93", // 30% tint + 300: "#d73584", // 20% tint + 400: "#d21c74", // 10% tint + // Brand color + 500: "#cd0365", + // Unused for now: '#b9035b', // 10% shade + 600: "#a40251", // 20% shade + 700: "#900247", // 30% shade + 800: "#670233", // 50% shade + 900: "#290114", // 80% shade + }, + orange: { + 50: "#fae0de", // 80% tint + 100: "#f3b2ae", // 50% tint + 200: "#ee938d", // 30% tint + 300: "#ec837d", // 20% tint + 400: "#e9746c", // 10% tint + // Brand color + 500: "#e7645c", + // Unused for now: '#d05a53', // 10% shade + 600: "#b9504a", // 20% shade + 700: "#a24640", // 30% shade + 800: "#74322e", // 50% shade + 900: "#2e1412", // 80% shade + }, +}; + +export default colors; diff --git a/src/theme/components/alert.ts b/src/theme/components/alert.ts new file mode 100644 index 000000000..777b41486 --- /dev/null +++ b/src/theme/components/alert.ts @@ -0,0 +1,26 @@ +import { theme } from "@chakra-ui/react"; +import { StyleConfig, StyleFunctionProps } from "@chakra-ui/theme-tools"; + +const Alert: StyleConfig = { + variants: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + toast: (props: StyleFunctionProps) => { + const base = { + ...theme.components.Alert.variants!.solid(props), + }; + return { + ...base, + container: { + ...base.container, + bg: + props.status === "success" || props.status === "info" + ? "teal.800" + : "red.600", + }, + }; + }, + }, +}; + +export default Alert; diff --git a/src/theme/components/avatar.ts b/src/theme/components/avatar.ts new file mode 100644 index 000000000..bd75909a2 --- /dev/null +++ b/src/theme/components/avatar.ts @@ -0,0 +1,21 @@ +import { avatarAnatomy } from "@chakra-ui/anatomy"; +import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react"; + +const { + definePartsStyle, + defineMultiStyleConfig +} = createMultiStyleConfigHelpers(avatarAnatomy.keys); + +const md2 = defineStyle({ + width: 10, + height: 10, + fontSize: "md" +}); + +const sizes = { + "2md": definePartsStyle({ container: md2 }) +}; + +const avatarTheme = defineMultiStyleConfig({ sizes }); + +export default avatarTheme; diff --git a/src/theme/components/button.ts b/src/theme/components/button.ts new file mode 100644 index 000000000..661ea357f --- /dev/null +++ b/src/theme/components/button.ts @@ -0,0 +1,112 @@ +import { theme, StyleFunctionProps } from "@chakra-ui/react"; +import { StyleConfig } from "@chakra-ui/theme-tools"; + +const Button: StyleConfig = { + baseStyle: { + borderRadius: "button", + }, + variants: { + unstyled: { + borderRadius: "unset", + }, + zoom: (props: StyleFunctionProps) => { + const base = { + ...theme.components.Button.variants!.solid(props), + }; + return { + ...base, + _hover: { + ...base._hover, + backgroundColor: "gray.400", + }, + _active: { + ...base._active, + backgroundColor: "gray.500", + }, + }; + }, + secondary: () => ({ + borderWidth: "2px", + borderColor: "black", + color: "black", + bg: "transparent", + _hover: { + bg: "blackAlpha.50", + }, + _active: { + bg: "blackAlpha.100", + }, + }), + ghost: () => ({ + color: "black", + bg: "transparent", + _hover: { + bg: "blackAlpha.50", + }, + _active: { + bg: "blackAlpha.100", + }, + }), + primary: () => ({ + color: "white", + bg: "black", + _hover: { + bg: "blackAlpha.800", + _disabled: { + bg: "black", + }, + }, + _active: { + bg: "blackAlpha.700", + }, + }), + toolbar: () => ({ + color: "black", + bg: "white", + _hover: { + bg: "whiteAlpha.900", + _disabled: { + bg: "white", + }, + }, + _active: { + bg: "whiteAlpha.800", + }, + _focusVisible: { + boxShadow: "outlineDark", + }, + }), + sidebar: (props: StyleFunctionProps) => { + const base = { + ...theme.components.Button.variants!.ghost(props), + }; + return { + ...base, + _hover: { + ...base._hover, + bg: "white", + color: "gray.700", + _disabled: { + bg: base.bg, + }, + }, + _active: { + ...base._hover, + bg: "white", + color: "gray.800", + }, + }; + }, + language: () => ({ + borderWidth: "2px", + borderColor: "gray.200", + color: "green.500", + _hover: { + color: "green.600", + bg: "gray.100", + }, + }), + }, +}; + +export default Button; diff --git a/src/theme/components/heading.ts b/src/theme/components/heading.ts new file mode 100644 index 000000000..e365f764e --- /dev/null +++ b/src/theme/components/heading.ts @@ -0,0 +1,15 @@ +const Heading = { + variants: { + label: { + fontSize: "4xl", + color: "#cd0365" + }, + subtitle: { + fontSize: "xl", + fontWeight: "normal", + color: "#cd0365" + } + } +}; + +export default Heading; diff --git a/src/theme/components/text.ts b/src/theme/components/text.ts new file mode 100644 index 000000000..68ab483ef --- /dev/null +++ b/src/theme/components/text.ts @@ -0,0 +1,15 @@ +const Text = { + sizes: { + sm: { + fontSize: "sm" + }, + md: { + fontSize: "md" + } + }, + defaultProps: { + size: "md" + } +}; + +export default Text; diff --git a/src/theme/components/tooltip.ts b/src/theme/components/tooltip.ts new file mode 100644 index 000000000..b9aaa65d5 --- /dev/null +++ b/src/theme/components/tooltip.ts @@ -0,0 +1,9 @@ +import { StyleConfig } from '@chakra-ui/theme-tools'; + +const Tooltip: StyleConfig = { + baseStyle: { + fontSize: 'md', + }, +}; + +export default Tooltip; diff --git a/src/theme/constants/colors.ts b/src/theme/constants/colors.ts new file mode 100644 index 000000000..9bde76f7b --- /dev/null +++ b/src/theme/constants/colors.ts @@ -0,0 +1,5 @@ +// Use the theme colours rather than adding here. + +// Used for the full screen modal header. Would be good +// to reconcile with the theme. Chakra's grays aren't pure. +export const brandGrey = "#e5e5e5"; diff --git a/src/theme/constants/dimensions.ts b/src/theme/constants/dimensions.ts new file mode 100644 index 000000000..77e5659fd --- /dev/null +++ b/src/theme/constants/dimensions.ts @@ -0,0 +1 @@ +export const headerHeight = "64px"; diff --git a/src/theme/default-graph.ts b/src/theme/default-graph.ts new file mode 100644 index 000000000..d5cf28804 --- /dev/null +++ b/src/theme/default-graph.ts @@ -0,0 +1,14 @@ +export const defaultGraphColors = [ + // Green + "rgb(0, 200, 0)", + // Blue + "rgb(62, 182, 253)", + // Pink + "rgb(205, 3, 101)", + // Salmon + "rgb(231, 100, 92)", + // Purple + "rgb(108, 75, 193)", + // Teal + "rgb(123, 205, 194)", +]; diff --git a/src/theme/fonts.ts b/src/theme/fonts.ts new file mode 100644 index 000000000..7a5a9e73c --- /dev/null +++ b/src/theme/fonts.ts @@ -0,0 +1,6 @@ +const fonts = { + heading: "GT Walsheim, sans-serif", + body: "Helvetica Now, sans-serif", +}; + +export default fonts; diff --git a/src/theme/graph.ts b/src/theme/graph.ts new file mode 100644 index 000000000..aeccd9db2 --- /dev/null +++ b/src/theme/graph.ts @@ -0,0 +1,17 @@ +// micro:bit brand colors +export const defaultGraphColors = [ + // green.500 + "#00a000", + // teal.500 + "#7bcdc2", + // pink.500 + "#cd0365", + // blue.500 + "#2a94d6", + // purple.500 + "#6c4bc1", + // Grey 50% + "#808080", + // orange.500 + "#e7645c", +]; diff --git a/src/theme/radii.ts b/src/theme/radii.ts new file mode 100644 index 000000000..f254d5784 --- /dev/null +++ b/src/theme/radii.ts @@ -0,0 +1,6 @@ +const radii = { + // Design radius for buttons and other larger items + button: '2rem', +}; + +export default radii; diff --git a/src/theme/shadows.ts b/src/theme/shadows.ts new file mode 100644 index 000000000..7532a2d7d --- /dev/null +++ b/src/theme/shadows.ts @@ -0,0 +1,6 @@ +const shadows = { + outline: "0 0 0 4px rgba(66, 153, 225, 0.6)", + outlineDark: "0 0 0 4px rgba(66, 153, 225)" +}; + +export default shadows; diff --git a/src/theme/theme.ts b/src/theme/theme.ts new file mode 100644 index 000000000..37edee589 --- /dev/null +++ b/src/theme/theme.ts @@ -0,0 +1,35 @@ +import { extendTheme, withDefaultVariant } from "@chakra-ui/react"; +import colors from "./colors"; +import Alert from "./components/alert"; +import Button from "./components/button"; +import Text from "./components/text"; +import Heading from "./components/heading"; +import Tooltip from "./components/tooltip"; +import fonts from "./fonts"; +import radii from "./radii"; +import Avatar from "./components/avatar"; +import shadows from "./shadows"; + +// See https://chakra-ui.com/docs/theming/customize-theme +const overrides = { + fonts, + radii, + colors, + shadows, + components: { + Alert, + Button, + Heading, + Tooltip, + Text, + Avatar, + }, +}; + +export default extendTheme( + overrides, + withDefaultVariant({ + variant: "secondary", + components: ["Button", "IconButton"], + }) +); diff --git a/src/urls.ts b/src/urls.ts new file mode 100644 index 000000000..9496174de --- /dev/null +++ b/src/urls.ts @@ -0,0 +1,7 @@ +export const basepath = import.meta.env.BASE_URL ?? "/"; + +if (!basepath.endsWith("/")) { + throw new Error("URL configuration broken: " + basepath); +} + +export const createHomePageUrl = () => `${basepath}`; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 9faaeeaea..91d3b1708 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,7 +1,11 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - +/// /// + +interface ImportMetaEnv { + readonly VITE_VERSION: string; + readonly VITE_STAGE: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/vite.config.ts b/vite.config.ts index 5a916f566..848397418 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,31 +4,32 @@ * * SPDX-License-Identifier: MIT */ -import { defineConfig, loadEnv } from 'vite'; +import { defineConfig, loadEnv } from "vite"; import react from "@vitejs/plugin-react"; +import svgr from "vite-plugin-svgr"; export default defineConfig(({ mode }) => { - const commonEnv = loadEnv(mode, process.cwd(), ''); + const commonEnv = loadEnv(mode, process.cwd(), ""); return { - base: process.env.BASE_URL ?? '/', - plugins: [ - react() - ], + base: process.env.BASE_URL ?? "/", + plugins: [react(), svgr()], define: { - 'import.meta.env.VITE_APP_VERSION': JSON.stringify(process.env.npm_package_version), + "import.meta.env.VITE_APP_VERSION": JSON.stringify( + process.env.npm_package_version + ), }, build: { - target: 'es2017', + target: "es2017", rollupOptions: { - input: 'index.html', + input: "index.html", }, }, server: commonEnv.API_PROXY ? { port: 5173, proxy: { - '/api/v1': { + "/api/v1": { target: commonEnv.API_PROXY, changeOrigin: true, }, @@ -45,4 +46,4 @@ export default defineConfig(({ mode }) => { }, }, }; -}); \ No newline at end of file +}); From 0d2a936e07fd1c536e06f0b9f918c31b6204e577 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 25 Jun 2024 17:20:52 +0100 Subject: [PATCH 005/172] Use common translations with standard keys --- lang/ui.en.json | 88 +++++++++++++---------- src/components/AboutDialog.tsx | 2 +- src/messages/ui.en.json | 128 +++++++++++++++++++++------------ 3 files changed, 136 insertions(+), 82 deletions(-) diff --git a/lang/ui.en.json b/lang/ui.en.json index 1abd09004..2d27fe61d 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -1,24 +1,20 @@ { - "about.developedInPartnership": { - "defaultMessage": "Developed in partnership with Center for Computational Thinking and Design, Aarhus University", - "description": "" + "about": { + "defaultMessage": "About", + "description": "Menu item link to view information about app" }, - "about.microbitHeartImageAlt": { - "defaultMessage": "micro:bit board with the 5 by 5 LED grid showing a heart", - "description": "" + "about-dialog-alt": { + "defaultMessage": "micro:bit board showing a heart on the LED display", + "description": "Alt text for image in about dialog" }, - "about.softwareVersions": { - "defaultMessage": "Software versions", + "about.developedInPartnership": { + "defaultMessage": "Developed in partnership with Center for Computational Thinking and Design, Aarhus University", "description": "" }, "actions.cancel": { "defaultMessage": "Cancel", "description": "" }, - "actions.close": { - "defaultMessage": "Close", - "description": "" - }, "actions.reconnect": { "defaultMessage": "Reconnect", "description": "" @@ -71,6 +67,10 @@ "defaultMessage": "arrow pointing right", "description": "" }, + "close-action": { + "defaultMessage": "Close", + "description": "Close button text or label" + }, "compatibility.platform.notSupported": { "defaultMessage": "The tool is not supported on your current platform.", "description": "" @@ -659,6 +659,18 @@ "defaultMessage": "Status: Training the model in progress.", "description": "" }, + "cookies-action": { + "defaultMessage": "Cookies", + "description": "Action to show dialog to choose website cookie preferences" + }, + "copied": { + "defaultMessage": "Copied", + "description": "Displayed as copy button text briefly after successful copy action" + }, + "copy-action": { + "defaultMessage": "Copy", + "description": "Copy button text" + }, "disconnectedWarning.bluetooth1": { "defaultMessage": "The connection to the micro:bit has been lost.", "description": "" @@ -719,28 +731,16 @@ "defaultMessage": "Start new session", "description": "" }, - "helpMenu.about": { - "defaultMessage": "About", - "description": "" - }, - "helpMenu.cookies": { - "defaultMessage": "Cookies", - "description": "" - }, - "helpMenu.helpAndSupport": { - "defaultMessage": "Help & support", - "description": "" - }, - "helpMenu.label": { + "help-label": { "defaultMessage": "Help", - "description": "" + "description": "Help icon aria label" }, - "helpMenu.privacyPolicy": { - "defaultMessage": "Privacy policy", - "description": "" + "help-support": { + "defaultMessage": "Help & support", + "description": "Link or menu item link to support site" }, - "helpMenu.termsOfUse": { - "defaultMessage": "Terms of use", + "helpMenu.cookies": { + "defaultMessage": "Cookies", "description": "" }, "homepage.Link": { @@ -751,9 +751,9 @@ "defaultMessage": "More information about \"{item}\"", "description": "" }, - "languageDialog.title": { + "language": { "defaultMessage": "Language", - "description": "" + "description": "Language option text" }, "loading": { "defaultMessage": "loading", @@ -899,6 +899,10 @@ "defaultMessage": "Open the newest MakeCode template to use the updated extension.", "description": "" }, + "privacy": { + "defaultMessage": "Privacy policy", + "description": "Link to view privacy policy" + }, "prototype.warning": { "defaultMessage": "This is a prototype version and is subject to change without notice", "description": "" @@ -943,9 +947,9 @@ "defaultMessage": "introduction video", "description": "" }, - "settings.label": { - "defaultMessage": "Settings", - "description": "" + "settings-menu-action": { + "defaultMessage": "Settings actions menu", + "description": "Label for settings actions menu button" }, "sign-up.content": { "defaultMessage": "Welcome to our prototype of the micro:bit machine learning tool. We’d love you to try it out and give us your feedback. Please enter your email so we can keep you updated as it evolves.", @@ -974,5 +978,17 @@ "sign-up.title": { "defaultMessage": "Join our testing community", "description": "" + }, + "software-versions": { + "defaultMessage": "Software versions", + "description": "Software versions text" + }, + "support-request": { + "defaultMessage": "Please consider raising a support request.", + "description": "Support request link text" + }, + "terms": { + "defaultMessage": "Terms of use", + "description": "Link or menu item link to view terms of use" } } \ No newline at end of file diff --git a/src/components/AboutDialog.tsx b/src/components/AboutDialog.tsx index 1a730a836..736cce8cb 100644 --- a/src/components/AboutDialog.tsx +++ b/src/components/AboutDialog.tsx @@ -75,7 +75,7 @@ const AboutDialog = ({ ( Date: Tue, 25 Jun 2024 17:20:59 +0100 Subject: [PATCH 006/172] Remove redundant `as IconType` layer (icons.ts) for icons --- src/components/LanguageMenuItem.tsx | 4 ++-- src/icons.ts | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 src/icons.ts diff --git a/src/components/LanguageMenuItem.tsx b/src/components/LanguageMenuItem.tsx index 36c221dbb..a2c55072e 100644 --- a/src/components/LanguageMenuItem.tsx +++ b/src/components/LanguageMenuItem.tsx @@ -1,6 +1,6 @@ import { Icon, MenuItem, useDisclosure } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { LanguageIcon } from "../icons"; +import { IoMdGlobe } from "react-icons/io"; import { LanguageDialog } from "./LanguageDialog"; interface LanguageMenuItemProps { @@ -18,7 +18,7 @@ const LanguageMenuItem = ({ finalFocusRef }: LanguageMenuItemProps) => { finalFocusRef={finalFocusRef} /> } + icon={} onClick={languageDisclosure.onOpen} > diff --git a/src/icons.ts b/src/icons.ts deleted file mode 100644 index 1fbc5b244..000000000 --- a/src/icons.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { IconType } from "react-icons"; -import { IoMdGlobe } from "react-icons/io"; - -export const LanguageIcon = IoMdGlobe as IconType; From 06c74726aeeff2d07ad8778524173e4e55d0b2bc Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:12:21 +0100 Subject: [PATCH 007/172] React: Setup theming, homepage UI, connection dialogs, and more (#267) * Use more common translations with standard keys * Set up theming * Homepage UI * Add Data page and routing * Train and test model pages placeholder * Connection dialogs * Add connection flow provider and context * WebUSB flashing [WIP] --- lang/ui.en.json | 128 +++--- package-lock.json | 205 ++++----- package.json | 7 +- src/App.tsx | 41 +- src/components/AboutDialog.tsx | 49 +- src/components/ActionBar.tsx | 6 +- src/components/AppLogo.tsx | 16 +- src/components/ArrowOne.tsx | 33 ++ src/components/ArrowTwo.tsx | 33 ++ src/components/BluetoothPatternInput.tsx | 178 ++++++++ src/components/BrokenFirmwareDialog.tsx | 110 +++++ src/components/ClickableTooltip.tsx | 26 ++ src/components/ConnectBatteryDialog.tsx | 51 +++ src/components/ConnectCableDialog.tsx | 81 ++++ src/components/ConnectContainerDialog.tsx | 87 ++++ src/components/ConnectFirstView.tsx | 36 ++ src/components/ConnectionFlowDialogs.tsx | 315 +++++++++++++ src/components/DefaultPageLayout.tsx | 81 ++-- src/components/DownloadingDialog.tsx | 57 +++ .../EnterBluetoothPatternDialog.tsx | 80 ++++ src/components/InfoToolTip.tsx | 30 ++ src/components/Link.tsx | 2 +- src/components/LiveGraph.tsx | 88 ++++ src/components/LiveGraphPanel.tsx | 53 +++ src/components/LoadingAnimation/index.tsx | 13 + .../LoadingAnimation/styles.module.css | 57 +++ src/components/LoadingDialog.tsx | 49 ++ src/components/ManualFlashingDialog.tsx | 96 ++++ src/components/MicrobitLogo.tsx | 48 +- src/components/PrototypeVersionWarning.tsx | 27 ++ src/components/ResourceCard.tsx | 47 ++ .../SelectMicrobitBluetoothDialog.tsx | 86 ++++ src/components/SelectMicrobitUsbDialog.tsx | 82 ++++ src/components/StartResumeActions.tsx | 37 ++ src/components/TabView.tsx | 42 ++ src/components/TryAgainDialog.tsx | 138 ++++++ src/components/UnsupportedMicrobitDialog.tsx | 112 +++++ src/components/WhatYouWillNeedDialog.tsx | 132 ++++++ src/connection-flow.ts | 184 ++++++++ src/connections.tsx | 45 ++ src/constants.ts | 2 +- src/{theme => deployment/default}/colors.ts | 31 ++ .../default}/components/alert.ts | 0 .../default}/components/avatar.ts | 10 +- .../default}/components/button.ts | 24 +- .../default}/components/heading.ts | 8 +- .../default}/components/text.ts | 10 +- .../default}/components/tooltip.ts | 4 +- .../default}/default-graph.ts | 0 src/{theme => deployment/default}/fonts.ts | 2 +- src/{theme => deployment/default}/graph.ts | 0 src/deployment/default/index.tsx | 35 ++ src/deployment/default/logging.ts | 13 + src/{theme => deployment/default}/radii.ts | 2 +- src/{theme => deployment/default}/shadows.ts | 2 +- src/{theme => deployment/default}/theme.ts | 0 src/deployment/deployment.d.ts | 10 + src/deployment/index.ts | 63 +++ src/device/board-id.ts | 58 +++ src/device/board-serial-info.test.ts | 52 +++ src/device/board-serial-info.ts | 35 ++ src/device/constants.ts | 80 ++++ src/device/dap-wrapper.ts | 417 ++++++++++++++++++ src/device/device.ts | 38 ++ src/device/get-hex-file.ts | 24 + src/device/microbit-usb.ts | 112 +++++ src/device/partial-flashing-utils.ts | 128 ++++++ src/flags.test.ts | 8 +- src/logging/NullLoggingProvider.tsx | 14 + src/logging/logging-hooks.ts | 23 + src/logging/logging.ts | 18 + src/logging/mock.ts | 23 + src/messages/chunk-util.ts | 2 +- src/messages/ui.en.json | 176 +++++--- src/pages/AddDataPage.tsx | 26 ++ src/pages/HomePage.tsx | 104 ++++- src/pages/TestDataPage.tsx | 5 + src/pages/TrainDataPage.tsx | 5 + src/patternMatrixTransforms.test.ts | 133 ++++++ src/patternMatrixTransforms.ts | 66 +++ src/steps-config.ts | 38 ++ src/theme/constants/colors.ts | 5 - src/theme/constants/dimensions.ts | 1 - src/urls.ts | 4 + vite.config.ts | 19 +- 85 files changed, 4430 insertions(+), 388 deletions(-) create mode 100644 src/components/ArrowOne.tsx create mode 100644 src/components/ArrowTwo.tsx create mode 100644 src/components/BluetoothPatternInput.tsx create mode 100644 src/components/BrokenFirmwareDialog.tsx create mode 100644 src/components/ClickableTooltip.tsx create mode 100644 src/components/ConnectBatteryDialog.tsx create mode 100644 src/components/ConnectCableDialog.tsx create mode 100644 src/components/ConnectContainerDialog.tsx create mode 100644 src/components/ConnectFirstView.tsx create mode 100644 src/components/ConnectionFlowDialogs.tsx create mode 100644 src/components/DownloadingDialog.tsx create mode 100644 src/components/EnterBluetoothPatternDialog.tsx create mode 100644 src/components/InfoToolTip.tsx create mode 100644 src/components/LiveGraph.tsx create mode 100644 src/components/LiveGraphPanel.tsx create mode 100644 src/components/LoadingAnimation/index.tsx create mode 100644 src/components/LoadingAnimation/styles.module.css create mode 100644 src/components/LoadingDialog.tsx create mode 100644 src/components/ManualFlashingDialog.tsx create mode 100644 src/components/PrototypeVersionWarning.tsx create mode 100644 src/components/ResourceCard.tsx create mode 100644 src/components/SelectMicrobitBluetoothDialog.tsx create mode 100644 src/components/SelectMicrobitUsbDialog.tsx create mode 100644 src/components/StartResumeActions.tsx create mode 100644 src/components/TabView.tsx create mode 100644 src/components/TryAgainDialog.tsx create mode 100644 src/components/UnsupportedMicrobitDialog.tsx create mode 100644 src/components/WhatYouWillNeedDialog.tsx create mode 100644 src/connection-flow.ts create mode 100644 src/connections.tsx rename src/{theme => deployment/default}/colors.ts (80%) rename src/{theme => deployment/default}/components/alert.ts (100%) rename src/{theme => deployment/default}/components/avatar.ts (63%) rename src/{theme => deployment/default}/components/button.ts (83%) rename src/{theme => deployment/default}/components/heading.ts (73%) rename src/{theme => deployment/default}/components/text.ts (58%) rename src/{theme => deployment/default}/components/tooltip.ts (51%) rename src/{theme => deployment/default}/default-graph.ts (100%) rename src/{theme => deployment/default}/fonts.ts (66%) rename src/{theme => deployment/default}/graph.ts (100%) create mode 100644 src/deployment/default/index.tsx create mode 100644 src/deployment/default/logging.ts rename src/{theme => deployment/default}/radii.ts (84%) rename src/{theme => deployment/default}/shadows.ts (66%) rename src/{theme => deployment/default}/theme.ts (100%) create mode 100644 src/deployment/deployment.d.ts create mode 100644 src/deployment/index.ts create mode 100644 src/device/board-id.ts create mode 100644 src/device/board-serial-info.test.ts create mode 100644 src/device/board-serial-info.ts create mode 100644 src/device/constants.ts create mode 100644 src/device/dap-wrapper.ts create mode 100644 src/device/device.ts create mode 100644 src/device/get-hex-file.ts create mode 100644 src/device/microbit-usb.ts create mode 100644 src/device/partial-flashing-utils.ts create mode 100644 src/logging/NullLoggingProvider.tsx create mode 100644 src/logging/logging-hooks.ts create mode 100644 src/logging/logging.ts create mode 100644 src/logging/mock.ts create mode 100644 src/pages/AddDataPage.tsx create mode 100644 src/pages/TestDataPage.tsx create mode 100644 src/pages/TrainDataPage.tsx create mode 100644 src/patternMatrixTransforms.test.ts create mode 100644 src/patternMatrixTransforms.ts create mode 100644 src/steps-config.ts delete mode 100644 src/theme/constants/colors.ts delete mode 100644 src/theme/constants/dimensions.ts diff --git a/lang/ui.en.json b/lang/ui.en.json index 2d27fe61d..a3d4be09a 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -7,18 +7,22 @@ "defaultMessage": "micro:bit board showing a heart on the LED display", "description": "Alt text for image in about dialog" }, - "about.developedInPartnership": { + "about-dialog-title": { "defaultMessage": "Developed in partnership with Center for Computational Thinking and Design, Aarhus University", - "description": "" - }, - "actions.cancel": { - "defaultMessage": "Cancel", - "description": "" + "description": "Title of about dialog" }, "actions.reconnect": { "defaultMessage": "Reconnect", "description": "" }, + "add-data-intro-description": { + "defaultMessage": "Add samples of the actions you would like your model to recognise (e.g. waving and clapping).", + "description": "Add data step home page description" + }, + "add-data-title": { + "defaultMessage": "Add data", + "description": "Add data step title" + }, "alert.data.classNameLengthAlert": { "defaultMessage": "Action names cannot be longer than {maxLen} characters.", "description": "" @@ -67,6 +71,14 @@ "defaultMessage": "arrow pointing right", "description": "" }, + "back-action": { + "defaultMessage": "Back", + "description": "Back button text" + }, + "cancel-action": { + "defaultMessage": "Cancel", + "description": "Cancel button text" + }, "close-action": { "defaultMessage": "Close", "description": "Close button text or label" @@ -83,6 +95,10 @@ "defaultMessage": "WebGL not available. Enable WebGL to see 3D data view.", "description": "" }, + "connect-help-alt": { + "defaultMessage": "WebUSB connection dialog with BBC micro:bit entry labelled 1 and Connect button labelled 2", + "description": "Alt text for image in connect help dialog" + }, "connectFailed.bluetooth1": { "defaultMessage": "The connection to the micro:bit could not be established.", "description": "" @@ -107,10 +123,6 @@ "defaultMessage": "Failed to connect to micro:bit 1", "description": "" }, - "connectMB.backButton": { - "defaultMessage": "Back", - "description": "" - }, "connectMB.bluetooth.cancelledConnection": { "defaultMessage": "You didn't choose a micro:bit. Do you want to try again?", "description": "" @@ -531,30 +543,6 @@ "defaultMessage": "machine learning tool", "description": "" }, - "content.index.toolProcessCards.data.description": { - "defaultMessage": "Add samples of the actions you would like your model to recognise (e.g. waving and clapping).", - "description": "" - }, - "content.index.toolProcessCards.data.title": { - "defaultMessage": "Add data", - "description": "" - }, - "content.index.toolProcessCards.model.description": { - "defaultMessage": "Find out if it correctly recognises each action. Add more data to improve the model.", - "description": "" - }, - "content.index.toolProcessCards.model.title": { - "defaultMessage": "Test model", - "description": "" - }, - "content.index.toolProcessCards.train.description": { - "defaultMessage": "Ask the computer to use your training samples to train the machine learning model to recognise different actions.", - "description": "" - }, - "content.index.toolProcessCards.train.title": { - "defaultMessage": "Train model", - "description": "" - }, "content.model.addData": { "defaultMessage": "Add data", "description": "" @@ -731,6 +719,10 @@ "defaultMessage": "Start new session", "description": "" }, + "get-started-resource-title": { + "defaultMessage": "Get started", + "description": "Title of a resource" + }, "help-label": { "defaultMessage": "Help", "description": "Help icon aria label" @@ -739,9 +731,13 @@ "defaultMessage": "Help & support", "description": "Link or menu item link to support site" }, - "helpMenu.cookies": { - "defaultMessage": "Cookies", - "description": "" + "homepage-subtitle": { + "defaultMessage": "Introduce students to machine learning concepts through physical movement and data", + "description": "Subtitle of machine learning tool home page" + }, + "homepage-title": { + "defaultMessage": "micro:bit machine learning tool", + "description": "Title of machine learning tool home page" }, "homepage.Link": { "defaultMessage": "Home page", @@ -751,6 +747,10 @@ "defaultMessage": "More information about \"{item}\"", "description": "" }, + "introducing-microbit-resource-title": { + "defaultMessage": "Introducing the micro:bit machine learning tool", + "description": "Title of a resource" + }, "language": { "defaultMessage": "Language", "description": "Language option text" @@ -759,9 +759,9 @@ "defaultMessage": "loading", "description": "" }, - "menu.data.helpHeading": { - "defaultMessage": "1. Add data", - "description": "" + "main-menu": { + "defaultMessage": "Main menu", + "description": "Main menu label" }, "menu.model.connectInputMicrobit": { "defaultMessage": "Connect micro:bit", @@ -771,10 +771,6 @@ "defaultMessage": "Disconnect output micro:bit", "description": "" }, - "menu.model.helpHeading": { - "defaultMessage": "3. Test model", - "description": "" - }, "menu.model.noModel": { "defaultMessage": "No model", "description": "" @@ -791,10 +787,6 @@ "defaultMessage": "Add more data", "description": "" }, - "menu.trainer.helpHeading": { - "defaultMessage": "2. Train model", - "description": "" - }, "menu.trainer.notConnected1": { "defaultMessage": "You do not have a micro:bit connected", "description": "" @@ -819,6 +811,14 @@ "defaultMessage": "Train model", "description": "" }, + "not-found": { + "defaultMessage": "Machine learning tool home page", + "description": "Page not found link text" + }, + "not-found-title": { + "defaultMessage": "Page not found", + "description": "Page not found page title" + }, "performanceWarning.content1": { "defaultMessage": "The quality of data being received from your micro:bit is low. This could be due to a connection issue.", "description": "" @@ -939,6 +939,14 @@ "defaultMessage": "Follow these instructions to restart the connection process.", "description": "" }, + "reload-action": { + "defaultMessage": "Click to reload the page", + "description": "Reload page button text" + }, + "resources": { + "defaultMessage": "Resources", + "description": "Title of resources section" + }, "resources.getStarted.video": { "defaultMessage": "get started video", "description": "" @@ -951,6 +959,10 @@ "defaultMessage": "Settings actions menu", "description": "Label for settings actions menu button" }, + "sign-up-action": { + "defaultMessage": "Sign up", + "description": "Sign up button text" + }, "sign-up.content": { "defaultMessage": "Welcome to our prototype of the micro:bit machine learning tool. We’d love you to try it out and give us your feedback. Please enter your email so we can keep you updated as it evolves.", "description": "" @@ -967,10 +979,6 @@ "defaultMessage": "Please enter a valid email address", "description": "" }, - "sign-up.sign-up-action": { - "defaultMessage": "Sign up", - "description": "" - }, "sign-up.skip-action": { "defaultMessage": "Skip", "description": "" @@ -990,5 +998,21 @@ "terms": { "defaultMessage": "Terms of use", "description": "Link or menu item link to view terms of use" + }, + "test-model-intro-description": { + "defaultMessage": "Find out if it correctly recognises each action. Add more data to improve the model.", + "description": "Test model step home page description" + }, + "test-model-title": { + "defaultMessage": "Test model", + "description": "Test model step title" + }, + "train-model-intro-description": { + "defaultMessage": "Ask the computer to use your training samples to train the machine learning model to recognise different actions.", + "description": "Train model step home page description" + }, + "train-model-title": { + "defaultMessage": "Train model", + "description": "Train model step title" } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e4dc6963b..d01377be0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "ml-tool", "version": "0.6.0-local", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@chakra-ui/icons": "^2.1.1", @@ -65,7 +66,7 @@ "playwright": "^1.42.1", "prettier": "2.3.2", "typescript": "^5.4.2", - "vite": "^5.0.7", + "vite": "^5.1.5", "vite-plugin-pwa": "^0.19.8", "vite-plugin-svgr": "^4.2.0", "vitest": "^1.3.1", @@ -3524,9 +3525,9 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", "cpu": [ "ppc64" ], @@ -3540,9 +3541,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -3556,9 +3557,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -3572,9 +3573,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -3588,9 +3589,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -3604,9 +3605,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -3620,9 +3621,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -3636,9 +3637,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -3652,9 +3653,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -3668,9 +3669,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -3684,9 +3685,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -3700,9 +3701,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -3716,9 +3717,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -3732,9 +3733,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -3748,9 +3749,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -3764,9 +3765,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -3780,9 +3781,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -3796,9 +3797,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -3812,9 +3813,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -3828,9 +3829,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -3844,9 +3845,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -3860,9 +3861,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -3876,9 +3877,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -8333,9 +8334,9 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -8345,29 +8346,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/escalade": { @@ -13246,14 +13247,14 @@ "integrity": "sha512-CTpAkEVXMNJl2ojgtpLXHgz23dh8z81u6/HEPiQFOvBc/c2pde6TVHmH4uwY0d/GLF3tb7+VDAj4+2eJaQSdZQ==" }, "node_modules/vite": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", - "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", + "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==", "dev": true, "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" }, "bin": { "vite": "bin/vite.js" diff --git a/package.json b/package.json index 0f6da351a..c243bf545 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,14 @@ "dev": "cross-env VITE_VERSION=$npm_package_version vite", "invalidate": "invalidate-cloudfront-distribution", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "postinstall": "npm run theme", "preview": "vite preview", - "start": "npm run dev", + "start": "vite dev", "test": "vitest", "test:e2e": "playwright test --headed", "test:e2e:headless": "playwright test", + "theme:watch": "chakra-cli tokens src/deployment/default/theme.ts --watch", + "theme": "chakra-cli tokens src/deployment/default/theme.ts", "i18n:compile": "node bin/tidy-lang.cjs && formatjs compile-folder --ast lang src/messages" }, "devDependencies": { @@ -49,7 +52,7 @@ "playwright": "^1.42.1", "prettier": "2.3.2", "typescript": "^5.4.2", - "vite": "^5.0.7", + "vite": "^5.1.5", "vite-plugin-pwa": "^0.19.8", "vite-plugin-svgr": "^4.2.0", "vitest": "^1.3.1", diff --git a/src/App.tsx b/src/App.tsx index 028d7b9ba..c248a5260 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ import { ChakraProvider } from "@chakra-ui/react"; import React, { ReactNode, useMemo } from "react"; import { @@ -9,31 +7,42 @@ import { ScrollRestoration, createBrowserRouter, } from "react-router-dom"; -import { ConsentProvider } from "./compliance"; import ErrorBoundary from "./components/ErrorBoundary"; import ErrorHandlerErrorView from "./components/ErrorHandlerErrorView"; import NotFound from "./components/NotFound"; import TranslationProvider from "./messages/TranslationProvider"; import SettingsProvider from "./settings"; -import theme from "./theme/theme"; import HomePage from "./pages/HomePage"; -import { createHomePageUrl } from "./urls"; +import { createHomePageUrl, createStepPageUrl } from "./urls"; +import { deployment, useDeployment } from "./deployment"; +import { stepsConfig } from "./steps-config"; +import { LoggingProvider } from "./logging/logging-hooks"; +import { ConnectionFlowProvider } from "./connections"; export interface ProviderLayoutProps { children: ReactNode; } +// TODO: Use for logging provider +const logging = deployment.logging; + const Providers = ({ children }: ProviderLayoutProps) => { + const deployment = useDeployment(); + const { ConsentProvider } = deployment.compliance; return ( - - - - - {children} - - - + + + + + + + {children} + + + + + ); @@ -64,6 +73,12 @@ const createRouter = () => { path: createHomePageUrl(), element: , }, + ...stepsConfig.map((step) => { + return { + path: createStepPageUrl(step.id), + element: , + }; + }), { path: "*", element: , diff --git a/src/components/AboutDialog.tsx b/src/components/AboutDialog.tsx index 736cce8cb..162284c7d 100644 --- a/src/components/AboutDialog.tsx +++ b/src/components/AboutDialog.tsx @@ -9,7 +9,8 @@ import { AspectRatio, Box, Button, - Center, + HStack, + Icon, Image, Link, ModalCloseButton, @@ -20,14 +21,16 @@ import { Td, Text, Tr, - useClipboard, VStack, + VisuallyHidden, + useClipboard, } from "@chakra-ui/react"; -import { RiFileCopy2Line } from "react-icons/ri"; -import AppLogo from "./AppLogo"; -import microbitHeartImage from "../images/microbit-heart.png"; -import { FormattedMessage, useIntl } from "react-intl"; import { ReactNode } from "react"; +import { RiFileCopy2Line, RiGithubFill } from "react-icons/ri"; +import { FormattedMessage, useIntl } from "react-intl"; +import aarhusLogo from "../images/aulogo_uk_var2_blue.png"; +import microbitHeartImage from "../images/microbit-heart.png"; +import MicrobitLogo from "./MicrobitLogo"; interface AboutDialogProps { appName: string; @@ -49,6 +52,7 @@ const AboutDialog = ({ { name: `micro:bit ${appName}`, value: import.meta.env.VITE_VERSION, + href: "https://github.com/microbit-foundation/ml-trainer", }, ]; @@ -70,12 +74,13 @@ const AboutDialog = ({ -
        - -
        - + + + + + ( {v.name} {v.value} + + {/* Move padding so we get a reasonable click target. */} + + + GitHub + + ))} - + diff --git a/src/components/ActionBar.tsx b/src/components/ActionBar.tsx index d84c7d8be..fa4fea05c 100644 --- a/src/components/ActionBar.tsx +++ b/src/components/ActionBar.tsx @@ -1,13 +1,11 @@ import { BoxProps, HStack } from "@chakra-ui/react"; import { ReactNode } from "react"; -import { headerHeight } from "../theme/constants/dimensions"; -import { brandGrey } from "../theme/constants/colors"; export type ToolbarVariant = "full-screen" | "primary"; const styles = { - primary: { bgColor: "green.500", h: headerHeight, minH: headerHeight }, - "full-screen": { bgColor: brandGrey, h: "4rem" }, + primary: { bgColor: "green.500", h: "64px", minH: "64px" }, + "full-screen": { bgColor: "brand.500", h: "4rem" }, }; export interface ActionBarProps extends BoxProps { diff --git a/src/components/AppLogo.tsx b/src/components/AppLogo.tsx index 8f97c03bf..70cdafcbc 100644 --- a/src/components/AppLogo.tsx +++ b/src/components/AppLogo.tsx @@ -1,9 +1,9 @@ -import { As, Divider, Heading, HStack } from "@chakra-ui/react"; +import { As, Divider, HStack, Image } from "@chakra-ui/react"; +import AppNameLogo from "../images/app-name.svg"; import MicrobitLogo from "./MicrobitLogo"; const AppLogo = ({ color = "#FFF", - name, as, }: { color?: string; @@ -26,17 +26,7 @@ const AppLogo = ({ h="33px" borderWidth="1px" /> - - - {name} - - + micro:bit ); diff --git a/src/components/ArrowOne.tsx b/src/components/ArrowOne.tsx new file mode 100644 index 000000000..d7fd82be1 --- /dev/null +++ b/src/components/ArrowOne.tsx @@ -0,0 +1,33 @@ +import { Box, Text } from "@chakra-ui/react"; + +const ArrowOne = () => { + return ( + + + + + + + + 1 + + + + + ); +}; + +export default ArrowOne; diff --git a/src/components/ArrowTwo.tsx b/src/components/ArrowTwo.tsx new file mode 100644 index 000000000..a4c05f310 --- /dev/null +++ b/src/components/ArrowTwo.tsx @@ -0,0 +1,33 @@ +import { Box, Text } from "@chakra-ui/react"; + +const ArrowTwo = () => { + return ( + + + + + + + + + 2 + + + + + ); +}; +export default ArrowTwo; diff --git a/src/components/BluetoothPatternInput.tsx b/src/components/BluetoothPatternInput.tsx new file mode 100644 index 000000000..e20ef11cf --- /dev/null +++ b/src/components/BluetoothPatternInput.tsx @@ -0,0 +1,178 @@ +import { + Button, + FormControl, + FormLabel, + Grid, + GridItem, + NumberInput, + NumberInputField, + VisuallyHidden, +} from "@chakra-ui/react"; +import { useCallback, useState } from "react"; +import { FormattedMessage } from "react-intl"; +import { + generateMatrix, + getHighlightedColumns, + transformColumnsToMatrix, + transformMatrixToColumns, + updateMatrixColumns, +} from "../patternMatrixTransforms"; +import React from "react"; + +interface BluetoothPatternInputProps { + pattern: boolean[]; + onChange: (matrix: boolean[]) => void; + invalid: boolean; +} + +const matrixDim = 5; + +const BluetoothPatternInput = ({ + pattern, + onChange, + invalid, +}: BluetoothPatternInputProps) => { + const [highlighted, setHighlighted] = useState( + generateMatrix(matrixDim, false) + ); + const matrixColumns = transformMatrixToColumns(pattern, matrixDim); + + const clearHighlighted = useCallback(() => { + setHighlighted(generateMatrix(matrixDim, false)); + }, []); + + const updateMatrix = useCallback( + (colIdx: number, rowIdx: number) => { + const columns = updateMatrixColumns(matrixColumns, { colIdx, rowIdx }); + const matrix = transformColumnsToMatrix(columns) as boolean[]; + onChange(matrix); + }, + [matrixColumns, onChange] + ); + + const columnInputOnChange = useCallback( + (colIdx: number): ((value: string) => void) => { + return (v) => { + updateMatrix(colIdx, matrixDim - parseInt(v)); + }; + }, + [updateMatrix] + ); + + return ( + + {matrixColumns.map((cells, colIdx) => ( + + {cells.map((c, rowIdx) => ( + + { + clearHighlighted(); + updateMatrix(colIdx, rowIdx); + }} + onMouseEnter={() => { + setHighlighted( + getHighlightedColumns(matrixColumns, { colIdx, rowIdx }) + ); + }} + onMouseLeave={clearHighlighted} + isOn={c} + isHighlighted={highlighted[colIdx][rowIdx]} + /> + + ))} + + c).length} + /> + + + ))} + + ); +}; + +interface PatternBoxProps { + isOn: boolean; + onClick: () => void; + onMouseEnter: () => void; + onMouseLeave: () => void; + isHighlighted: boolean; +} + +const PatternBox = ({ + isOn, + onClick, + onMouseEnter, + onMouseLeave, + isHighlighted, +}: PatternBoxProps) => { + return ( + + + + + + + + + ); +}; + +export default BrokenFirmwareDialog; diff --git a/src/components/ClickableTooltip.tsx b/src/components/ClickableTooltip.tsx new file mode 100644 index 000000000..b23cca6d1 --- /dev/null +++ b/src/components/ClickableTooltip.tsx @@ -0,0 +1,26 @@ +import { Flex, Tooltip, TooltipProps, useDisclosure } from "@chakra-ui/react"; +import { ReactNode } from "react"; + +interface ClickableTooltipProps extends TooltipProps { + children: ReactNode; +} + +// Chakra Tooltip doesn't support triggering on mobile/tablets: +// https://github.com/chakra-ui/chakra-ui/issues/2691 + +const ClickableTooltip = ({ children, ...rest }: ClickableTooltipProps) => { + const label = useDisclosure(); + return ( + + + {children} + + + ); +}; + +export default ClickableTooltip; diff --git a/src/components/ConnectBatteryDialog.tsx b/src/components/ConnectBatteryDialog.tsx new file mode 100644 index 000000000..c4c6e2a63 --- /dev/null +++ b/src/components/ConnectBatteryDialog.tsx @@ -0,0 +1,51 @@ +import { Icon, Image, Link, Text, VStack } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import microbitConnectedImage from "../images/stylised-microbit-connected.svg"; +import ConnectContainerDialog, { + ConnectContainerDialogProps, +} from "./ConnectContainerDialog"; +import { RiExternalLinkLine } from "react-icons/ri"; + +export interface ConnectBatteryDialogProps + extends Omit {} + +const ConnectBatteryDialog = ({ ...props }: ConnectBatteryDialogProps) => { + return ( + + + + + + + + + + + + + ); +}; + +export default ConnectBatteryDialog; diff --git a/src/components/ConnectCableDialog.tsx b/src/components/ConnectCableDialog.tsx new file mode 100644 index 000000000..bf8990ea5 --- /dev/null +++ b/src/components/ConnectCableDialog.tsx @@ -0,0 +1,81 @@ +import { Image, Text, VStack } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import connectCableImage from "../images/connect-cable.gif"; +import ConnectContainerDialog, { + ConnectContainerDialogProps, +} from "./ConnectContainerDialog"; +import { ConnType } from "../connection-flow"; + +enum LinkType { + Switch, + Skip, + None, +} +interface Config { + headingId: string; + subtitleId: string; + linkTextId?: string; + onLink: LinkType; +} + +const configs: Record = { + [ConnType.Bluetooth]: { + headingId: "connectMB.connectCable.heading", + subtitleId: "connectMB.connectCable.subtitle", + linkTextId: "connectMB.connectCable.skip", + onLink: LinkType.Skip, + }, + [ConnType.RadioRemote]: { + headingId: "connectMB.connectCableMB1.heading", + subtitleId: "connectMB.connectCableMB1.subtitle", + onLink: LinkType.None, + }, + [ConnType.RadioBridge]: { + headingId: "connectMB.connectCableMB2.heading", + subtitleId: "connectMB.connectCableMB2.subtitle", + linkTextId: "connectMB.radioStart.switchBluetooth", + onLink: LinkType.Switch, + }, +}; + +export interface ConnectCableDialogProps + extends Omit { + type: ConnType; + onSkip: () => void; + onSwitch: () => void; +} + +const ConnectCableDialog = ({ + type, + onSkip, + onSwitch, + ...props +}: ConnectCableDialogProps) => { + const { subtitleId, onLink, ...typeProps } = configs[type]; + const linkConfig = { + [LinkType.None]: undefined, + [LinkType.Skip]: onSkip, + [LinkType.Switch]: onSwitch, + }; + return ( + + + + + + + + + ); +}; + +export default ConnectCableDialog; diff --git a/src/components/ConnectContainerDialog.tsx b/src/components/ConnectContainerDialog.tsx new file mode 100644 index 000000000..f0add80ce --- /dev/null +++ b/src/components/ConnectContainerDialog.tsx @@ -0,0 +1,87 @@ +import { + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalOverlay, +} from "@chakra-ui/modal"; +import { + Button, + HStack, + Heading, + ModalCloseButton, + VStack, +} from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { FormattedMessage } from "react-intl"; + +export interface ConnectContainerDialogProps { + isOpen: boolean; + onClose: () => void; + headingId: string; + onLinkClick?: () => void; + linkTextId?: string; + onNextClick?: () => void; + children: ReactNode; + onBackClick?: () => void; +} + +const ConnectContainerDialog = ({ + isOpen, + onClose, + headingId, + onLinkClick, + linkTextId, + onNextClick, + onBackClick, + children, +}: ConnectContainerDialogProps) => { + return ( + + + + + + + + + + {children} + + + + {onLinkClick && linkTextId && ( + + )} + + {onBackClick && ( + + )} + {onNextClick && ( + + )} + + + + + + ); +}; + +export default ConnectContainerDialog; diff --git a/src/components/ConnectFirstView.tsx b/src/components/ConnectFirstView.tsx new file mode 100644 index 000000000..8c25cfbb6 --- /dev/null +++ b/src/components/ConnectFirstView.tsx @@ -0,0 +1,36 @@ +import { Button, Text, VStack } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import { useConnectionFlow } from "../connections"; +import { useCallback } from "react"; +import { ConnEvent } from "../connection-flow"; + +const ConnectFirstView = () => { + const connecting = false; + const isReconnect = false; + const { dispatch } = useConnectionFlow(); + + const handleConnect = useCallback(() => { + dispatch(ConnEvent.Start); + }, [dispatch]); + + return ( + + + + + + + + + + + + ); +}; +export default ConnectFirstView; diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx new file mode 100644 index 000000000..b18e23a1f --- /dev/null +++ b/src/components/ConnectionFlowDialogs.tsx @@ -0,0 +1,315 @@ +import { useDisclosure } from "@chakra-ui/react"; +import { useCallback, useEffect, useState } from "react"; +import { ConnEvent, ConnStage, ConnType } from "../connection-flow"; +import { useConnectionFlow } from "../connections"; +import { getHexFileUrl } from "../device/get-hex-file"; +import MicrobitWebUSBConnection from "../device/microbit-usb"; +import { useLogging } from "../logging/logging-hooks"; +import BrokenFirmwareDialog from "./BrokenFirmwareDialog"; +import ConnectBatteryDialog from "./ConnectBatteryDialog"; +import ConnectCableDialog from "./ConnectCableDialog"; +import DownloadingDialog from "./DownloadingDialog"; +import EnterBluetoothPatternDialog from "./EnterBluetoothPatternDialog"; +import LoadingDialog from "./LoadingDialog"; +import ManualFlashingDialog from "./ManualFlashingDialog"; +import SelectMicrobitBluetoothDialog from "./SelectMicrobitBluetoothDialog"; +import SelectMicrobitUsbDialog from "./SelectMicrobitUsbDialog"; +import TryAgainDialog from "./TryAgainDialog"; +import UnsupportedMicrobitDialog from "./UnsupportedMicrobitDialog"; +import WhatYouWillNeedDialog from "./WhatYouWillNeedDialog"; + +const ConnectionDialogs = () => { + // Check compatability + const logging = useLogging(); + + const [isBluetoothSupported, isUsbSupported] = [true, true]; + const { state, dispatch } = useConnectionFlow(); + const [flashProgress, setFlashProgress] = useState(0); + const { isOpen, onClose: onCloseDialog, onOpen } = useDisclosure(); + + useEffect(() => { + if (state.stage === ConnStage.Start) { + onOpen(); + } + }, [onOpen, state]); + + const handleWebUsbError = (err: unknown) => { + if (state.type === ConnType.Bluetooth) { + return dispatch(ConnEvent.InstructManualFlashing); + } + // We might get Error objects as Promise rejection arguments + if ( + typeof err === "object" && + err !== null && + !("message" in err) && + "promise" in err && + "reason" in err + ) { + err = err.reason; + } + if ( + typeof err !== "object" || + err === null || + !(typeof err === "object" && "message" in err) + ) { + return dispatch(ConnEvent.TryAgainReplugMicrobit); + } + + const errMessage = err.message as string; + + // This is somewhat fragile but worth it for scenario specific errors. + // These messages changed to be prefixed in 2023 so we've relaxed the checks. + if (/No valid interfaces found/.test(errMessage)) { + // This comes from DAPjs's WebUSB open. + return dispatch(ConnEvent.BadFirmware); + } else if (/No device selected/.test(errMessage)) { + return dispatch(ConnEvent.TryAgainSelectMicrobit); + } else if (/Unable to claim interface/.test(errMessage)) { + return dispatch(ConnEvent.TryAgainCloseTabs); + } else { + return dispatch(ConnEvent.TryAgainReplugMicrobit); + } + }; + + const requestUSBConnectionAndFlash = async () => { + dispatch(ConnEvent.WebUsbChooseMicrobit); + try { + const device = new MicrobitWebUSBConnection(logging); + await device.connect(); + await flashMicrobit(device); + if (state.type === ConnType.RadioBridge) { + connectMicrobitsSerial(); + } + } catch (e) { + logging.error( + `USB request device failed/cancelled: ${JSON.stringify(e)}` + ); + handleWebUsbError(e); + } + }; + + async function flashMicrobit(usb: MicrobitWebUSBConnection): Promise { + const deviceVersion = usb.getBoardVersion(); + const hexUrl = deviceVersion + ? getHexFileUrl(deviceVersion, state.type) + : deviceVersion; + + if (!hexUrl) { + dispatch(ConnEvent.MicrobitUnsupported); + return; + } + + await usb.flashHex(hexUrl, (progress) => { + if (state.stage !== ConnStage.FlashingInProgress) { + dispatch(ConnEvent.FlashingInProgress); + } + setFlashProgress(progress * 100); + }); + + // TODO: + // Store radio/bluetooth details. Radio is essential to pass to micro:bit 2. + // Bluetooth saves the user from entering the pattern. + // const deviceId = usb.getDeviceId(); + // if (flashStage === "bluetooth") { + // $btPatternInput = MBSpecs.Utility.nameToPattern( + // MBSpecs.Utility.serialNumberToName(deviceId) + // ); + // } + // if (flashStage === "radio-remote") { + // $radioBridgeRemoteDeviceId = deviceId; + // } + + // Next UI state: + dispatch(ConnEvent.FlashingComplete); + } + + const connectMicrobitsSerial = () => { + dispatch(ConnEvent.ConnectingMicrobits); + // TODO: Replace with real connecting logic + setTimeout(() => { + onClose(); + }, 5000); + }; + + const connectBluetooth = () => { + dispatch(ConnEvent.ConnectingBluetooth); + // TODO: Replace with real connecting logic + const isSuccess = true; + setTimeout(() => { + if (isSuccess) { + onClose(); + } else { + dispatch(ConnEvent.TryAgainBluetoothConnect); + } + }, 5000); + }; + + // TODO: Flag reconnect failed + const reconnectFailed = false; + const onSwitchTypeClick = useCallback( + () => dispatch(ConnEvent.Switch), + [dispatch] + ); + const onBackClick = useCallback(() => dispatch(ConnEvent.Back), [dispatch]); + const onNextClick = useCallback(() => dispatch(ConnEvent.Next), [dispatch]); + const onSkip = useCallback( + () => dispatch(ConnEvent.SkipFlashing), + [dispatch] + ); + const onTryAgain = useCallback( + () => dispatch(ConnEvent.TryAgain), + [dispatch] + ); + const onStartBluetooth = useCallback( + () => dispatch(ConnEvent.GoToBluetoothStart), + [dispatch] + ); + const onInstructManualFlashing = useCallback( + () => dispatch(ConnEvent.InstructManualFlashing), + [dispatch] + ); + const onClose = useCallback(() => { + dispatch(ConnEvent.Close); + onCloseDialog(); + }, [dispatch, onCloseDialog]); + const dialogCommonProps = { isOpen, onClose }; + + switch (state.stage) { + case ConnStage.Start: { + return ( + + ); + } + case ConnStage.ConnectCable: { + const commonProps = { onBackClick, onNextClick, ...dialogCommonProps }; + return ( + + ); + } + case ConnStage.WebUsbFlashingTutorial: { + return ( + + ); + } + case ConnStage.ManualFlashingTutorial: { + return ( + + ); + } + case ConnStage.ConnectBattery: { + return ( + + ); + } + case ConnStage.EnterBluetoothPattern: { + return ( + + ); + } + case ConnStage.ConnectBluetoothTutorial: { + return ( + + ); + } + case ConnStage.WebUsbChooseMicrobit: { + // Browser dialog is shown, no custom dialog shown at the same time + return <>; + } + case ConnStage.FlashingInProgress: { + const headingIdVariations = { + [ConnType.Bluetooth]: "connectMB.usbDownloading.header", + [ConnType.RadioRemote]: "connectMB.usbDownloadingMB1.header", + [ConnType.RadioBridge]: "connectMB.usbDownloadingMB2.header", + }; + return ( + + ); + } + case ConnStage.ConnectingBluetooth: { + return ( + + ); + } + case ConnStage.ConnectingMicrobits: { + return ( + + ); + } + case ConnStage.TryAgainBluetoothConnect: + case ConnStage.TryAgainReplugMicrobit: + case ConnStage.TryAgainSelectMicrobit: + case ConnStage.TryAgainCloseTabs: { + return ( + + ); + } + case ConnStage.BadFirmware: { + return ( + + ); + } + case ConnStage.MicrobitUnsupported: { + return ( + + ); + } + } +}; + +export default ConnectionDialogs; diff --git a/src/components/DefaultPageLayout.tsx b/src/components/DefaultPageLayout.tsx index c9056929f..32ecbbe33 100644 --- a/src/components/DefaultPageLayout.tsx +++ b/src/components/DefaultPageLayout.tsx @@ -1,48 +1,42 @@ -import { - Box, - Flex, - HStack, - IconButton, - Menu, - MenuButton, - MenuDivider, - MenuList, - VStack, -} from "@chakra-ui/react"; -import { ReactNode, useEffect, useRef } from "react"; -import { RiMenuLine } from "react-icons/ri"; +import { Flex, HStack, IconButton, VStack } from "@chakra-ui/react"; +import { ReactNode, useCallback, useEffect } from "react"; +import { RiHome2Line } from "react-icons/ri"; import { useIntl } from "react-intl"; -import { APP_NAME } from "../constants"; +import { useNavigate } from "react-router"; +import { TOOL_NAME } from "../constants"; +import { createHomePageUrl } from "../urls"; import ActionBar from "./ActionBar"; import AppLogo from "./AppLogo"; +import ConnectionDialogs from "./ConnectionFlowDialogs"; import HelpMenu from "./HelpMenu"; -import LanguageMenuItem from "./LanguageMenuItem"; +import PrototypeVersionWarning from "./PrototypeVersionWarning"; import SettingsMenu from "./SettingsMenu"; interface DefaultPageLayoutProps { titleId: string; children: ReactNode; toolbarItemsRight?: ReactNode; - toolbarItemsRightMenu?: ReactNode; - layoutBreakpointOverride?: string; } const DefaultPageLayout = ({ titleId, children, toolbarItemsRight, - toolbarItemsRightMenu, - layoutBreakpointOverride, }: DefaultPageLayoutProps) => { - const toolbarHamburgerRef = useRef(null); const intl = useIntl(); + const navigate = useNavigate(); useEffect(() => { document.title = intl.formatMessage({ id: titleId }); }, [intl, titleId]); + const handleHomeClick = useCallback(() => { + navigate(createHomePageUrl()); + }, [navigate]); + return ( <> + } + zIndex={2} + position="sticky" + top={0} + itemsLeft={} itemsRight={ - <> - - {toolbarItemsRight} - - - - - - - - } - variant="plain" - size="lg" - fontSize="xl" - /> - - {toolbarItemsRightMenu} - {toolbarItemsRightMenu && } - - - - - - + + {toolbarItemsRight} + } + aria-label={intl.formatMessage({ id: "homepage.Link" })} + variant="plain" + size="lg" + fontSize="xl" + /> + + + } /> + {children} diff --git a/src/components/DownloadingDialog.tsx b/src/components/DownloadingDialog.tsx new file mode 100644 index 000000000..5d4ea1811 --- /dev/null +++ b/src/components/DownloadingDialog.tsx @@ -0,0 +1,57 @@ +import { + Heading, + Modal, + ModalBody, + ModalContent, + ModalOverlay, + Progress, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; + +export interface DownloadingDialogProps { + isOpen: boolean; + headingId: string; + progress: number; +} + +const DownloadingDialog = ({ + isOpen, + headingId, + progress, +}: DownloadingDialogProps) => { + return ( + {}} + size="3xl" + isCentered + > + + + + + + + + + + + + + + + + + ); +}; + +export default DownloadingDialog; diff --git a/src/components/EnterBluetoothPatternDialog.tsx b/src/components/EnterBluetoothPatternDialog.tsx new file mode 100644 index 000000000..95014692a --- /dev/null +++ b/src/components/EnterBluetoothPatternDialog.tsx @@ -0,0 +1,80 @@ +import { Text, VStack } from "@chakra-ui/react"; +import { useCallback, useState } from "react"; +import { FormattedMessage } from "react-intl"; +import BluetoothPatternInput from "./BluetoothPatternInput"; +import ConnectContainerDialog, { + ConnectContainerDialogProps, +} from "./ConnectContainerDialog"; + +const isPatternValid = (pattern: boolean[]) => { + for (let col = 0; col < 5; col++) { + let isAnyHighlighted = false; + for (let row = 0; row < 5; row++) { + if (pattern[row * 5 + col]) { + isAnyHighlighted = true; + } + } + if (!isAnyHighlighted) { + return false; + } + } + return true; +}; +export interface EnterBluetoothPatternDialogProps + extends Omit {} + +const EnterBluetoothPatternDialog = ({ + onNextClick, + onBackClick, + ...props +}: EnterBluetoothPatternDialogProps) => { + const [showInvalid, setShowInvalid] = useState(false); + const [bluetoothPattern, setBluetoothPattern] = useState( + Array(25).fill(false) + ); + + const handleNextClick = useCallback(() => { + if (!isPatternValid(bluetoothPattern)) { + setShowInvalid(true); + return; + } + onNextClick && onNextClick(); + }, [bluetoothPattern, onNextClick]); + + const handleBackClick = useCallback(() => { + setShowInvalid(false); + onBackClick && onBackClick(); + }, [onBackClick]); + + const handlePatternChange = useCallback((newPattern: boolean[]) => { + setBluetoothPattern(newPattern); + setShowInvalid(false); + }, []); + + return ( + + + + + + + + + + + + + + ); +}; + +export default EnterBluetoothPatternDialog; diff --git a/src/components/InfoToolTip.tsx b/src/components/InfoToolTip.tsx new file mode 100644 index 000000000..10149488d --- /dev/null +++ b/src/components/InfoToolTip.tsx @@ -0,0 +1,30 @@ +import { Icon, Text, VStack } from "@chakra-ui/react"; +import { RiInformationLine } from "react-icons/ri"; +import { FormattedMessage } from "react-intl"; +import ClickableTooltip from "./ClickableTooltip"; + +interface InfoToolTipProps { + titleId: string; + descriptionId: string; +} +const InfoToolTip = ({ titleId, descriptionId }: InfoToolTipProps) => { + return ( + + + + + + + + + } + > + + + ); +}; +export default InfoToolTip; diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 82c5af35b..d46eecf5e 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -1,6 +1,6 @@ import { Link as RouterLink, - type LinkProps as RouterLinkProps, + LinkProps as RouterLinkProps, } from "react-router-dom"; import { chakra } from "@chakra-ui/react"; import { Ref, forwardRef } from "react"; diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx new file mode 100644 index 000000000..f51eda460 --- /dev/null +++ b/src/components/LiveGraph.tsx @@ -0,0 +1,88 @@ +import { HStack } from "@chakra-ui/react"; +import { useSize } from "@chakra-ui/react-use-size"; +import { useEffect, useRef, useState } from "react"; +import { SmoothieChart, TimeSeries } from "smoothie"; + +const LiveGraph = () => { + // Updates width to ensure that the canvas fills the whole screen + // export let width: number; + + const connected = false; + + const canvasRef = useRef(null); + + const [chart, setChart] = useState(undefined); + const lineWidth = 2; + + const liveGraphContainerRef = useRef(null); + const { width, height } = useSize(liveGraphContainerRef) ?? { + width: 100, + height: 100, + }; + + // On mount draw smoothieChart + useEffect(() => { + if (!canvasRef.current) { + return; + } + const smoothieChart = new SmoothieChart({ + maxValue: 2.3, + minValue: -2, + millisPerPixel: 7, + grid: { + fillStyle: "#ffffff00", + strokeStyle: "rgba(48,48,48,0.20)", + millisPerLine: 3000, + borderVisible: false, + }, + interpolation: "linear", + }); + + const lineX = new TimeSeries(); + const lineY = new TimeSeries(); + const lineZ = new TimeSeries(); + const recordLines = new TimeSeries(); + + smoothieChart.addTimeSeries(lineX, { lineWidth, strokeStyle: "#f9808e" }); + smoothieChart.addTimeSeries(lineY, { lineWidth, strokeStyle: "#80f98e" }); + smoothieChart.addTimeSeries(lineZ, { lineWidth, strokeStyle: "#808ef9" }); + smoothieChart.addTimeSeries(recordLines, { + lineWidth: 3, + strokeStyle: "#4040ff44", + fillStyle: "#0000ff07", + }); + setChart(smoothieChart); + smoothieChart.streamTo(canvasRef.current, 0); + smoothieChart.render(); + return () => { + smoothieChart.stop(); + }; + }, []); + + useEffect(() => { + if (connected) { + chart?.start(); + } else { + chart?.stop(); + } + }, [chart, connected]); + + // TODO Recording logic + return ( + + + + ); +}; + +export default LiveGraph; diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx new file mode 100644 index 000000000..f9c88b7da --- /dev/null +++ b/src/components/LiveGraphPanel.tsx @@ -0,0 +1,53 @@ +import { Button, HStack, Text } from "@chakra-ui/react"; +import { MdBolt } from "react-icons/md"; +import { FormattedMessage } from "react-intl"; +import InfoToolTip from "./InfoToolTip"; +import LiveGraph from "./LiveGraph"; + +const LiveGraphPanel = () => { + // TODO: replace with hook + const isConnected = false; + return ( + + + + + {isConnected ? ( + + ) : ( + + )} + + + + + + + + ); +}; + +const LiveIndicator = () => ( + + + LIVE + +); + +export default LiveGraphPanel; diff --git a/src/components/LoadingAnimation/index.tsx b/src/components/LoadingAnimation/index.tsx new file mode 100644 index 000000000..d57d01ed0 --- /dev/null +++ b/src/components/LoadingAnimation/index.tsx @@ -0,0 +1,13 @@ +import { Box, HStack } from "@chakra-ui/react"; +import styles from "./styles.module.css"; + +// TODO: Maybe use framer-motion for this +const LoadingAnimation = () => { + return ( + + + + ); +}; + +export default LoadingAnimation; diff --git a/src/components/LoadingAnimation/styles.module.css b/src/components/LoadingAnimation/styles.module.css new file mode 100644 index 000000000..ec833ffc9 --- /dev/null +++ b/src/components/LoadingAnimation/styles.module.css @@ -0,0 +1,57 @@ +/* Modified from https://github.com/lukehaas/css-loaders */ +.loader, +.loader:before, +.loader:after { + border-radius: 50%; + width: 25px; + height: 25px; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + -webkit-animation: load7 1.8s infinite ease-in-out; + animation: load7 1.8s infinite ease-in-out; +} +.loader { + font-size: 10px; + position: absolute; + top: -25px; + text-indent: -9999em; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; +} +.loader:before, +.loader:after { + content: ""; + position: absolute; + top: 0; +} +.loader:before { + left: -3.5em; + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; +} +.loader:after { + left: 3.5em; +} +@-webkit-keyframes load7 { + 0%, + 80%, + 100% { + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + box-shadow: 0 2.5em 0 -1.3em; + } +} +@keyframes load7 { + 0%, + 80%, + 100% { + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + box-shadow: 0 2.5em 0 0; + } +} diff --git a/src/components/LoadingDialog.tsx b/src/components/LoadingDialog.tsx new file mode 100644 index 000000000..3c2f735d6 --- /dev/null +++ b/src/components/LoadingDialog.tsx @@ -0,0 +1,49 @@ +import { + Heading, + Modal, + ModalBody, + ModalContent, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import LoadingAnimation from "./LoadingAnimation"; + +export interface LoadingDialogProps { + headingId: string; + isOpen: boolean; +} + +const LoadingDialog = ({ headingId, isOpen }: LoadingDialogProps) => { + return ( + {}} + size="3xl" + isCentered + > + + + + + + + + + + + + + + + + + + + ); +}; + +export default LoadingDialog; diff --git a/src/components/ManualFlashingDialog.tsx b/src/components/ManualFlashingDialog.tsx new file mode 100644 index 000000000..e36edfe43 --- /dev/null +++ b/src/components/ManualFlashingDialog.tsx @@ -0,0 +1,96 @@ +import { Image, Text, VStack } from "@chakra-ui/react"; +import Bowser from "bowser"; +import { ReactNode, useCallback, useEffect } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; +import transferProgramChromeOS from "../images/transfer_program_chromeos.gif"; +import transferProgramMacOS from "../images/transfer_program_macos.gif"; +import transferProgramWindows from "../images/transfer_program_windows.gif"; +import ConnectContainerDialog, { + ConnectContainerDialogProps, +} from "./ConnectContainerDialog"; +import { getHexFileUrl } from "../device/get-hex-file"; +import { ConnType } from "../connection-flow"; + +interface ImageProps { + src: string; + height: number; +} + +// See https://github.com/lancedikson/bowser/blob/master/src/constants.js +const getImageProps = (os: string): ImageProps => { + switch (os) { + case "Chrome OS": + return { src: transferProgramChromeOS, height: 300 }; + case "Windows": + return { src: transferProgramWindows, height: 362 }; + case "macOS": + return { src: transferProgramMacOS, height: 360 }; + default: + return { src: transferProgramWindows, height: 392 }; + } +}; + +export interface ManualFlashingDialogProps + extends Omit {} + +const download = (data: string, filename: string) => { + const a = document.createElement("a"); + a.download = filename; + a.href = URL.createObjectURL(new Blob([data], { type: "text/csv" })); + a.click(); + a.remove(); +}; + +// Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. +const ManualFlashingDialog = ({ ...props }: ManualFlashingDialogProps) => { + const intl = useIntl(); + const browser = Bowser.getParser(window.navigator.userAgent); + const osName = browser.getOS().name ?? "unknown"; + + const imageProps = getImageProps(osName); + + const handleDownload = useCallback(() => { + download( + getHexFileUrl("universal", ConnType.Bluetooth)!, + "machine-learning-tool-program.hex" + ); + }, []); + + useEffect(() => { + handleDownload(); + }, [handleDownload]); + + return ( + + + + + ( + + {chunks} + + ), + }} + /> + + + + + {intl.formatMessage({ + + + ); +}; + +export default ManualFlashingDialog; diff --git a/src/components/MicrobitLogo.tsx b/src/components/MicrobitLogo.tsx index fb71054da..4c22a1639 100644 --- a/src/components/MicrobitLogo.tsx +++ b/src/components/MicrobitLogo.tsx @@ -1,29 +1,33 @@ const MicrobitLogo = ({ alt, - fill = "#fff" + fill = "#fff", + height, }: { alt: string; fill?: string; -}) => ( - - {alt} - - - - + height?: number; +}) => { + return ( + + {alt} + + + + + - - -); + + ); +}; export default MicrobitLogo; diff --git a/src/components/PrototypeVersionWarning.tsx b/src/components/PrototypeVersionWarning.tsx new file mode 100644 index 000000000..fc5e62413 --- /dev/null +++ b/src/components/PrototypeVersionWarning.tsx @@ -0,0 +1,27 @@ +import { HStack, Text } from "@chakra-ui/react"; +import { RiErrorWarningLine } from "react-icons/ri"; +import { FormattedMessage } from "react-intl"; + +const PrototypeVersionWarning = () => { + return ( + + + + + + + ); +}; + +export default PrototypeVersionWarning; diff --git a/src/components/ResourceCard.tsx b/src/components/ResourceCard.tsx new file mode 100644 index 000000000..440cb452f --- /dev/null +++ b/src/components/ResourceCard.tsx @@ -0,0 +1,47 @@ +import { + AspectRatio, + HStack, + Heading, + Image, + LinkBox, + LinkOverlay, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import Link from "./Link"; + +interface ResourceCardProps { + titleId: string; + path: string; + imgSrc: string; +} + +const ResourceCard = ({ titleId, path, imgSrc }: ResourceCardProps) => { + return ( + + + + + + + + + + + + + + + ); +}; + +export default ResourceCard; diff --git a/src/components/SelectMicrobitBluetoothDialog.tsx b/src/components/SelectMicrobitBluetoothDialog.tsx new file mode 100644 index 000000000..a1ab5e84c --- /dev/null +++ b/src/components/SelectMicrobitBluetoothDialog.tsx @@ -0,0 +1,86 @@ +import { + Box, + Flex, + Image, + List, + ListItem, + Text, + VisuallyHidden, +} from "@chakra-ui/react"; +import { FormattedMessage, useIntl } from "react-intl"; +import selectMicrobitImage from "../images/select-microbit-bluetooth.png"; +import ConnectContainerDialog, { + ConnectContainerDialogProps, +} from "./ConnectContainerDialog"; +import ArrowOne from "./ArrowOne"; +import ArrowTwo from "./ArrowTwo"; + +export interface SelectMicrobitBluetoothDialogProps + extends Omit {} + +const SelectMicrobitBluetoothDialog = ({ + ...props +}: SelectMicrobitBluetoothDialogProps) => { + const intl = useIntl(); + + return ( + + + {intl.formatMessage({ + + + + + + + + 1. + + + + + + + + + + 2. + + + + + + + + + + + + + + + + ); +}; + +export default SelectMicrobitBluetoothDialog; diff --git a/src/components/SelectMicrobitUsbDialog.tsx b/src/components/SelectMicrobitUsbDialog.tsx new file mode 100644 index 000000000..85a555d88 --- /dev/null +++ b/src/components/SelectMicrobitUsbDialog.tsx @@ -0,0 +1,82 @@ +import { + Box, + Flex, + Image, + List, + ListItem, + Text, + VisuallyHidden, +} from "@chakra-ui/react"; +import { FormattedMessage, useIntl } from "react-intl"; +import selectMicrobitImage from "../images/select-microbit-web-usb.png"; +import ConnectContainerDialog, { + ConnectContainerDialogProps, +} from "./ConnectContainerDialog"; +import ArrowOne from "./ArrowOne"; +import ArrowTwo from "./ArrowTwo"; + +export interface SelectMicrobitDialogProps + extends Omit {} + +const SelectMicrobitUsbDialog = ({ ...props }: SelectMicrobitDialogProps) => { + const intl = useIntl(); + + return ( + + + {intl.formatMessage({ + + + + + + + + 1. + + + + + + + + + + 2. + + + + + + + + + + + + + + + + ); +}; + +export default SelectMicrobitUsbDialog; diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx new file mode 100644 index 000000000..de690eaaa --- /dev/null +++ b/src/components/StartResumeActions.tsx @@ -0,0 +1,37 @@ +import { Button, HStack } from "@chakra-ui/react"; +import { useCallback } from "react"; +import { FormattedMessage } from "react-intl"; +import { useNavigate } from "react-router"; +import { createStepPageUrl } from "../urls"; +import { useConnectionFlow } from "../connections"; +import { ConnEvent } from "../connection-flow"; + +const StartResumeActions = () => { + // TODO hasExistingSession to check local storage + const hasExistingSession = true; + const navigate = useNavigate(); + const { dispatch } = useConnectionFlow(); + + const handleNewSession = useCallback(() => { + navigate(createStepPageUrl("add-data")); + dispatch(ConnEvent.Start); + }, [dispatch, navigate]); + return ( + + {hasExistingSession && ( + + )} + + + ); +}; + +export default StartResumeActions; diff --git a/src/components/TabView.tsx b/src/components/TabView.tsx new file mode 100644 index 000000000..d557aec97 --- /dev/null +++ b/src/components/TabView.tsx @@ -0,0 +1,42 @@ +import { Tab, TabIndicator, TabList, Tabs, VStack } from "@chakra-ui/react"; +import { useIntl } from "react-intl"; +import { StepId, stepsConfig } from "../steps-config"; +import { createStepPageUrl } from "../urls"; + +interface TabViewProps { + activeStep: StepId; +} +const TabView = ({ activeStep }: TabViewProps) => { + const intl = useIntl(); + const activeIndex = stepsConfig.findIndex((s) => s.id === activeStep); + return ( + + + + {stepsConfig.map((step, idx) => ( + + {`${idx + 1}. ${intl.formatMessage({ id: `${step.id}-title` })}`} + + ))} + + + + + ); +}; + +export default TabView; diff --git a/src/components/TryAgainDialog.tsx b/src/components/TryAgainDialog.tsx new file mode 100644 index 000000000..629872eea --- /dev/null +++ b/src/components/TryAgainDialog.tsx @@ -0,0 +1,138 @@ +import { + Button, + HStack, + Heading, + ListItem, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + UnorderedList, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import { ConnStage } from "../connection-flow"; + +const OneLineContent = ({ textId }: { textId: string }) => { + return ( + + + + ); +}; + +const ReplugMicrobitContent = () => { + return ( + <> + + + + + {[ + "connectMB.usbTryAgain.replugMicrobit3", + "connectMB.usbTryAgain.replugMicrobit4", + ].map((textId) => ( + + + + + + ))} + + + + ); +}; + +const CloseTabsContent = () => { + return ( + + + + + + + + + ); +}; + +const configs = { + [ConnStage.TryAgainReplugMicrobit]: { + headingId: "connectMB.usbTryAgain.heading", + children: , + }, + [ConnStage.TryAgainCloseTabs]: { + headingId: "connectMB.usbTryAgain.heading", + children: , + }, + [ConnStage.TryAgainSelectMicrobit]: { + headingId: "connectMB.usbTryAgain.heading", + children: , + }, + [ConnStage.TryAgainBluetoothConnect]: { + headingId: "connectMB.bluetooth.heading", + children: ( + + ), + }, +}; + +interface TryAgainWebUsbDialogProps { + isOpen: boolean; + onClose: () => void; + onTryAgain: () => void; + type: + | ConnStage.TryAgainReplugMicrobit + | ConnStage.TryAgainCloseTabs + | ConnStage.TryAgainSelectMicrobit + | ConnStage.TryAgainBluetoothConnect; +} + +const TryAgainWebUsbDialog = ({ + type, + isOpen, + onClose, + onTryAgain, +}: TryAgainWebUsbDialogProps) => { + const config = configs[type]; + return ( + + + + + + + + + + {config.children} + + + + + + + + + + + + + ); +}; + +export default TryAgainWebUsbDialog; diff --git a/src/components/UnsupportedMicrobitDialog.tsx b/src/components/UnsupportedMicrobitDialog.tsx new file mode 100644 index 000000000..9a876f010 --- /dev/null +++ b/src/components/UnsupportedMicrobitDialog.tsx @@ -0,0 +1,112 @@ +import { + Button, + HStack, + Heading, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { FormattedMessage } from "react-intl"; +import Link from "./Link"; + +interface UnsupportedMicrobitDialogProps { + isOpen: boolean; + onClose: () => void; + onStartBluetoothClick: () => void; +} + +const UnsupportedMicrobitDialog = ({ + isOpen, + onClose, + onStartBluetoothClick, +}: UnsupportedMicrobitDialogProps) => { + // TODO: Check if bluetooth is compatible + const isBluetoothSupported = true; + return ( + + + + + + + + + + + ( + + {chunks} + + ), + }} + /> + + + {isBluetoothSupported ? ( + + ) : ( + ( + + {chunks} + + ), + }} + /> + )} + + + + + + + {isBluetoothSupported ? ( + + ) : ( + + )} + + + + + + ); +}; + +export default UnsupportedMicrobitDialog; diff --git a/src/components/WhatYouWillNeedDialog.tsx b/src/components/WhatYouWillNeedDialog.tsx new file mode 100644 index 000000000..8219c255e --- /dev/null +++ b/src/components/WhatYouWillNeedDialog.tsx @@ -0,0 +1,132 @@ +import { Grid, GridItem, Image, Text, VStack } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import ConnectContainerDialog, { + ConnectContainerDialogProps, +} from "./ConnectContainerDialog"; +import batteryPackImage from "../images/stylised-battery-pack.svg"; +import microbitImage from "../images/stylised-microbit-black.svg"; +import twoMicrobitsImage from "../images/stylised-two-microbits-black.svg"; +import usbCableImage from "../images/stylised-usb-cable.svg"; +import computerImage from "../images/stylised_computer.svg"; +import computerBluetoothImage from "../images/stylised_computer_w_bluetooth.svg"; +import { ConnType } from "../connection-flow"; + +const whatYouWillNeedRadioConfig = { + headingId: "connectMB.radioStart.heading", + reconnectHeadingId: "reconnectFailed.radioHeading", + linkTextId: "connectMB.radioStart.switchBluetooth", + items: [ + { + imgSrc: twoMicrobitsImage, + titleId: "connectMB.radioStart.requirements1", + }, + { + imgSrc: computerImage, + titleId: "connectMB.radioStart.requirements2", + subtitleId: "connectMB.radioStart.requirements2.subtitle", + }, + { + imgSrc: usbCableImage, + titleId: "connectMB.radioStart.requirements3", + }, + { + imgSrc: batteryPackImage, + titleId: "connectMB.radioStart.requirements4", + subtitleId: "connectMB.radioStart.requirements4.subtitle", + }, + ], +}; + +const whatYouWillNeedBluetoothConfig = { + headingId: "connectMB.bluetoothStart.heading", + reconnectHeadingId: "reconnectFailed.bluetoothHeading", + linkTextId: "connectMB.bluetoothStart.switchRadio", + items: [ + { + imgSrc: microbitImage, + titleId: "connectMB.bluetoothStart.requirements1", + }, + { + imgSrc: computerBluetoothImage, + titleId: "connectMB.bluetoothStart.requirements2", + subtitleId: "connectMB.bluetoothStart.requirements2.subtitle", + }, + { + imgSrc: usbCableImage, + titleId: "connectMB.bluetoothStart.requirements3", + }, + { + imgSrc: batteryPackImage, + titleId: "connectMB.bluetoothStart.requirements4", + subtitleId: "connectMB.bluetoothStart.requirements4.subtitle", + }, + ], +}; + +export interface WhatYouWillNeedDialogProps + extends Omit< + ConnectContainerDialogProps, + "children" | "onBack" | "headingId" + > { + reconnect: boolean; + type: ConnType; +} + +const WhatYouWillNeedDialog = ({ + reconnect, + type, + ...props +}: WhatYouWillNeedDialogProps) => { + const configs = { + [ConnType.Bluetooth]: whatYouWillNeedBluetoothConfig, + [ConnType.RadioRemote]: whatYouWillNeedRadioConfig, + [ConnType.RadioBridge]: whatYouWillNeedRadioConfig, + }; + const { items, headingId, reconnectHeadingId, linkTextId } = configs[type]; + return ( + + {reconnect && ( + + + + )} + + {items.map(({ imgSrc, titleId, subtitleId }) => { + return ( + + + + + + + + {subtitleId && ( + + + + )} + + + + ); + })} + + + ); +}; + +export default WhatYouWillNeedDialog; diff --git a/src/connection-flow.ts b/src/connection-flow.ts new file mode 100644 index 000000000..82da8cbe8 --- /dev/null +++ b/src/connection-flow.ts @@ -0,0 +1,184 @@ +import { Reducer } from "react"; + +export enum ConnStage { + None, + Start, + ConnectCable, + WebUsbFlashingTutorial, + ManualFlashingTutorial, + ConnectBattery, + EnterBluetoothPattern, + ConnectBluetoothTutorial, + + // Stages that are not user-controlled + WebUsbChooseMicrobit, + ConnectingBluetooth, + ConnectingMicrobits, + FlashingInProgress, + + // Failure stages + TryAgainReplugMicrobit, + TryAgainCloseTabs, + TryAgainSelectMicrobit, + TryAgainBluetoothConnect, + BadFirmware, + MicrobitUnsupported, +} + +export enum ConnType { + Bluetooth, + RadioBridge, + RadioRemote, +} + +export type ConnState = { + stage: ConnStage; + type: ConnType; + isUsbSupported: boolean; +}; + +export enum ConnEvent { + // User triggered events + Start, + Switch, + Next, + Back, + SkipFlashing, + TryAgain, + GoToBluetoothStart, + Close, + + // Web USB Flashing events + WebUsbChooseMicrobit, + FlashingInProgress, + FlashingComplete, + + // Web USB Flashing failure events + TryAgainReplugMicrobit, + TryAgainCloseTabs, + TryAgainSelectMicrobit, + InstructManualFlashing, + BadFirmware, + MicrobitUnsupported, + + // Bluetooth connection event + ConnectingBluetooth, + + // Bluetooth connection failure event + TryAgainBluetoothConnect, + + // Connecting microbits for radio connection + ConnectingMicrobits, +} + +type StageAndType = Pick; + +export const connectionDialogReducer: Reducer = ( + state, + event +) => { + switch (event) { + case ConnEvent.Start: + return { ...state, stage: ConnStage.Start }; + case ConnEvent.Close: + return { ...state, stage: ConnStage.None }; + case ConnEvent.SkipFlashing: + return { ...state, stage: ConnStage.ConnectBattery }; + case ConnEvent.FlashingInProgress: + return { ...state, stage: ConnStage.FlashingInProgress }; + case ConnEvent.InstructManualFlashing: + return { ...state, stage: ConnStage.ManualFlashingTutorial }; + case ConnEvent.WebUsbChooseMicrobit: + return { ...state, stage: ConnStage.WebUsbChooseMicrobit }; + case ConnEvent.ConnectingBluetooth: + return { ...state, stage: ConnStage.ConnectingBluetooth }; + case ConnEvent.ConnectingMicrobits: + return { ...state, stage: ConnStage.ConnectingMicrobits }; + case ConnEvent.Next: + return { ...state, ...getNextStageAndType(state, 1) }; + case ConnEvent.Back: + return { ...state, ...getNextStageAndType(state, -1) }; + case ConnEvent.Switch: + return { + ...state, + type: + state.type === ConnType.Bluetooth + ? ConnType.RadioRemote + : ConnType.Bluetooth, + }; + case ConnEvent.GoToBluetoothStart: + return { + ...state, + stage: ConnStage.Start, + type: ConnType.Bluetooth, + }; + case ConnEvent.FlashingComplete: + return { + ...state, + stage: + state.type === ConnType.RadioRemote + ? ConnStage.ConnectBattery + : ConnStage.ConnectingMicrobits, + }; + case ConnEvent.TryAgain: + return { + ...state, + stage: + state.stage === ConnStage.TryAgainBluetoothConnect + ? ConnStage.ConnectBluetoothTutorial + : ConnStage.ConnectCable, + }; + default: + return state; + } +}; + +const getStageAndTypeOrder = (state: ConnState): StageAndType[] => { + const { RadioRemote, RadioBridge, Bluetooth } = ConnType; + if (state.type === ConnType.Bluetooth) { + return [ + { stage: ConnStage.Start, type: Bluetooth }, + { stage: ConnStage.ConnectCable, type: Bluetooth }, + // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. + !state.isUsbSupported || state.stage === ConnStage.ManualFlashingTutorial + ? { stage: ConnStage.ManualFlashingTutorial, type: Bluetooth } + : { stage: ConnStage.WebUsbFlashingTutorial, type: Bluetooth }, + { stage: ConnStage.ConnectBattery, type: Bluetooth }, + { stage: ConnStage.EnterBluetoothPattern, type: Bluetooth }, + { stage: ConnStage.ConnectBluetoothTutorial, type: Bluetooth }, + ]; + } + return [ + { stage: ConnStage.Start, type: RadioRemote }, + { stage: ConnStage.ConnectCable, type: RadioRemote }, + { stage: ConnStage.WebUsbFlashingTutorial, type: RadioRemote }, + { stage: ConnStage.ConnectBattery, type: RadioRemote }, + { stage: ConnStage.ConnectCable, type: RadioBridge }, + { stage: ConnStage.WebUsbFlashingTutorial, type: RadioBridge }, + ]; +}; + +const getStageAndTypeIdx = ( + { stage, type }: StageAndType, + order: StageAndType[] +) => { + for (let idx = 0; idx < order.length; idx++) { + const step = order[idx]; + if (step.stage === stage && step.type === type) { + return idx; + } + } + throw new Error("Should be able to match stage and type again order"); +}; + +const getNextStageAndType = (state: ConnState, step: number): StageAndType => { + const order = getStageAndTypeOrder(state); + const curr = { stage: state.stage, type: state.type }; + const currIdx = getStageAndTypeIdx(curr, order); + const newIdx = currIdx + step; + // If impossible step stage, stick to current step + if (newIdx === order.length || newIdx < 0) { + return curr; + } + return order[newIdx]; +}; diff --git a/src/connections.tsx b/src/connections.tsx new file mode 100644 index 000000000..766873e4f --- /dev/null +++ b/src/connections.tsx @@ -0,0 +1,45 @@ +import { ReactNode, createContext, useContext, useReducer } from "react"; +import { + ConnEvent, + ConnStage, + ConnState, + ConnType, + connectionDialogReducer, +} from "./connection-flow"; + +type ConnectionFlowContextValue = { + state: ConnState; + dispatch: React.Dispatch; +}; + +export const ConnectionFlowContext = + createContext(null); + +interface ConnectionFlowProviderProps { + children: ReactNode; +} + +export const ConnectionFlowProvider = ({ + children, +}: ConnectionFlowProviderProps) => { + const isBluetoothSupported = true; + const [state, dispatch] = useReducer(connectionDialogReducer, { + // TODO: Check bt and usb compatibility + type: isBluetoothSupported ? ConnType.Bluetooth : ConnType.RadioRemote, + stage: ConnStage.None, + isUsbSupported: true, + }); + return ( + + {children} + + ); +}; + +export const useConnectionFlow = (): ConnectionFlowContextValue => { + const connFlow = useContext(ConnectionFlowContext); + if (!connFlow) { + throw new Error("Missing provider"); + } + return connFlow; +}; diff --git a/src/constants.ts b/src/constants.ts index 67a7a19be..7c69f135f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1 @@ -export const APP_NAME = "micro:bit machine learning"; +export const TOOL_NAME = "machine learning tool"; diff --git a/src/theme/colors.ts b/src/deployment/default/colors.ts similarity index 80% rename from src/theme/colors.ts rename to src/deployment/default/colors.ts index db415e837..cb8c7572f 100644 --- a/src/theme/colors.ts +++ b/src/deployment/default/colors.ts @@ -1,9 +1,40 @@ +import { theme } from "@chakra-ui/theme"; + +const gray = { + 10: "#fcfcfc", + 25: "#f5f5f5", + ...theme.colors.gray, + // Brand grey + 500: "#e5e5e5", +}; + +const brand = { + // Produced with using the hue from the brand blue at 400 but adjusting 500 + // to be an acceptable button background color with white text at all sizes + // (WCGA AA 4.5) + // https://huetone.ardov.me/ + 50: "#edf7ff", + 100: "#c1e1fb", + 200: "#94ccf6", + 300: "#62b3ed", + // Brand colour, but too light for white + 400: "#2a94d6", + // 4.5 contrast with white + 500: "#007dbc", + 600: "#0071aa", + 700: "#16567e", + 800: "#1d4662", + 900: "#023a5a", +}; + const colors = { // Brand guidelines say: // "Each of the main primary colours can be tinted by 80%, 50%, 30%, 20% and 10% if needed." // We've assumed this means tints and shades. // Colours created via e.g. // https://maketintsandshades.com/#6C4BC1 + gray, + brand, purple: { 50: "#e2dbf3", // 80% tint 100: "#b6a5e0", // 50% tint diff --git a/src/theme/components/alert.ts b/src/deployment/default/components/alert.ts similarity index 100% rename from src/theme/components/alert.ts rename to src/deployment/default/components/alert.ts diff --git a/src/theme/components/avatar.ts b/src/deployment/default/components/avatar.ts similarity index 63% rename from src/theme/components/avatar.ts rename to src/deployment/default/components/avatar.ts index bd75909a2..5dfbecab7 100644 --- a/src/theme/components/avatar.ts +++ b/src/deployment/default/components/avatar.ts @@ -1,19 +1,17 @@ import { avatarAnatomy } from "@chakra-ui/anatomy"; import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react"; -const { - definePartsStyle, - defineMultiStyleConfig -} = createMultiStyleConfigHelpers(avatarAnatomy.keys); +const { definePartsStyle, defineMultiStyleConfig } = + createMultiStyleConfigHelpers(avatarAnatomy.keys); const md2 = defineStyle({ width: 10, height: 10, - fontSize: "md" + fontSize: "md", }); const sizes = { - "2md": definePartsStyle({ container: md2 }) + "2md": definePartsStyle({ container: md2 }), }; const avatarTheme = defineMultiStyleConfig({ sizes }); diff --git a/src/theme/components/button.ts b/src/deployment/default/components/button.ts similarity index 83% rename from src/theme/components/button.ts rename to src/deployment/default/components/button.ts index 661ea357f..c79105543 100644 --- a/src/theme/components/button.ts +++ b/src/deployment/default/components/button.ts @@ -25,16 +25,23 @@ const Button: StyleConfig = { }, }; }, + link: () => ({ + borderWidth: "0", + color: "purple.500", + fontWeight: "normal", + bg: "transparent", + }), secondary: () => ({ borderWidth: "2px", - borderColor: "black", - color: "black", + borderColor: "brand.500", + color: "brand.700", bg: "transparent", _hover: { - bg: "blackAlpha.50", + borderColor: "brand.600", }, _active: { - bg: "blackAlpha.100", + bg: "brand.50", + borderColor: "brand.700", }, }), ghost: () => ({ @@ -49,15 +56,16 @@ const Button: StyleConfig = { }), primary: () => ({ color: "white", - bg: "black", + bg: "brand.500", _hover: { - bg: "blackAlpha.800", + bg: "brand.600", _disabled: { - bg: "black", + bg: "brand.500", + opacity: 0.6, }, }, _active: { - bg: "blackAlpha.700", + bg: "brand.700", }, }), toolbar: () => ({ diff --git a/src/theme/components/heading.ts b/src/deployment/default/components/heading.ts similarity index 73% rename from src/theme/components/heading.ts rename to src/deployment/default/components/heading.ts index e365f764e..ff79c21fe 100644 --- a/src/theme/components/heading.ts +++ b/src/deployment/default/components/heading.ts @@ -2,14 +2,14 @@ const Heading = { variants: { label: { fontSize: "4xl", - color: "#cd0365" + color: "#cd0365", }, subtitle: { fontSize: "xl", fontWeight: "normal", - color: "#cd0365" - } - } + color: "#cd0365", + }, + }, }; export default Heading; diff --git a/src/theme/components/text.ts b/src/deployment/default/components/text.ts similarity index 58% rename from src/theme/components/text.ts rename to src/deployment/default/components/text.ts index 68ab483ef..3fc813535 100644 --- a/src/theme/components/text.ts +++ b/src/deployment/default/components/text.ts @@ -1,15 +1,15 @@ const Text = { sizes: { sm: { - fontSize: "sm" + fontSize: "sm", }, md: { - fontSize: "md" - } + fontSize: "md", + }, }, defaultProps: { - size: "md" - } + size: "md", + }, }; export default Text; diff --git a/src/theme/components/tooltip.ts b/src/deployment/default/components/tooltip.ts similarity index 51% rename from src/theme/components/tooltip.ts rename to src/deployment/default/components/tooltip.ts index b9aaa65d5..73741e475 100644 --- a/src/theme/components/tooltip.ts +++ b/src/deployment/default/components/tooltip.ts @@ -1,8 +1,8 @@ -import { StyleConfig } from '@chakra-ui/theme-tools'; +import { StyleConfig } from "@chakra-ui/theme-tools"; const Tooltip: StyleConfig = { baseStyle: { - fontSize: 'md', + fontSize: "md", }, }; diff --git a/src/theme/default-graph.ts b/src/deployment/default/default-graph.ts similarity index 100% rename from src/theme/default-graph.ts rename to src/deployment/default/default-graph.ts diff --git a/src/theme/fonts.ts b/src/deployment/default/fonts.ts similarity index 66% rename from src/theme/fonts.ts rename to src/deployment/default/fonts.ts index 7a5a9e73c..35ead9c4a 100644 --- a/src/theme/fonts.ts +++ b/src/deployment/default/fonts.ts @@ -1,5 +1,5 @@ const fonts = { - heading: "GT Walsheim, sans-serif", + heading: "Helvetica Now, sans-serif", body: "Helvetica Now, sans-serif", }; diff --git a/src/theme/graph.ts b/src/deployment/default/graph.ts similarity index 100% rename from src/theme/graph.ts rename to src/deployment/default/graph.ts diff --git a/src/deployment/default/index.tsx b/src/deployment/default/index.tsx new file mode 100644 index 000000000..658d681fe --- /dev/null +++ b/src/deployment/default/index.tsx @@ -0,0 +1,35 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { ReactNode, createContext } from "react"; +import { CookieConsent, DeploymentConfigFactory } from ".."; +import { NullLogging } from "./logging"; +import theme from "./theme"; + +const stubConsentValue: CookieConsent = { + analytics: false, + functional: true, +}; +const stubConsentContext = createContext( + stubConsentValue +); + +const defaultDeploymentFactory: DeploymentConfigFactory = () => ({ + chakraTheme: theme, + // This isn't ideal as it's the branded version. You can just remove the field to remove the welcome dialog. + welcomeVideoYouTubeId: "mREwMW69qKc", + logging: new NullLogging(), + compliance: { + ConsentProvider: ({ children }: { children: ReactNode }) => ( + + {children} + + ), + consentContext: stubConsentContext, + manageCookies: undefined, + }, +}); + +export default defaultDeploymentFactory; diff --git a/src/deployment/default/logging.ts b/src/deployment/default/logging.ts new file mode 100644 index 000000000..4e597c300 --- /dev/null +++ b/src/deployment/default/logging.ts @@ -0,0 +1,13 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { Logging } from "../../logging/logging"; + +export class NullLogging implements Logging { + event(): void {} + error(): void {} + log(): void {} +} diff --git a/src/theme/radii.ts b/src/deployment/default/radii.ts similarity index 84% rename from src/theme/radii.ts rename to src/deployment/default/radii.ts index f254d5784..5297f29c7 100644 --- a/src/theme/radii.ts +++ b/src/deployment/default/radii.ts @@ -1,6 +1,6 @@ const radii = { // Design radius for buttons and other larger items - button: '2rem', + button: "2rem", }; export default radii; diff --git a/src/theme/shadows.ts b/src/deployment/default/shadows.ts similarity index 66% rename from src/theme/shadows.ts rename to src/deployment/default/shadows.ts index 7532a2d7d..ee9f47396 100644 --- a/src/theme/shadows.ts +++ b/src/deployment/default/shadows.ts @@ -1,6 +1,6 @@ const shadows = { outline: "0 0 0 4px rgba(66, 153, 225, 0.6)", - outlineDark: "0 0 0 4px rgba(66, 153, 225)" + outlineDark: "0 0 0 4px rgba(66, 153, 225)", }; export default shadows; diff --git a/src/theme/theme.ts b/src/deployment/default/theme.ts similarity index 100% rename from src/theme/theme.ts rename to src/deployment/default/theme.ts diff --git a/src/deployment/deployment.d.ts b/src/deployment/deployment.d.ts new file mode 100644 index 000000000..10d162381 --- /dev/null +++ b/src/deployment/deployment.d.ts @@ -0,0 +1,10 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ + +/** + * See vite.config.ts + */ +declare module "theme-package"; diff --git a/src/deployment/index.ts b/src/deployment/index.ts new file mode 100644 index 000000000..d0fca8491 --- /dev/null +++ b/src/deployment/index.ts @@ -0,0 +1,63 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { ReactNode, useContext } from "react"; +import { Logging } from "../logging/logging"; + +export type DeploymentConfigFactory = ( + env: Record +) => DeploymentConfig; + +// This is configured via a vite alias, defaulting to ./default +import { default as df } from "theme-package"; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const deploymentFactory: DeploymentConfigFactory = df; +export const deployment = deploymentFactory(import.meta.env); + +export interface CookieConsent { + analytics: boolean; + functional: boolean; +} + +export interface DeploymentConfig { + welcomeVideoYouTubeId?: string; + squareLogo?: ReactNode; + horizontalLogo?: ReactNode; + compliance: { + /** + * A provider that will be used to wrap the app UI. + */ + ConsentProvider: (props: { children: ReactNode }) => JSX.Element; + /** + * Context that will be used to read the current consent value. + * The provider is not used directly. + */ + consentContext: React.Context; + /** + * Optional hook for the user to revisit cookie settings. + */ + manageCookies: (() => void) | undefined; + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chakraTheme: any; + + supportLink?: string; + guideLink?: string; + termsOfUseLink?: string; + privacyPolicyLink?: string; + translationLink?: string; + + logging: Logging; +} + +export const useDeployment = (): DeploymentConfig => { + return deployment; +}; + +export const useCookieConsent = (): CookieConsent | undefined => { + const { compliance } = useDeployment(); + return useContext(compliance.consentContext); +}; diff --git a/src/device/board-id.ts b/src/device/board-id.ts new file mode 100644 index 000000000..6df8c2744 --- /dev/null +++ b/src/device/board-id.ts @@ -0,0 +1,58 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ + +/** + * Validates micro:bit board IDs. + */ +export class BoardId { + private static v1Normalized = new BoardId(0x9900); + private static v2Normalized = new BoardId(0x9903); + + constructor(public id: number) { + if (!this.isV1() && !this.isV2()) { + throw new Error(`Could not recognise the Board ID ${id.toString(16)}`); + } + } + + isV1(): boolean { + return this.id === 0x9900 || this.id === 0x9901; + } + + isV2(): boolean { + return ( + this.id === 0x9903 || + this.id === 0x9904 || + this.id === 0x9905 || + this.id === 0x9906 + ); + } + + /** + * Return the board ID using the default ID for the board type. + * Used to integrate with MicropythonFsHex. + */ + normalize() { + return this.isV1() ? BoardId.v1Normalized : BoardId.v2Normalized; + } + + /** + * toString matches the input to parse. + * + * @returns the ID as a string. + */ + toString() { + return this.id.toString(16); + } + + /** + * @param value The ID as a hex string with no 0x prefix (e.g. 9900). + * @returns the valid board ID + * @throws if the ID isn't known. + */ + static parse(value: string): BoardId { + return new BoardId(parseInt(value, 16)); + } +} diff --git a/src/device/board-serial-info.test.ts b/src/device/board-serial-info.test.ts new file mode 100644 index 000000000..60c383344 --- /dev/null +++ b/src/device/board-serial-info.test.ts @@ -0,0 +1,52 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { BoardId } from "./board-id"; +import { BoardSerialInfo } from "./board-serial-info"; +import { vi } from "vitest"; + +describe("BoardSerialInfo", () => { + const valid = { + serialNumber: "9904360251974e450039900a00000041000000009796990b", + } as USBDevice; + const weirdLength = { + serialNumber: "9904360251974e450039900a000000410000000097969", + } as USBDevice; + const missing = { serialNumber: "" } as USBDevice; + const log = vi.fn(); + afterEach(() => { + log.mockReset(); + }); + + it("throws if serialNumber missing", () => { + expect(() => BoardSerialInfo.parse(missing, log)).toThrowError(); + + expect(log.mock.calls).toEqual([]); + }); + + it("parses serials", () => { + const result = BoardSerialInfo.parse(valid, log); + expect(result).toEqual({ + id: BoardId.parse("9904"), + familyId: "3602", + hic: "9796990b", + }); + + expect(log.mock.calls).toEqual([]); + }); + + it("logs if unexpected length", () => { + const result = BoardSerialInfo.parse(weirdLength, log); + expect(result).toEqual({ + id: BoardId.parse("9904"), + familyId: "3602", + hic: "00097969", + }); + + expect(log.mock.calls).toEqual([ + ["USB serial number unexpected length: 45"], + ]); + }); +}); diff --git a/src/device/board-serial-info.ts b/src/device/board-serial-info.ts new file mode 100644 index 000000000..8651992cc --- /dev/null +++ b/src/device/board-serial-info.ts @@ -0,0 +1,35 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { BoardId } from "./board-id"; + +export class BoardSerialInfo { + constructor( + public id: BoardId, + public familyId: string, + public hic: string + ) {} + static parse(device: USBDevice, log: (msg: string) => void) { + const serial = device.serialNumber; + if (!serial) { + throw new Error("Could not detected ID from connected board."); + } + if (serial.length !== 48) { + log(`USB serial number unexpected length: ${serial.length}`); + } + const id = serial.substring(0, 4); + const familyId = serial.substring(4, 8); + const hic = serial.slice(-8); + return new BoardSerialInfo(BoardId.parse(id), familyId, hic); + } + + eq(other: BoardSerialInfo) { + return ( + other.id === this.id && + other.familyId === this.familyId && + other.hic === this.hic + ); + } +} diff --git a/src/device/constants.ts b/src/device/constants.ts new file mode 100644 index 000000000..1ad71a495 --- /dev/null +++ b/src/device/constants.ts @@ -0,0 +1,80 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ + +// https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/constants.ts +// https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/constants.ts + +// CRA's build tooling doesn't support const enums so we've converted them to prefixed constants here. +// If we move this to a separate library then we can replace them. +// In the meantime we should prune the list below to what we actually use. + +// FICR Registers +export const FICR = { + CODEPAGESIZE: 0x10000000 | 0x10, + CODESIZE: 0x10000000 | 0x14, +}; + +export const DapCmd = { + DAP_INFO: 0x00, + DAP_CONNECT: 0x02, + DAP_DISCONNECT: 0x03, + DAP_TRANSFER: 0x05, + DAP_TRANSFER_BLOCK: 0x06, + // Many more. +}; + +export const Csw = { + CSW_SIZE: 0x00000007, + CSW_SIZE32: 0x00000002, + CSW_ADDRINC: 0x00000030, + CSW_SADDRINC: 0x00000010, + CSW_DBGSTAT: 0x00000040, + CSW_HPROT: 0x02000000, + CSW_MSTRDBG: 0x20000000, + CSW_RESERVED: 0x01000000, + CSW_VALUE: -1, // see below + // Many more. +}; +Csw.CSW_VALUE = + Csw.CSW_RESERVED | + Csw.CSW_MSTRDBG | + Csw.CSW_HPROT | + Csw.CSW_DBGSTAT | + Csw.CSW_SADDRINC; + +export const DapVal = { + AP_ACC: 1 << 0, + READ: 1 << 1, + WRITE: 0 << 1, + // More. +}; + +export const ApReg = { + CSW: 0x00, + TAR: 0x04, + DRW: 0x0c, + // More. +}; + +export const CortexSpecialReg = { + // Debug Exception and Monitor Control Register + DEMCR: 0xe000edfc, + // DWTENA in armv6 architecture reference manual + DEMCR_VC_CORERESET: 1 << 0, + + // CPUID Register + CPUID: 0xe000ed00, + + // Debug Halting Control and Status Register + DHCSR: 0xe000edf0, + S_RESET_ST: 1 << 25, + + NVIC_AIRCR: 0xe000ed0c, + NVIC_AIRCR_VECTKEY: 0x5fa << 16, + NVIC_AIRCR_SYSRESETREQ: 1 << 2, + + // Many more. +}; diff --git a/src/device/dap-wrapper.ts b/src/device/dap-wrapper.ts new file mode 100644 index 000000000..ee9ac97c4 --- /dev/null +++ b/src/device/dap-wrapper.ts @@ -0,0 +1,417 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { CortexM, DAPLink, WebUSB } from "dapjs"; +import { Logging } from "../logging/logging"; +import { + ApReg, + CortexSpecialReg, + Csw, + DapCmd, + DapVal, + FICR, +} from "./constants"; +import { + apReg, + bufferConcat, + CoreRegister, + regRequest, +} from "./partial-flashing-utils"; +import { BoardSerialInfo } from "./board-serial-info"; + +export class DAPWrapper { + transport: WebUSB; + daplink: DAPLink; + cortexM: CortexM; + + _pageSize: number | undefined; + _numPages: number | undefined; + + private loggedBoardSerialInfo: BoardSerialInfo | undefined; + + private initialConnectionComplete: boolean = false; + + constructor(public device: USBDevice, private logging: Logging) { + this.transport = new WebUSB(this.device); + this.daplink = new DAPLink(this.transport); + this.cortexM = new CortexM(this.transport); + } + + /** + * The page size. Throws if we've not connected. + */ + get pageSize(): number { + if (this._pageSize === undefined) { + throw new Error("pageSize not defined until connected"); + } + return this._pageSize; + } + + /** + * The number of pages. Throws if we've not connected. + */ + get numPages() { + if (this._numPages === undefined) { + throw new Error("numPages not defined until connected"); + } + return this._numPages; + } + + get boardSerialInfo(): BoardSerialInfo { + return BoardSerialInfo.parse( + this.device, + this.logging.log.bind(this.logging) + ); + } + + // Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L119 + async reconnectAsync(): Promise { + if (this.initialConnectionComplete) { + await this.disconnectAsync(); + + this.transport = new WebUSB(this.device); + this.daplink = new DAPLink(this.transport); + this.cortexM = new CortexM(this.transport); + } else { + this.initialConnectionComplete = true; + } + + await this.daplink.connect(); + await this.cortexM.connect(); + + this.logging.event({ + type: "WebUSB-info", + message: "connected", + }); + + const serialInfo = this.boardSerialInfo; + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + this.logging.log(`Detected board ID ${serialInfo.id}`); + + if ( + !this.loggedBoardSerialInfo || + !this.loggedBoardSerialInfo.eq(this.boardSerialInfo) + ) { + this.loggedBoardSerialInfo = this.boardSerialInfo; + this.logging.event({ + type: "WebUSB-info", + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + message: "board-id/" + this.boardSerialInfo.id, + }); + this.logging.event({ + type: "WebUSB-info", + message: + "board-family-hic/" + + this.boardSerialInfo.familyId + + this.boardSerialInfo.hic, + }); + } + + this._pageSize = await this.cortexM.readMem32(FICR.CODEPAGESIZE); + this._numPages = await this.cortexM.readMem32(FICR.CODESIZE); + } + + async startSerial(listener: (data: string) => void): Promise { + const currentBaud = await this.daplink.getSerialBaudrate(); + if (currentBaud !== 115200) { + // Changing the baud rate causes a micro:bit reset, so only do it if necessary + await this.daplink.setSerialBaudrate(115200); + } + this.daplink.on(DAPLink.EVENT_SERIAL_DATA, listener); + await this.daplink.startSerialRead(1); + } + + stopSerial(listener: (data: string) => void): void { + this.daplink.stopSerialRead(); + this.daplink.removeListener(DAPLink.EVENT_SERIAL_DATA, listener); + } + + async disconnectAsync(): Promise { + if ( + this.device.opened && + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (this.transport as any).interfaceNumber !== undefined + ) { + return this.daplink.disconnect(); + } + } + + // Send a packet to the micro:bit directly via WebUSB and return the response. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/transport/cmsis_dap.ts#L161 + private async send(packet: number[]): Promise { + const array = Uint8Array.from(packet); + await this.transport.write(array.buffer); + + const response = await this.transport.read(); + return new Uint8Array(response.buffer); + } + + // Send a command along with relevant data to the micro:bit directly via WebUSB and handle the response. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/transport/cmsis_dap.ts#L74 + private async cmdNums( + op: number /* DapCmd */, + data: number[] + ): Promise { + data.unshift(op); + + const buf = await this.send(data); + + if (buf[0] !== op) { + throw new Error(`Bad response for ${op} -> ${buf[0]}`); + } + + switch (op) { + case DapCmd.DAP_CONNECT: + case DapCmd.DAP_INFO: + case DapCmd.DAP_TRANSFER: + case DapCmd.DAP_TRANSFER_BLOCK: + break; + default: + if (buf[1] !== 0) { + throw new Error(`Bad status for ${op} -> ${buf[1]}`); + } + } + + return buf; + } + + // Read a certain register a specified amount of times. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/dap.ts#L117 + private async readRegRepeat( + regId: number /* Reg */, + cnt: number + ): Promise { + const request = regRequest(regId); + const sendargs = [0, cnt]; + + for (let i = 0; i < cnt; ++i) { + sendargs.push(request); + } + + // Transfer the read requests to the micro:bit and retrieve the data read. + const buf = await this.cmdNums(DapCmd.DAP_TRANSFER, sendargs); + + if (buf[1] !== cnt) { + throw new Error("(many) Bad #trans " + buf[1]); + } else if (buf[2] !== 1) { + throw new Error("(many) Bad transfer status " + buf[2]); + } + + return buf.subarray(3, 3 + cnt * 4); + } + + // Write to a certain register a specified amount of data. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/dap.ts#L138 + private async writeRegRepeat( + regId: number /* Reg */, + data: Uint32Array + ): Promise { + const request = regRequest(regId, true); + const sendargs = [0, data.length, 0, request]; + + data.forEach((d) => { + // separate d into bytes + sendargs.push( + d & 0xff, + (d >> 8) & 0xff, + (d >> 16) & 0xff, + (d >> 24) & 0xff + ); + }); + + // Transfer the write requests to the micro:bit and retrieve the response status. + const buf = await this.cmdNums(DapCmd.DAP_TRANSFER_BLOCK, sendargs); + + if (buf[3] !== 1) { + throw new Error("(many-wr) Bad transfer status " + buf[2]); + } + } + + // Core functionality reading a block of data from micro:bit RAM at a specified address. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L181 + private async readBlockCore( + addr: number, + words: number + ): Promise { + // Set up CMSIS-DAP to read/write from/to the RAM address addr using the register + // ApReg.DRW to write to or read from. + await this.cortexM.writeAP(ApReg.CSW, Csw.CSW_VALUE | Csw.CSW_SIZE32); + await this.cortexM.writeAP(ApReg.TAR, addr); + + let lastSize = words % 15; + if (lastSize === 0) { + lastSize = 15; + } + + const blocks = []; + + for (let i = 0; i < Math.ceil(words / 15); i++) { + const b: Uint8Array = await this.readRegRepeat( + apReg(ApReg.DRW, DapVal.READ), + i === blocks.length - 1 ? lastSize : 15 + ); + blocks.push(b); + } + + return bufferConcat(blocks).subarray(0, words * 4); + } + + // Core functionality writing a block of data to micro:bit RAM at a specified address. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L205 + private async writeBlockCore( + addr: number, + words: Uint32Array + ): Promise { + try { + // Set up CMSIS-DAP to read/write from/to the RAM address addr using the register ApReg.DRW to write to or read from. + await this.cortexM.writeAP(ApReg.CSW, Csw.CSW_VALUE | Csw.CSW_SIZE32); + await this.cortexM.writeAP(ApReg.TAR, addr); + + await this.writeRegRepeat(apReg(ApReg.DRW, DapVal.WRITE), words); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (e.dapWait) { + // Retry after a delay if required. + this.logging.log(`Transfer wait, write block`); + await new Promise((resolve) => setTimeout(resolve, 100)); + return await this.writeBlockCore(addr, words); + } else { + throw e; + } + } + } + + // Reads a block of data from micro:bit RAM at a specified address. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L143 + async readBlockAsync(addr: number, words: number): Promise { + const bufs = []; + const end = addr + words * 4; + let ptr = addr; + + // Read a single page at a time. + while (ptr < end) { + let nextptr = ptr + this.pageSize; + if (ptr === addr) { + nextptr &= ~(this.pageSize - 1); + } + const len = Math.min(nextptr - ptr, end - ptr); + bufs.push(await this.readBlockCore(ptr, len >> 2)); + ptr = nextptr; + } + const result = bufferConcat(bufs); + return result.subarray(0, words * 4); + } + + // Writes a block of data to micro:bit RAM at a specified address. + async writeBlockAsync(address: number, data: Uint32Array): Promise { + const payloadSize = this.transport.packetSize - 8; + if (data.buffer.byteLength > payloadSize) { + let start = 0; + let end = payloadSize; + + // Split write up into smaller writes whose data can each be held in a single packet. + while (start !== end) { + const temp = new Uint32Array(data.buffer.slice(start, end)); + await this.writeBlockCore(address + start, temp); + + start = end; + end = Math.min(data.buffer.byteLength, end + payloadSize); + } + } else { + await this.writeBlockCore(address, data); + } + } + + // Execute code at a certain address with specified values in the registers. + // Waits for execution to halt. + async executeAsync( + address: number, + code: Uint32Array, + sp: number, + pc: number, + lr: number, + ...registers: number[] + ) { + if (registers.length > 12) { + throw new Error( + `Only 12 general purpose registers but got ${registers.length} values` + ); + } + + await this.cortexM.halt(true); + await this.writeBlockAsync(address, code); + await this.cortexM.writeCoreRegister(CoreRegister.PC, pc); + await this.cortexM.writeCoreRegister(CoreRegister.LR, lr); + await this.cortexM.writeCoreRegister(CoreRegister.SP, sp); + for (let i = 0; i < registers.length; ++i) { + await this.cortexM.writeCoreRegister(i, registers[i]); + } + await this.cortexM.resume(true); + return this.waitForHalt(); + } + + // Checks whether the micro:bit has halted or timeout has been reached. + // Recurses otherwise. + private async waitForHaltCore( + halted: boolean, + deadline: number + ): Promise { + if (new Date().getTime() > deadline) { + throw new Error("timeout"); + } + if (!halted) { + const isHalted = await this.cortexM.isHalted(); + // NB this is a Promise so no stack risk. + return this.waitForHaltCore(isHalted, deadline); + } + } + + // Initial function to call to wait for the micro:bit halt. + async waitForHalt(timeToWait = 10000): Promise { + const deadline = new Date().getTime() + timeToWait; + return this.waitForHaltCore(false, deadline); + } + + // Resets the micro:bit in software by writing to NVIC_AIRCR. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L347 + private async softwareReset() { + await this.cortexM.writeMem32( + CortexSpecialReg.NVIC_AIRCR, + CortexSpecialReg.NVIC_AIRCR_VECTKEY | + CortexSpecialReg.NVIC_AIRCR_SYSRESETREQ + ); + + // wait for the system to come out of reset + let dhcsr = await this.cortexM.readMem32(CortexSpecialReg.DHCSR); + + while ((dhcsr & CortexSpecialReg.S_RESET_ST) !== 0) { + dhcsr = await this.cortexM.readMem32(CortexSpecialReg.DHCSR); + } + } + + // Reset the micro:bit, possibly halting the core on reset. + // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L248 + async reset(halt = false) { + if (halt) { + await this.cortexM.halt(true); + + // VC_CORERESET causes the core to halt on reset. + const demcr = await this.cortexM.readMem32(CortexSpecialReg.DEMCR); + await this.cortexM.writeMem32( + CortexSpecialReg.DEMCR, + CortexSpecialReg.DEMCR | CortexSpecialReg.DEMCR_VC_CORERESET + ); + + await this.softwareReset(); + await this.waitForHalt(); + + // Unset the VC_CORERESET bit + await this.cortexM.writeMem32(CortexSpecialReg.DEMCR, demcr); + } else { + await this.softwareReset(); + } + } +} diff --git a/src/device/device.ts b/src/device/device.ts new file mode 100644 index 000000000..f2ea60e29 --- /dev/null +++ b/src/device/device.ts @@ -0,0 +1,38 @@ +export type MicrobitVersion = 1 | 2; + +export type Button = "A" | "B"; + +export type ButtonState = + | ButtonStates.Released + | ButtonStates.Pressed + | ButtonStates.LongPressed; + +export enum ButtonStates { + Released, + Pressed, + LongPressed, +} + +export const microbitServicesUUID = { + uart: "6e400001-b5a3-f393-e0a9-e50e24dcca9e", + accelerometer: "e95d0753-251d-470a-a062-fa1922dfa9a8", + deviceInfo: "0000180a-0000-1000-8000-00805f9b34fb", + led: "e95dd91d-251d-470a-a062-fa1922dfa9a8", + io: "e95d127b-251d-470a-a062-fa1922dfa9a8", + button: "e95d9882-251d-470a-a062-fa1922dfa9a8", +}; + +export const microbitCharacteristicsUUID = { + buttonA: "e95dda90-251d-470a-a062-fa1922dfa9a8", + buttonB: "e95dda91-251d-470a-a062-fa1922dfa9a8", + accelerometer: "e95dca4b-251d-470a-a062-fa1922dfa9a8", + // TODO: To remove io? Used for controlling pins + ioData: "e95d8d00-251d-470a-a062-fa1922dfa9a8", + // Allows the state of any|all LEDs in the 5x5 grid to be set to on or off with a single GATT operation. + ledMatrixState: "e95d7b77-251d-470a-a062-fa1922dfa9a8", + modelNumber: "00002a24-0000-1000-8000-00805f9b34fb", + // Used to listen for data from the micro:bit. + uartDataTX: "6e400002-b5a3-f393-e0a9-e50e24dcca9e", + // Used for sending data to the micro:bit + uartDataRX: "6e400003-b5a3-f393-e0a9-e50e24dcca9e", +}; diff --git a/src/device/get-hex-file.ts b/src/device/get-hex-file.ts new file mode 100644 index 000000000..7ef87dd64 --- /dev/null +++ b/src/device/get-hex-file.ts @@ -0,0 +1,24 @@ +import { ConnType } from "../connection-flow"; +import { MicrobitVersion } from "./device"; + +export const getHexFileUrl = ( + version: MicrobitVersion | "universal", + type: ConnType | "radio-remote-dev" | "radio-local" +): string | undefined => { + if (type === ConnType.Bluetooth) { + return { + 1: "firmware/ml-microbit-cpp-version-combined.hex", + 2: "firmware/MICROBIT.hex", + universal: "firmware/universal-hex.hex", + }[version]; + } + if (version !== 2) { + return undefined; + } + return { + "radio-remote-dev": "firmware/radio-remote-v0.2.1-dev.hex", + [ConnType.RadioRemote]: "firmware/radio-remote-v0.2.1.hex", + [ConnType.RadioBridge]: "firmware/radio-bridge-v0.2.1.hex", + "radio-local": "firmware/local-sensors-v0.2.1.hex", + }[type]; +}; diff --git a/src/device/microbit-usb.ts b/src/device/microbit-usb.ts new file mode 100644 index 000000000..d76bc8910 --- /dev/null +++ b/src/device/microbit-usb.ts @@ -0,0 +1,112 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { DAPLink } from "dapjs"; +import { Logging } from "../logging/logging"; +import { DAPWrapper } from "./dap-wrapper"; +import { MicrobitVersion } from "./device"; + +export const CortexSpecialReg = { + // Debug Halting Control and Status Register + DHCSR: 0xe000edf0, + S_RESET_ST: 1 << 25, + + NVIC_AIRCR: 0xe000ed0c, + NVIC_AIRCR_VECTKEY: 0x5fa << 16, + NVIC_AIRCR_SYSRESETREQ: 1 << 2, + + // Many more. +}; + +/** + * A USB connection to a micro:bit. + */ +class MicrobitWebUSBConnection { + private logging: Logging; + private device: USBDevice | undefined; // Undefined if disconnected + private connection: DAPWrapper | undefined; + + /** + * Creates a new MicrobitUSB object. + */ + constructor(logging: Logging) { + this.logging = logging; + } + + public async connect() { + const device = await this.chooseDevice(); + this.connection = new DAPWrapper(device, this.logging); + } + + private async chooseDevice(): Promise { + if (this.device) { + return this.device; + } + this.device = await navigator.usb.requestDevice({ + filters: [{ vendorId: 3368, productId: 516 }], + }); + return this.device; + } + + private logError(message: string, e: unknown) { + this.logging.error(`${message}: ${JSON.stringify(e)}`); + } + + getBoardVersion(): MicrobitVersion | null { + if (!this.connection) { + return null; + } + const boardId = this.connection.boardSerialInfo.id; + return boardId.isV1() ? 1 : boardId.isV2() ? 2 : null; + } + + getDeviceId(): string | null { + if (!this.connection) { + return null; + } + return this.connection.boardSerialInfo.id.toString(); + } + + /** + * Resets the micro:bit in software by writing to NVIC_AIRCR. + * Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L347 + */ + public async softwareReset(): Promise { + await this.connection?.reset(); + } + + /** + * Flashes a .hex file to the micro:bit. + * @param {string} url The hex file to flash. (As a link) + * @param {(progress: number) => void} progressCallback A callback for progress. + */ + public async flashHex( + url: string, + progressCallback: (progress: number) => void + ): Promise { + if (!this.connection) { + throw new Error("Must be connected now"); + } + const hexFile = await fetch(url); + const buffer = await hexFile.arrayBuffer(); + const target = new DAPLink(this.connection?.transport); + + target.on(DAPLink.EVENT_PROGRESS, (progress: number) => { + progressCallback(progress); + }); + + try { + await target.connect(); + await target.flash(buffer); + await target.disconnect(); + } catch (e) { + this.logError("Failed to flash hex", e); + throw e; + } + } +} + +export default MicrobitWebUSBConnection; diff --git a/src/device/partial-flashing-utils.ts b/src/device/partial-flashing-utils.ts new file mode 100644 index 000000000..cc9221921 --- /dev/null +++ b/src/device/partial-flashing-utils.ts @@ -0,0 +1,128 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { DapVal } from "./constants"; + +// Represents the micro:bit's core registers +// Drawn from https://armmbed.github.io/dapjs/docs/enums/coreregister.html +export const CoreRegister = { + SP: 13, + LR: 14, + PC: 15, +}; + +export const read32FromUInt8Array = (data: Uint8Array, i: number): number => { + return ( + (data[i] | + (data[i + 1] << 8) | + (data[i + 2] << 16) | + (data[i + 3] << 24)) >>> + 0 + ); +}; + +export const bufferConcat = (bufs: Uint8Array[]): Uint8Array => { + let len = 0; + for (const b of bufs) { + len += b.length; + } + const r = new Uint8Array(len); + len = 0; + for (const b of bufs) { + r.set(b, len); + len += b.length; + } + return r; +}; + +// Returns the MurmurHash of the data passed to it, used for checksum calculation. +// Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L14 +export const murmur3_core = (data: Uint8Array): [number, number] => { + let h0 = 0x2f9be6cc; + let h1 = 0x1ec3a6c8; + + for (let i = 0; i < data.byteLength; i += 4) { + let k = read32FromUInt8Array(data, i) >>> 0; + k = Math.imul(k, 0xcc9e2d51); + k = (k << 15) | (k >>> 17); + k = Math.imul(k, 0x1b873593); + + h0 ^= k; + h1 ^= k; + h0 = (h0 << 13) | (h0 >>> 19); + h1 = (h1 << 13) | (h1 >>> 19); + h0 = (Math.imul(h0, 5) + 0xe6546b64) >>> 0; + h1 = (Math.imul(h1, 5) + 0xe6546b64) >>> 0; + } + return [h0, h1]; +}; + +// Returns a representation of an Access Port Register. +// Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/util.ts#L63 +export const apReg = (r: number, mode: number): number /* Reg */ => { + const v = r | mode | DapVal.AP_ACC; + return 4 + ((v & 0x0c) >> 2); +}; + +// Returns a code representing a request to read/write a certain register. +// Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/util.ts#L92 +export const regRequest = (regId: number, isWrite: boolean = false): number => { + let request = !isWrite ? 1 << 1 /* READ */ : 0 << 1; /* WRITE */ + + if (regId < 4) { + request |= 0 << 0 /* DP_ACC */; + } else { + request |= 1 << 0 /* AP_ACC */; + } + + request |= (regId & 3) << 2; + + return request; +}; + +export class Page { + constructor(readonly targetAddr: number, readonly data: Uint8Array) {} +} + +// Split buffer into pages, each of pageSize size. +// Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L209 +export const pageAlignBlocks = ( + buffer: Uint8Array, + targetAddr: number, + pageSize: number +): Page[] => { + const unaligned = new Uint8Array(buffer); + const pages = []; + for (let i = 0; i < unaligned.byteLength; ) { + const newbuf = new Uint8Array(pageSize).fill(0xff); + const startPad = (targetAddr + i) & (pageSize - 1); + const newAddr = targetAddr + i - startPad; + for (; i < unaligned.byteLength; ++i) { + if (targetAddr + i >= newAddr + pageSize) break; + newbuf[targetAddr + i - newAddr] = unaligned[i]; + } + const page = new Page(newAddr, newbuf); + pages.push(page); + } + return pages; +}; + +// Filter out all pages whose calculated checksum matches the corresponding checksum passed as an argument. +// Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L523 +export const onlyChanged = ( + pages: Page[], + checksums: Uint8Array, + pageSize: number +): Page[] => { + return pages.filter((page) => { + const idx = page.targetAddr / pageSize; + if (idx * 8 + 8 > checksums.length) return true; // out of range? + const c0 = read32FromUInt8Array(checksums, idx * 8); + const c1 = read32FromUInt8Array(checksums, idx * 8 + 4); + const ch = murmur3_core(page.data); + if (c0 === ch[0] && c1 === ch[1]) return false; + return true; + }); +}; diff --git a/src/flags.test.ts b/src/flags.test.ts index 851d363b2..2e6fd3d6a 100644 --- a/src/flags.test.ts +++ b/src/flags.test.ts @@ -6,7 +6,7 @@ describe("flags", () => { const flags = flagsForParams("production", params); - expect(Object.values(flags).every(x => !x)).toEqual(true); + expect(Object.values(flags).every((x) => !x)).toEqual(true); }); it("enables by stage", () => { @@ -32,19 +32,19 @@ describe("flags", () => { it("enable everything", () => { const params = new URLSearchParams([["flag", "*"]]); const flags = flagsForParams("production", params); - expect(Object.values(flags).every(x => x)).toEqual(true); + expect(Object.values(flags).every((x) => x)).toEqual(true); }); it("enable nothing", () => { const params = new URLSearchParams([["flag", "none"]]); const flags = flagsForParams("review", params); - expect(Object.values(flags).every(x => !x)).toEqual(true); + expect(Object.values(flags).every((x) => !x)).toEqual(true); }); it("can combine none with specific enabled flags in review", () => { const params = new URLSearchParams([ ["flag", "none"], - ["flag", "exampleOptInB"] + ["flag", "exampleOptInB"], ]); const flags = flagsForParams("review", params); diff --git a/src/logging/NullLoggingProvider.tsx b/src/logging/NullLoggingProvider.tsx new file mode 100644 index 000000000..8d8ae1c88 --- /dev/null +++ b/src/logging/NullLoggingProvider.tsx @@ -0,0 +1,14 @@ +/** + * (c) 2022, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { ReactNode } from "react"; +import { NullLogging } from "../deployment/default/logging"; +import { LoggingProvider } from "./logging-hooks"; + +const NullLoggingProvider = ({ children }: { children: ReactNode }) => ( + {children} +); + +export default NullLoggingProvider; diff --git a/src/logging/logging-hooks.ts b/src/logging/logging-hooks.ts new file mode 100644 index 000000000..1d23b856c --- /dev/null +++ b/src/logging/logging-hooks.ts @@ -0,0 +1,23 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { createContext, useContext } from "react"; +import { Logging } from "./logging"; + +// Exported for class-based error boundary. +export const LoggingContext = createContext(undefined); + +export const LoggingProvider = LoggingContext.Provider; + +/** + * Hook exposing logging. + */ +export const useLogging = (): Logging => { + const logging = useContext(LoggingContext); + if (!logging) { + throw new Error("Missing provider"); + } + return logging; +}; diff --git a/src/logging/logging.ts b/src/logging/logging.ts new file mode 100644 index 000000000..5f6592657 --- /dev/null +++ b/src/logging/logging.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +export interface Event { + type: string; + message?: string; + value?: number; + detail?: any; +} + +export interface Logging { + event(event: Event): void; + error(e: any): void; + log(e: any): void; +} diff --git a/src/logging/mock.ts b/src/logging/mock.ts new file mode 100644 index 000000000..3c8f706be --- /dev/null +++ b/src/logging/mock.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { Event, Logging } from "./logging"; + +export class MockLogging implements Logging { + events: Event[] = []; + errors: any[] = []; + logs: any[] = []; + + event(event: Event): void { + this.events.push(event); + } + error(e: any): void { + this.errors.push(e); + } + log(e: any): void { + this.logs.push(e); + } +} diff --git a/src/messages/chunk-util.ts b/src/messages/chunk-util.ts index ac7582607..3471d2aed 100644 --- a/src/messages/chunk-util.ts +++ b/src/messages/chunk-util.ts @@ -1,5 +1,5 @@ const defaultWaiter = (waitTime: number): Promise => - new Promise(resolve => setTimeout(resolve, waitTime)); + new Promise((resolve) => setTimeout(resolve, waitTime)); export const retryAsyncLoad = async ( load: () => Promise, diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 92ccf0092..2dfbecf2a 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -11,7 +11,7 @@ "value": "micro:bit board showing a heart on the LED display" } ], - "about.developedInPartnership": [ + "about-dialog-title": [ { "type": 0, "value": "Developed in partnership with " @@ -27,16 +27,22 @@ "value": "link" } ], - "actions.cancel": [ + "actions.reconnect": [ { "type": 0, - "value": "Cancel" + "value": "Reconnect" } ], - "actions.reconnect": [ + "add-data-intro-description": [ { "type": 0, - "value": "Reconnect" + "value": "Add samples of the actions you would like your model to recognise (e.g. waving and clapping)." + } + ], + "add-data-title": [ + { + "type": 0, + "value": "Add data" } ], "alert.data.classNameLengthAlert": [ @@ -127,6 +133,18 @@ "value": "arrow pointing right" } ], + "back-action": [ + { + "type": 0, + "value": "Back" + } + ], + "cancel-action": [ + { + "type": 0, + "value": "Cancel" + } + ], "close-action": [ { "type": 0, @@ -151,6 +169,12 @@ "value": "WebGL not available. Enable WebGL to see 3D data view." } ], + "connect-help-alt": [ + { + "type": 0, + "value": "WebUSB connection dialog with BBC micro:bit entry labelled 1 and Connect button labelled 2" + } + ], "connectFailed.bluetooth1": [ { "type": 0, @@ -187,12 +211,6 @@ "value": "Failed to connect to micro:bit 1" } ], - "connectMB.backButton": [ - { - "type": 0, - "value": "Back" - } - ], "connectMB.bluetooth.cancelledConnection": [ { "type": 0, @@ -933,42 +951,6 @@ "value": "machine learning tool" } ], - "content.index.toolProcessCards.data.description": [ - { - "type": 0, - "value": "Add samples of the actions you would like your model to recognise (e.g. waving and clapping)." - } - ], - "content.index.toolProcessCards.data.title": [ - { - "type": 0, - "value": "Add data" - } - ], - "content.index.toolProcessCards.model.description": [ - { - "type": 0, - "value": "Find out if it correctly recognises each action. Add more data to improve the model." - } - ], - "content.index.toolProcessCards.model.title": [ - { - "type": 0, - "value": "Test model" - } - ], - "content.index.toolProcessCards.train.description": [ - { - "type": 0, - "value": "Ask the computer to use your training samples to train the machine learning model to recognise different actions." - } - ], - "content.index.toolProcessCards.train.title": [ - { - "type": 0, - "value": "Train model" - } - ], "content.model.addData": [ { "type": 0, @@ -1241,6 +1223,12 @@ "value": "Start new session" } ], + "get-started-resource-title": [ + { + "type": 0, + "value": "Get started" + } + ], "help-label": [ { "type": 0, @@ -1253,10 +1241,16 @@ "value": "Help & support" } ], - "helpMenu.cookies": [ + "homepage-subtitle": [ { "type": 0, - "value": "Cookies" + "value": "Introduce students to machine learning concepts through physical movement and data" + } + ], + "homepage-title": [ + { + "type": 0, + "value": "micro:bit machine learning tool" } ], "homepage.Link": [ @@ -1279,6 +1273,12 @@ "value": "\"" } ], + "introducing-microbit-resource-title": [ + { + "type": 0, + "value": "Introducing the micro:bit machine learning tool" + } + ], "language": [ { "type": 0, @@ -1291,10 +1291,10 @@ "value": "loading" } ], - "menu.data.helpHeading": [ + "main-menu": [ { "type": 0, - "value": "1. Add data" + "value": "Main menu" } ], "menu.model.connectInputMicrobit": [ @@ -1309,12 +1309,6 @@ "value": "Disconnect output micro:bit" } ], - "menu.model.helpHeading": [ - { - "type": 0, - "value": "3. Test model" - } - ], "menu.model.noModel": [ { "type": 0, @@ -1339,12 +1333,6 @@ "value": "Add more data" } ], - "menu.trainer.helpHeading": [ - { - "type": 0, - "value": "2. Train model" - } - ], "menu.trainer.notConnected1": [ { "type": 0, @@ -1381,6 +1369,18 @@ "value": "Train model" } ], + "not-found": [ + { + "type": 0, + "value": "Machine learning tool home page" + } + ], + "not-found-title": [ + { + "type": 0, + "value": "Page not found" + } + ], "performanceWarning.content1": [ { "type": 0, @@ -1561,6 +1561,18 @@ "value": "Follow these instructions to restart the connection process." } ], + "reload-action": [ + { + "type": 0, + "value": "Click to reload the page" + } + ], + "resources": [ + { + "type": 0, + "value": "Resources" + } + ], "resources.getStarted.video": [ { "type": 0, @@ -1579,6 +1591,12 @@ "value": "Settings actions menu" } ], + "sign-up-action": [ + { + "type": 0, + "value": "Sign up" + } + ], "sign-up.content": [ { "type": 0, @@ -1617,12 +1635,6 @@ "value": "Please enter a valid email address" } ], - "sign-up.sign-up-action": [ - { - "type": 0, - "value": "Sign up" - } - ], "sign-up.skip-action": [ { "type": 0, @@ -1666,5 +1678,29 @@ "type": 0, "value": "Terms of use" } + ], + "test-model-intro-description": [ + { + "type": 0, + "value": "Find out if it correctly recognises each action. Add more data to improve the model." + } + ], + "test-model-title": [ + { + "type": 0, + "value": "Test model" + } + ], + "train-model-intro-description": [ + { + "type": 0, + "value": "Ask the computer to use your training samples to train the machine learning model to recognise different actions." + } + ], + "train-model-title": [ + { + "type": 0, + "value": "Train model" + } ] -} \ No newline at end of file +} diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx new file mode 100644 index 000000000..53b32a525 --- /dev/null +++ b/src/pages/AddDataPage.tsx @@ -0,0 +1,26 @@ +import { Grid, VStack } from "@chakra-ui/react"; +import ConnectFirstView from "../components/ConnectFirstView"; +import DefaultPageLayout from "../components/DefaultPageLayout"; +import LiveGraphPanel from "../components/LiveGraphPanel"; +import TabView from "../components/TabView"; +import { addDataConfig } from "../steps-config"; + +const AddDataPage = () => { + const noStoredData = true; + const isInputConnected = false; + return ( + + + + {noStoredData && !isInputConnected ? ( + + ) : ( + TODO: Grid layout! + )} + + + + ); +}; + +export default AddDataPage; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 277d5bc3e..3b16b706a 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,11 +1,111 @@ +import { Grid, Heading, Image, Stack, Text, VStack } from "@chakra-ui/react"; +import { FormattedMessage, useIntl } from "react-intl"; import DefaultPageLayout from "../components/DefaultPageLayout"; +import ResourceCard from "../components/ResourceCard"; +import StartResumeActions from "../components/StartResumeActions"; +import resourceGetStartedImage from "../images/resource-get-started.jpg"; +import resourceIntroducingToolImage from "../images/resource-introducing-tool.jpg"; +import { stepsConfig } from "../steps-config"; + +export const Paths = { + HOME: "/", + PLAYGROUND: "playground", + INTRODUCING_TOOL: "resources/introducing-the-microbit-machine-learning-tool", + GET_STARTED: "resources/get-started", + DATA: "add-data", + TRAINING: "train-model", + MODEL: "test-model", + FILTERS: "training/filters", +} as const; + +const resources = [ + { + titleId: "introducing-microbit-resource-title", + path: Paths.INTRODUCING_TOOL, + imgSrc: resourceIntroducingToolImage, + }, + { + titleId: "get-started-resource-title", + path: Paths.GET_STARTED, + imgSrc: resourceGetStartedImage, + }, +]; const HomePage = () => { + const intl = useIntl(); + return ( - -

        Hi!

        + + + + + + + + + + + + + {stepsConfig.map(({ id, imgSrc }, idx) => ( + + ))} + + + + + + + + {resources.map((r, idx) => ( + + ))} + + + ); }; +interface StepProps { + title: string; + imgSrc: string; + description: string; +} + +const Step = ({ title, imgSrc, description }: StepProps) => ( + + + {title} + + + {description} + +); + export default HomePage; diff --git a/src/pages/TestDataPage.tsx b/src/pages/TestDataPage.tsx new file mode 100644 index 000000000..d12ebe47d --- /dev/null +++ b/src/pages/TestDataPage.tsx @@ -0,0 +1,5 @@ +const TestDataPage = () => { + return <>Test data page!; +}; + +export default TestDataPage; diff --git a/src/pages/TrainDataPage.tsx b/src/pages/TrainDataPage.tsx new file mode 100644 index 000000000..922b4aa1c --- /dev/null +++ b/src/pages/TrainDataPage.tsx @@ -0,0 +1,5 @@ +const TrainDataPage = () => { + return <>Train data page!; +}; + +export default TrainDataPage; diff --git a/src/patternMatrixTransforms.test.ts b/src/patternMatrixTransforms.test.ts new file mode 100644 index 000000000..fb80a4306 --- /dev/null +++ b/src/patternMatrixTransforms.test.ts @@ -0,0 +1,133 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { + getHighlightedColumns, + transformColumnsToMatrix, + transformMatrixToColumns, + updateMatrixColumns, +} from "./patternMatrixTransforms"; + +describe("transformMatrixToColumns", () => { + test("2 x 2", () => { + const dim = 2; + const matrix = [1, 2, 3, 4]; + const columns = [ + [1, 3], + [2, 4], + ]; + expect(transformMatrixToColumns(matrix, dim)).toEqual(columns); + }); +}); + +describe("transformColumnsToMatrix", () => { + test("2 x 2", () => { + const matrix = [1, 2, 3, 4]; + const columns = [ + [1, 3], + [2, 4], + ]; + expect(transformColumnsToMatrix(columns)).toEqual(matrix); + }); +}); + +describe("getHighlightedColumns", () => { + test("cell position is above lit up cell", () => { + const matrixColumns = [ + [false, false, true], + [false, false, false], + [false, false, false], + ]; + const cellPos = { colIdx: 0, rowIdx: 0 }; + const highlightedColumns = [ + [true, true, false], + [false, false, false], + [false, false, false], + ]; + expect(getHighlightedColumns(matrixColumns, cellPos)).toEqual( + highlightedColumns + ); + }); + test("cell position is below lit up cell", () => { + const matrixColumns = [ + [true, true, true], + [false, false, false], + [false, false, false], + ]; + const cellPos = { colIdx: 0, rowIdx: 2 }; + const highlightedColumns = [ + [true, true, false], + [false, false, false], + [false, false, false], + ]; + expect(getHighlightedColumns(matrixColumns, cellPos)).toEqual( + highlightedColumns + ); + }); + test("cell position is on lit up cell", () => { + const matrixColumns = [ + [false, true, true], + [false, false, false], + [false, false, false], + ]; + const cellPos = { colIdx: 0, rowIdx: 1 }; + const highlightedColumns = [ + [false, false, false], + [false, false, false], + [false, false, false], + ]; + expect(getHighlightedColumns(matrixColumns, cellPos)).toEqual( + highlightedColumns + ); + }); +}); + +describe("updateMatrixColumns", () => { + test("cell position is above lit up cell", () => { + const matrixColumns = [ + [false, false, true], + [false, false, true], + [false, false, true], + ]; + const cellPos = { colIdx: 1, rowIdx: 0 }; + const newMatrixColumns = [ + [false, false, true], + [true, true, true], + [false, false, true], + ]; + expect(updateMatrixColumns(matrixColumns, cellPos)).toEqual( + newMatrixColumns + ); + }); + test("cell position is below lit up cell", () => { + const matrixColumns = [ + [false, false, true], + [true, true, true], + [false, false, true], + ]; + const cellPos = { colIdx: 1, rowIdx: 2 }; + const newMatrixColumns = [ + [false, false, true], + [false, false, true], + [false, false, true], + ]; + expect(updateMatrixColumns(matrixColumns, cellPos)).toEqual( + newMatrixColumns + ); + }); + test("cell position is on lit up cell", () => { + const matrixColumns = [ + [false, false, true], + [false, false, true], + [false, false, true], + ]; + const cellPos = { colIdx: 0, rowIdx: 2 }; + expect(updateMatrixColumns(matrixColumns, cellPos)).toEqual(matrixColumns); + }); +}); diff --git a/src/patternMatrixTransforms.ts b/src/patternMatrixTransforms.ts new file mode 100644 index 000000000..20a564c1c --- /dev/null +++ b/src/patternMatrixTransforms.ts @@ -0,0 +1,66 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +export const transformMatrixToColumns = ( + m: T[], + matrixDimension: number +): T[][] => { + const cols = []; + for (let colId = 1; colId <= matrixDimension; colId++) { + const remainder = colId === matrixDimension ? 0 : colId; + cols.push(m.filter((_c, i) => (i + 1) % matrixDimension === remainder)); + } + return cols; +}; + +export const transformColumnsToMatrix = (cols: T[][]): T[] => { + let matrix: T[] = []; + for (let i = 0; i < cols.length; i++) { + matrix = [...matrix, ...cols.map((c) => c[i])]; + } + return matrix; +}; + +export const generateMatrix = (dimension: number, fillValue: T) => { + return new Array(dimension).fill( + new Array(dimension).fill(fillValue) + ); +}; + +export type MatrixColumns = boolean[][]; + +export interface CellPosition { + colIdx: number; + rowIdx: number; +} + +export const getHighlightedColumns = ( + matrixColumns: MatrixColumns, + cellPos: CellPosition +) => { + const col = matrixColumns[cellPos.colIdx]; + const highlightedCol = col.map( + (isOn, idx) => + (!isOn && cellPos.rowIdx <= idx) || (isOn && cellPos.rowIdx > idx) + ); + const highlightedColumns = generateMatrix(matrixColumns.length, false); + highlightedColumns[cellPos.colIdx] = highlightedCol; + return highlightedColumns; +}; + +export const updateMatrixColumns = ( + matrixColumns: MatrixColumns, + cellPos: CellPosition +) => { + const newCol = Array(matrixColumns.length) + .fill(false) + .fill(true, cellPos.rowIdx); + return [ + ...matrixColumns.slice(0, cellPos.colIdx), + newCol, + ...matrixColumns.slice(cellPos.colIdx + 1), + ]; +}; diff --git a/src/steps-config.ts b/src/steps-config.ts new file mode 100644 index 000000000..0d94a39a1 --- /dev/null +++ b/src/steps-config.ts @@ -0,0 +1,38 @@ +import addDataImage from "./images/add_data.svg"; +import testModelImage from "./images/test_model_blue.svg"; +import trainModelImage from "./images/train_model_blue.svg"; +import AddDataPage from "./pages/AddDataPage"; +import TestDataPage from "./pages/TestDataPage"; +import TrainDataPage from "./pages/TrainDataPage"; + +export type StepId = "add-data" | "train-model" | "test-model"; + +export interface StepConfig { + id: StepId; + imgSrc: string; + pageElement: () => JSX.Element; +} + +export const addDataConfig: StepConfig = { + id: "add-data", + imgSrc: addDataImage, + pageElement: AddDataPage, +}; + +export const trainDataConfig: StepConfig = { + id: "train-model", + imgSrc: trainModelImage, + pageElement: TrainDataPage, +}; + +export const testDataConfig: StepConfig = { + id: "test-model", + imgSrc: testModelImage, + pageElement: TestDataPage, +}; + +export const stepsConfig: StepConfig[] = [ + addDataConfig, + trainDataConfig, + testDataConfig, +]; diff --git a/src/theme/constants/colors.ts b/src/theme/constants/colors.ts deleted file mode 100644 index 9bde76f7b..000000000 --- a/src/theme/constants/colors.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Use the theme colours rather than adding here. - -// Used for the full screen modal header. Would be good -// to reconcile with the theme. Chakra's grays aren't pure. -export const brandGrey = "#e5e5e5"; diff --git a/src/theme/constants/dimensions.ts b/src/theme/constants/dimensions.ts deleted file mode 100644 index 77e5659fd..000000000 --- a/src/theme/constants/dimensions.ts +++ /dev/null @@ -1 +0,0 @@ -export const headerHeight = "64px"; diff --git a/src/urls.ts b/src/urls.ts index 9496174de..6e074e2a3 100644 --- a/src/urls.ts +++ b/src/urls.ts @@ -1,3 +1,5 @@ +import { StepId } from "./steps-config"; + export const basepath = import.meta.env.BASE_URL ?? "/"; if (!basepath.endsWith("/")) { @@ -5,3 +7,5 @@ if (!basepath.endsWith("/")) { } export const createHomePageUrl = () => `${basepath}`; + +export const createStepPageUrl = (stepId: StepId) => `${basepath}${stepId}`; diff --git a/vite.config.ts b/vite.config.ts index 848397418..756f91b21 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,11 +4,18 @@ * * SPDX-License-Identifier: MIT */ -import { defineConfig, loadEnv } from "vite"; import react from "@vitejs/plugin-react"; +import fs from "node:fs"; +import path from "node:path"; +import { UserConfig, defineConfig, loadEnv } from "vite"; import svgr from "vite-plugin-svgr"; -export default defineConfig(({ mode }) => { +// Support optionally pulling in external branding if the module is installed. +const theme = "TODO: theme package"; +const external = `node_modules/${theme}`; +const internal = "src/deployment/default"; + +export default defineConfig(({ mode }): UserConfig => { const commonEnv = loadEnv(mode, process.cwd(), ""); return { @@ -38,6 +45,7 @@ export default defineConfig(({ mode }) => { : undefined, test: { globals: true, + environment: "jsdom", poolOptions: { threads: { // threads disabled for now due to https://github.com/vitest-dev/vitest/issues/1982 @@ -45,5 +53,12 @@ export default defineConfig(({ mode }) => { }, }, }, + resolve: { + alias: { + "theme-package": fs.existsSync(external) + ? theme + : path.resolve(__dirname, internal), + }, + }, }; }); From fb56a464505feb4aff57f78d4537a4beb624e751 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 5 Jul 2024 11:53:35 +0100 Subject: [PATCH 008/172] Navigate to Add Data page when clicking Start session if connected --- src/components/StartResumeActions.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index de690eaaa..da9b3055a 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -12,10 +12,15 @@ const StartResumeActions = () => { const navigate = useNavigate(); const { dispatch } = useConnectionFlow(); + // TODO: check input connected + const isInputConnected = true const handleNewSession = useCallback(() => { - navigate(createStepPageUrl("add-data")); - dispatch(ConnEvent.Start); - }, [dispatch, navigate]); + if (isInputConnected) { + navigate(createStepPageUrl("add-data")); + } else { + dispatch(ConnEvent.Start); + } + }, [dispatch, isInputConnected, navigate]); return ( {hasExistingSession && ( From c2232ab9daa1b62ee8cda5b789498030f663b885 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 5 Jul 2024 11:53:56 +0100 Subject: [PATCH 009/172] Tweak connect first view spacing --- src/components/ConnectFirstView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ConnectFirstView.tsx b/src/components/ConnectFirstView.tsx index 8c25cfbb6..43fd6d089 100644 --- a/src/components/ConnectFirstView.tsx +++ b/src/components/ConnectFirstView.tsx @@ -15,7 +15,7 @@ const ConnectFirstView = () => { return ( - + From 209ee38b47fcfa9e8649d9617cb345f53fd8ad58 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 5 Jul 2024 11:54:20 +0100 Subject: [PATCH 010/172] Add data action panel UI --- src/pages/AddDataPage.tsx | 62 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index 53b32a525..4e090cd0a 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -1,4 +1,23 @@ -import { Grid, VStack } from "@chakra-ui/react"; +import { + Button, + Grid, + HStack, + Icon, + IconButton, + Menu, + MenuButton, + MenuItem, + MenuList, + VStack, +} from "@chakra-ui/react"; +import { MdMoreVert } from "react-icons/md"; +import { + RiAddLine, + RiDeleteBin2Line, + RiDownload2Line, + RiUpload2Line, +} from "react-icons/ri"; +import { FormattedMessage, useIntl } from "react-intl"; import ConnectFirstView from "../components/ConnectFirstView"; import DefaultPageLayout from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; @@ -6,6 +25,7 @@ import TabView from "../components/TabView"; import { addDataConfig } from "../steps-config"; const AddDataPage = () => { + const intl = useIntl(); const noStoredData = true; const isInputConnected = false; return ( @@ -18,6 +38,46 @@ const AddDataPage = () => { TODO: Grid layout! )} + + + + + + } + isRound + /> + + }> + + + }> + + + }> + + + + + +
        ); From 19d8374d056ca0cdee477d160731dd393b04fdd2 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 5 Jul 2024 11:54:52 +0100 Subject: [PATCH 011/172] Tweak live graph panel z-index so that add data menu is not obscured when opened --- src/components/LiveGraphPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index f9c88b7da..c0a1ced39 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -17,7 +17,6 @@ const LiveGraphPanel = () => { right={0} px={7} py={4} - zIndex={1} > From cf129c5f41a755e9000a7a9b7c055c104f505999 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 5 Jul 2024 16:12:45 +0100 Subject: [PATCH 012/172] Add dummy gesture data --- src/dummy-gesture-data.ts | 897 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 897 insertions(+) create mode 100644 src/dummy-gesture-data.ts diff --git a/src/dummy-gesture-data.ts b/src/dummy-gesture-data.ts new file mode 100644 index 000000000..7ed17d5a7 --- /dev/null +++ b/src/dummy-gesture-data.ts @@ -0,0 +1,897 @@ +export const dummyGestureData = [ + { + ID: 1717765478850, + name: "still", + matrix: [ + false, + false, + false, + false, + false, + true, + true, + false, + true, + true, + false, + false, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + false, + ], + recordings: [ + { + ID: 1717765566215, + data: { + x: [ + 0.02, 0, 0.004, 0, 0, 0, 0.008, 0.012, 0.004, 0.008, 0.004, 0, + 0.004, 0.008, 0, -0.004, 0.008, 0.02, 0.004, -0.004, 0.004, 0.008, + 0.02, 0.008, 0.008, 0.008, 0.012, 0.016, 0.012, 0.012, 0.008, 0.012, + -0.004, 0, 0.016, 0.02, 0.02, 0.004, -0.012, 0, 0.024, 0.016, 0.012, + 0.004, 0, 0.004, 0.012, 0.012, 0.008, 0.024, 0.02, 0.012, 0.004, + 0.004, 0, 0.016, 0.016, 0.012, 0.008, 0.012, 0.004, -0.004, 0.008, + 0.012, 0.004, 0.004, -0.004, 0.012, 0.016, 0.004, 0.008, 0.016, + 0.032, 0.012, -0.012, -0.008, 0.004, 0.028, 0.02, 0.008, -0.004, 0, + 0.024, 0.024, 0.008, 0.008, -0.004, 0.004, 0.016, 0.02, + ], + y: [ + -0.168, -0.16, -0.144, -0.116, -0.128, -0.136, -0.148, -0.176, + -0.184, -0.18, -0.152, -0.136, -0.156, -0.168, -0.168, -0.16, + -0.148, -0.144, -0.12, -0.14, -0.172, -0.172, -0.168, -0.132, + -0.124, -0.152, -0.16, -0.136, -0.124, -0.132, -0.176, -0.172, + -0.14, -0.124, -0.136, -0.172, -0.172, -0.14, -0.104, -0.136, + -0.168, -0.164, -0.152, -0.144, -0.136, -0.14, -0.116, -0.128, + -0.16, -0.164, -0.164, -0.128, -0.124, -0.14, -0.16, -0.156, -0.148, + -0.152, -0.168, -0.156, -0.128, -0.136, -0.144, -0.16, -0.148, + -0.124, -0.128, -0.148, -0.136, -0.14, -0.152, -0.16, -0.164, + -0.136, -0.116, -0.132, -0.16, -0.156, -0.14, -0.112, -0.112, + -0.136, -0.156, -0.16, -0.128, -0.116, -0.112, -0.124, -0.14, + -0.148, + ], + z: [ + -1.048, -1.052, -1.036, -1.036, -1.056, -1.072, -1.056, -1.048, + -1.06, -1.048, -1.048, -1.044, -1.04, -1.048, -1.056, -1.048, + -1.028, -1.028, -1.052, -1.076, -1.068, -1.04, -1.052, -1.068, + -1.048, -1.06, -1.056, -1.084, -1.088, -1.068, -1.04, -1.028, + -1.048, -1.068, -1.044, -1.008, -1.016, -1.048, -1.072, -1.056, + -1.044, -1.068, -1.064, -1.056, -1.044, -1.056, -1.068, -1.084, + -1.092, -1.076, -1.064, -1.048, -1.044, -1.044, -1.056, -1.064, + -1.056, -1.068, -1.028, -1.004, -1.032, -1.04, -1.048, -1.064, + -1.06, -1.06, -1.064, -1.052, -1.06, -1.072, -1.072, -1.048, -1.028, + -1.056, -1.084, -1.084, -1.06, -1.028, -1.028, -1.056, -1.08, + -1.068, -1.044, -1.028, -1.04, -1.048, -1.056, -1.064, -1.044, + -1.036, + ], + }, + }, + { + ID: 1717765560761, + data: { + x: [ + 0.976, 0.968, 0.96, 0.968, 0.956, 0.96, 0.956, 0.968, 0.972, 0.972, + 0.964, 0.956, 0.96, 0.964, 0.964, 0.968, 0.988, 0.956, 0.952, 0.952, + 0.964, 0.972, 0.972, 0.96, 0.968, 0.96, 0.964, 0.968, 0.968, 0.968, + 0.964, 0.956, 0.956, 0.968, 0.968, 0.964, 0.964, 0.964, 0.968, + 0.956, 0.964, 0.984, 0.976, 0.964, 0.968, 0.952, 0.956, 0.956, + 0.972, 0.968, 0.964, 0.964, 0.96, 0.968, 0.964, 0.964, 0.96, 0.968, + 0.968, 0.972, 0.968, 0.96, 0.968, 0.976, 0.956, 0.96, 0.964, 0.964, + 0.968, 0.968, 0.968, 0.96, 0.96, 0.964, 0.972, 0.956, 0.96, 0.956, + 0.96, 0.972, 0.968, 0.964, 0.96, 0.956, 0.964, 0.98, 0.98, 0.96, + 0.964, 0.96, 0.956, + ], + y: [ + -0.224, -0.204, -0.2, -0.208, -0.212, -0.204, -0.184, -0.192, -0.22, + -0.228, -0.2, -0.204, -0.216, -0.22, -0.192, -0.196, -0.208, -0.236, + -0.244, -0.22, -0.208, -0.192, -0.224, -0.224, -0.22, -0.228, -0.22, + -0.204, -0.216, -0.204, -0.2, -0.204, -0.224, -0.22, -0.224, -0.22, + -0.2, -0.204, -0.228, -0.22, -0.212, -0.228, -0.192, -0.196, -0.216, + -0.212, -0.212, -0.22, -0.228, -0.22, -0.216, -0.22, -0.236, -0.236, + -0.208, -0.216, -0.216, -0.216, -0.228, -0.228, -0.208, -0.2, + -0.208, -0.212, -0.216, -0.22, -0.204, -0.216, -0.228, -0.212, + -0.204, -0.2, -0.204, -0.196, -0.208, -0.236, -0.236, -0.216, + -0.208, -0.208, -0.22, -0.24, -0.24, -0.216, -0.212, -0.224, -0.22, + -0.216, -0.212, -0.236, -0.216, + ], + z: [ + -0.188, -0.184, -0.172, -0.176, -0.184, -0.188, -0.184, -0.18, + -0.164, -0.156, -0.168, -0.176, -0.184, -0.164, -0.18, -0.176, + -0.184, -0.176, -0.168, -0.176, -0.192, -0.156, -0.16, -0.18, + -0.168, -0.172, -0.176, -0.172, -0.172, -0.188, -0.188, -0.196, + -0.184, -0.172, -0.172, -0.172, -0.172, -0.18, -0.172, -0.172, + -0.176, -0.18, -0.156, -0.156, -0.18, -0.184, -0.196, -0.176, -0.18, + -0.18, -0.188, -0.188, -0.172, -0.172, -0.188, -0.18, -0.176, + -0.176, -0.164, -0.172, -0.164, -0.176, -0.18, -0.164, -0.164, + -0.176, -0.188, -0.204, -0.192, -0.176, -0.188, -0.192, -0.196, + -0.18, -0.172, -0.184, -0.172, -0.176, -0.176, -0.176, -0.156, + -0.16, -0.168, -0.18, -0.164, -0.164, -0.152, -0.156, -0.168, + -0.192, -0.208, + ], + }, + }, + { + ID: 1717765554773, + data: { + x: [ + -0.944, -0.96, -0.96, -0.972, -0.968, -0.976, -0.976, -0.968, + -0.972, -0.968, -0.96, -0.948, -0.956, -0.956, -0.964, -0.96, + -0.976, -0.964, -0.964, -0.964, -0.964, -0.972, -0.968, -0.96, + -0.96, -0.976, -0.976, -0.976, -0.972, -0.972, -0.968, -0.968, + -0.964, -0.964, -0.968, -0.968, -0.964, -0.968, -0.964, -0.968, + -0.96, -0.956, -0.952, -0.952, -0.956, -0.96, -0.964, -0.968, -0.96, + -0.968, -0.968, -0.972, -0.968, -0.964, -0.964, -0.96, -0.96, + -0.984, -0.964, -0.968, -0.952, -0.952, -0.964, -0.968, -0.968, + -0.956, -0.956, -0.96, -0.956, -0.948, -0.968, -0.968, -0.968, + -0.968, -0.964, -0.972, -0.964, -0.964, -0.956, -0.964, -0.956, + -0.96, -0.96, -0.972, -0.96, -0.96, -0.968, -0.972, -0.964, -0.964, + ], + y: [ + 0.344, 0.336, 0.336, 0.336, 0.328, 0.328, 0.324, 0.336, 0.336, + 0.336, 0.324, 0.332, 0.332, 0.34, 0.344, 0.324, 0.324, 0.328, 0.332, + 0.336, 0.336, 0.336, 0.332, 0.34, 0.336, 0.336, 0.324, 0.32, 0.32, + 0.324, 0.32, 0.324, 0.336, 0.324, 0.336, 0.336, 0.344, 0.328, 0.344, + 0.336, 0.348, 0.332, 0.328, 0.332, 0.348, 0.344, 0.332, 0.332, + 0.344, 0.336, 0.332, 0.324, 0.32, 0.328, 0.332, 0.348, 0.336, 0.328, + 0.328, 0.336, 0.34, 0.344, 0.328, 0.332, 0.332, 0.34, 0.344, 0.344, + 0.352, 0.34, 0.332, 0.324, 0.328, 0.324, 0.34, 0.34, 0.348, 0.356, + 0.344, 0.344, 0.34, 0.336, 0.336, 0.336, 0.34, 0.336, 0.34, 0.34, + 0.34, 0.34, + ], + z: [ + -0.052, -0.056, -0.06, -0.06, -0.064, -0.08, -0.06, -0.052, -0.036, + -0.044, -0.048, -0.056, -0.056, -0.06, -0.052, -0.056, -0.064, + -0.052, -0.06, -0.064, -0.056, -0.056, -0.048, -0.048, -0.048, + -0.052, -0.056, -0.064, -0.048, -0.056, -0.04, -0.056, -0.044, + -0.044, -0.048, -0.052, -0.068, -0.068, -0.06, -0.06, -0.048, + -0.052, -0.06, -0.036, -0.052, -0.06, -0.068, -0.064, -0.052, + -0.048, -0.052, -0.064, -0.064, -0.048, -0.044, -0.036, -0.044, + -0.072, -0.052, -0.052, -0.04, -0.052, -0.072, -0.064, -0.076, + -0.048, -0.044, -0.044, -0.056, -0.052, -0.064, -0.056, -0.052, + -0.052, -0.048, -0.04, -0.052, -0.052, -0.056, -0.056, -0.056, + -0.06, -0.064, -0.056, -0.044, -0.048, -0.056, -0.064, -0.06, -0.06, + ], + }, + }, + { + ID: 1717765549053, + data: { + x: [ + -0.276, -0.276, -0.264, -0.276, -0.276, -0.276, -0.276, -0.284, + -0.276, -0.272, -0.272, -0.268, -0.284, -0.276, -0.28, -0.276, + -0.272, -0.276, -0.28, -0.28, -0.272, -0.272, -0.268, -0.272, + -0.288, -0.276, -0.284, -0.28, -0.276, -0.272, -0.272, -0.272, + -0.272, -0.288, -0.292, -0.276, -0.268, -0.268, -0.276, -0.28, + -0.272, -0.272, -0.276, -0.268, -0.268, -0.26, -0.26, -0.272, + -0.276, -0.272, -0.284, -0.276, -0.272, -0.272, -0.276, -0.268, + -0.268, -0.268, -0.26, -0.256, -0.26, -0.272, -0.276, -0.268, + -0.268, -0.28, -0.292, -0.28, -0.268, -0.276, -0.268, -0.268, + -0.256, -0.272, -0.272, -0.28, -0.276, -0.268, -0.268, -0.28, -0.28, + -0.28, -0.272, -0.268, -0.272, -0.272, -0.272, -0.276, -0.272, + -0.276, -0.276, + ], + y: [ + -0.052, -0.056, -0.064, -0.076, -0.076, -0.068, -0.064, -0.06, + -0.076, -0.076, -0.064, -0.06, -0.06, -0.064, -0.068, -0.068, -0.06, + -0.068, -0.072, -0.06, -0.06, -0.056, -0.068, -0.064, -0.072, + -0.068, -0.068, -0.072, -0.076, -0.072, -0.068, -0.064, -0.076, + -0.076, -0.08, -0.092, -0.068, -0.064, -0.068, -0.064, -0.076, + -0.072, -0.072, -0.072, -0.072, -0.064, -0.076, -0.076, -0.076, + -0.088, -0.092, -0.092, -0.092, -0.092, -0.084, -0.072, -0.084, + -0.092, -0.072, -0.072, -0.064, -0.064, -0.08, -0.068, -0.084, + -0.08, -0.088, -0.084, -0.084, -0.076, -0.088, -0.08, -0.088, + -0.092, -0.076, -0.076, -0.076, -0.072, -0.088, -0.084, -0.08, + -0.076, -0.08, -0.072, -0.076, -0.076, -0.076, -0.072, -0.08, -0.08, + -0.076, + ], + z: [ + 0.98, 0.992, 0.996, 0.992, 0.996, 0.988, 0.98, 0.98, 0.98, 0.984, + 0.992, 0.98, 0.956, 0.976, 0.972, 0.968, 0.972, 0.976, 0.976, 0.968, + 0.972, 0.972, 0.976, 0.98, 0.984, 0.968, 0.968, 0.984, 0.992, 0.992, + 0.988, 0.984, 0.98, 0.96, 0.96, 0.976, 0.976, 0.98, 0.968, 0.968, + 0.976, 0.976, 0.968, 0.984, 0.988, 0.988, 0.976, 0.964, 0.964, + 0.964, 0.984, 0.98, 0.992, 1, 0.996, 1, 1.004, 0.992, 1.008, 1, + 0.968, 0.948, 0.968, 0.98, 0.98, 0.948, 0.948, 0.968, 0.984, 0.988, + 0.992, 1.012, 1, 0.996, 0.98, 0.98, 0.984, 0.988, 0.976, 0.972, + 0.956, 0.964, 0.976, 0.968, 0.976, 0.984, 0.976, 0.976, 0.98, 0.988, + 0.968, + ], + }, + }, + { + ID: 1717765543427, + data: { + x: [ + 0.212, 0.212, 0.208, 0.2, 0.204, 0.204, 0.216, 0.22, 0.208, 0.2, + 0.208, 0.216, 0.212, 0.204, 0.2, 0.212, 0.212, 0.212, 0.204, 0.208, + 0.216, 0.204, 0.204, 0.212, 0.208, 0.208, 0.2, 0.208, 0.2, 0.204, + 0.22, 0.216, 0.208, 0.196, 0.196, 0.208, 0.216, 0.216, 0.204, 0.2, + 0.2, 0.208, 0.216, 0.216, 0.216, 0.212, 0.208, 0.216, 0.224, 0.22, + 0.22, 0.22, 0.228, 0.224, 0.224, 0.212, 0.224, 0.204, 0.212, 0.212, + 0.224, 0.224, 0.212, 0.208, 0.224, 0.216, 0.212, 0.216, 0.216, + 0.216, 0.216, 0.232, 0.232, 0.216, 0.22, 0.228, 0.228, 0.228, 0.224, + 0.22, 0.212, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.228, 0.22, 0.212, + 0.216, + ], + y: [ + 0.508, 0.504, 0.516, 0.52, 0.512, 0.492, 0.512, 0.516, 0.512, 0.508, + 0.492, 0.504, 0.516, 0.516, 0.512, 0.504, 0.508, 0.52, 0.508, 0.504, + 0.504, 0.508, 0.508, 0.5, 0.504, 0.512, 0.52, 0.512, 0.508, 0.5, + 0.512, 0.508, 0.512, 0.516, 0.512, 0.512, 0.508, 0.504, 0.508, + 0.508, 0.516, 0.504, 0.512, 0.52, 0.52, 0.516, 0.508, 0.516, 0.512, + 0.508, 0.508, 0.508, 0.512, 0.512, 0.508, 0.508, 0.516, 0.508, + 0.504, 0.516, 0.516, 0.516, 0.516, 0.512, 0.504, 0.512, 0.516, + 0.508, 0.508, 0.528, 0.524, 0.524, 0.508, 0.504, 0.516, 0.52, 0.516, + 0.52, 0.516, 0.512, 0.504, 0.5, 0.516, 0.524, 0.524, 0.516, 0.508, + 0.508, 0.512, 0.516, 0.504, + ], + z: [ + -0.892, -0.884, -0.896, -0.908, -0.904, -0.908, -0.904, -0.9, + -0.908, -0.924, -0.912, -0.908, -0.9, -0.904, -0.908, -0.9, -0.896, + -0.908, -0.912, -0.9, -0.884, -0.888, -0.904, -0.892, -0.892, + -0.908, -0.912, -0.892, -0.912, -0.924, -0.896, -0.888, -0.884, + -0.904, -0.92, -0.908, -0.896, -0.884, -0.88, -0.884, -0.912, -0.9, + -0.9, -0.904, -0.908, -0.916, -0.908, -0.892, -0.884, -0.892, + -0.888, -0.904, -0.896, -0.892, -0.896, -0.912, -0.908, -0.924, + -0.896, -0.884, -0.884, -0.876, -0.888, -0.904, -0.892, -0.9, -0.9, + -0.892, -0.9, -0.904, -0.904, -0.888, -0.892, -0.9, -0.9, -0.892, + -0.88, -0.872, -0.892, -0.904, -0.896, -0.888, -0.88, -0.892, + -0.892, -0.892, -0.892, -0.896, -0.892, -0.896, -0.904, + ], + }, + }, + { + ID: 1717765538099, + data: { + x: [ + -0.328, -0.336, -0.324, -0.32, -0.332, -0.336, -0.332, -0.332, + -0.336, -0.336, -0.328, -0.328, -0.32, -0.316, -0.336, -0.328, + -0.324, -0.324, -0.328, -0.328, -0.328, -0.332, -0.324, -0.324, + -0.324, -0.32, -0.32, -0.32, -0.32, -0.328, -0.332, -0.324, -0.316, + -0.32, -0.316, -0.316, -0.324, -0.32, -0.328, -0.32, -0.32, -0.316, + -0.316, -0.324, -0.32, -0.324, -0.328, -0.32, -0.324, -0.32, -0.316, + -0.312, -0.32, -0.312, -0.328, -0.336, -0.324, -0.32, -0.32, -0.3, + -0.316, -0.324, -0.32, -0.316, -0.304, -0.312, -0.32, -0.328, + -0.312, -0.316, -0.316, -0.316, -0.32, -0.324, -0.316, -0.316, + -0.32, -0.324, -0.324, -0.324, -0.312, -0.312, -0.32, -0.32, -0.328, + -0.324, -0.316, -0.316, -0.316, -0.316, + ], + y: [ + -0.868, -0.872, -0.86, -0.844, -0.848, -0.84, -0.836, -0.84, -0.84, + -0.848, -0.844, -0.852, -0.852, -0.848, -0.844, -0.848, -0.852, + -0.864, -0.86, -0.868, -0.86, -0.864, -0.86, -0.86, -0.852, -0.856, + -0.852, -0.852, -0.864, -0.864, -0.872, -0.864, -0.86, -0.864, + -0.86, -0.86, -0.872, -0.868, -0.868, -0.868, -0.86, -0.856, -0.864, + -0.868, -0.864, -0.864, -0.864, -0.872, -0.864, -0.872, -0.864, + -0.848, -0.86, -0.864, -0.864, -0.864, -0.864, -0.864, -0.852, + -0.852, -0.856, -0.868, -0.876, -0.856, -0.856, -0.852, -0.852, + -0.868, -0.868, -0.868, -0.864, -0.86, -0.86, -0.856, -0.86, -0.852, + -0.848, -0.852, -0.856, -0.872, -0.868, -0.876, -0.868, -0.864, + -0.876, -0.868, -0.868, -0.86, -0.852, -0.852, + ], + z: [ + -0.48, -0.464, -0.452, -0.436, -0.456, -0.448, -0.448, -0.46, -0.48, + -0.484, -0.48, -0.476, -0.46, -0.456, -0.46, -0.444, -0.452, -0.452, + -0.444, -0.472, -0.476, -0.468, -0.464, -0.468, -0.464, -0.452, + -0.452, -0.448, -0.452, -0.468, -0.468, -0.456, -0.46, -0.448, + -0.448, -0.448, -0.464, -0.448, -0.46, -0.456, -0.452, -0.448, + -0.456, -0.448, -0.436, -0.452, -0.46, -0.456, -0.468, -0.448, + -0.468, -0.456, -0.452, -0.456, -0.46, -0.48, -0.464, -0.448, + -0.468, -0.444, -0.448, -0.452, -0.452, -0.468, -0.452, -0.452, + -0.452, -0.456, -0.464, -0.452, -0.444, -0.476, -0.472, -0.472, + -0.468, -0.452, -0.444, -0.46, -0.464, -0.46, -0.448, -0.444, + -0.444, -0.432, -0.456, -0.452, -0.44, -0.444, -0.448, -0.468, + ], + }, + }, + ], + output: {}, + confidence: { + currentConfidence: 0.9898542761802673, + requiredConfidence: 0.8, + isConfident: true, + }, + }, + { + ID: 1717765568963, + name: "wave", + matrix: [ + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + true, + false, + true, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + false, + ], + recordings: [ + { + ID: 1717765679450, + data: { + x: [ + -0.512, -0.476, -0.412, -0.332, -0.248, -0.176, -0.132, -0.164, + -0.216, -0.248, -0.244, -0.276, -0.292, -0.292, -0.252, -0.22, + -0.176, -0.068, 0.032, 0.012, -0.036, -0.1, -0.168, -0.26, -0.44, + -0.584, -0.62, -0.6, -0.608, -0.612, -0.604, -0.612, -0.584, -0.528, + -0.448, -0.368, -0.272, -0.208, -0.18, -0.144, -0.156, -0.144, + -0.156, -0.2, -0.236, -0.288, -0.312, -0.304, -0.3, -0.312, -0.356, + -0.364, -0.38, -0.444, -0.5, -0.612, -0.684, -0.756, -0.772, -0.72, + -0.644, -0.536, -0.44, -0.376, -0.324, -0.308, -0.308, -0.28, -0.26, + -0.292, -0.316, -0.344, -0.34, -0.344, -0.316, -0.272, -0.244, + -0.232, -0.268, -0.344, -0.436, -0.52, -0.576, -0.644, -0.716, + -0.78, -0.844, -0.784, -0.732, -0.712, + ], + y: [ + 2.04, 2.04, 2.04, 1.784, 1.516, 1.224, 0.956, 0.768, 0.672, 0.54, + 0.396, 0.3, 0.164, 0.068, -0.004, -0.04, -0.116, -0.384, -0.752, + -0.976, -1.156, -1.18, -1.284, -1.432, -1.508, -1.624, -1.872, + -2.04, -2.04, -2.04, -2.04, -1.94, -1.692, -1.512, -1.328, -1.192, + -1.04, -0.896, -0.724, -0.5, -0.328, -0.244, -0.12, 0.016, 0.148, + 0.216, 0.264, 0.312, 0.412, 0.552, 0.772, 0.896, 1.032, 1.312, 1.6, + 1.932, 2.04, 2.04, 2.04, 2.04, 1.996, 1.74, 1.456, 1.2, 0.956, + 0.744, 0.564, 0.376, 0.136, 0, -0.132, -0.24, -0.276, -0.332, + -0.388, -0.452, -0.54, -0.688, -0.796, -0.864, -0.92, -0.992, + -1.128, -1.272, -1.488, -1.724, -1.944, -2.04, -2.04, -2.04, + ], + z: [ + -0.472, -0.364, -0.268, -0.228, -0.24, -0.276, -0.324, -0.292, + -0.212, -0.168, -0.208, -0.232, -0.212, -0.2, -0.224, -0.344, -0.42, + -0.332, -0.092, 0.188, 0.372, 0.356, 0.304, 0.244, 0.212, 0.08, + -0.152, -0.4, -0.292, -0.064, 0.092, 0.108, 0.084, 0.008, -0.004, + 0.024, 0.096, 0.14, 0.172, 0.16, 0.132, 0.08, 0.088, 0.024, -0.052, + -0.04, -0.076, -0.108, -0.108, -0.18, -0.264, -0.284, -0.328, + -0.488, -0.644, -0.756, -0.768, -0.752, -0.656, -0.54, -0.464, + -0.436, -0.42, -0.376, -0.308, -0.24, -0.208, -0.18, -0.176, -0.192, + -0.212, -0.176, -0.088, 0.004, 0.124, 0.192, 0.208, 0.248, 0.308, + 0.384, 0.408, 0.34, 0.2, 0.06, -0.068, -0.164, -0.212, -0.172, + -0.12, -0.036, + ], + }, + }, + { + ID: 1717765674496, + data: { + x: [ + -0.544, -0.452, -0.372, -0.312, -0.26, -0.244, -0.248, -0.28, + -0.332, -0.384, -0.4, -0.42, -0.44, -0.416, -0.456, -0.48, -0.5, + -0.508, -0.532, -0.536, -0.584, -0.632, -0.716, -0.76, -0.724, + -0.64, -0.532, -0.46, -0.416, -0.364, -0.288, -0.284, -0.312, + -0.332, -0.316, -0.328, -0.364, -0.36, -0.36, -0.32, -0.304, -0.256, + -0.26, -0.32, -0.404, -0.472, -0.62, -0.792, -0.912, -0.916, -0.908, + -0.864, -0.78, -0.712, -0.62, -0.52, -0.452, -0.412, -0.384, -0.38, + -0.368, -0.444, -0.432, -0.484, -0.484, -0.5, -0.5, -0.492, -0.488, + -0.496, -0.508, -0.504, -0.484, -0.5, -0.472, -0.512, -0.572, + -0.656, -0.708, -0.704, -0.66, -0.572, -0.52, -0.456, -0.392, + -0.364, -0.368, -0.4, -0.38, -0.368, -0.364, + ], + y: [ + -1.156, -1.012, -0.78, -0.536, -0.404, -0.268, -0.12, 0.084, 0.276, + 0.332, 0.3, 0.288, 0.376, 0.536, 0.712, 0.84, 0.98, 1.068, 1.204, + 1.312, 1.476, 1.744, 2.04, 2.04, 2.04, 1.9, 1.656, 1.456, 1.272, + 1.016, 0.688, 0.464, 0.368, 0.296, 0.144, -0.024, -0.096, -0.104, + -0.14, -0.208, -0.32, -0.444, -0.568, -0.652, -0.752, -0.976, -1.2, + -1.456, -1.884, -2.04, -2.04, -2.04, -1.828, -1.48, -1.2, -0.924, + -0.78, -0.652, -0.516, -0.344, -0.14, 0.032, 0.132, 0.22, 0.192, + 0.204, 0.24, 0.3, 0.424, 0.544, 0.62, 0.628, 0.72, 0.908, 1.124, + 1.42, 1.712, 2.02, 2.04, 2.04, 2.04, 1.844, 1.604, 1.372, 1.076, + 0.784, 0.584, 0.516, 0.424, 0.192, -0.028, + ], + z: [ + 0.336, 0.296, 0.16, 0.016, -0.024, 0.04, 0.056, -0.028, -0.124, + -0.164, -0.088, -0.072, -0.18, -0.288, -0.396, -0.396, -0.376, + -0.404, -0.48, -0.632, -0.768, -0.804, -0.876, -0.84, -0.708, + -0.644, -0.652, -0.676, -0.596, -0.516, -0.416, -0.308, -0.272, + -0.248, -0.16, -0.028, 0.012, 0.004, 0.048, 0.152, 0.316, 0.4, + 0.444, 0.484, 0.504, 0.548, 0.616, 0.6, 0.476, 0.328, 0.32, 0.336, + 0.332, 0.348, 0.348, 0.264, 0.184, 0.14, 0.104, 0.056, 0.12, 0.132, + 0.112, 0.084, 0.036, 0.076, 0.108, 0.032, -0.096, -0.236, -0.248, + -0.16, -0.192, -0.32, -0.468, -0.552, -0.564, -0.596, -0.66, -0.628, + -0.516, -0.424, -0.456, -0.432, -0.404, -0.332, -0.312, -0.28, + -0.256, -0.192, -0.092, + ], + }, + }, + { + ID: 1717765669623, + data: { + x: [ + -0.372, -0.376, -0.392, -0.352, -0.352, -0.404, -0.444, -0.468, + -0.464, -0.456, -0.444, -0.444, -0.472, -0.528, -0.608, -0.704, + -0.808, -0.912, -0.992, -0.996, -0.988, -0.948, -0.916, -0.856, + -0.844, -0.772, -0.676, -0.572, -0.52, -0.508, -0.46, -0.42, -0.396, + -0.384, -0.376, -0.388, -0.384, -0.38, -0.396, -0.396, -0.428, + -0.472, -0.492, -0.504, -0.544, -0.6, -0.66, -0.72, -0.724, -0.68, + -0.6, -0.528, -0.492, -0.444, -0.388, -0.392, -0.428, -0.436, + -0.432, -0.464, -0.504, -0.516, -0.532, -0.512, -0.472, -0.508, + -0.496, -0.46, -0.44, -0.492, -0.544, -0.644, -0.768, -0.916, + -1.048, -1.18, -1.192, -1.176, -1.092, -1.008, -0.916, -0.804, + -0.704, -0.6, -0.48, -0.348, -0.288, -0.272, -0.296, -0.324, + ], + y: [ + 0.544, 0.428, 0.268, 0.036, -0.084, -0.124, -0.116, -0.1, -0.132, + -0.224, -0.328, -0.44, -0.512, -0.656, -0.832, -1.024, -1.272, + -1.496, -1.776, -2.024, -2.028, -1.844, -1.56, -1.268, -0.94, + -0.752, -0.656, -0.592, -0.408, -0.276, -0.216, -0.108, 0.004, + 0.152, 0.304, 0.368, 0.344, 0.352, 0.428, 0.52, 0.636, 0.732, 0.848, + 1.056, 1.428, 1.812, 2.04, 2.04, 2.04, 2.04, 1.756, 1.472, 1.244, + 0.968, 0.668, 0.5, 0.424, 0.332, 0.188, 0.048, -0.012, 0, 0, -0.064, + -0.156, -0.22, -0.28, -0.368, -0.46, -0.576, -0.812, -1.036, -1.108, + -1.188, -1.304, -1.464, -1.628, -1.76, -1.728, -1.556, -1.34, + -1.132, -0.86, -0.68, -0.576, -0.536, -0.38, -0.088, 0.176, 0.304, + ], + z: [ + -0.308, -0.228, -0.124, -0.168, -0.16, -0.072, 0.008, 0.032, 0.056, + 0.076, 0.176, 0.32, 0.38, 0.388, 0.416, 0.42, 0.36, 0.316, 0.284, + 0.28, 0.308, 0.252, 0.26, 0.152, 0.056, 0.04, 0.064, 0.12, 0.088, + 0.016, -0.02, -0.012, -0.024, -0.044, -0.032, -0.004, 0.024, -0.04, + -0.108, -0.22, -0.296, -0.38, -0.444, -0.548, -0.712, -0.864, -0.9, + -0.856, -0.792, -0.72, -0.704, -0.652, -0.636, -0.56, -0.484, + -0.392, -0.332, -0.316, -0.252, -0.108, 0.02, 0.068, 0.064, 0.088, + 0.104, 0.18, 0.248, 0.252, 0.228, 0.34, 0.444, 0.492, 0.416, 0.304, + 0.248, 0.272, 0.264, 0.3, 0.256, 0.224, 0.248, 0.224, 0.176, 0.156, + 0.204, 0.176, 0.136, 0.032, -0.056, -0.096, + ], + }, + }, + { + ID: 1717765664759, + data: { + x: [ + -0.524, -0.416, -0.384, -0.352, -0.352, -0.344, -0.356, -0.396, + -0.428, -0.452, -0.468, -0.468, -0.484, -0.5, -0.528, -0.548, + -0.556, -0.576, -0.604, -0.624, -0.616, -0.628, -0.656, -0.656, + -0.576, -0.508, -0.428, -0.412, -0.392, -0.364, -0.348, -0.328, + -0.332, -0.328, -0.364, -0.416, -0.456, -0.48, -0.476, -0.476, + -0.516, -0.6, -0.684, -0.712, -0.804, -0.88, -0.916, -0.924, -0.916, + -0.888, -0.836, -0.776, -0.76, -0.732, -0.692, -0.604, -0.536, + -0.504, -0.484, -0.488, -0.48, -0.5, -0.488, -0.492, -0.508, -0.508, + -0.46, -0.444, -0.456, -0.452, -0.42, -0.44, -0.456, -0.476, -0.472, + -0.476, -0.452, -0.412, -0.372, -0.348, -0.364, -0.384, -0.4, + -0.376, -0.412, -0.456, -0.504, -0.504, -0.508, -0.484, -0.492, + ], + y: [ + -0.824, -0.72, -0.52, -0.292, -0.124, 0.008, 0.084, 0.164, 0.256, + 0.336, 0.416, 0.432, 0.484, 0.608, 0.78, 0.924, 0.988, 1.1, 1.284, + 1.428, 1.608, 1.96, 2.04, 2.04, 1.98, 1.584, 1.284, 1.148, 1.004, + 0.808, 0.576, 0.416, 0.22, -0.012, -0.132, -0.152, -0.176, -0.252, + -0.38, -0.54, -0.7, -0.892, -1.028, -1.252, -1.324, -1.476, -1.64, + -1.824, -1.812, -1.612, -1.4, -1.16, -0.836, -0.552, -0.408, -0.372, + -0.332, -0.16, 0.048, 0.264, 0.452, 0.56, 0.576, 0.588, 0.584, + 0.584, 0.596, 0.7, 0.86, 1.048, 1.248, 1.56, 1.936, 2.04, 2.04, + 2.04, 1.868, 1.66, 1.4, 1.124, 0.884, 0.748, 0.54, 0.3, 0.18, 0.072, + 0.02, -0.016, -0.076, -0.172, -0.308, + ], + z: [ + 0.124, 0.112, 0.176, 0.156, 0.072, -0.024, -0.068, -0.092, -0.136, + -0.22, -0.312, -0.34, -0.376, -0.392, -0.468, -0.544, -0.636, + -0.644, -0.616, -0.604, -0.584, -0.632, -0.728, -0.648, -0.436, + -0.296, -0.268, -0.292, -0.34, -0.324, -0.268, -0.224, -0.212, + -0.248, -0.236, -0.216, -0.164, -0.1, -0.02, 0.048, 0.144, 0.284, + 0.408, 0.424, 0.424, 0.408, 0.38, 0.32, 0.304, 0.272, 0.228, 0.184, + 0.036, -0.132, -0.172, -0.088, 0.036, 0.116, 0.128, 0.056, -0.024, + -0.052, -0.056, -0.004, 0.02, -0.056, -0.176, -0.268, -0.32, -0.332, + -0.38, -0.472, -0.564, -0.64, -0.556, -0.396, -0.324, -0.368, + -0.432, -0.392, -0.34, -0.304, -0.264, -0.272, -0.256, -0.256, + -0.244, -0.216, -0.064, 0.084, 0.176, + ], + }, + }, + { + ID: 1717765659660, + data: { + x: [ + -0.428, -0.42, -0.424, -0.408, -0.452, -0.48, -0.476, -0.452, -0.44, + -0.444, -0.452, -0.456, -0.504, -0.524, -0.508, -0.52, -0.524, + -0.592, -0.696, -0.816, -0.892, -0.968, -1.06, -1.08, -1.032, + -0.968, -0.888, -0.824, -0.74, -0.608, -0.468, -0.36, -0.312, + -0.328, -0.356, -0.36, -0.412, -0.464, -0.508, -0.54, -0.552, + -0.548, -0.552, -0.548, -0.536, -0.536, -0.544, -0.552, -0.56, + -0.588, -0.668, -0.72, -0.744, -0.7, -0.628, -0.556, -0.472, -0.46, + -0.48, -0.48, -0.484, -0.488, -0.54, -0.572, -0.592, -0.596, -0.608, + -0.596, -0.572, -0.544, -0.548, -0.552, -0.556, -0.588, -0.612, + -0.712, -0.804, -0.888, -0.936, -0.96, -0.98, -0.96, -0.9, -0.844, + -0.768, -0.68, -0.624, -0.556, -0.488, -0.436, -0.404, + ], + y: [ + 0.68, 0.556, 0.452, 0.24, 0.092, 0.068, 0.068, 0.008, -0.16, -0.316, + -0.412, -0.376, -0.376, -0.424, -0.576, -0.704, -0.812, -0.908, + -1.028, -1.176, -1.336, -1.524, -1.616, -1.624, -1.628, -1.548, + -1.38, -1.132, -0.96, -0.864, -0.86, -0.776, -0.54, -0.22, -0.008, + 0.116, 0.212, 0.292, 0.4, 0.472, 0.5, 0.52, 0.588, 0.66, 0.7, 0.744, + 0.86, 1.06, 1.32, 1.6, 1.904, 2.04, 2.04, 1.996, 1.704, 1.312, + 0.972, 0.704, 0.564, 0.444, 0.3, 0.164, 0.048, 0.012, 0.004, 0.016, + -0.032, -0.096, -0.152, -0.152, -0.172, -0.324, -0.492, -0.572, + -0.684, -0.792, -1.024, -1.28, -1.544, -1.684, -1.756, -1.68, + -1.524, -1.372, -1.212, -0.988, -0.736, -0.604, -0.564, -0.496, + -0.352, + ], + z: [ + -0.532, -0.5, -0.42, -0.232, -0.072, -0.004, -0.08, -0.132, 0, 0.22, + 0.3, 0.24, 0.168, 0.216, 0.32, 0.424, 0.428, 0.472, 0.468, 0.452, + 0.376, 0.304, 0.308, 0.356, 0.464, 0.564, 0.52, 0.356, 0.264, 0.288, + 0.372, 0.408, 0.284, 0.112, -0.048, -0.132, -0.108, -0.14, -0.196, + -0.228, -0.208, -0.256, -0.304, -0.36, -0.352, -0.332, -0.332, + -0.42, -0.584, -0.788, -0.86, -0.816, -0.732, -0.72, -0.736, -0.696, + -0.56, -0.448, -0.376, -0.352, -0.292, -0.168, -0.02, 0.048, 0.068, + 0.036, 0.036, 0.104, 0.188, 0.196, 0.176, 0.204, 0.32, 0.4, 0.344, + 0.372, 0.396, 0.4, 0.368, 0.396, 0.448, 0.484, 0.472, 0.5, 0.48, + 0.352, 0.212, 0.164, 0.148, 0.116, 0.032, + ], + }, + }, + { + ID: 1717765654503, + data: { + x: [ + -0.312, -0.352, -0.4, -0.468, -0.464, -0.416, -0.396, -0.308, -0.22, + -0.084, -0.016, -0.012, -0.064, -0.104, -0.152, -0.252, -0.352, + -0.404, -0.484, -0.508, -0.52, -0.516, -0.5, -0.468, -0.444, -0.432, + -0.4, -0.404, -0.396, -0.284, -0.304, -0.448, -0.684, -0.848, + -1.028, -1.184, -1.264, -1.24, -1.092, -0.86, -0.636, -0.444, + -0.288, -0.212, -0.16, -0.188, -0.22, -0.224, -0.22, -0.308, -0.38, + -0.416, -0.424, -0.44, -0.46, -0.504, -0.524, -0.5, -0.512, -0.504, + -0.44, -0.408, -0.424, -0.448, -0.424, -0.452, -0.484, -0.52, + -0.588, -0.584, -0.52, -0.412, -0.356, -0.316, -0.244, -0.224, + -0.248, -0.276, -0.324, -0.364, -0.42, -0.468, -0.476, -0.496, + -0.484, -0.484, -0.448, -0.46, -0.428, -0.38, -0.38, + ], + y: [ + 1.256, 1.584, 2.032, 2.04, 2.04, 2.04, 2.04, 1.928, 1.624, 1.16, + 0.716, 0.4, 0.264, 0.112, -0.108, -0.216, -0.172, -0.16, -0.22, + -0.348, -0.368, -0.292, -0.256, -0.352, -0.488, -0.52, -0.52, -0.44, + -0.36, -0.644, -1.112, -1.312, -1.524, -1.908, -2.04, -2.04, -1.916, + -1.68, -1.548, -1.552, -1.54, -1.428, -1.248, -0.972, -0.748, + -0.456, -0.296, -0.236, -0.172, 0.052, 0.204, 0.212, 0.188, 0.244, + 0.428, 0.612, 0.64, 0.552, 0.548, 0.6, 0.592, 0.652, 0.82, 0.956, + 1.04, 1.264, 1.588, 1.996, 2.04, 2.04, 2.04, 1.784, 1.536, 1.24, + 0.872, 0.552, 0.42, 0.28, 0.128, 0.032, 0.068, 0.08, -0.008, -0.112, + -0.112, -0.116, -0.18, -0.272, -0.356, -0.424, -0.448, + ], + z: [ + -0.932, -1.012, -1.192, -1.34, -1.34, -1.144, -0.956, -0.936, + -0.932, -0.876, -0.748, -0.588, -0.472, -0.364, -0.192, 0.028, + 0.136, 0.116, 0.12, 0.22, 0.292, 0.236, 0.22, 0.212, 0.272, 0.292, + 0.244, 0.26, 0.24, 0.224, 0.408, 0.472, 0.6, 0.56, 0.296, 0.084, + 0.02, 0.12, 0.324, 0.548, 0.664, 0.612, 0.56, 0.468, 0.364, 0.188, + 0.076, 0.096, 0.104, -0.056, -0.164, -0.132, -0.068, -0.14, -0.256, + -0.336, -0.328, -0.3, -0.304, -0.364, -0.448, -0.468, -0.528, + -0.632, -0.708, -0.82, -1.02, -1.208, -1.212, -1.052, -0.904, + -0.872, -0.844, -0.764, -0.656, -0.52, -0.448, -0.408, -0.276, + -0.14, -0.076, -0.084, -0.024, 0.108, 0.116, 0.084, 0.128, 0.22, + 0.248, 0.236, 0.28, + ], + }, + }, + ], + output: {}, + confidence: { + currentConfidence: 0.0034859327133744955, + requiredConfidence: 0.8, + isConfident: false, + }, + }, + { + ID: 1717765681522, + name: "clap", + matrix: [ + true, + true, + true, + true, + true, + true, + false, + false, + false, + true, + true, + false, + false, + false, + true, + true, + false, + false, + false, + true, + true, + true, + true, + true, + true, + ], + recordings: [ + { + ID: 1717765716738, + data: { + x: [ + -1.372, -1.5, -1.608, -1.636, -1.556, -1.428, -1.252, -1.04, -0.844, + -0.684, -0.544, -0.468, -0.436, -0.396, -0.408, -0.372, -0.304, + -0.244, -0.236, -0.188, -0.092, -0.052, -0.072, -0.02, -0.008, + -0.06, -0.212, -0.34, -0.432, -0.516, -0.576, -0.736, -0.796, -0.72, + -0.52, -0.38, -0.228, -0.052, 0.032, 0.056, 0.048, 0.008, 0, 0.004, + -0.028, -0.104, -0.18, -0.208, -0.276, -0.32, -0.372, -0.448, -0.52, + -0.532, -0.496, -0.492, -0.564, -0.576, -0.62, -0.832, -1.156, + -1.42, -1.528, -1.62, -1.628, -1.536, -1.372, -1.172, -1.02, -0.892, + -0.74, -0.604, -0.524, -0.456, -0.372, -0.292, -0.232, -0.196, + -0.188, -0.188, -0.168, -0.136, -0.068, -0.06, -0.072, -0.112, + -0.22, -0.388, -0.552, -0.696, -0.768, + ], + y: [ + 0.176, 0.26, 0.276, 0.36, 0.42, 0.436, 0.408, 0.3, 0.208, 0.064, + -0.076, -0.116, -0.112, -0.1, -0.084, -0.104, -0.148, -0.14, -0.08, + -0.084, -0.116, -0.144, -0.176, -0.304, -0.332, -0.328, -0.348, + -0.4, -0.504, -0.532, -0.58, -0.632, -0.756, -0.892, -1.1, -1.208, + -1.176, -1.124, -0.904, -0.688, -0.52, -0.388, -0.308, -0.256, + -0.212, -0.148, -0.116, -0.104, -0.056, 0.024, 0.152, 0.208, 0.236, + 0.296, 0.412, 0.476, 0.348, 0.132, -0.016, 0.112, 0.516, 0.652, + 0.588, 0.544, 0.58, 0.624, 0.604, 0.488, 0.316, 0.108, -0.036, + -0.072, -0.068, -0.052, -0.04, -0.028, -0.024, -0.052, -0.08, + -0.092, -0.064, -0.04, -0.044, -0.204, -0.46, -0.548, -0.412, + -0.324, -0.252, -0.148, -0.092, + ], + z: [ + -1.32, -1.404, -1.244, -1.316, -1.408, -1.328, -1.032, -0.708, + -0.448, -0.28, -0.16, -0.02, 0.184, 0.296, 0.368, 0.44, 0.536, + 0.676, 0.804, 0.92, 0.98, 1.012, 1.064, 1.184, 1.256, 1.16, 1.084, + 1.088, 1.148, 1.26, 1.332, 1.544, 1.78, 1.944, 2.036, 2.04, 2.008, + 1.832, 1.756, 1.688, 1.476, 1.244, 1.016, 0.828, 0.744, 0.672, + 0.536, 0.304, 0.124, 0.06, 0.136, 0.212, 0.048, -0.248, -0.252, + 0.14, 0.348, 0.056, -0.468, -0.596, -0.764, -1.264, -1.604, -1.8, + -1.656, -1.38, -1.076, -0.8, -0.556, -0.412, -0.332, -0.192, 0.032, + 0.228, 0.356, 0.452, 0.532, 0.688, 0.772, 0.844, 0.904, 0.976, + 1.024, 1.048, 1.096, 1.16, 1.064, 0.988, 0.96, 0.94, 1, + ], + }, + }, + { + ID: 1717765710734, + data: { + x: [ + -0.632, -0.512, -0.452, -0.436, -0.448, -0.428, -0.42, -0.4, -0.4, + -0.404, -0.352, -0.308, -0.336, -0.276, -0.232, -0.22, -0.272, + -0.304, -0.328, -0.4, -0.488, -0.524, -0.528, -0.556, -0.548, + -0.588, -0.668, -0.524, -0.356, -0.272, -0.256, -0.244, -0.24, + -0.256, -0.268, -0.312, -0.344, -0.368, -0.364, -0.404, -0.388, + -0.392, -0.396, -0.416, -0.436, -0.452, -0.412, -0.448, -0.512, + -0.528, -0.556, -0.516, -0.552, -0.708, -0.796, -0.98, -1.256, + -1.496, -1.6, -1.62, -1.52, -1.392, -1.256, -1.108, -0.972, -0.88, + -0.772, -0.696, -0.66, -0.632, -0.552, -0.492, -0.464, -0.372, + -0.34, -0.324, -0.288, -0.244, -0.212, -0.228, -0.24, -0.26, -0.268, + -0.288, -0.248, -0.256, -0.34, -0.452, -0.596, -0.724, -0.844, + ], + y: [ + -0.184, -0.304, -0.284, -0.18, -0.104, -0.04, -0.016, -0.024, -0.04, + -0.04, -0.028, -0.044, -0.06, -0.108, -0.196, -0.268, -0.272, -0.24, + -0.216, -0.156, -0.12, -0.256, -0.408, -0.5, -0.608, -0.692, -0.784, + -1.02, -1.352, -1.288, -1.076, -0.868, -0.736, -0.612, -0.504, + -0.412, -0.316, -0.26, -0.252, -0.288, -0.26, -0.188, -0.08, 0.004, + 0.004, 0.056, 0.108, 0.144, 0.112, 0.044, 0.144, 0.328, 0.328, + 0.048, 0.024, 0.3, 0.404, 0.444, 0.512, 0.608, 0.564, 0.48, 0.344, + 0.196, 0.04, -0.116, -0.208, -0.224, -0.192, -0.112, -0.044, -0.024, + -0.04, -0.032, -0.024, -0.064, -0.1, -0.136, -0.132, -0.12, -0.14, + -0.136, -0.072, -0.06, -0.232, -0.452, -0.528, -0.464, -0.444, + -0.496, -0.584, + ], + z: [ + -0.184, -0.004, 0.152, 0.308, 0.416, 0.468, 0.532, 0.608, 0.68, + 0.74, 0.812, 0.9, 0.972, 0.98, 0.92, 0.884, 0.964, 1, 0.956, 0.872, + 0.936, 1.072, 1.26, 1.436, 1.548, 1.736, 1.896, 1.996, 2.04, 2.04, + 1.9, 1.736, 1.58, 1.356, 1.124, 0.88, 0.712, 0.644, 0.612, 0.552, + 0.528, 0.452, 0.408, 0.412, 0.312, 0.144, 0.044, 0.12, 0.156, + -0.056, -0.232, -0.008, 0.276, 0.048, -0.6, -0.904, -0.872, -1.052, + -1.328, -1.444, -1.408, -1.172, -0.868, -0.652, -0.516, -0.408, + -0.336, -0.204, -0.012, 0.06, 0.152, 0.3, 0.416, 0.492, 0.64, 0.828, + 0.94, 0.956, 0.916, 0.908, 0.92, 0.892, 0.86, 0.844, 0.896, 1.1, + 1.32, 1.388, 1.364, 1.456, 1.564, + ], + }, + }, + { + ID: 1717765704869, + data: { + x: [ + -0.788, -0.86, -0.8, -0.616, -0.448, -0.364, -0.264, -0.224, -0.22, + -0.184, -0.2, -0.184, -0.22, -0.244, -0.244, -0.272, -0.316, -0.352, + -0.352, -0.344, -0.364, -0.456, -0.5, -0.564, -0.588, -0.704, + -0.652, -0.924, -1.256, -1.564, -1.776, -1.748, -1.536, -1.272, + -0.988, -0.724, -0.532, -0.456, -0.412, -0.456, -0.516, -0.564, + -0.616, -0.62, -0.592, -0.552, -0.512, -0.4, -0.28, -0.324, -0.252, + -0.156, -0.148, -0.288, -0.396, -0.472, -0.552, -0.664, -0.748, + -0.876, -1, -0.988, -0.788, -0.528, -0.364, -0.26, -0.216, -0.248, + -0.26, -0.24, -0.272, -0.28, -0.268, -0.284, -0.248, -0.248, -0.284, + -0.292, -0.304, -0.292, -0.304, -0.36, -0.444, -0.436, -0.456, + -0.584, -0.68, -0.884, -1.224, -1.484, -1.636, + ], + y: [ + -0.832, -1.016, -1.228, -1.456, -1.496, -1.288, -1.068, -0.932, + -0.84, -0.756, -0.608, -0.452, -0.34, -0.308, -0.308, -0.232, + -0.136, -0.128, -0.084, 0.028, 0.164, 0.232, 0.26, 0.316, 0.444, + 0.376, 0.108, 0.072, 0.244, 0.388, 0.788, 1.096, 0.968, 0.74, 0.432, + 0.092, -0.232, -0.464, -0.468, -0.432, -0.364, -0.216, -0.084, + -0.02, 0, -0.06, -0.088, -0.14, -0.148, -0.176, -0.144, -0.164, + -0.164, -0.1, -0.056, -0.152, -0.332, -0.512, -0.696, -0.884, + -0.984, -1.096, -1.28, -1.524, -1.468, -1.188, -0.928, -0.752, -0.6, + -0.532, -0.452, -0.396, -0.348, -0.328, -0.324, -0.3, -0.264, + -0.208, -0.132, 0.008, 0.104, 0.096, 0.052, 0.136, 0.308, 0.24, + 0.112, 0.24, 0.272, 0.384, 0.46, + ], + z: [ + 1.684, 1.848, 1.968, 2.004, 2.028, 2, 1.828, 1.616, 1.34, 1.072, + 0.912, 0.8, 0.652, 0.536, 0.356, 0.292, 0.328, 0.276, 0.08, -0.032, + -0.004, -0.052, -0.12, -0.2, -0.148, 0.088, -0.3, -1.004, -1.376, + -1.764, -2.032, -2.024, -1.676, -1.204, -0.848, -0.552, -0.18, 0.06, + 0.32, 0.436, 0.424, 0.34, 0.316, 0.388, 0.456, 0.536, 0.472, 0.44, + 0.592, 0.788, 0.904, 0.96, 0.956, 0.988, 1.028, 1.112, 1.124, 1.068, + 1.116, 1.372, 1.548, 1.588, 1.776, 2.036, 2.04, 2.024, 1.788, 1.572, + 1.356, 1.104, 0.916, 0.792, 0.68, 0.604, 0.608, 0.584, 0.52, 0.36, + 0.2, 0.208, 0.248, 0.168, -0.02, -0.212, -0.072, 0, -0.42, -0.808, + -1.284, -1.716, -1.616, + ], + }, + }, + { + ID: 1717765700042, + data: { + x: [ + -0.296, -0.248, -0.244, -0.216, -0.16, -0.192, -0.16, -0.204, + -0.284, -0.34, -0.384, -0.424, -0.468, -0.444, -0.42, -0.396, + -0.372, -0.508, -0.608, -0.68, -0.764, -0.908, -1.092, -1.248, + -1.396, -1.384, -1.16, -0.856, -0.64, -0.488, -0.444, -0.492, + -0.536, -0.588, -0.632, -0.672, -0.684, -0.692, -0.712, -0.664, + -0.532, -0.42, -0.528, -0.484, -0.384, -0.284, -0.352, -0.416, + -0.364, -0.368, -0.552, -0.76, -0.888, -1.008, -1.004, -0.836, + -0.632, -0.46, -0.348, -0.292, -0.272, -0.276, -0.236, -0.216, + -0.284, -0.352, -0.368, -0.404, -0.392, -0.416, -0.4, -0.416, + -0.388, -0.424, -0.416, -0.436, -0.46, -0.528, -0.556, -0.652, -0.8, + -0.984, -1.168, -1.352, -1.428, -1.3, -1.1, -0.868, -0.676, -0.556, + -0.524, + ], + y: [ + -0.592, -0.528, -0.484, -0.412, -0.388, -0.392, -0.388, -0.34, + -0.276, -0.212, -0.172, -0.088, -0.012, 0.152, 0.152, 0.072, 0.06, + 0.284, 0.424, 0.312, 0.316, 0.416, 0.544, 0.716, 1.024, 1.14, 0.88, + 0.512, 0.196, -0.104, -0.264, -0.216, -0.144, -0.08, -0.06, -0.056, + -0.016, 0.008, 0.064, 0.092, 0.012, -0.112, -0.156, -0.144, -0.14, + -0.24, -0.272, -0.164, -0.12, -0.188, -0.304, -0.504, -0.716, -0.84, + -0.98, -1.128, -1.216, -1.108, -0.996, -0.78, -0.644, -0.552, + -0.524, -0.516, -0.416, -0.336, -0.304, -0.296, -0.336, -0.324, + -0.196, -0.048, -0.06, -0.136, -0.092, 0.164, 0.288, 0.248, 0.164, + 0.276, 0.356, 0.32, 0.424, 0.704, 0.996, 0.972, 0.8, 0.532, 0.22, + -0.052, -0.188, + ], + z: [ + 2.036, 1.744, 1.356, 1.124, 0.98, 0.796, 0.54, 0.36, 0.268, 0.22, + 0.108, 0.008, -0.064, 0.076, 0.28, 0.156, -0.192, -0.34, -0.176, + -0.268, -0.6, -0.964, -1.376, -1.624, -1.836, -1.816, -1.508, -1.1, + -0.644, -0.328, -0.028, 0.156, 0.228, 0.256, 0.308, 0.344, 0.36, + 0.412, 0.44, 0.472, 0.532, 0.46, 0.48, 0.532, 0.644, 0.668, 0.704, + 0.88, 1.164, 1.3, 1.3, 1.34, 1.484, 1.52, 1.692, 2.012, 2.04, 2.04, + 2.028, 1.932, 1.752, 1.528, 1.22, 0.88, 0.7, 0.58, 0.584, 0.544, + 0.412, 0.3, 0.328, 0.472, 0.544, 0.244, -0.044, -0.096, 0.096, + 0.056, -0.2, -0.496, -0.684, -1.124, -1.54, -1.912, -1.92, -1.556, + -1.152, -0.8, -0.472, -0.12, 0.112, + ], + }, + }, + { + ID: 1717765694490, + data: { + x: [ + -0.448, -0.452, -0.496, -0.504, -0.464, -0.444, -0.452, -0.452, + -0.416, -0.412, -0.388, -0.412, -0.356, -0.368, -0.424, -0.428, + -0.476, -0.548, -0.664, -0.764, -0.864, -0.868, -0.788, -0.66, + -0.552, -0.456, -0.376, -0.288, -0.264, -0.272, -0.288, -0.272, + -0.224, -0.2, -0.192, -0.224, -0.316, -0.408, -0.488, -0.516, + -0.588, -0.584, -0.62, -0.648, -0.648, -0.64, -0.668, -0.664, -0.82, + -1.1, -1.364, -1.556, -1.536, -1.396, -1.252, -1.068, -0.916, -0.8, + -0.752, -0.692, -0.692, -0.704, -0.672, -0.628, -0.628, -0.644, + -0.696, -0.668, -0.556, -0.532, -0.52, -0.5, -0.464, -0.444, -0.42, + -0.504, -0.52, -0.488, -0.444, -0.532, -0.592, -0.64, -0.716, + -0.748, -0.704, -0.584, -0.536, -0.492, -0.452, -0.436, -0.388, + ], + y: [ + 0.14, 0.204, 0.256, 0.248, 0.216, 0.12, 0.016, -0.024, -0.048, + -0.064, -0.144, -0.224, -0.256, -0.28, -0.3, -0.408, -0.528, -0.66, + -0.716, -0.692, -0.704, -0.888, -1.108, -1.224, -1.06, -0.9, -0.768, + -0.692, -0.592, -0.456, -0.372, -0.276, -0.252, -0.24, -0.256, + -0.22, -0.116, -0.024, 0.004, 0.052, 0.14, 0.252, 0.164, 0.036, + 0.064, 0.328, 0.404, 0.092, 0.008, 0.216, 0.516, 0.608, 0.528, + 0.368, 0.232, 0.084, -0.04, -0.16, -0.228, -0.256, -0.188, -0.12, + -0.096, -0.044, 0.008, 0.08, 0.156, 0.172, 0.068, -0.036, -0.104, + -0.144, -0.132, -0.176, -0.212, -0.216, -0.188, -0.196, -0.308, + -0.408, -0.472, -0.476, -0.56, -0.672, -0.828, -1.048, -1.16, + -1.072, -0.968, -0.844, -0.736, + ], + z: [ + 0.372, 0.404, 0.452, 0.468, 0.52, 0.564, 0.588, 0.636, 0.596, 0.632, + 0.664, 0.74, 0.836, 0.876, 0.924, 1.024, 1.272, 1.668, 1.884, 1.884, + 1.924, 1.92, 1.912, 1.948, 2.02, 1.896, 1.76, 1.436, 1.172, 1.016, + 0.808, 0.684, 0.58, 0.524, 0.408, 0.28, 0.192, 0.124, 0.044, 0.016, + 0.02, 0.172, 0.184, 0.016, -0.228, -0.052, 0.172, -0.212, -0.704, + -1.116, -1.392, -1.52, -1.616, -1.604, -1.312, -0.98, -0.596, + -0.352, -0.208, -0.096, 0.004, 0.1, 0.1, 0.124, 0.196, 0.256, 0.264, + 0.276, 0.368, 0.436, 0.548, 0.612, 0.624, 0.648, 0.668, 0.648, + 0.712, 0.76, 0.86, 1.012, 1.116, 1.264, 1.424, 1.548, 1.764, 2.036, + 2.04, 2.04, 2.04, 1.928, 1.78, + ], + }, + }, + { + ID: 1717765689141, + data: { + x: [ + -0.444, -0.4, -0.368, -0.368, -0.304, -0.26, -0.28, -0.312, -0.368, + -0.336, -0.316, -0.328, -0.408, -0.436, -0.42, -0.412, -0.456, + -0.456, -0.44, -0.412, -0.304, -0.26, -0.244, -0.192, -0.192, + -0.184, -0.164, -0.12, -0.144, -0.152, -0.14, -0.128, -0.108, + -0.184, -0.248, -0.284, -0.364, -0.468, -0.516, -0.5, -0.516, -0.52, + -0.532, -0.568, -0.596, -0.608, -0.672, -0.764, -0.896, -0.988, + -1.096, -1.268, -1.352, -1.372, -1.304, -1.168, -0.992, -0.832, + -0.736, -0.652, -0.604, -0.62, -0.592, -0.576, -0.536, -0.516, + -0.496, -0.464, -0.432, -0.444, -0.432, -0.42, -0.416, -0.384, + -0.348, -0.308, -0.328, -0.384, -0.364, -0.34, -0.38, -0.464, + -0.532, -0.592, -0.616, -0.556, -0.5, -0.416, -0.396, -0.344, + ], + y: [ + 0, -0.04, -0.02, 0, -0.024, -0.08, -0.164, -0.236, -0.248, -0.284, + -0.34, -0.388, -0.412, -0.44, -0.484, -0.588, -0.668, -0.764, + -0.892, -1.044, -1.144, -1.1, -1, -0.916, -0.828, -0.76, -0.696, + -0.648, -0.572, -0.504, -0.476, -0.444, -0.488, -0.484, -0.436, + -0.272, -0.132, -0.068, 0.024, 0.084, 0.1, 0.016, 0.056, 0.268, + 0.268, 0.112, -0.004, 0.112, 0.18, 0.208, 0.256, 0.396, 0.516, 0.6, + 0.64, 0.504, 0.344, 0.184, 0.064, -0.056, -0.024, 0.06, 0.084, + 0.092, 0.048, -0.004, -0.016, -0.032, -0.052, -0.072, -0.128, + -0.176, -0.18, -0.196, -0.224, -0.292, -0.34, -0.344, -0.396, + -0.528, -0.644, -0.6, -0.476, -0.456, -0.54, -0.724, -0.864, -1, + -1.1, -1.136, + ], + z: [ + 0.256, 0.38, 0.532, 0.632, 0.744, 0.816, 0.856, 0.864, 0.868, 0.868, + 0.82, 0.784, 0.86, 0.98, 1.108, 1.188, 1.348, 1.556, 1.82, 2.032, + 2.04, 2.04, 2.04, 2.04, 1.956, 1.688, 1.42, 1.208, 1.044, 0.94, + 0.848, 0.752, 0.588, 0.468, 0.288, 0.132, 0.028, -0.072, -0.004, + 0.16, 0.24, 0.148, -0.032, 0.056, 0.22, 0.072, -0.14, -0.228, -0.34, + -0.74, -1.116, -1.476, -1.684, -1.716, -1.608, -1.408, -1.204, + -0.956, -0.688, -0.484, -0.304, -0.128, 0.004, 0.124, 0.224, 0.32, + 0.468, 0.564, 0.668, 0.732, 0.728, 0.688, 0.704, 0.728, 0.712, + 0.712, 0.74, 0.8, 0.916, 0.988, 0.952, 0.968, 1.036, 1.084, 1.248, + 1.392, 1.628, 1.948, 2.04, 2.04, + ], + }, + }, + ], + // output: {}, + // confidence: { + // currentConfidence: 0.0066597783006727695, + // requiredConfidence: 0.8, + // isConfident: false, + // }, + }, +]; From d93e2b29c825fb96169341f6774619b105613eeb Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 5 Jul 2024 16:17:15 +0100 Subject: [PATCH 013/172] Add data page grid view UI --- src/components/AddDataGridGestureRow.tsx | 69 ++++++++++++ src/components/AddDataGridView.tsx | 64 ++++++++++++ src/components/GestureGridItem.tsx | 48 +++++++++ src/components/InfoToolTip.tsx | 2 +- src/components/RecordingGraph.tsx | 46 ++++++++ src/gestures.ts | 16 +++ src/images/record-icon.svg | 34 ++++++ src/pages/AddDataPage.tsx | 8 +- src/recording-graph.test.ts | 34 ++++++ src/recording-graph.ts | 127 +++++++++++++++++++++++ 10 files changed, 443 insertions(+), 5 deletions(-) create mode 100644 src/components/AddDataGridGestureRow.tsx create mode 100644 src/components/AddDataGridView.tsx create mode 100644 src/components/GestureGridItem.tsx create mode 100644 src/components/RecordingGraph.tsx create mode 100644 src/gestures.ts create mode 100644 src/images/record-icon.svg create mode 100644 src/recording-graph.test.ts create mode 100644 src/recording-graph.ts diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx new file mode 100644 index 000000000..5531c4623 --- /dev/null +++ b/src/components/AddDataGridGestureRow.tsx @@ -0,0 +1,69 @@ +import { + Card, + CardBody, + CloseButton, + GridItem, + HStack, + Icon, + IconButton, +} from "@chakra-ui/react"; +import { useIntl } from "react-intl"; +import { GestureData } from "../gestures"; +import RecordIcon from "../images/record-icon.svg?react"; +import GestureGridItem from "./GestureGridItem"; +import RecordingGraph from "./RecordingGraph"; + +const AddDataGridGestureRow = ({ gesture }: { gesture: GestureData }) => { + const intl = useIntl(); + return ( + <> + {}} /> + + + + + + } + /> + + {gesture.recordings.map((recording, idx) => ( + + + + + ))} + + + + + ); +}; + +export default AddDataGridGestureRow; diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx new file mode 100644 index 000000000..b31e5a14e --- /dev/null +++ b/src/components/AddDataGridView.tsx @@ -0,0 +1,64 @@ +import { + Grid, + GridItem, + GridProps, + HStack, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import AddDataGridGestureRow from "./AddDataGridGestureRow"; +import InfoToolTip, { InfoToolTipProps } from "./InfoToolTip"; +import { dummyGestureData } from "../dummy-gesture-data"; + +const gridCommonProps: Partial = { + gridTemplateColumns: "200px 1fr", + gap: 3, + alignItems: "center", + px: 10, +}; + +const AddDataGridView = () => { + return ( + + + + + + + {dummyGestureData.map((g) => ( + + ))} + + + ); +}; + +const GridColumnHeadingItem = (props: InfoToolTipProps) => { + return ( + + + + + + + + + ); +}; + +export default AddDataGridView; diff --git a/src/components/GestureGridItem.tsx b/src/components/GestureGridItem.tsx new file mode 100644 index 000000000..51e085658 --- /dev/null +++ b/src/components/GestureGridItem.tsx @@ -0,0 +1,48 @@ +import { + Card, + CardBody, + CardHeader, + CloseButton, + GridItem, + Input, +} from "@chakra-ui/react"; +import { useIntl } from "react-intl"; + +interface GestureGridItemProps { + name: string; + onCloseClick: () => void; +} + +const GestureGridItem = ({ name, onCloseClick }: GestureGridItemProps) => { + const intl = useIntl(); + return ( + + + + + + + + + + + ); +}; + +export default GestureGridItem; diff --git a/src/components/InfoToolTip.tsx b/src/components/InfoToolTip.tsx index 10149488d..c184ed468 100644 --- a/src/components/InfoToolTip.tsx +++ b/src/components/InfoToolTip.tsx @@ -3,7 +3,7 @@ import { RiInformationLine } from "react-icons/ri"; import { FormattedMessage } from "react-intl"; import ClickableTooltip from "./ClickableTooltip"; -interface InfoToolTipProps { +export interface InfoToolTipProps { titleId: string; descriptionId: string; } diff --git a/src/components/RecordingGraph.tsx b/src/components/RecordingGraph.tsx new file mode 100644 index 000000000..b64e98374 --- /dev/null +++ b/src/components/RecordingGraph.tsx @@ -0,0 +1,46 @@ +import { Box } from "@chakra-ui/react"; +import { + Chart, + LineController, + LineElement, + LinearScale, + PointElement, + registerables, +} from "chart.js"; +import { useEffect, useRef } from "react"; +import { XYZData } from "../gestures"; +import { getConfig as getRecordingChartConfig } from "../recording-graph"; + +interface RecordingGraphProps { + data: XYZData; +} + +const RecordingGraph = ({ data }: RecordingGraphProps) => { + const canvasRef = useRef(null); + + useEffect(() => { + Chart.unregister(...registerables); + Chart.register([LinearScale, LineController, PointElement, LineElement]); + const chart = new Chart( + canvasRef.current?.getContext("2d") ?? new HTMLCanvasElement(), + getRecordingChartConfig(data) + ); + return () => { + chart.destroy(); + }; + }); + + return ( + + + + ); +}; + +export default RecordingGraph; diff --git a/src/gestures.ts b/src/gestures.ts new file mode 100644 index 000000000..8db93101d --- /dev/null +++ b/src/gestures.ts @@ -0,0 +1,16 @@ +export interface XYZData { + x: number[]; + y: number[]; + z: number[]; +} + +interface RecordingData { + ID: number; + data: XYZData; +} + +export interface GestureData { + name: string; + ID: number; + recordings: RecordingData[]; +} diff --git a/src/images/record-icon.svg b/src/images/record-icon.svg new file mode 100644 index 000000000..4ac17ec73 --- /dev/null +++ b/src/images/record-icon.svg @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index 4e090cd0a..ed93fc4b4 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -1,6 +1,5 @@ import { Button, - Grid, HStack, Icon, IconButton, @@ -18,6 +17,7 @@ import { RiUpload2Line, } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; +import AddDataGridView from "../components/AddDataGridView"; import ConnectFirstView from "../components/ConnectFirstView"; import DefaultPageLayout from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; @@ -26,8 +26,8 @@ import { addDataConfig } from "../steps-config"; const AddDataPage = () => { const intl = useIntl(); - const noStoredData = true; - const isInputConnected = false; + const noStoredData = false; + const isInputConnected = true; return ( @@ -35,7 +35,7 @@ const AddDataPage = () => { {noStoredData && !isInputConnected ? ( ) : ( - TODO: Grid layout! + )}
        { + test("smoothen empty data", () => { + const xyz = { + x: [], + y: [], + z: [], + }; + expect(smoothenXYZData(xyz)).toEqual(xyz); + }); + test("smoothen xyz data", () => { + const xyz = { + x: [1, 1, 1, 1, 1], + y: [4, 4, 12, 10, 10], + z: [8, 8, 24, 20, 20], + }; + const smoothXYZData = { + x: [1, 1, 1, 1, 1], + y: [4, 4, 6, 7, 7.75], + z: [8, 8, 12, 14, 15.5], + }; + expect(smoothenXYZData(xyz)).toEqual(smoothXYZData); + }); +}); diff --git a/src/recording-graph.ts b/src/recording-graph.ts new file mode 100644 index 000000000..60242e37f --- /dev/null +++ b/src/recording-graph.ts @@ -0,0 +1,127 @@ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { ChartConfiguration, ChartTypeRegistry } from "chart.js"; +import { XYZData } from "./gestures"; + +const smoothen = (d: number[]): number[] => { + if (d.length === 0) { + return d; + } + const newData: number[] = []; + let prevValue = d[0]; + d.forEach((v) => { + const newValue = v * 0.25 + prevValue * 0.75; + newData.push(newValue); + prevValue = newValue; + }); + return newData; +}; + +// Smoothen data +export function smoothenXYZData(d: XYZData): XYZData { + return { + x: smoothen(d.x), + y: smoothen(d.y), + z: smoothen(d.z), + }; +} + +interface Pos { + x: number; + y: number; +} + +const processDimensionData = (data: number[]) => { + const formatToChartPos = (y: number, idx: number) => ({ x: idx, y }); + return smoothen(data).map(formatToChartPos); +}; + +export const getConfig = ({ + x: rawX, + y: rawY, + z: rawZ, +}: XYZData): ChartConfiguration => { + const x = processDimensionData(rawX); + const y = processDimensionData(rawY); + const z = processDimensionData(rawZ); + return { + type: "line", + data: { + datasets: [ + { + label: "x", + borderColor: "red", + borderWidth: 1, + pointRadius: 0, + pointHoverRadius: 0, + data: x, + }, + { + label: "y", + borderColor: "green", + borderWidth: 1, + pointRadius: 0, + pointHoverRadius: 0, + data: y, + }, + { + label: "z", + borderColor: "blue", + borderWidth: 1, + pointRadius: 0, + pointHoverRadius: 0, + data: z, + }, + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + + interaction: { + intersect: false, + mode: "index", + }, + scales: { + x: { + type: "linear", + min: 0, + max: rawX.length, + grid: { + drawTicks: false, + display: false, + }, + ticks: { + display: false, //this will remove only the label + }, + display: false, + }, + y: { + type: "linear", + min: -2.5, + max: 2.5, + grid: { + drawTicks: false, + display: false, + }, + ticks: { + display: false, //this will remove only the label + }, + display: false, + }, + }, + plugins: { + legend: { + display: false, + }, + title: { + display: false, + }, + }, + }, + }; +}; From 5403a59894160b710b5b1fb3a28b94020e3c46da Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 5 Jul 2024 16:18:07 +0100 Subject: [PATCH 014/172] Format with prettier --- src/components/StartResumeActions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index da9b3055a..80523ed27 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -13,7 +13,7 @@ const StartResumeActions = () => { const { dispatch } = useConnectionFlow(); // TODO: check input connected - const isInputConnected = true + const isInputConnected = true; const handleNewSession = useCallback(() => { if (isInputConnected) { navigate(createStepPageUrl("add-data")); From 08da776644e89ac96321f9b8f2649227815e7229 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 8 Jul 2024 16:52:43 +0100 Subject: [PATCH 015/172] Recording dialog UI --- src/components/AddDataGridGestureRow.tsx | 9 + src/components/RecordingDialog.tsx | 180 ++++++++++++++++++++ src/deployment/default/colors.ts | 15 ++ src/deployment/default/components/button.ts | 13 ++ 4 files changed, 217 insertions(+) create mode 100644 src/components/RecordingDialog.tsx diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx index 5531c4623..14d726899 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridGestureRow.tsx @@ -6,17 +6,25 @@ import { HStack, Icon, IconButton, + useDisclosure, } from "@chakra-ui/react"; import { useIntl } from "react-intl"; import { GestureData } from "../gestures"; import RecordIcon from "../images/record-icon.svg?react"; import GestureGridItem from "./GestureGridItem"; import RecordingGraph from "./RecordingGraph"; +import RecordingDialog from "./RecordingDialog"; const AddDataGridGestureRow = ({ gesture }: { gesture: GestureData }) => { const intl = useIntl(); + const { isOpen, onClose, onOpen } = useDisclosure(); return ( <> + {}} /> @@ -29,6 +37,7 @@ const AddDataGridGestureRow = ({ gesture }: { gesture: GestureData }) => { > void; + actionName: string; +} + +enum RecordingStatus { + None, + Recording, + Countdown, +} + +const RecordingDialog = ({ + isOpen, + actionName, + onClose, +}: RecordingDialogProps) => { + const intl = useIntl(); + const [recordingStatus, setRecordingStatus] = useState( + RecordingStatus.Countdown + ); + const [countdownIdx, setIsCountdownIdx] = useState(0); + + const countdownConfigs: CountdownConfig[] = useMemo( + () => [ + { value: 3, duration: 500, fontSize: "8xl" }, + { value: 2, duration: 500, fontSize: "8xl" }, + { value: 1, duration: 500, fontSize: "8xl" }, + { + value: intl.formatMessage({ id: "content.data.recordingDialog.go" }), + duration: 1000, + fontSize: "6xl", + }, + ], + [intl] + ); + + const handleOnClose = useCallback(() => { + setRecordingStatus(RecordingStatus.None); + setIsCountdownIdx(0); + onClose(); + }, [onClose]); + + useEffect(() => { + if (isOpen) { + // When dialog is opened, restart countdown + setRecordingStatus(RecordingStatus.Countdown); + setIsCountdownIdx(0); + } + }, [isOpen]); + + useEffect(() => { + if (recordingStatus === RecordingStatus.Countdown) { + const config = countdownConfigs[countdownIdx]; + + setTimeout(() => { + if (countdownIdx < countdownConfigs.length - 1) { + setIsCountdownIdx(countdownIdx + 1); + return; + } else { + setRecordingStatus(RecordingStatus.Recording); + } + }, config.duration); + } + }, [countdownConfigs, isOpen, recordingStatus, countdownIdx]); + + useEffect(() => { + if (recordingStatus === RecordingStatus.Recording) { + // TODO: Record samples + setTimeout(() => { + if (recordingStatus === RecordingStatus.Recording) { + handleOnClose(); + } + }, recordingDuration); + } + }, [handleOnClose, recordingStatus]); + + return ( + + + + + + + + + + + {recordingStatus === RecordingStatus.Recording ? ( + + + + ) : ( + + {countdownConfigs[countdownIdx].value} + + )} + + + + + + + + + + + ); +}; + +export default RecordingDialog; diff --git a/src/deployment/default/colors.ts b/src/deployment/default/colors.ts index cb8c7572f..6f5c15410 100644 --- a/src/deployment/default/colors.ts +++ b/src/deployment/default/colors.ts @@ -27,6 +27,20 @@ const brand = { 900: "#023a5a", }; +// Taken from the red in https://windicss.org/utilities/general/colors.html +const red = { + 50: "#fef2f2", + 100: "#fee2e2", + 200: "#fecaca", + 300: "#fca5a5", + 400: "#f87171", + 500: "#ef4444", + 600: "#dc2626", + 700: "#b91c1c", + 800: "#991b1b", + 900: "#7f1d1d", +}; + const colors = { // Brand guidelines say: // "Each of the main primary colours can be tinted by 80%, 50%, 30%, 20% and 10% if needed." @@ -35,6 +49,7 @@ const colors = { // https://maketintsandshades.com/#6C4BC1 gray, brand, + red, purple: { 50: "#e2dbf3", // 80% tint 100: "#b6a5e0", // 50% tint diff --git a/src/deployment/default/components/button.ts b/src/deployment/default/components/button.ts index c79105543..ccdc688a0 100644 --- a/src/deployment/default/components/button.ts +++ b/src/deployment/default/components/button.ts @@ -68,6 +68,19 @@ const Button: StyleConfig = { bg: "brand.700", }, }), + warning: () => ({ + borderWidth: "2px", + borderColor: "red.500", + color: "red.500", + bg: "transparent", + _hover: { + borderColor: "red.600", + }, + _active: { + bg: "red.50", + borderColor: "red.500", + }, + }), toolbar: () => ({ color: "black", bg: "white", From 1a6c1497eb7dc790070a932dea85479a8348e45f Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 8 Jul 2024 16:56:24 +0100 Subject: [PATCH 016/172] Stub micro:bit connection for disabling record button --- src/components/AddDataGridGestureRow.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx index 14d726899..4bf824f7d 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridGestureRow.tsx @@ -18,6 +18,8 @@ import RecordingDialog from "./RecordingDialog"; const AddDataGridGestureRow = ({ gesture }: { gesture: GestureData }) => { const intl = useIntl(); const { isOpen, onClose, onOpen } = useDisclosure(); + // TODO: Replace with checking if micro:bit is connected + const isConnected = false; return ( <> { { id: "content.data.recordAction" }, { action: gesture.name } )} + isDisabled={!isConnected} icon={ Date: Tue, 9 Jul 2024 09:18:43 +0100 Subject: [PATCH 017/172] Gestures data provider and context --- src/App.tsx | 13 ++++-- src/components/AddDataGridView.tsx | 5 +- src/gestures.ts | 16 ------- src/gestures.tsx | 73 ++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 23 deletions(-) delete mode 100644 src/gestures.ts create mode 100644 src/gestures.tsx diff --git a/src/App.tsx b/src/App.tsx index c248a5260..992ac079c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ import { deployment, useDeployment } from "./deployment"; import { stepsConfig } from "./steps-config"; import { LoggingProvider } from "./logging/logging-hooks"; import { ConnectionFlowProvider } from "./connections"; +import GesturesProvider from "./gestures"; export interface ProviderLayoutProps { children: ReactNode; @@ -35,11 +36,13 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - - {children} - - + + + + {children} + + + diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index b31e5a14e..dcf65500e 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -9,7 +9,7 @@ import { import { FormattedMessage } from "react-intl"; import AddDataGridGestureRow from "./AddDataGridGestureRow"; import InfoToolTip, { InfoToolTipProps } from "./InfoToolTip"; -import { dummyGestureData } from "../dummy-gesture-data"; +import { useGestureData } from "../gestures"; const gridCommonProps: Partial = { gridTemplateColumns: "200px 1fr", @@ -19,6 +19,7 @@ const gridCommonProps: Partial = { }; const AddDataGridView = () => { + const [gestureData] = useGestureData(); return ( { /> - {dummyGestureData.map((g) => ( + {gestureData.map((g) => ( ))} diff --git a/src/gestures.ts b/src/gestures.ts deleted file mode 100644 index 8db93101d..000000000 --- a/src/gestures.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface XYZData { - x: number[]; - y: number[]; - z: number[]; -} - -interface RecordingData { - ID: number; - data: XYZData; -} - -export interface GestureData { - name: string; - ID: number; - recordings: RecordingData[]; -} diff --git a/src/gestures.tsx b/src/gestures.tsx new file mode 100644 index 000000000..49ff5f300 --- /dev/null +++ b/src/gestures.tsx @@ -0,0 +1,73 @@ +import { ReactNode, createContext, useContext } from "react"; +import { dummyGestureData } from "./dummy-gesture-data"; +import { useStorage } from "./hooks/use-storage"; + +export interface XYZData { + x: number[]; + y: number[]; + z: number[]; +} + +interface RecordingData { + ID: number; + data: XYZData; +} + +export interface GestureData { + name: string; + ID: number; + recordings: RecordingData[]; +} + +type GestureContextValue = [ + GestureData[], + (gestureData: GestureData[]) => void +]; + +const isValidGestureData = (value: unknown): value is GestureData[] => { + if (typeof value !== "object" && !Array.isArray(value)) { + return false; + } + const array = value as unknown[]; + for (const item of array) { + if (typeof item !== "object" || item === null) { + return false; + } + if (!("name" in item) || !("ID" in item) || !("recordings" in item)) { + return false; + } + if (typeof item.recordings !== "object") { + return false; + } + // TODO: Validate recordings + } + return true; +}; + +const GestureContext = createContext( + undefined +); + +export const useGestureData = (): GestureContextValue => { + const gestureData = useContext(GestureContext); + if (!gestureData) { + throw new Error("Missing provider"); + } + return gestureData; +}; + +const GesturesProvider = ({ children }: { children: ReactNode }) => { + const gestures = useStorage( + "local", + "gestures", + dummyGestureData, + isValidGestureData + ); + return ( + + {children} + + ); +}; + +export default GesturesProvider; From f62bef638638ed001123715b0d17a35b69856505 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 9 Jul 2024 10:39:08 +0100 Subject: [PATCH 018/172] Handle import data samples --- src/components/UploadDataSamplesMenuItem.tsx | 78 ++++++++++++++++++++ src/gestures.tsx | 3 +- src/pages/AddDataPage.tsx | 13 +--- 3 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 src/components/UploadDataSamplesMenuItem.tsx diff --git a/src/components/UploadDataSamplesMenuItem.tsx b/src/components/UploadDataSamplesMenuItem.tsx new file mode 100644 index 000000000..b46ac4e95 --- /dev/null +++ b/src/components/UploadDataSamplesMenuItem.tsx @@ -0,0 +1,78 @@ +import { Input, MenuItem } from "@chakra-ui/react"; +import { useCallback, useRef } from "react"; +import { RiUpload2Line } from "react-icons/ri"; +import { FormattedMessage } from "react-intl"; +import { GestureData, useGestureData } from "../gestures"; + +/** + * Reads file as text via a FileReader. + * + * @param file A file (e.g. from a file input or drop operation). + * @returns The a promise of text from that file. + */ +const readFileAsText = async (file: File): Promise => { + const reader = new FileReader(); + return new Promise((resolve, reject) => { + reader.onload = (e: ProgressEvent) => { + resolve(e.target!.result as string); + }; + reader.onerror = (e: ProgressEvent) => { + const error = e.target?.error || new Error("Error reading file as text"); + reject(error); + }; + reader.readAsText(file); + }); +}; + +const UploadDataSamplesMenuItem = () => { + const [, updateGestures] = useGestureData(); + const inputRef = useRef(null); + + const handleChooseFile = useCallback(() => { + inputRef.current && inputRef.current.click(); + }, []); + + const onOpen = useCallback( + async (files: File[]) => { + if (files.length === 0) { + throw new Error("Expected to be called with at least one file"); + } + const gestureData = await readFileAsText(files[0]); + updateGestures(JSON.parse(gestureData) as GestureData[]); + }, + [updateGestures] + ); + + const handleOpenFile = useCallback( + async (e: React.ChangeEvent) => { + const files = e.target.files; + if (files) { + const filesArray = Array.from(files); + // Clear the input so we're triggered if the user opens the same file again. + inputRef.current!.value = ""; + if (filesArray.length > 0) { + await onOpen(filesArray); + } + } + }, + [onOpen] + ); + + return ( + <> + } onClick={handleChooseFile}> + + + + + ); +}; + +export default UploadDataSamplesMenuItem; diff --git a/src/gestures.tsx b/src/gestures.tsx index 49ff5f300..ce50f84e9 100644 --- a/src/gestures.tsx +++ b/src/gestures.tsx @@ -1,5 +1,4 @@ import { ReactNode, createContext, useContext } from "react"; -import { dummyGestureData } from "./dummy-gesture-data"; import { useStorage } from "./hooks/use-storage"; export interface XYZData { @@ -60,7 +59,7 @@ const GesturesProvider = ({ children }: { children: ReactNode }) => { const gestures = useStorage( "local", "gestures", - dummyGestureData, + [], isValidGestureData ); return ( diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index ed93fc4b4..456e25582 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -10,24 +10,21 @@ import { VStack, } from "@chakra-ui/react"; import { MdMoreVert } from "react-icons/md"; -import { - RiAddLine, - RiDeleteBin2Line, - RiDownload2Line, - RiUpload2Line, -} from "react-icons/ri"; +import { RiAddLine, RiDeleteBin2Line, RiDownload2Line } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import AddDataGridView from "../components/AddDataGridView"; import ConnectFirstView from "../components/ConnectFirstView"; import DefaultPageLayout from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; import TabView from "../components/TabView"; +import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; import { addDataConfig } from "../steps-config"; const AddDataPage = () => { const intl = useIntl(); const noStoredData = false; const isInputConnected = true; + return ( @@ -65,9 +62,7 @@ const AddDataPage = () => { isRound /> - }> - - + }> From 64ba26e0644982361d432b23b68c9d7700ea4713 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 9 Jul 2024 11:30:44 +0100 Subject: [PATCH 019/172] Handle delete gesture action, refactor to `useGestureAction` hook Gesture context state has been updated to store an object instead of an array. --- src/App.tsx | 2 +- src/components/AddDataGridGestureRow.tsx | 23 +++- src/components/AddDataGridView.tsx | 6 +- src/components/RecordingGraph.tsx | 2 +- src/components/UploadDataSamplesMenuItem.tsx | 8 +- src/gestures-hooks.tsx | 108 +++++++++++++++++++ src/gestures.tsx | 72 ------------- src/recording-graph.ts | 2 +- 8 files changed, 137 insertions(+), 86 deletions(-) create mode 100644 src/gestures-hooks.tsx delete mode 100644 src/gestures.tsx diff --git a/src/App.tsx b/src/App.tsx index 992ac079c..ce1c5ec25 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,7 +18,7 @@ import { deployment, useDeployment } from "./deployment"; import { stepsConfig } from "./steps-config"; import { LoggingProvider } from "./logging/logging-hooks"; import { ConnectionFlowProvider } from "./connections"; -import GesturesProvider from "./gestures"; +import { GesturesProvider } from "./gestures-hooks"; export interface ProviderLayoutProps { children: ReactNode; diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx index 4bf824f7d..9363ca7dd 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridGestureRow.tsx @@ -9,17 +9,32 @@ import { useDisclosure, } from "@chakra-ui/react"; import { useIntl } from "react-intl"; -import { GestureData } from "../gestures"; +import { GestureData, useGestureActions } from "../gestures-hooks"; import RecordIcon from "../images/record-icon.svg?react"; import GestureGridItem from "./GestureGridItem"; import RecordingGraph from "./RecordingGraph"; import RecordingDialog from "./RecordingDialog"; +import { useCallback } from "react"; const AddDataGridGestureRow = ({ gesture }: { gesture: GestureData }) => { const intl = useIntl(); - const { isOpen, onClose, onOpen } = useDisclosure(); + const { isOpen, onClose, onOpen: onStartRecording } = useDisclosure(); + const actions = useGestureActions(); // TODO: Replace with checking if micro:bit is connected const isConnected = false; + + const handleDeleteGesture = useCallback(() => { + const confirmationText = intl.formatMessage( + { id: "alert.deleteGestureConfirm" }, + { action: gesture.name } + ); + // Browser confirmation dialog + if (!window.confirm(confirmationText)) { + return; + } + actions.deleteGesture(gesture.ID); + }, [actions, gesture.ID, gesture.name, intl]); + return ( <> { onClose={onClose} actionName={gesture.name} /> - {}} /> + { > = { gridTemplateColumns: "200px 1fr", @@ -19,7 +19,7 @@ const gridCommonProps: Partial = { }; const AddDataGridView = () => { - const [gestureData] = useGestureData(); + const [gestures] = useGestureData(); return ( { /> - {gestureData.map((g) => ( + {gestures.data.map((g) => ( ))} diff --git a/src/components/RecordingGraph.tsx b/src/components/RecordingGraph.tsx index b64e98374..b0cae5427 100644 --- a/src/components/RecordingGraph.tsx +++ b/src/components/RecordingGraph.tsx @@ -8,7 +8,7 @@ import { registerables, } from "chart.js"; import { useEffect, useRef } from "react"; -import { XYZData } from "../gestures"; +import { XYZData } from "../gestures-hooks"; import { getConfig as getRecordingChartConfig } from "../recording-graph"; interface RecordingGraphProps { diff --git a/src/components/UploadDataSamplesMenuItem.tsx b/src/components/UploadDataSamplesMenuItem.tsx index b46ac4e95..e708e4a60 100644 --- a/src/components/UploadDataSamplesMenuItem.tsx +++ b/src/components/UploadDataSamplesMenuItem.tsx @@ -2,7 +2,7 @@ import { Input, MenuItem } from "@chakra-ui/react"; import { useCallback, useRef } from "react"; import { RiUpload2Line } from "react-icons/ri"; import { FormattedMessage } from "react-intl"; -import { GestureData, useGestureData } from "../gestures"; +import { GestureData, useGestureActions } from "../gestures-hooks"; /** * Reads file as text via a FileReader. @@ -25,7 +25,7 @@ const readFileAsText = async (file: File): Promise => { }; const UploadDataSamplesMenuItem = () => { - const [, updateGestures] = useGestureData(); + const actions = useGestureActions(); const inputRef = useRef(null); const handleChooseFile = useCallback(() => { @@ -38,9 +38,9 @@ const UploadDataSamplesMenuItem = () => { throw new Error("Expected to be called with at least one file"); } const gestureData = await readFileAsText(files[0]); - updateGestures(JSON.parse(gestureData) as GestureData[]); + actions.setGestures(JSON.parse(gestureData) as GestureData[]); }, - [updateGestures] + [actions] ); const handleOpenFile = useCallback( diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx new file mode 100644 index 000000000..2edcdfae5 --- /dev/null +++ b/src/gestures-hooks.tsx @@ -0,0 +1,108 @@ +import { ReactNode, createContext, useContext, useMemo } from "react"; +import { useStorage } from "./hooks/use-storage"; + +export interface XYZData { + x: number[]; + y: number[]; + z: number[]; +} + +interface RecordingData { + ID: number; + data: XYZData; +} + +export interface GestureData { + name: string; + ID: number; + recordings: RecordingData[]; +} + +interface GestureContextState { + data: GestureData[]; +} + +type GestureContextValue = [ + GestureContextState, + (gestureData: GestureContextState) => void +]; + +const isValidGestureData = (v: unknown): v is GestureContextState => { + if (typeof v !== "object") { + return false; + } + const valueObject = v as object; + if (!("data" in valueObject)) { + return false; + } + const data = valueObject.data; + if (typeof data !== "object" && !Array.isArray(data)) { + return false; + } + const array = data as unknown[]; + for (const item of array) { + if (typeof item !== "object" || item === null) { + return false; + } + if (!("name" in item) || !("ID" in item) || !("recordings" in item)) { + return false; + } + if (typeof item.recordings !== "object") { + return false; + } + // TODO: Validate recordings + } + return true; +}; + +const GestureContext = createContext( + undefined +); + +export const useGestureData = (): GestureContextValue => { + const gestureData = useContext(GestureContext); + if (!gestureData) { + throw new Error("Missing provider"); + } + return gestureData; +}; + +const initialGestureContextState: GestureContextState = { data: [] }; + +export const GesturesProvider = ({ children }: { children: ReactNode }) => { + const gestures = useStorage( + "local", + "gestures", + initialGestureContextState, + isValidGestureData + ); + return ( + + {children} + + ); +}; + +export const useGestureActions = () => { + const [gestures, setGestures] = useGestureData(); + const actions = useMemo( + () => new GestureActions(gestures, setGestures), + [gestures, setGestures] + ); + return actions; +}; + +export class GestureActions { + constructor( + private state: GestureContextState, + private setState: (gestureData: GestureContextState) => void + ) {} + + setGestures = (gesture: GestureData[]) => { + this.setState({ ...this.state, data: gesture }); + }; + + deleteGesture = (id: GestureData["ID"]) => { + this.setGestures(this.state.data.filter((g) => g.ID !== id)); + }; +} diff --git a/src/gestures.tsx b/src/gestures.tsx deleted file mode 100644 index ce50f84e9..000000000 --- a/src/gestures.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { ReactNode, createContext, useContext } from "react"; -import { useStorage } from "./hooks/use-storage"; - -export interface XYZData { - x: number[]; - y: number[]; - z: number[]; -} - -interface RecordingData { - ID: number; - data: XYZData; -} - -export interface GestureData { - name: string; - ID: number; - recordings: RecordingData[]; -} - -type GestureContextValue = [ - GestureData[], - (gestureData: GestureData[]) => void -]; - -const isValidGestureData = (value: unknown): value is GestureData[] => { - if (typeof value !== "object" && !Array.isArray(value)) { - return false; - } - const array = value as unknown[]; - for (const item of array) { - if (typeof item !== "object" || item === null) { - return false; - } - if (!("name" in item) || !("ID" in item) || !("recordings" in item)) { - return false; - } - if (typeof item.recordings !== "object") { - return false; - } - // TODO: Validate recordings - } - return true; -}; - -const GestureContext = createContext( - undefined -); - -export const useGestureData = (): GestureContextValue => { - const gestureData = useContext(GestureContext); - if (!gestureData) { - throw new Error("Missing provider"); - } - return gestureData; -}; - -const GesturesProvider = ({ children }: { children: ReactNode }) => { - const gestures = useStorage( - "local", - "gestures", - [], - isValidGestureData - ); - return ( - - {children} - - ); -}; - -export default GesturesProvider; diff --git a/src/recording-graph.ts b/src/recording-graph.ts index 60242e37f..50d8eedf2 100644 --- a/src/recording-graph.ts +++ b/src/recording-graph.ts @@ -5,7 +5,7 @@ */ import { ChartConfiguration, ChartTypeRegistry } from "chart.js"; -import { XYZData } from "./gestures"; +import { XYZData } from "./gestures-hooks"; const smoothen = (d: number[]): number[] => { if (d.length === 0) { From 9f5e5d82b7439e22d0b2c45de40408dc95a1e328 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 9 Jul 2024 12:14:03 +0100 Subject: [PATCH 020/172] Handle delete gesture recording --- src/components/AddDataGridGestureRow.tsx | 26 ++++++++++++++++-------- src/gestures-hooks.tsx | 17 ++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx index 9363ca7dd..fe0c8595a 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridGestureRow.tsx @@ -35,6 +35,13 @@ const AddDataGridGestureRow = ({ gesture }: { gesture: GestureData }) => { actions.deleteGesture(gesture.ID); }, [actions, gesture.ID, gesture.name, intl]); + const handleDeleteGestureRecording = useCallback( + (idx: number) => { + actions.deleteGestureRecording(gesture.ID, idx); + }, + [actions, gesture.ID] + ); + return ( <> { /> - - + + { aria-label={intl.formatMessage({ id: "content.data.deleteRecording", })} + onClick={() => { + handleDeleteGestureRecording(idx); + }} /> diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 2edcdfae5..102b1f6a5 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -105,4 +105,21 @@ export class GestureActions { deleteGesture = (id: GestureData["ID"]) => { this.setGestures(this.state.data.filter((g) => g.ID !== id)); }; + + deleteGestureRecording = ( + gestureId: GestureData["ID"], + recordingIdx: number + ) => { + const newGestures = this.state.data + .map((g) => { + if (gestureId !== g.ID) { + return g; + } + const recordings = g.recordings.filter((_r, i) => i !== recordingIdx); + return { ...g, recordings }; + }) + // remove gestures without recordings + .filter((g) => g.recordings.length > 0); + this.setGestures(newGestures); + }; } From b81f4564334ecca6d0ca55d8faedba52a423c985 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 9 Jul 2024 13:36:24 +0100 Subject: [PATCH 021/172] Select and highlight row when clicking on cards --- src/components/AddDataGridGestureRow.tsx | 25 +++++++++++++++++++++--- src/components/AddDataGridView.tsx | 14 ++++++++++--- src/components/GestureGridItem.tsx | 13 +++++++++--- src/components/RecordingGraph.tsx | 2 +- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx index fe0c8595a..2dd4f5669 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridGestureRow.tsx @@ -16,7 +16,17 @@ import RecordingGraph from "./RecordingGraph"; import RecordingDialog from "./RecordingDialog"; import { useCallback } from "react"; -const AddDataGridGestureRow = ({ gesture }: { gesture: GestureData }) => { +interface AddDataGridGestureRowProps { + gesture: GestureData; + selected: boolean; + onSelectRow: () => void; +} + +const AddDataGridGestureRow = ({ + gesture, + selected, + onSelectRow, +}: AddDataGridGestureRowProps) => { const intl = useIntl(); const { isOpen, onClose, onOpen: onStartRecording } = useDisclosure(); const actions = useGestureActions(); @@ -49,16 +59,25 @@ const AddDataGridGestureRow = ({ gesture }: { gesture: GestureData }) => { onClose={onClose} actionName={gesture.name} /> - + - + = { gridTemplateColumns: "200px 1fr", @@ -20,6 +21,8 @@ const gridCommonProps: Partial = { const AddDataGridView = () => { const [gestures] = useGestureData(); + const [selected, setSelected] = useState(0); + return ( { /> - {gestures.data.map((g) => ( - + {gestures.data.map((g, idx) => ( + setSelected(idx)} + /> ))} diff --git a/src/components/GestureGridItem.tsx b/src/components/GestureGridItem.tsx index 51e085658..9d5ae3e72 100644 --- a/src/components/GestureGridItem.tsx +++ b/src/components/GestureGridItem.tsx @@ -2,22 +2,29 @@ import { Card, CardBody, CardHeader, + CardProps, CloseButton, GridItem, Input, } from "@chakra-ui/react"; import { useIntl } from "react-intl"; -interface GestureGridItemProps { +interface GestureGridItemProps extends CardProps { name: string; onCloseClick: () => void; + onSelectRow: () => void; } -const GestureGridItem = ({ name, onCloseClick }: GestureGridItemProps) => { +const GestureGridItem = ({ + name, + onCloseClick, + onSelectRow, + ...cardProps +}: GestureGridItemProps) => { const intl = useIntl(); return ( - + { return () => { chart.destroy(); }; - }); + }, [data]); return ( Date: Tue, 9 Jul 2024 14:53:55 +0100 Subject: [PATCH 022/172] Handle gesture name change and validate name length --- src/components/AddDataGridGestureRow.tsx | 9 +++++- src/components/GestureGridItem.tsx | 37 ++++++++++++++++++++++++ src/gestures-hooks.tsx | 7 +++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx index 2dd4f5669..ee27d7c0f 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridGestureRow.tsx @@ -38,7 +38,6 @@ const AddDataGridGestureRow = ({ { id: "alert.deleteGestureConfirm" }, { action: gesture.name } ); - // Browser confirmation dialog if (!window.confirm(confirmationText)) { return; } @@ -52,6 +51,13 @@ const AddDataGridGestureRow = ({ [actions, gesture.ID] ); + const handleGestureNameChange = useCallback( + (newName: string) => { + actions.setGestureName(gesture.ID, newName); + }, + [actions, gesture.ID] + ); + return ( <> void; onSelectRow: () => void; + onNameChange: (newName: string) => void; } +const gestureNameMaxLength = 18; + const GestureGridItem = ({ name, onCloseClick, onSelectRow, + onNameChange, ...cardProps }: GestureGridItemProps) => { const intl = useIntl(); + const toast = useToast(); + const toastId = "name-too-long-toast"; + + const handleNameChange: React.FocusEventHandler = + useCallback((e) => onNameChange(e.target.value), [onNameChange]); + + const onChange: React.ChangeEventHandler = useCallback( + (e) => { + // Validate gesture name + if ( + e.target.value.trim().length >= gestureNameMaxLength && + !toast.isActive(toastId) + ) { + toast({ + id: toastId, + position: "top", + duration: 5_000, + title: intl.formatMessage( + { id: "alert.data.classNameLengthAlert" }, + { maxLen: gestureNameMaxLength } + ), + variant: "subtle", + status: "error", + }); + } + }, + [intl, toast] + ); + return ( @@ -44,6 +79,8 @@ const GestureGridItem = ({ placeholder={intl.formatMessage({ id: "content.data.classPlaceholderNewClass", })} + onBlur={handleNameChange} + onChange={onChange} size="sm" /> diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 102b1f6a5..36aa0c7fe 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -106,6 +106,13 @@ export class GestureActions { this.setGestures(this.state.data.filter((g) => g.ID !== id)); }; + setGestureName = (id: GestureData["ID"], name: string) => { + const newGestures = this.state.data.map((g) => { + return id !== g.ID ? g : { ...g, name: name.trim() }; + }); + this.setGestures(newGestures); + }; + deleteGestureRecording = ( gestureId: GestureData["ID"], recordingIdx: number From 64a2f607eb60195a01811dd416129c8386c2577b Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 9 Jul 2024 16:27:21 +0100 Subject: [PATCH 023/172] Update name onChange instead of onBlur --- src/components/AddDataGridView.tsx | 2 +- src/components/GestureGridItem.tsx | 10 ++++------ src/pages/AddDataPage.tsx | 11 ++++++++++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index d11b1c1a0..abf4b57a2 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -46,7 +46,7 @@ const AddDataGridView = () => { {gestures.data.map((g, idx) => ( setSelected(idx)} diff --git a/src/components/GestureGridItem.tsx b/src/components/GestureGridItem.tsx index 76bcc4036..398d3d03c 100644 --- a/src/components/GestureGridItem.tsx +++ b/src/components/GestureGridItem.tsx @@ -31,12 +31,9 @@ const GestureGridItem = ({ const toast = useToast(); const toastId = "name-too-long-toast"; - const handleNameChange: React.FocusEventHandler = - useCallback((e) => onNameChange(e.target.value), [onNameChange]); - const onChange: React.ChangeEventHandler = useCallback( (e) => { - // Validate gesture name + // Validate gesture name length if ( e.target.value.trim().length >= gestureNameMaxLength && !toast.isActive(toastId) @@ -52,9 +49,11 @@ const GestureGridItem = ({ variant: "subtle", status: "error", }); + return; } + onNameChange(e.target.value); }, - [intl, toast] + [intl, onNameChange, toast] ); return ( @@ -79,7 +78,6 @@ const GestureGridItem = ({ placeholder={intl.formatMessage({ id: "content.data.classPlaceholderNewClass", })} - onBlur={handleNameChange} onChange={onChange} size="sm" /> diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index 456e25582..1e6b01164 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -19,10 +19,19 @@ import LiveGraphPanel from "../components/LiveGraphPanel"; import TabView from "../components/TabView"; import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; import { addDataConfig } from "../steps-config"; +import { useGestureData } from "../gestures-hooks"; +import { useMemo } from "react"; const AddDataPage = () => { const intl = useIntl(); - const noStoredData = false; + const [gestures] = useGestureData(); + const noStoredData = useMemo(() => { + const gestureData = gestures.data; + return ( + gestureData.length !== 0 && + gestureData.some((g) => g.recordings.length > 0) + ); + }, [gestures.data]); const isInputConnected = true; return ( From 66ff4f7d93e6cd8c8a66b4b32afbd7907a6e2ac5 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 9 Jul 2024 17:52:43 +0100 Subject: [PATCH 024/172] WIP Add data walkthrough --- ...tem.tsx => AddDataGestureNameGridItem.tsx} | 61 +++++----- .../AddDataGestureRecordingGridItem.tsx | 105 ++++++++++++++++++ src/components/AddDataGridGestureRow.tsx | 104 ++--------------- src/components/AddDataGridView.tsx | 29 +++-- src/components/AddDataGridWalkThrough.tsx | 49 ++++++++ src/gestures-hooks.tsx | 23 ++-- 6 files changed, 231 insertions(+), 140 deletions(-) rename src/components/{GestureGridItem.tsx => AddDataGestureNameGridItem.tsx} (55%) create mode 100644 src/components/AddDataGestureRecordingGridItem.tsx create mode 100644 src/components/AddDataGridWalkThrough.tsx diff --git a/src/components/GestureGridItem.tsx b/src/components/AddDataGestureNameGridItem.tsx similarity index 55% rename from src/components/GestureGridItem.tsx rename to src/components/AddDataGestureNameGridItem.tsx index 398d3d03c..ab7bb27e9 100644 --- a/src/components/GestureGridItem.tsx +++ b/src/components/AddDataGestureNameGridItem.tsx @@ -2,7 +2,6 @@ import { Card, CardBody, CardHeader, - CardProps, CloseButton, GridItem, Input, @@ -10,34 +9,35 @@ import { } from "@chakra-ui/react"; import { useCallback } from "react"; import { useIntl } from "react-intl"; +import { useGestureActions } from "../gestures-hooks"; -interface GestureGridItemProps extends CardProps { +interface AddDataGestureNameGridItemProps { name: string; - onCloseClick: () => void; - onSelectRow: () => void; - onNameChange: (newName: string) => void; + onCloseClick?: () => void; + onSelectRow?: () => void; + gestureId: number; + selected: boolean; } const gestureNameMaxLength = 18; -const GestureGridItem = ({ +const AddDataGestureNameGridItem = ({ name, onCloseClick, onSelectRow, - onNameChange, - ...cardProps -}: GestureGridItemProps) => { + gestureId, + selected, +}: AddDataGestureNameGridItemProps) => { const intl = useIntl(); const toast = useToast(); const toastId = "name-too-long-toast"; + const actions = useGestureActions(); const onChange: React.ChangeEventHandler = useCallback( (e) => { + const name = e.target.value.trim(); // Validate gesture name length - if ( - e.target.value.trim().length >= gestureNameMaxLength && - !toast.isActive(toastId) - ) { + if (name.length >= gestureNameMaxLength && !toast.isActive(toastId)) { toast({ id: toastId, position: "top", @@ -51,23 +51,32 @@ const GestureGridItem = ({ }); return; } - onNameChange(e.target.value); + actions.setGestureName(gestureId, name); }, - [intl, onNameChange, toast] + [actions, gestureId, intl, toast] ); return ( - - - + + + {onCloseClick && ( + + )} void; +} + +const AddDataGestureRecordingGridItem = ({ + gesture, + selected, + onSelectRow, +}: AddDataGestureRecordingGridItemProps) => { + const intl = useIntl(); + const { isOpen, onClose, onOpen: onStartRecording } = useDisclosure(); + const actions = useGestureActions(); + // TODO: Replace with checking if micro:bit is connected + const isConnected = true; + + const handleDeleteGestureRecording = useCallback( + (idx: number) => { + actions.deleteGestureRecording(gesture.ID, idx); + }, + [actions, gesture.ID] + ); + + return ( + <> + + + + + + + } + /> + + {gesture.recordings.map((recording, idx) => ( + + { + handleDeleteGestureRecording(idx); + }} + /> + + + ))} + + + + + ); +}; + +export default AddDataGestureRecordingGridItem; diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx index ee27d7c0f..4a6b542da 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridGestureRow.tsx @@ -1,20 +1,8 @@ -import { - Card, - CardBody, - CloseButton, - GridItem, - HStack, - Icon, - IconButton, - useDisclosure, -} from "@chakra-ui/react"; +import { useCallback } from "react"; import { useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; -import RecordIcon from "../images/record-icon.svg?react"; -import GestureGridItem from "./GestureGridItem"; -import RecordingGraph from "./RecordingGraph"; -import RecordingDialog from "./RecordingDialog"; -import { useCallback } from "react"; +import AddDataGestureRecordingGridItem from "./AddDataGestureRecordingGridItem"; +import AddDataGestureNameGridItem from "./AddDataGestureNameGridItem"; interface AddDataGridGestureRowProps { gesture: GestureData; @@ -28,10 +16,7 @@ const AddDataGridGestureRow = ({ onSelectRow, }: AddDataGridGestureRowProps) => { const intl = useIntl(); - const { isOpen, onClose, onOpen: onStartRecording } = useDisclosure(); const actions = useGestureActions(); - // TODO: Replace with checking if micro:bit is connected - const isConnected = false; const handleDeleteGesture = useCallback(() => { const confirmationText = intl.formatMessage( @@ -44,87 +29,20 @@ const AddDataGridGestureRow = ({ actions.deleteGesture(gesture.ID); }, [actions, gesture.ID, gesture.name, intl]); - const handleDeleteGestureRecording = useCallback( - (idx: number) => { - actions.deleteGestureRecording(gesture.ID, idx); - }, - [actions, gesture.ID] - ); - - const handleGestureNameChange = useCallback( - (newName: string) => { - actions.setGestureName(gesture.ID, newName); - }, - [actions, gesture.ID] - ); - return ( <> - - + - - - - - - } - /> - - {gesture.recordings.map((recording, idx) => ( - - { - handleDeleteGestureRecording(idx); - }} - /> - - - ))} - - - ); }; diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index abf4b57a2..2aab9aea5 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -6,11 +6,12 @@ import { Text, VStack, } from "@chakra-ui/react"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useGestureData } from "../gestures-hooks"; import AddDataGridGestureRow from "./AddDataGridGestureRow"; import InfoToolTip, { InfoToolTipProps } from "./InfoToolTip"; +import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; const gridCommonProps: Partial = { gridTemplateColumns: "200px 1fr", @@ -22,6 +23,12 @@ const gridCommonProps: Partial = { const AddDataGridView = () => { const [gestures] = useGestureData(); const [selected, setSelected] = useState(0); + const showWalkThrough = useMemo( + () => + gestures.data.length === 0 || + (gestures.data.length === 1 && gestures.data[0].recordings.length === 0), + [gestures.data] + ); return ( @@ -44,14 +51,18 @@ const AddDataGridView = () => { /> - {gestures.data.map((g, idx) => ( - setSelected(idx)} - /> - ))} + {showWalkThrough ? ( + + ) : ( + gestures.data.map((g, idx) => ( + setSelected(idx)} + /> + )) + )} ); diff --git a/src/components/AddDataGridWalkThrough.tsx b/src/components/AddDataGridWalkThrough.tsx new file mode 100644 index 000000000..4f811e780 --- /dev/null +++ b/src/components/AddDataGridWalkThrough.tsx @@ -0,0 +1,49 @@ +import { GridItem, VStack, Image, Text, HStack } from "@chakra-ui/react"; +import { GestureData } from "../gestures-hooks"; +import greetingEmojiWithArrowImage from "../images/greeting-emoji-with-arrow.svg"; +import upCurveArrowImage from "../images/curve-arrow-up.svg"; +import AddDataGestureNameGridItem from "./AddDataGestureNameGridItem"; +import AddDataGestureRecordingGridItem from "./AddDataGestureRecordingGridItem"; +import { FormattedMessage } from "react-intl"; + +interface AddDataGridWalkThrough { + gesture: GestureData; +} + +const AddDataGridWalkThrough = ({ gesture }: AddDataGridWalkThrough) => { + return ( + <> + + {gesture.name.length === 0 ? ( + + + + + + + + + ) : ( + <> + + {/* Empty grid item to fill first column of grid */} + + + + + + + + + + + )} + + ); +}; + +export default AddDataGridWalkThrough; diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 36aa0c7fe..094c7de55 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -67,7 +67,9 @@ export const useGestureData = (): GestureContextValue => { return gestureData; }; -const initialGestureContextState: GestureContextState = { data: [] }; +const initialGestureContextState: GestureContextState = { + data: [{ name: "", recordings: [], ID: 0 }], +}; export const GesturesProvider = ({ children }: { children: ReactNode }) => { const gestures = useStorage( @@ -108,7 +110,7 @@ export class GestureActions { setGestureName = (id: GestureData["ID"], name: string) => { const newGestures = this.state.data.map((g) => { - return id !== g.ID ? g : { ...g, name: name.trim() }; + return id !== g.ID ? g : { ...g, name }; }); this.setGestures(newGestures); }; @@ -117,16 +119,13 @@ export class GestureActions { gestureId: GestureData["ID"], recordingIdx: number ) => { - const newGestures = this.state.data - .map((g) => { - if (gestureId !== g.ID) { - return g; - } - const recordings = g.recordings.filter((_r, i) => i !== recordingIdx); - return { ...g, recordings }; - }) - // remove gestures without recordings - .filter((g) => g.recordings.length > 0); + const newGestures = this.state.data.map((g) => { + if (gestureId !== g.ID) { + return g; + } + const recordings = g.recordings.filter((_r, i) => i !== recordingIdx); + return { ...g, recordings }; + }); this.setGestures(newGestures); }; } From ec4f2a36b752277a55d77b5d15a019e2df0ebce8 Mon Sep 17 00:00:00 2001 From: Grace Date: Wed, 10 Jul 2024 12:50:25 +0100 Subject: [PATCH 025/172] Stubbing the adding of gesture recording --- .../AddDataGestureRecordingGridItem.tsx | 1 + src/components/RecordingDialog.tsx | 13 +++++++++++-- src/gestures-hooks.tsx | 16 ++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/components/AddDataGestureRecordingGridItem.tsx b/src/components/AddDataGestureRecordingGridItem.tsx index aba140419..0dc95bbf0 100644 --- a/src/components/AddDataGestureRecordingGridItem.tsx +++ b/src/components/AddDataGestureRecordingGridItem.tsx @@ -42,6 +42,7 @@ const AddDataGestureRecordingGridItem = ({ return ( <> void; actionName: string; + gestureId: GestureData["ID"]; } enum RecordingStatus { @@ -38,8 +41,10 @@ const RecordingDialog = ({ isOpen, actionName, onClose, + gestureId, }: RecordingDialogProps) => { const intl = useIntl(); + const actions = useGestureActions(); const [recordingStatus, setRecordingStatus] = useState( RecordingStatus.Countdown ); @@ -90,14 +95,18 @@ const RecordingDialog = ({ useEffect(() => { if (recordingStatus === RecordingStatus.Recording) { - // TODO: Record samples setTimeout(() => { if (recordingStatus === RecordingStatus.Recording) { + // TODO: Record samples + // Stubbing of recording of gesture + actions.addGestureRecordings(gestureId, [ + dummyGestureData[0].recordings[0], + ]); handleOnClose(); } }, recordingDuration); } - }, [handleOnClose, recordingStatus]); + }, [actions, gestureId, handleOnClose, recordingStatus]); return ( void ) {} - setGestures = (gesture: GestureData[]) => { - this.setState({ ...this.state, data: gesture }); + addGestureRecordings = (id: GestureData["ID"], recs: RecordingData[]) => { + const newGestures = this.state.data.map((g) => { + return id !== g.ID ? g : { ...g, recordings: [...recs, ...g.recordings] }; + }); + this.setGestures(newGestures); + }; + + setGestures = (gestures: GestureData[]) => { + if (gestures.length === 0) { + // Always have at least one gesture + this.setState({ ...this.state, ...initialGestureContextState }); + return; + } + this.setState({ ...this.state, data: gestures }); }; deleteGesture = (id: GestureData["ID"]) => { From 0ef78ef90f930240fb31a69257c039847086f4db Mon Sep 17 00:00:00 2001 From: Grace Date: Wed, 10 Jul 2024 14:42:18 +0100 Subject: [PATCH 026/172] Handle add new gesture --- src/components/AddDataGridGestureRow.tsx | 16 +++++++++++----- src/components/AddDataGridWalkThrough.tsx | 2 +- src/components/RecordingDialog.tsx | 2 +- src/gestures-hooks.tsx | 13 +++++++++++-- src/pages/AddDataPage.tsx | 21 +++++++++++++++++---- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridGestureRow.tsx index 4a6b542da..6a6ee535d 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridGestureRow.tsx @@ -3,6 +3,7 @@ import { useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; import AddDataGestureRecordingGridItem from "./AddDataGestureRecordingGridItem"; import AddDataGestureNameGridItem from "./AddDataGestureNameGridItem"; +import { GridItem } from "@chakra-ui/react"; interface AddDataGridGestureRowProps { gesture: GestureData; @@ -38,11 +39,16 @@ const AddDataGridGestureRow = ({ onSelectRow={onSelectRow} selected={selected} /> - + {gesture.name.length > 0 || gesture.recordings.length > 0 ? ( + + ) : ( + // Empty grid item to fill column space + + )} ); }; diff --git a/src/components/AddDataGridWalkThrough.tsx b/src/components/AddDataGridWalkThrough.tsx index 4f811e780..ae14cbd05 100644 --- a/src/components/AddDataGridWalkThrough.tsx +++ b/src/components/AddDataGridWalkThrough.tsx @@ -31,7 +31,7 @@ const AddDataGridWalkThrough = ({ gesture }: AddDataGridWalkThrough) => { <> {/* Empty grid item to fill first column of grid */} - + diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index b27dc504e..3345b2373 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -46,7 +46,7 @@ const RecordingDialog = ({ const intl = useIntl(); const actions = useGestureActions(); const [recordingStatus, setRecordingStatus] = useState( - RecordingStatus.Countdown + RecordingStatus.None ); const [countdownIdx, setIsCountdownIdx] = useState(0); diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index b2ec6d611..e20134c0a 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,6 +1,5 @@ import { ReactNode, createContext, useContext, useMemo } from "react"; import { useStorage } from "./hooks/use-storage"; - export interface XYZData { x: number[]; y: number[]; @@ -67,8 +66,14 @@ export const useGestureData = (): GestureContextValue => { return gestureData; }; +const generateNewGesture = (): GestureData => ({ + name: "", + recordings: [], + ID: Date.now(), +}); + const initialGestureContextState: GestureContextState = { - data: [{ name: "", recordings: [], ID: 0 }], + data: [generateNewGesture()], }; export const GesturesProvider = ({ children }: { children: ReactNode }) => { @@ -100,6 +105,10 @@ export class GestureActions { private setState: (gestureData: GestureContextState) => void ) {} + addNewGesture = () => { + this.setGestures([...this.state.data, generateNewGesture()]); + }; + addGestureRecordings = (id: GestureData["ID"], recs: RecordingData[]) => { const newGestures = this.state.data.map((g) => { return id !== g.ID ? g : { ...g, recordings: [...recs, ...g.recordings] }; diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index 1e6b01164..bd901358d 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -19,12 +19,15 @@ import LiveGraphPanel from "../components/LiveGraphPanel"; import TabView from "../components/TabView"; import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; import { addDataConfig } from "../steps-config"; -import { useGestureData } from "../gestures-hooks"; -import { useMemo } from "react"; +import { useGestureActions, useGestureData } from "../gestures-hooks"; +import { useCallback, useMemo } from "react"; const AddDataPage = () => { const intl = useIntl(); const [gestures] = useGestureData(); + const actions = useGestureActions(); + const isInputConnected = true; + const noStoredData = useMemo(() => { const gestureData = gestures.data; return ( @@ -32,7 +35,10 @@ const AddDataPage = () => { gestureData.some((g) => g.recordings.length > 0) ); }, [gestures.data]); - const isInputConnected = true; + + const handleAddNewGesture = useCallback(() => { + actions.addNewGesture(); + }, [actions]); return ( @@ -53,7 +59,14 @@ const AddDataPage = () => { borderColor="gray.200" alignItems="center" > - From 3a0a30b7b2b2737a6b1e89e60d177a2e77eb0a5a Mon Sep 17 00:00:00 2001 From: Grace Date: Wed, 10 Jul 2024 14:45:41 +0100 Subject: [PATCH 027/172] Handle delete all data samples onClick --- src/gestures-hooks.tsx | 4 ++++ src/pages/AddDataPage.tsx | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index e20134c0a..394c28ded 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -149,4 +149,8 @@ export class GestureActions { }); this.setGestures(newGestures); }; + + deleteAllGestures = () => { + this.setGestures([]); + }; } diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index bd901358d..9e5d4acb6 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -88,7 +88,10 @@ const AddDataPage = () => { }> - }> + } + onClick={actions.deleteAllGestures} + > From 69cb7706b4f84aa62704fba91cd289604d9e9453 Mon Sep 17 00:00:00 2001 From: Grace Date: Wed, 10 Jul 2024 14:52:12 +0100 Subject: [PATCH 028/172] Handle download dataset --- src/pages/AddDataPage.tsx | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index 9e5d4acb6..98146bdcf 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -19,7 +19,11 @@ import LiveGraphPanel from "../components/LiveGraphPanel"; import TabView from "../components/TabView"; import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; import { addDataConfig } from "../steps-config"; -import { useGestureActions, useGestureData } from "../gestures-hooks"; +import { + GestureData, + useGestureActions, + useGestureData, +} from "../gestures-hooks"; import { useCallback, useMemo } from "react"; const AddDataPage = () => { @@ -40,6 +44,10 @@ const AddDataPage = () => { actions.addNewGesture(); }, [actions]); + const handleDatasetDownload = useCallback(() => { + downloadDataset(gestures.data); + }, [gestures.data]); + return ( @@ -85,7 +93,10 @@ const AddDataPage = () => { /> - }> + } + onClick={handleDatasetDownload} + > { ); }; +const downloadDataset = (gestures: GestureData[]) => { + const a = document.createElement("a"); + a.setAttribute( + "href", + "data:application/json;charset=utf-8," + + encodeURIComponent(JSON.stringify(gestures, null, 2)) + ); + a.setAttribute("download", "dataset"); + a.style.display = "none"; + a.click(); +}; + export default AddDataPage; From 5de69ab627ec44cff3c606e6c3ad77fd07196994 Mon Sep 17 00:00:00 2001 From: Grace Date: Wed, 10 Jul 2024 14:58:46 +0100 Subject: [PATCH 029/172] Set focus when recording dialog closes --- src/components/AddDataGestureRecordingGridItem.tsx | 8 +++++++- src/components/RecordingDialog.tsx | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/AddDataGestureRecordingGridItem.tsx b/src/components/AddDataGestureRecordingGridItem.tsx index 0dc95bbf0..615e7b82f 100644 --- a/src/components/AddDataGestureRecordingGridItem.tsx +++ b/src/components/AddDataGestureRecordingGridItem.tsx @@ -13,7 +13,7 @@ import { GestureData, useGestureActions } from "../gestures-hooks"; import RecordIcon from "../images/record-icon.svg?react"; import RecordingGraph from "./RecordingGraph"; import RecordingDialog from "./RecordingDialog"; -import { useCallback } from "react"; +import { useCallback, useRef } from "react"; interface AddDataGestureRecordingGridItemProps { gesture: GestureData; @@ -28,6 +28,7 @@ const AddDataGestureRecordingGridItem = ({ }: AddDataGestureRecordingGridItemProps) => { const intl = useIntl(); const { isOpen, onClose, onOpen: onStartRecording } = useDisclosure(); + const closeRecordingDialogFocusRef = useRef(null); const actions = useGestureActions(); // TODO: Replace with checking if micro:bit is connected const isConnected = true; @@ -46,6 +47,7 @@ const AddDataGestureRecordingGridItem = ({ isOpen={isOpen} onClose={onClose} actionName={gesture.name} + finalFocusRef={closeRecordingDialogFocusRef} /> void; actionName: string; gestureId: GestureData["ID"]; + finalFocusRef: React.MutableRefObject; } enum RecordingStatus { @@ -42,6 +43,7 @@ const RecordingDialog = ({ actionName, onClose, gestureId, + finalFocusRef, }: RecordingDialogProps) => { const intl = useIntl(); const actions = useGestureActions(); @@ -116,6 +118,7 @@ const RecordingDialog = ({ onClose={handleOnClose} size="lg" isCentered + finalFocusRef={finalFocusRef} > From 81445a2d3dae1979100c26c77c32f2c91b0eebdc Mon Sep 17 00:00:00 2001 From: Grace Date: Wed, 10 Jul 2024 17:01:53 +0100 Subject: [PATCH 030/172] Implement scrollable areas to adjust for many gestures (y-axis scrolling) or many recordings (x-axis scrolling) --- src/components/AddDataGridView.tsx | 31 ++++---- src/components/ConnectFirstView.tsx | 8 +- src/pages/AddDataPage.tsx | 114 ++++++++++++++-------------- 3 files changed, 82 insertions(+), 71 deletions(-) diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index 2aab9aea5..ae0b7e185 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -1,23 +1,17 @@ -import { - Grid, - GridItem, - GridProps, - HStack, - Text, - VStack, -} from "@chakra-ui/react"; +import { Grid, GridItem, GridProps, HStack, Text } from "@chakra-ui/react"; import { useMemo, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useGestureData } from "../gestures-hooks"; import AddDataGridGestureRow from "./AddDataGridGestureRow"; -import InfoToolTip, { InfoToolTipProps } from "./InfoToolTip"; import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; +import InfoToolTip, { InfoToolTipProps } from "./InfoToolTip"; const gridCommonProps: Partial = { gridTemplateColumns: "200px 1fr", gap: 3, - alignItems: "center", px: 10, + py: 2, + w: "100%", }; const AddDataGridView = () => { @@ -31,10 +25,12 @@ const AddDataGridView = () => { ); return ( - + <> { descriptionId="content.data.dataDescription" /> - + {showWalkThrough ? ( ) : ( @@ -64,7 +67,7 @@ const AddDataGridView = () => { )) )} - + ); }; diff --git a/src/components/ConnectFirstView.tsx b/src/components/ConnectFirstView.tsx index 43fd6d089..9b4aa9726 100644 --- a/src/components/ConnectFirstView.tsx +++ b/src/components/ConnectFirstView.tsx @@ -14,7 +14,13 @@ const ConnectFirstView = () => { }, [dispatch]); return ( - + diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index 98146bdcf..e4e68d6e0 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -51,65 +51,67 @@ const AddDataPage = () => { return ( - - {noStoredData && !isInputConnected ? ( - - ) : ( - - )} - - - - - - - } - isRound - /> - - - } - onClick={handleDatasetDownload} - > - - - } - onClick={actions.deleteAllGestures} - > - - - - + + + + } + isRound + /> + + + } + onClick={handleDatasetDownload} + > + + + } + onClick={actions.deleteAllGestures} + > + + + + + - - + + ); }; From c627d9447691a16e21595bb74fcdbae5070e5806 Mon Sep 17 00:00:00 2001 From: Grace Date: Wed, 10 Jul 2024 17:18:24 +0100 Subject: [PATCH 031/172] Handle train click with check if there is sufficient training data --- src/gestures-hooks.tsx | 8 ++++++++ src/pages/AddDataPage.tsx | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 394c28ded..ef18f42c8 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -105,6 +105,14 @@ export class GestureActions { private setState: (gestureData: GestureContextState) => void ) {} + isSufficientForTraining = (): boolean => { + const gestures = this.state.data; + if (gestures.length < 2) { + return false; + } + return !gestures.some((g) => g.recordings.length < 3); + }; + addNewGesture = () => { this.setGestures([...this.state.data, generateNewGesture()]); }; diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index e4e68d6e0..fcb33dbc0 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -25,9 +25,12 @@ import { useGestureData, } from "../gestures-hooks"; import { useCallback, useMemo } from "react"; +import { createStepPageUrl } from "../urls"; +import { useNavigate } from "react-router"; const AddDataPage = () => { const intl = useIntl(); + const navigate = useNavigate(); const [gestures] = useGestureData(); const actions = useGestureActions(); const isInputConnected = true; @@ -48,6 +51,10 @@ const AddDataPage = () => { downloadDataset(gestures.data); }, [gestures.data]); + const handleTrain = useCallback(() => { + navigate(createStepPageUrl("train-model")); + }, [navigate]); + return ( @@ -79,7 +86,10 @@ const AddDataPage = () => { - From 4caf5c31e99ef959329fd91dd52997211cb35961 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 10:03:08 +0100 Subject: [PATCH 032/172] WIP Training page - Insufficent data status --- src/components/TrainingButton.tsx | 20 ++++++++ src/components/TrainingStatusView.tsx | 66 +++++++++++++++++++++++++++ src/pages/AddDataPage.tsx | 12 ++--- src/pages/TrainDataPage.tsx | 5 -- src/pages/TrainModelPage.tsx | 38 +++++++++++++++ src/steps-config.ts | 8 ++-- 6 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 src/components/TrainingButton.tsx create mode 100644 src/components/TrainingStatusView.tsx delete mode 100644 src/pages/TrainDataPage.tsx create mode 100644 src/pages/TrainModelPage.tsx diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx new file mode 100644 index 000000000..6c5f312c4 --- /dev/null +++ b/src/components/TrainingButton.tsx @@ -0,0 +1,20 @@ +import { Button, ButtonProps } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import { useGestureActions } from "../gestures-hooks"; + +const TrainingButton = (props: ButtonProps) => { + const actions = useGestureActions(); + + // TODO: disable when isTraining + return ( + + ); +}; + +export default TrainingButton; diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx new file mode 100644 index 000000000..e291403a3 --- /dev/null +++ b/src/components/TrainingStatusView.tsx @@ -0,0 +1,66 @@ +import { Button, Heading, Text } from "@chakra-ui/react"; +import { ReactNode, useCallback } from "react"; +import { FormattedMessage } from "react-intl"; +import { useGestureActions } from "../gestures-hooks"; +import { useNavigate } from "react-router"; +import { createStepPageUrl } from "../urls"; +import TrainingButton from "./TrainingButton"; + +const TrainingStatusView = () => { + const navigate = useNavigate(); + const actions = useGestureActions(); + const isSufficientData = actions.isSufficientForTraining(); + + const navigateToDataPage = useCallback(() => { + navigate(createStepPageUrl("add-data")); + }, [navigate]); + + // TODO: Train + const handleTrain = useCallback(() => {}, []); + + if (!isSufficientData) { + return ( + + + + ); + } + return ( + + + + ); +}; + +interface TrainingStatusSectionProps { + statusId: string; + descriptionId?: string; + children: ReactNode; +} + +const TrainingStatusSection = ({ + statusId, + descriptionId, + children, +}: TrainingStatusSectionProps) => { + return ( + <> + + + + {descriptionId && ( + + + + )} + {children} + + ); +}; + +export default TrainingStatusView; diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index fcb33dbc0..b3b811eb2 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -27,6 +27,7 @@ import { import { useCallback, useMemo } from "react"; import { createStepPageUrl } from "../urls"; import { useNavigate } from "react-router"; +import TrainingButton from "../components/TrainingButton"; const AddDataPage = () => { const intl = useIntl(); @@ -51,7 +52,7 @@ const AddDataPage = () => { downloadDataset(gestures.data); }, [gestures.data]); - const handleTrain = useCallback(() => { + const navigateToTrainModelPage = useCallback(() => { navigate(createStepPageUrl("train-model")); }, [navigate]); @@ -75,7 +76,7 @@ const AddDataPage = () => { alignItems="center" > - + { - return <>Train data page!; -}; - -export default TrainDataPage; diff --git a/src/pages/TrainModelPage.tsx b/src/pages/TrainModelPage.tsx new file mode 100644 index 000000000..f29f30fa8 --- /dev/null +++ b/src/pages/TrainModelPage.tsx @@ -0,0 +1,38 @@ +import { Heading, Image, Text, VStack } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import DefaultPageLayout from "../components/DefaultPageLayout"; +import TabView from "../components/TabView"; +import TrainingStatusView from "../components/TrainingStatusView"; +import trainModelImage from "../images/train_model_black.svg"; +import { trainModelConfig } from "../steps-config"; + +const TrainModelPage = () => { + return ( + + + + + + + + + + + + + + + + + + ); +}; + +export default TrainModelPage; diff --git a/src/steps-config.ts b/src/steps-config.ts index 0d94a39a1..0a2733c4f 100644 --- a/src/steps-config.ts +++ b/src/steps-config.ts @@ -3,7 +3,7 @@ import testModelImage from "./images/test_model_blue.svg"; import trainModelImage from "./images/train_model_blue.svg"; import AddDataPage from "./pages/AddDataPage"; import TestDataPage from "./pages/TestDataPage"; -import TrainDataPage from "./pages/TrainDataPage"; +import TrainModelPage from "./pages/TrainModelPage"; export type StepId = "add-data" | "train-model" | "test-model"; @@ -19,10 +19,10 @@ export const addDataConfig: StepConfig = { pageElement: AddDataPage, }; -export const trainDataConfig: StepConfig = { +export const trainModelConfig: StepConfig = { id: "train-model", imgSrc: trainModelImage, - pageElement: TrainDataPage, + pageElement: TrainModelPage, }; export const testDataConfig: StepConfig = { @@ -33,6 +33,6 @@ export const testDataConfig: StepConfig = { export const stepsConfig: StepConfig[] = [ addDataConfig, - trainDataConfig, + trainModelConfig, testDataConfig, ]; From 9b1bf56a2a96c191bf89e135f9331c03bb9fdce2 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 12:23:39 +0100 Subject: [PATCH 033/172] ml.ts and ml tests --- src/ml.test.ts | 88 ++ src/ml.ts | 125 ++ src/mlFilters.ts | 93 ++ .../gesture-data-bad-labels.json | 1024 +++++++++++++++++ src/test-fixtures/gesture-data.json | 1024 +++++++++++++++++ src/test-fixtures/test-data-shake-still.json | 573 +++++++++ 6 files changed, 2927 insertions(+) create mode 100644 src/ml.test.ts create mode 100644 src/ml.ts create mode 100644 src/mlFilters.ts create mode 100644 src/test-fixtures/gesture-data-bad-labels.json create mode 100644 src/test-fixtures/gesture-data.json create mode 100644 src/test-fixtures/test-data-shake-still.json diff --git a/src/ml.test.ts b/src/ml.test.ts new file mode 100644 index 000000000..8dd666e24 --- /dev/null +++ b/src/ml.test.ts @@ -0,0 +1,88 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2024, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import * as tf from "@tensorflow/tfjs"; +import { GestureData } from "./gestures-hooks"; +import { prepareFeaturesAndLabels, trainModel } from "./ml"; +import gestureDataBadLabels from "./test-fixtures/gesture-data-bad-labels.json"; +import gestureData from "./test-fixtures/gesture-data.json"; +import testdataShakeStill from "./test-fixtures/test-data-shake-still.json"; + +let tensorFlowModel: tf.LayersModel | void; +beforeAll(async () => { + // No webgl in tests running in node. + tf.setBackend("cpu"); + + // This creates determinism in the model training step. + const randomSpy = vi.spyOn(Math, "random"); + randomSpy.mockImplementation(() => 0.5); + + tensorFlowModel = await trainModel({ data: gestureData }); +}); + +const getModelResults = (data: GestureData[]) => { + const { features, labels } = prepareFeaturesAndLabels(data); + + if (!tensorFlowModel) { + throw Error("No model returned"); + } + + const tensorFlowResult = tensorFlowModel.evaluate( + tf.tensor(features), + tf.tensor(labels) + ); + const tensorFlowResultAccuracy = (tensorFlowResult as tf.Scalar[])[1] + .dataSync()[0] + .toFixed(4); + const tensorflowPredictionResult = ( + tensorFlowModel.predict(tf.tensor(features)) as tf.Tensor + ).dataSync(); + return { + tensorFlowResultAccuracy, + tensorflowPredictionResult, + labels, + }; +}; + +describe("Model tests", () => { + test("returns acceptable results on training data", async () => { + const { tensorFlowResultAccuracy, tensorflowPredictionResult, labels } = + getModelResults(gestureData); + const d = labels[0].length; // dimensions + for (let i = 0, j = 0; i < tensorflowPredictionResult.length; i += d, j++) { + const result = tensorflowPredictionResult.slice(i, i + d); + expect(result.indexOf(Math.max(...result))).toBe( + labels[j].indexOf(Math.max(...labels[j])) + ); + } + expect(tensorFlowResultAccuracy).toBe("1.0000"); + }); + + // The action names don't matter, the order of the actions in the data.json file does. + // Training data is shake, still, circle. This data is still, circle, shake. + test("returns incorrect results on wrongly labelled training data", async () => { + const { tensorFlowResultAccuracy, tensorflowPredictionResult, labels } = + getModelResults(gestureDataBadLabels); + const d = labels[0].length; // dimensions + for (let i = 0, j = 0; i < tensorflowPredictionResult.length; i += d, j++) { + const result = tensorflowPredictionResult.slice(i, i + d); + expect(result.indexOf(Math.max(...result))).not.toBe( + labels[j].indexOf(Math.max(...labels[j])) + ); + } + expect(tensorFlowResultAccuracy).toBe("0.0000"); + }); + + test("returns correct results on testing data", async () => { + const { tensorFlowResultAccuracy } = getModelResults(testdataShakeStill); + // The model thinks two samples of still are circle. + // 14 samples; 1.0 / 14 = 0.0714; 0.0714 * 12 correct inferences = 0.8571 + expect(parseFloat(tensorFlowResultAccuracy)).toBeGreaterThan(0.85); + }); +}); diff --git a/src/ml.ts b/src/ml.ts new file mode 100644 index 000000000..ce83f1acd --- /dev/null +++ b/src/ml.ts @@ -0,0 +1,125 @@ +import * as tf from "@tensorflow/tfjs"; +import { GestureData, XYZData } from "./gestures-hooks"; +import { Filter, mlFilters } from "./mlFilters"; +import { SymbolicTensor } from "@tensorflow/tfjs"; + +export enum Axes { + X = "x", + Y = "y", + Z = "z", +} + +const mlSettings = { + duration: 1800, // Duration of recording + numSamples: 80, // number of samples in one recording (when recording samples) + minSamples: 80, // minimum number of samples for reliable detection (when detecting gestures) + automaticClassification: true, // If true, automatically classify gestures + updatesPrSecond: 4, // Times algorithm predicts data pr second + numEpochs: 80, // Number of epochs for ML + learningRate: 0.5, + includedAxes: [Axes.X, Axes.Y, Axes.Z], + includedFilters: new Set([ + Filter.MAX, + Filter.MEAN, + Filter.MIN, + Filter.STD, + Filter.PEAKS, + Filter.ACC, + Filter.ZCR, + Filter.RMS, + ]), +}; + +interface TrainModelInput { + data: GestureData[]; + onTrainEnd?: () => void; + onTraining?: (progress: number) => void; + onError?: () => void; +} + +export const trainModel = async ({ + data, + onTrainEnd, + onTraining, + onError, +}: TrainModelInput): Promise => { + const { features, labels } = prepareFeaturesAndLabels(data); + const nn: tf.LayersModel = createModel(data); + const totalNumEpochs = mlSettings.numEpochs; + + try { + await nn.fit(tf.tensor(features), tf.tensor(labels), { + epochs: totalNumEpochs, + batchSize: 16, + validationSplit: 0.1, + callbacks: { + onTrainEnd, + onEpochEnd: (epoch: number) => { + // Epochs indexed at 0 + onTraining && onTraining(epoch / (totalNumEpochs - 1)); + }, + }, + }); + } catch (err) { + onError && onError(); + console.error("tensorflow training process failed:", err); + } + return nn; +}; + +// Exported for testing +export const prepareFeaturesAndLabels = ( + gestureData: GestureData[] +): { features: number[][]; labels: number[][] } => { + const features: number[][] = []; + const labels: number[][] = []; + const numGestures = gestureData.length; + + gestureData.forEach((gesture, index) => { + gesture.recordings.forEach((recording) => { + // Prepare features + features.push(applyFilters(recording.data)); + + // Prepare labels + const label: number[] = new Array(numGestures) as number[]; + label.fill(0, 0, numGestures); + label[index] = 1; + labels.push(label); + }); + }); + return { features, labels }; +}; + +function createModel(gestureData: GestureData[]): tf.LayersModel { + const numberOfClasses: number = gestureData.length; + const inputShape = [ + mlSettings.includedFilters.size * mlSettings.includedAxes.length, + ]; + + const input = tf.input({ shape: inputShape }); + const normalizer = tf.layers.batchNormalization().apply(input); + const dense = tf.layers + .dense({ units: 16, activation: "relu" }) + .apply(normalizer); + const softmax = tf.layers + .dense({ units: numberOfClasses, activation: "softmax" }) + .apply(dense) as SymbolicTensor; + const model = tf.model({ inputs: input, outputs: softmax }); + + model.compile({ + loss: "categoricalCrossentropy", + optimizer: tf.train.sgd(0.5), + metrics: ["accuracy"], + }); + + return model; +} + +// Exported for testing +// applyFilters reduces array of x, y and z inputs to a single number array with values. +export const applyFilters = ({ x, y, z }: XYZData): number[] => { + return Array.from(mlSettings.includedFilters).reduce((acc, filter) => { + const filterStrategy = mlFilters[filter]; + return [...acc, filterStrategy(x), filterStrategy(y), filterStrategy(z)]; + }, [] as number[]); +}; diff --git a/src/mlFilters.ts b/src/mlFilters.ts new file mode 100644 index 000000000..7cf100afc --- /dev/null +++ b/src/mlFilters.ts @@ -0,0 +1,93 @@ +export enum Filter { + MAX = "max", + MIN = "min", + MEAN = "mean", + STD = "std", + PEAKS = "peaks", + ACC = "acc", + ZCR = "zcr", + RMS = "rms", +} + +type FilterStrategy = (data: number[]) => number; + +const mean: FilterStrategy = (d) => d.reduce((a, b) => a + b) / d.length; + +const stddev: FilterStrategy = (d) => + Math.sqrt(d.reduce((a, b) => a + Math.pow(b - mean(d), 2), 0) / d.length); + +const peaks: FilterStrategy = (data) => { + const lag = 5; + const threshold = 3.5; + const influence = 0.5; + + let peaksCounter = 0; + + if (data.length < lag + 2) { + throw new Error("data sample is too short"); + } + + // init variables + const signals = Array(data.length).fill(0) as number[]; + const filteredY = data.slice(0); + const lead_in = data.slice(0, lag); + + const avgFilter: number[] = []; + avgFilter[lag - 1] = mean(lead_in); + const stdFilter: number[] = []; + stdFilter[lag - 1] = stddev(lead_in); + + for (let i = lag; i < data.length; i++) { + if ( + Math.abs(data[i] - avgFilter[i - 1]) > 0.1 && + Math.abs(data[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1] + ) { + if (data[i] > avgFilter[i - 1]) { + signals[i] = +1; // positive signal + if (i - 1 > 0 && signals[i - 1] == 0) { + peaksCounter++; + } + } else { + signals[i] = -1; // negative signal + } + // make influence lower + filteredY[i] = influence * data[i] + (1 - influence) * filteredY[i - 1]; + } else { + signals[i] = 0; // no signal + filteredY[i] = data[i]; + } + + // adjust the filters + const y_lag = filteredY.slice(i - lag, i); + avgFilter[i] = mean(y_lag); + stdFilter[i] = stddev(y_lag); + } + return peaksCounter; +}; + +const zeroCrossingRate: FilterStrategy = (data) => { + let count = 0; + for (let i = 1; i < data.length; i++) { + if ( + (data[i] >= 0 && data[i - 1] < 0) || + (data[i] < 0 && data[i - 1] >= 0) + ) { + count++; + } + } + return count / (data.length - 1); +}; + +const rms: FilterStrategy = (d) => + Math.sqrt(d.reduce((a, b) => a + Math.pow(b, 2), 0) / d.length); + +export const mlFilters: Record = { + [Filter.MAX]: (d) => Math.max(...d), + [Filter.MIN]: (d) => Math.min(...d), + [Filter.MEAN]: mean, + [Filter.STD]: stddev, + [Filter.PEAKS]: peaks, + [Filter.ACC]: (d) => d.reduce((a, b) => a + Math.abs(b)), + [Filter.ZCR]: zeroCrossingRate, + [Filter.RMS]: rms, +}; diff --git a/src/test-fixtures/gesture-data-bad-labels.json b/src/test-fixtures/gesture-data-bad-labels.json new file mode 100644 index 000000000..79f01fc5a --- /dev/null +++ b/src/test-fixtures/gesture-data-bad-labels.json @@ -0,0 +1,1024 @@ +[ + { + "ID": 1705437833024, + "name": "Circle", + "recordings": [ + { + "ID": 1705437969952, + "data": { + "x": [ + -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, -0.06, + -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, -0.064, -0.06, + -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, -0.06, -0.06, -0.068, + -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, -0.056, -0.056, -0.056, -0.056, + -0.06, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, + -0.064, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, + -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, -0.052, + -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, -0.06, -0.056, + -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, -0.056, -0.068, -0.06, + -0.06, -0.052, -0.06 + ], + "y": [ + 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, 0.772, + 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, 0.772, 0.76, 0.764, + 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, 0.764, 0.76, 0.76, 0.764, + 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, + 0.76, 0.756, 0.756, 0.772, 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, + 0.756, 0.76, 0.764, 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, + 0.764, 0.764, 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, + 0.76, 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, 0.76, + 0.764, 0.752 + ], + "z": [ + -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, -0.692, + -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, -0.688, -0.692, -0.684, + -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, -0.7, -0.7, -0.708, -0.7, -0.696, + -0.692, -0.696, -0.7, -0.7, -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, + -0.7, -0.692, -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, + -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, -0.696, + -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, -0.708, -0.7, -0.712, + -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, -0.696, -0.704, -0.696, -0.708, + -0.7, -0.7, -0.7, -0.704, -0.7, -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, + -0.7 + ] + } + }, + { + "ID": 1705437870493, + "data": { + "x": [ + -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, -0.7, -0.648, + -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, -0.752, -0.704, -0.68, + -0.692, -0.708, -0.72, -0.756, -0.764, -0.768, -0.772, -0.736, -0.748, -0.74, + -0.772, -0.764, -0.752, -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, + -0.748, -0.748, -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, + -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, -0.748, + -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, -0.756, -0.744, -0.732, + -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, -0.772, -0.74, -0.732, -0.74, + -0.764, -0.776, -0.772, -0.764, -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, + -0.812, -0.808 + ], + "y": [ + -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, -0.548, -0.552, + -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, -0.484, -0.5, -0.504, -0.508, + -0.484, -0.492, -0.496, -0.484, -0.48, -0.476, -0.484, -0.492, -0.468, -0.484, + -0.476, -0.484, -0.492, -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, + -0.492, -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, -0.468, + -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, -0.48, -0.492, -0.476, + -0.472, -0.476, -0.484, -0.484, -0.484, -0.484, -0.492, -0.488, -0.456, -0.48, + -0.484, -0.472, -0.476, -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, + -0.472, -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, -0.472, + -0.428, -0.436 + ], + "z": [ + -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, -0.504, + -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, -0.584, -0.56, + -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, -0.576, -0.596, -0.572, + -0.604, -0.592, -0.568, -0.572, -0.56, -0.584, -0.584, -0.612, -0.572, -0.568, + -0.576, -0.588, -0.6, -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, + -0.588, -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, -0.592, + -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, -0.592, -0.624, -0.572, + -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, -0.58, -0.564, -0.552, -0.564, + -0.58, -0.592, -0.588, -0.584, -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, + -0.636, -0.608 + ] + } + }, + { + "ID": 1705437864426, + "data": { + "x": [ + 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, 0.876, + 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, 0.98, 0.952, + 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, 0.956, 0.936, 0.948, + 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, 0.948, 0.94, 0.928, 0.928, 0.94, + 0.96, 0.94, 0.94, 0.94, 0.936, 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, + 0.956, 0.96, 0.952, 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, + 0.944, 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, + 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, 0.968, + 0.968, 0.944 + ], + "y": [ + -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, -0.076, + -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, -0.072, -0.072, -0.08, + -0.076, -0.084, -0.104, -0.088, -0.104, -0.088, -0.116, -0.116, -0.132, + -0.116, -0.108, -0.104, -0.12, -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, + -0.112, -0.1, -0.1, -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, + -0.116, -0.108, -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, + -0.096, -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, + -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, -0.096, + -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, -0.08, -0.1, + -0.096, -0.092, -0.092, -0.112, -0.088 + ], + "z": [ + 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, -0.196, + -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, -0.248, -0.172, -0.14, + -0.136, -0.188, -0.204, -0.14, -0.116, -0.104, -0.18, -0.216, -0.192, -0.168, + -0.132, -0.16, -0.188, -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, + -0.176, -0.152, -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, + -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, -0.168, -0.16, + -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, -0.172, -0.168, -0.168, + -0.18, -0.176, -0.168, -0.16, -0.148, -0.148, -0.164, -0.184, -0.184, -0.176, + -0.144, -0.156, -0.176, -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, + -0.176, -0.14, -0.144 + ] + } + }, + { + "ID": 1705437860385, + "data": { + "x": [ + 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, -0.052, + -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, -0.04, -0.056, -0.052, + -0.052, -0.056, -0.056, -0.044, -0.044, -0.056, -0.052, -0.048, -0.048, + -0.048, -0.048, -0.048, -0.06, -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, + -0.056, -0.056, -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, + -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, -0.056, + -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, -0.052, -0.056, + -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, -0.052, -0.048, -0.056, + -0.056, -0.048, -0.052, -0.056, -0.048, -0.056, -0.048, -0.048, -0.052, + -0.052, -0.048, -0.052, -0.052, -0.044 + ], + "y": [ + 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, 0.224, + 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, 0.228, 0.224, 0.224, + 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, 0.228, 0.228, 0.228, 0.232, 0.236, + 0.232, 0.232, 0.228, 0.22, 0.232, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, + 0.224, 0.224, 0.232, 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, + 0.228, 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, 0.228, + 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, 0.228, 0.228, 0.236, + 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, 0.228, 0.228, 0.228, 0.228, 0.224, + 0.232, 0.232, 0.232 + ], + "z": [ + 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, 0.996, 1.012, + 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, 1.012, 1.012, 1.012, 1, + 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, 1.008, 1.008, 0.996, 0.996, 1.008, + 1.012, 1.008, 1.012, 1.008, 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, + 1.008, 1.004, 1.004, 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, + 1.004, 1, 1, 1, 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, + 1.004, 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, + 0.996, 1.004, 1.008, 1.012, 1.008, 1.004, 1.008, 1.008, 1.008 + ] + } + }, + { + "ID": 1705437854703, + "data": { + "x": [ + -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, -0.104, + -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, -0.064, -0.068, + -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, -0.072, -0.068, -0.072, + -0.068, -0.056, -0.064, -0.068, -0.068, -0.068, -0.068, -0.06, -0.072, -0.076, + -0.072, -0.068, -0.064, -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, + -0.068, -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, + -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, -0.076, -0.06, + -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, -0.064, -0.064, -0.072, -0.068, + -0.064, -0.064, -0.088, -0.06, -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, + -0.064, -0.064, -0.064, -0.068, -0.072, -0.068, -0.06 + ], + "y": [ + 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, 0.96, 0.96, + 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, 0.948, 0.952, 0.944, + 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, 0.944, 0.952, 0.952, 0.952, + 0.952, 0.952, 0.948, 0.948, 0.948, 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, + 0.948, 0.952, 0.948, 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, + 0.948, 0.944, 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, + 0.952, 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, + 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, 0.952, + 0.948, 0.948, 0.948, 0.948 + ], + "z": [ + -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, -0.396, -0.42, + -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, -0.44, -0.432, -0.428, + -0.428, -0.432, -0.428, -0.428, -0.432, -0.428, -0.424, -0.432, -0.436, + -0.428, -0.428, -0.432, -0.432, -0.428, -0.424, -0.436, -0.428, -0.432, + -0.432, -0.436, -0.424, -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, + -0.436, -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, + -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, -0.42, -0.428, + -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, -0.428, -0.436, -0.432, -0.436, + -0.44, -0.428, -0.432, -0.432, -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, + -0.424, -0.424, -0.432, -0.424, -0.432, -0.436 + ] + } + }, + { + "ID": 1705437847993, + "data": { + "x": [ + 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, 0.768, + 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, 0.76, 0.748, + 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, 0.764, 0.76, 0.748, + 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, 0.756, 0.748, 0.752, 0.744, + 0.744, 0.752, 0.756, 0.756, 0.752, 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, + 0.756, 0.752, 0.748, 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, + 0.756, 0.752, 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, + 0.736, 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, + 0.736, 0.74, 0.752, 0.74 + ], + "y": [ + 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, 0.248, + 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, 0.268, 0.268, 0.272, + 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, 0.264, 0.264, 0.268, 0.268, + 0.276, 0.276, 0.256, 0.256, 0.264, 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, + 0.276, 0.276, 0.284, 0.268, 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, + 0.276, 0.28, 0.272, 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, + 0.268, 0.272, 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, + 0.3, 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, + 0.284, 0.276, 0.272, 0.276 + ], + "z": [ + -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, -0.564, -0.552, + -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, -0.568, -0.572, -0.552, -0.548, + -0.54, -0.544, -0.556, -0.56, -0.56, -0.544, -0.536, -0.54, -0.532, -0.556, + -0.564, -0.572, -0.556, -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, + -0.552, -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, -0.536, + -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, -0.54, -0.568, -0.56, + -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, -0.544, -0.548, -0.548, -0.56, + -0.556, -0.544, -0.552, -0.56, -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, + -0.556, -0.56, -0.568, -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, + -0.564, -0.56 + ] + } + } + ], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + }, + { + "ID": 1705437876740, + "name": "Shake", + "recordings": [ + { + "ID": 1705438034019, + "data": { + "x": [ + -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, -0.536, -0.456, + -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, -0.916, -0.956, -1.032, -1.1, + -1.228, -1.284, -1.312, -1.36, -1.356, -1.296, -1.224, -1.096, -0.844, -0.964, + -0.948, -0.984, -0.896, -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, + -0.392, -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, + -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, -0.36, -0.476, + -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, -0.532, -0.532, -0.608, + -0.652, -0.668, -0.652, -0.764, -0.892, -0.92, -0.864, -0.848, -0.884, -0.872, + -0.848, -0.888, -0.952, -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, + -0.732, -0.84 + ], + "y": [ + 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, 0.392, 0.376, + 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, 0.2, 0.152, 0.128, 0.16, + 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, 0.284, 0.372, 0.284, 0.348, 0.412, + 0.408, 0.376, 0.36, 0.396, 0.452, 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, + 0.312, 0.34, 0.356, 0.332, 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, + 0.324, 0.308, 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, + 0.348, 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, + 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, 0.292, + 0.324 + ], + "z": [ + -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, -0.388, + -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, -0.204, -0.272, -0.308, + -0.308, -0.388, -0.424, -0.512, -0.592, -0.66, -0.696, -0.772, -0.812, -0.968, + -0.948, -1.02, -1.168, -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, + -1.12, -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, + -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, -0.556, -0.532, + -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, -0.392, -0.356, -0.364, + -0.388, -0.376, -0.388, -0.42, -0.408, -0.416, -0.376, -0.372, -0.368, -0.412, + -0.464, -0.492, -0.516, -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, + -0.976, -0.972 + ] + } + }, + { + "ID": 1705438029559, + "data": { + "x": [ + -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, -0.644, -0.536, + -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, -0.94, -0.912, -0.888, -0.816, + -0.836, -0.872, -0.844, -0.848, -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, + -0.664, -0.636, -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, + -0.364, -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, 0.068, + -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, -0.172, -0.264, + -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, -0.612, -0.552, -0.576, -0.676, + -0.804, -0.876, -0.864, -0.856, -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, + -0.856, -0.792, -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, + -0.668, -0.588, -0.508 + ], + "y": [ + 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, 0.132, + 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, 0.232, 0.256, + 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, 0.296, 0.292, 0.304, + 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, 0.368, 0.352, 0.356, 0.328, + 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, + 0.24, 0.248, 0.232, 0.196, 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, + 0.176, 0.152, 0.144, 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, + 0.252, 0.188, 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, + 0.276, 0.248, 0.236 + ], + "z": [ + -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, -1.064, + -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, -0.672, -0.712, + -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, -0.524, -0.484, -0.468, -0.516, + -0.54, -0.596, -0.624, -0.6, -0.596, -0.6, -0.624, -0.632, -0.656, -0.632, + -0.7, -0.744, -0.76, -0.768, -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, + -0.78, -1.112, -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, + -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, -1.064, -0.996, + -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, -0.796, -0.808, -0.812, -0.74, + -0.76, -0.72, -0.744, -0.728, -0.704, -0.692, -0.636, -0.644, -0.656, -0.676, + -0.616 + ] + } + }, + { + "ID": 1705438027130, + "data": { + "x": [ + -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, -0.532, + -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, -1.056, -0.968, -0.9, + -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, -0.8, -0.788, -0.748, -0.728, + -0.636, -0.56, -0.532, -0.504, -0.492, -0.432, -0.424, -0.464, -0.428, -0.408, + -0.336, -0.296, -0.252, -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, + -0.168, -0.136, -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, + -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, -0.628, -0.624, + -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, -0.84, -0.864, -0.888, -0.924, + -0.936, -0.88, -0.848, -0.888, -0.88, -0.832, -0.768, -0.724, -0.68, -0.664, + -0.664, -0.62, -0.572 + ], + "y": [ + 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, 0.164, + 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, 0.208, 0.212, + 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, 0.324, 0.316, 0.32, + 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, 0.276, 0.248, 0.232, 0.252, + 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, 0.176, 0.156, 0.136, 0.16, 0.18, + 0.208, 0.224, 0.204, 0.18, 0.188, 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, + 0.104, 0.112, 0.084, 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, + 0.1, 0.14, 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, + 0.268, 0.276, 0.284, 0.296 + ], + "z": [ + -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, -1.176, + -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, -0.964, -0.904, -0.824, + -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, -0.568, -0.56, -0.528, -0.468, + -0.492, -0.488, -0.48, -0.492, -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, + -0.704, -0.732, -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, + -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, -1.208, -1.208, + -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, -1.076, -1.072, -1.028, + -1.028, -0.984, -0.944, -0.968, -0.96, -0.964, -0.964, -0.952, -0.904, -0.856, + -0.808, -0.788, -0.756, -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, + -0.604, -0.592, -0.588 + ] + } + }, + { + "ID": 1705437979405, + "data": { + "x": [ + 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, -0.204, + -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, -0.136, -0.28, -0.392, + -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, -0.464, -0.516, -0.616, -0.648, + -0.6, -0.52, -0.48, -0.468, -0.468, -0.46, -0.432, -0.404, -0.392, -0.304, + -0.256, -0.208, -0.204, -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, + -0.792, -0.776, -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, + -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, -0.104, + 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, 0.092, 0.04, 0, + -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, 0.312, 0.188, 0.036, -0.06, + -0.156, -0.248 + ], + "y": [ + 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, 0.364, + 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, 0.392, 0.364, + 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, 0.312, 0.296, 0.312, + 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, 0.376, 0.316, 0.292, 0.308, + 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, 0.708, 0.728, 0.74, 0.748, 0.716, + 0.68, 0.624, 0.592, 0.564, 0.536, 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, + 0.416, 0.408, 0.42, 0.408, 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, + 0.448, 0.44, 0.464, 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, + 0.348, 0.368, 0.412, 0.484, 0.5 + ], + "z": [ + -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, -0.652, + -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, -1.364, -1.312, + -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, -1.004, -0.996, -1.036, + -1.056, -1.032, -0.996, -0.932, -0.868, -0.864, -0.824, -0.808, -0.764, + -0.708, -0.652, -0.624, -0.632, -0.644, -0.628, -0.612, -0.616, -0.692, + -0.788, -0.884, -0.932, -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, + -0.784, -0.74, -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, + -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, -0.92, + -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, -1.092, -1.132, + -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 + ] + } + }, + { + "ID": 1705437977128, + "data": { + "x": [ + -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, 0.16, + 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, -0.42, -0.596, + -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, -0.8, -0.868, -0.968, + -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, -0.456, -0.396, -0.436, -0.476, + -0.416, -0.364, -0.296, -0.292, -0.304, -0.332, -0.324, -0.272, -0.208, + -0.156, -0.112, -0.08, -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, + 0.168, 0.176, 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, + 0.156, 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, -0.404, + -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, -0.58, -0.52, -0.428 + ], + "y": [ + 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, 0.316, 0.5, + 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, 0.52, 0.44, 0.44, 0.464, + 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, 0.604, 0.584, 0.62, 0.608, 0.58, + 0.56, 0.564, 0.576, 0.592, 0.58, 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, + 0.456, 0.44, 0.4, 0.376, 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, + 0.38, 0.392, 0.436, 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, + 0.244, 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, + 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, 0.428 + ], + "z": [ + -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, -0.816, + -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, -1.18, -1.196, -1.236, + -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, -1.04, -0.964, -0.888, -0.856, + -0.772, -0.696, -0.62, -0.564, -0.6, -0.592, -0.536, -0.48, -0.432, -0.448, + -0.48, -0.484, -0.516, -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, + -0.62, -0.668, -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, + -0.84, -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, + -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, -1.152, + -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, -1.044, -1, -1, -1.012 + ] + } + }, + { + "ID": 1705437922352, + "data": { + "x": [ + -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, 0.064, 0.064, + -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, -0.688, -0.728, -0.772, + -0.784, -0.768, -0.732, -0.696, -0.648, -0.576, -0.544, -0.52, -0.504, -0.492, + -0.492, -0.484, -0.436, -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, + -0.248, -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, + -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, -0.088, + -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, -0.176, -0.176, + -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, -0.312, -0.376, -0.46, -0.436, + -0.348, -0.26, -0.268, -0.376, -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, + -0.456, -0.436, -0.448, -0.46 + ], + "y": [ + 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, 0.884, 1.036, + 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, 1.236, 1.188, 1.176, + 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, 1.036, 1.004, 1.004, 1, 0.992, + 0.98, 0.94, 0.92, 0.896, 0.884, 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, + 0.76, 0.728, 0.712, 0.724, 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, + 0.696, 0.732, 0.752, 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, + 0.932, 0.892, 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, + 0.932, 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, 0.928, + 0.9 + ], + "z": [ + -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, -0.868, -0.848, + -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, -0.504, -0.444, -0.416, -0.38, + -0.36, -0.316, -0.26, -0.2, -0.152, -0.136, -0.116, -0.116, -0.108, -0.044, + -0.004, 0.036, 0.072, 0.072, 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, + 0.096, 0.092, 0.068, -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, + -0.324, -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, -0.56, + -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, -0.628, -0.656, -0.672, + -0.524, -0.46, -0.468, -0.532, -0.596, -0.604, -0.6, -0.644, -0.66, -0.748, + -0.752, -0.76, -0.676, -0.608, -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, + -0.372, -0.38 + ] + } + }, + { + "ID": 1705437913803, + "data": { + "x": [ + 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, 0.86, + 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, 0.204, 0.044, + -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, -0.536, -0.488, -0.404, + -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, -0.548, -0.376, -0.444, -0.496, + -0.496, -0.428, -0.344, -0.724, -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, + 0.372, 0.34, 0.296, 0.22, 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, + 0.456, 0.5, 0.516, 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, + 0.344, 0.252, 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, + -0.16, -0.212, -0.316, -0.412, -0.42, -0.38, -0.296, -0.244 + ], + "y": [ + 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, -0.192, + -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, -0.044, -0.004, + 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, 0.36, 0.396, 0.424, 0.4, + 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, 0.332, 0.344, 0.244, 0.068, -0.096, + -0.092, -0.06, -0.02, 0.452, -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, + 0.06, 0.072, 0.072, 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, + 0.076, 0.04, 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, + 0.304, 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, + 0.536, 0.528, 0.552, 0.604, 0.648, 0.696, 0.64, 0.596 + ], + "z": [ + -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, -0.576, + -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, -0.28, -0.18, -0.248, + -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, -0.524, -0.58, -0.66, -0.78, + -0.808, -0.784, -0.732, -0.76, -0.744, -0.744, -0.8, -0.904, -1.032, -1.076, + -1.14, -1.3, -1.504, -1.62, -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, + -1.08, -1.064, -1.1, -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, + -1.332, -1.292, -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, + -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, -0.736, -0.712, + -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, -0.64, -0.692, -0.724, + -0.752, -0.768, -0.772, -0.792 + ] + } + }, + { + "ID": 1705437908508, + "data": { + "x": [ + -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, -1.108, + -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, -0.868, -0.908, + -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, -0.484, -0.424, -0.42, -0.436, + -0.408, -0.352, -0.272, -0.216, -0.208, -0.236, -0.188, -0.148, -0.104, + -0.084, -0.08, -0.076, -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, + -0.212, -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, -0.76, + -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, -0.972, -0.992, -1, + -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, -0.796, -0.692, -0.564, -0.404, + -0.388, -0.312, -0.32, -0.324, -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, + -0.508, -0.508, -0.508, -0.496 + ], + "y": [ + 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, 0.712, + 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, 0.96, 0.972, 0.96, + 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, 0.876, 0.832, 0.824, 0.8, 0.74, + 0.716, 0.692, 0.672, 0.628, 0.572, 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, + 0.54, 0.528, 0.54, 0.544, 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, + 0.472, 0.492, 0.52, 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, + 0.872, 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, 1, + 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, 0.776, 0.772, + 0.772, 0.776 + ], + "z": [ + -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, -0.036, + -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, -0.464, -0.508, + -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, -0.664, -0.72, -0.704, -0.744, + -0.784, -0.856, -0.884, -0.94, -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, + -0.744, -0.744, -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, + -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, -0.012, + 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, 0.148, 0.088, + 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, -0.592, -0.684, -0.652, + -0.912, -0.672, -0.604, -0.6, -0.584, -0.564, -0.504, -0.504, -0.496, -0.484, + -0.472, -0.46, -0.456, -0.448 + ] + } + }, + { + "ID": 1705437905957, + "data": { + "x": [ + -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, -0.496, + -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, -0.884, -0.936, + -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, -0.964, -0.872, -0.824, -0.796, + -0.832, -0.868, -0.804, -0.716, -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, + -0.516, -0.5, -0.476, -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, + -0.228, -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, -0.244, + -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, -0.928, -0.972, -1.004, + -0.976, -0.928, -0.92, -0.94, -0.952, -0.904, -0.848, -0.828, -0.856, -0.844, + -0.804, -0.78, -0.764, -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, + -0.64, -0.6, -0.588 + ], + "y": [ + 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, 0.92, 0.992, + 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, 0.856, 0.848, 0.812, + 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, 0.724, 0.708, 0.7, 0.692, + 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, + 0.492, 0.472, 0.508, 0.524, 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, + 0.808, 0.812, 0.848, 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, + 0.916, 0.86, 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, + 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, 0.716, + 0.724, 0.712 + ], + "z": [ + -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, -0.612, + -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, -0.356, -0.328, + -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, -0.204, -0.176, -0.148, + -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, 0.188, 0.216, 0.204, 0.164, 0.088, + 0.032, 0.044, 0.04, 0.068, 0.06, 0.016, -0.048, -0.124, -0.208, -0.296, + -0.404, -0.428, -0.4, -0.38, -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, + -0.8, -0.872, -0.88, -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, + -0.272, -0.28, -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, + -0.376, -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, + -0.38, -0.356, -0.32, -0.328 + ] + } + }, + { + "ID": 1705437896254, + "data": { + "x": [ + -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, -0.712, + -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, -0.808, -0.84, -0.944, + -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, -1.076, -1.048, -1.048, -1.112, + -1.144, -1.188, -1.208, -1.228, -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, + -1.02, -0.956, -0.912, -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, + -0.732, -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, + -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, -0.496, -0.48, + -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, -0.404, -0.46, -0.532, + -0.568, -0.484, -0.38, -0.444, -0.572, -0.728, -0.728, -0.616, -0.644, -0.656, + -0.704, -0.688, -0.644, -0.652 + ], + "y": [ + 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, 0.472, + 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, 0.244, 0.232, + 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, 0.056, 0.076, 0.104, + 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, 0.28, 0.308, 0.324, 0.348, 0.38, + 0.404, 0.408, 0.408, 0.384, 0.372, 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, + 0.38, 0.364, 0.412, 0.32, 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, + 0.372, 0.368, 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, + 0.468, 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, 0.32, + 0.32, 0.316 + ], + "z": [ + -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, -0.556, -0.524, + -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, -0.644, -0.72, -0.756, -0.744, + -0.724, -0.7, -0.684, -0.716, -0.716, -0.68, -0.668, -0.656, -0.708, -0.736, + -0.696, -0.712, -0.684, -0.7, -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, + -0.524, -0.496, -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, + -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, -0.392, -0.444, + -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, -0.716, -0.76, -0.76, + -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, -0.728, -0.66, -0.7, -0.756, + -0.86, -0.776, -0.76, -0.812, -0.876, -0.872, -0.908, -0.96, -0.976, -0.996, + -1.032, -0.976, -0.96 + ] + } + } + ], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + }, + { + "ID": 1705437793875, + "name": "Still", + "recordings": [ + { + "ID": 1705437992143, + "data": { + "x": [ + -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, -1.3, + -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, -2.04, -2.008, + -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, -0.232, -1.132, -2.04, + -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, 0.608, 0.332, -0.004, -0.876, + -2.04, -2.04, -2.04, -1.688, -1.396, -0.828, -0.032, 0.36, 0.492, 0.288, + 0.008, -0.492, -1.368, -2.04, -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, + 0.664, 0.544, 0.248, -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, + -0.612, 0.16, 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, + -1.668, -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, + -2.04, -2.04, -1.992 + ], + "y": [ + 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, 0.324, + 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, 0.328, 0.364, + 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, 1.076, -0.116, 0.048, + 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, 0.524, 0.416, 0.252, 0.308, + 0.376, 0.316, 0.356, 0.644, 0.672, 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, + 0.256, 0.272, 0.404, 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, + 0.284, 0.28, 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, + 0.452, 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, 0.288, + 0.204, 0.476, 1.112, 0.2 + ], + "z": [ + -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, -0.096, + -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, -0.108, 0.196, 0.268, + 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, -0.26, -0.092, -0.204, 0.304, 0.78, + 0.364, 0.108, -0.24, -0.528, -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, + 0.552, 0.42, 0.128, 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, + -0.004, -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, + -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, -0.288, + -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, 0.504, 0.196, + -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, -0.072, -0.176, 0.136, + 0.34, 0.7 + ] + } + }, + { + "ID": 1705437989960, + "data": { + "x": [ + -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, -1.496, + -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, 0.864, 0.256, + -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, 0.42, 0.608, 0.488, + 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, -1.588, -1.08, -0.512, -0.028, + 0.216, 0.268, 0.052, -0.38, -0.684, -0.788, -1.056, -1.768, -2.04, -2.04, + -1.344, -0.8, -0.436, -0.172, 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, + -1.544, -2.02, -2.016, -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, + -0.46, -0.816, -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, + 0.06, 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 + ], + "y": [ + 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, 0.456, 0.188, + 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, 0.32, 0.252, 0.208, 0.152, + 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, + 0.596, 0.644, 0.732, 0.58, 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, + 0.38, 0.284, 0.22, 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, + 0.348, 0.34, 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, + 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, 0.556, + 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, 0.328, 0.536, + 0.78, 0.7 + ], + "z": [ + -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, -0.596, -0.696, + -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, -0.888, -0.948, -0.972, -1.192, + -1.148, -0.808, -0.636, -0.42, 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, + -1.032, -0.972, -0.656, -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, + -0.364, -0.744, -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, + 1.116, 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, -0.7, + -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, -1.508, -1.86, + -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, 0.256, -0.24, -0.712, + -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, 0.18, 0.576, 1.292, 0.92 + ] + } + }, + { + "ID": 1705437987590, + "data": { + "x": [ + -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, 0.872, + 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, -0.312, -0.144, 0.076, + 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, 0.004, -1.244, -0.88, -0.372, -0.04, + 0.252, 0.616, 1.016, 1.332, 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, + -0.312, -0.052, 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, + -1.504, -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, + 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, 1.568, 1.692, + 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, 0.008, 0.68, 1.264, 1.68, + 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 + ], + "y": [ + 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, 0.068, + 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, 0.588, 0.452, + 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, 0.372, 0.344, 0.456, 0.368, + 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, 0.104, 0, 0.484, 0.416, 0.384, 0.356, + 0.32, 0.216, 0.244, 0.284, 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, + 0.336, 0.26, 0.224, 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, + 0.42, 0.396, 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, + 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, 0.044, 0.08, + 0.352, 0.632 + ], + "z": [ + -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, -0.384, + -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, -0.844, -0.48, + -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, -1.596, -1.952, -2.016, + -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, -0.06, -0.324, -0.676, -1.124, + -1.784, -2.04, -1.748, -1.408, -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, + -0.396, -0.836, -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, + 0.112, 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, + -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, -2.04, + -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, -0.472, -0.992, + -1.876, -2.04 + ] + } + }, + { + "ID": 1705437985014, + "data": { + "x": [ + -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, -1.04, -0.82, + -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, 1.188, 0.936, 0.456, 0.132, + -0.5, -1.94, -2.04, -2.04, -1.536, -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, + 0.812, 0.396, -0.2, -1.508, -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, + 0.776, 0.716, 0.46, -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, + 0.148, 0.316, 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, + -0.792, -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, + -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, 0.452, + 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, 0.076, 0.544, + 0.528 + ], + "y": [ + 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, 0.512, + 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, 0.164, 0.048, -0.22, + 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, 0.236, 0.132, 0.064, 0.124, 0.152, + 0.304, 0.248, 0.896, 1.34, 1.08, 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, + 0.236, 0.24, 0.66, 1.26, 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, + 0.124, 0.192, 0.244, 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, + 0.076, 0.092, 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, + 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, 0.672, + 0.496, 0.26, 0.232, 0.228 + ], + "z": [ + -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, -0.892, + -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, -1.144, -1.044, + -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, -0.476, -0.416, -0.764, + -0.892, -1.332, -1.872, -1.152, -0.744, -0.904, -0.704, -0.028, -0.296, + -0.256, -0.592, -0.848, -1, -1.404, -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, + -0.14, -0.3, -0.576, -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, + -0.492, -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, + -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, -1.328, + -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, -0.256, -0.28, -0.424, + -0.608, -0.652, -1.504, -1.412 + ] + } + }, + { + "ID": 1705437822437, + "data": { + "x": [ + -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, -0.896, -0.96, + -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, -0.204, -0.12, -0.164, + -0.248, -0.236, -0.088, -0.644, -1.068, -0.812, -0.644, -0.56, -0.372, -0.292, + -0.272, -0.076, -0.04, -0.08, -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, + -0.828, -0.72, -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, + -0.272, -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, -0.16, + -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, -0.872, -0.844, -0.72, + -0.624, -0.572, -0.48, -0.392, -0.356, -0.292, -0.244, -0.208, -0.288, -0.372, + -0.364, -0.216, -0.464, -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, + -0.468, -0.34, -0.272 + ], + "y": [ + 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, -1.216, + -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, 1.692, 1.224, + 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, -0.204, 0.568, 0.92, 1.304, + 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, -2.04, -2.04, -2.032, -1.288, -0.796, + 0.108, 1.112, 1.588, 1.832, 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, + -2.04, -1.68, -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, + -0.08, -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, + 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, -2.04, -2.004, + -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 + ], + "z": [ + -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, -0.764, + -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, -1.164, -1.144, + -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, -0.172, -0.32, -0.58, -0.672, + -0.952, -0.988, -1.092, -1.16, -1.104, -1.032, -0.924, -0.872, -0.736, -0.256, + 0.276, -0.392, -0.444, -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, + -0.884, -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, + -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, -0.372, + -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, -1.208, -0.992, -0.828, + -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, -0.084, -0.124, -0.208, -0.512, + -0.816, -1.18, -1.332 + ] + } + }, + { + "ID": 1705437819739, + "data": { + "x": [ + -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, -0.928, + -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, 0.14, -0.124, -0.264, + -0.324, -0.376, -0.636, -1.312, -1.216, -1.036, -0.744, -0.256, 0.188, 0.132, + 0.112, 0.164, 0.008, -0.132, -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, + -0.988, -0.648, -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, + -0.336, -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, -0.084, + 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, -0.468, -1.032, + -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, -0.132, -0.22, -0.088, -0.164, + -0.252, -0.32, -0.368, -0.432, -0.604, -0.876, -1.016, -1, -0.92, -0.848 + ], + "y": [ + -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, -1.256, + -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, 1.132, 0.848, + 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, -0.496, -0.092, 0.288, + 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, -1.436, -1.448, -1.124, -0.968, + -0.736, -0.62, 0.052, 0.148, 0.328, 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, + -0.828, -1.444, -1.42, -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, + 0.456, 0.816, 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, + -0.58, -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, + 0.036, -0.832, -1.244, -1.18, -0.9, -0.764, -0.8 + ], + "z": [ + -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, 1.76, 2.04, + 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, -2.04, -1.44, -1.12, -0.88, + -0.312, 0.328, 0.988, 1.404, 1.352, 0.476, -0.152, -1.276, -2.04, -2.04, + -2.04, -2.04, -1.668, -1.176, -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, + 0.636, -0.136, -1.344, -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, + -0.616, 0.18, 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, + -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, 1.32, 0.712, + 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, -1.692, -1.396, -1.04, + -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, 0.68 + ] + } + }, + { + "ID": 1705437816208, + "data": { + "x": [ + -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, 0.436, + 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, -1.112, -0.712, + -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, -0.164, -0.204, -0.144, + 0.252, -0.232, -1.312, -1.132, -0.92, -0.764, -0.432, -0.072, 0.096, 0.184, + 0.156, 0.052, -0.02, -0.068, 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, + -0.576, -0.328, -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, + -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, 0.052, 0.092, + 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, -1.376, -1.048, -0.872, + -0.776, -0.608, -0.324, -0.128, -0.004, 0.024, 0.024, -0.008, -0.12, -0.248, + -0.384, -0.356 + ], + "y": [ + -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, 1.008, + 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, -2.04, -2.04, -1.464, + -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, 1.88, 1.476, 0.836, -0.228, -2.04, + -2.04, -2.04, -2.024, -1.712, -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, + 1.084, 0.184, -2.04, -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, + 1.648, 1.532, 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, + -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, -0.748, + -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, 0.868, 1.392, + 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 + ], + "z": [ + -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, -0.072, + -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, 0.736, 0.544, 0.124, + -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, -1.136, -1, -0.92, -0.82, + -0.736, -0.34, 0.824, 0.26, -0.084, -0.072, -0.176, -0.42, -0.6, -0.852, + -1.132, -1.308, -1.16, -0.968, -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, + -0.364, -0.6, -0.932, -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, + 0.392, 0.376, -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, + -1.024, -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, + -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, -0.972, + -0.912, -0.944 + ] + } + }, + { + "ID": 1705437808407, + "data": { + "x": [ + -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, -0.464, -0.328, + -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, 0.116, 0.068, -0.084, -0.348, + -0.396, -0.628, -0.752, -0.744, -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, + -0.308, -0.324, -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, + -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, -0.94, -0.844, + -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, 0.284, 0.272, 0.288, 0.232, + 0.152, 0.004, -0.28, -0.408, -0.656, -0.836, -1.068, -1.036, -1.016, -0.952, + -0.844, -0.724, -0.68, -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, + 0.292, 0.42, 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 + ], + "y": [ + 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, 0.556, + 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, 0.144, 0.472, + 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, 0.524, 0.688, 0.664, + 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, 0.408, 0.18, 0.468, 0.744, + 0.724, 0.684, 0.88, 1.072, 1.108, 1, 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, + 0.5, 0.404, 0.364, 0.368, 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, + 0.908, 1.184, 1.192, 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, + 0.812, 0.78, 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, + 0.452, 0.732, 0.976 + ], + "z": [ + -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, -0.76, + -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, -0.492, -0.796, + -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, -1.488, -1.508, -1.12, + -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, -0.012, 0, 0.016, 0.028, -0.012, + -0.3, -0.692, -1.236, -1.444, -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, + -1.524, -1.288, -0.888, -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, + 0.144, 0.18, 0.116, -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, + -1.304, -1.312, -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, + -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, -1.076, -1.304 + ] + } + }, + { + "ID": 1705437804840, + "data": { + "x": [ + 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, -0.084, -0.284, + -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, 0.512, 0, -0.616, 0.156, + 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, 0.764, 0.416, 0.432, 0.444, 0.32, + 0.312, 0.348, 0.32, 0.472, 0.292, 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, + 0.708, 0.684, 0.516, 0.46, 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, + 0.032, 0.092, 0.136, 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, + 0.62, 0.572, 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, + 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, 0.72, 0.7, + 0.48, 0.304, 0.052 + ], + "y": [ + -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, 0.144, + 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, 1.212, 0.732, -1.24, + -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, -0.816, -0.044, 0.556, 1.332, + 1.496, 1.224, 1.184, 1.48, 1.644, 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, + -1.224, -1.252, -1.116, -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, + 1.844, 1.728, 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, + -0.976, -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, + 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, -0.936, + -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, 0.248 + ], + "z": [ + -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, -0.296, + -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, -0.284, -0.884, + -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, -0.732, -0.292, -0.34, + -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, -0.752, -0.66, -0.652, -0.716, + -0.632, -0.48, -0.452, -0.532, -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, + -1.24, -1, -0.872, -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, + -0.54, -0.552, -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, + -1.1, -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, + -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, -0.504, -0.652, + -0.704, -0.916 + ] + } + } + ], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + } +] diff --git a/src/test-fixtures/gesture-data.json b/src/test-fixtures/gesture-data.json new file mode 100644 index 000000000..a8f9852be --- /dev/null +++ b/src/test-fixtures/gesture-data.json @@ -0,0 +1,1024 @@ +[ + { + "ID": 1705437793875, + "name": "Shake", + "recordings": [ + { + "ID": 1705437992143, + "data": { + "x": [ + -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, -1.3, + -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, -2.04, -2.008, + -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, -0.232, -1.132, -2.04, + -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, 0.608, 0.332, -0.004, -0.876, + -2.04, -2.04, -2.04, -1.688, -1.396, -0.828, -0.032, 0.36, 0.492, 0.288, + 0.008, -0.492, -1.368, -2.04, -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, + 0.664, 0.544, 0.248, -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, + -0.612, 0.16, 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, + -1.668, -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, + -2.04, -2.04, -1.992 + ], + "y": [ + 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, 0.324, + 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, 0.328, 0.364, + 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, 1.076, -0.116, 0.048, + 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, 0.524, 0.416, 0.252, 0.308, + 0.376, 0.316, 0.356, 0.644, 0.672, 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, + 0.256, 0.272, 0.404, 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, + 0.284, 0.28, 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, + 0.452, 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, 0.288, + 0.204, 0.476, 1.112, 0.2 + ], + "z": [ + -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, -0.096, + -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, -0.108, 0.196, 0.268, + 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, -0.26, -0.092, -0.204, 0.304, 0.78, + 0.364, 0.108, -0.24, -0.528, -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, + 0.552, 0.42, 0.128, 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, + -0.004, -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, + -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, -0.288, + -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, 0.504, 0.196, + -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, -0.072, -0.176, 0.136, + 0.34, 0.7 + ] + } + }, + { + "ID": 1705437989960, + "data": { + "x": [ + -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, -1.496, + -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, 0.864, 0.256, + -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, 0.42, 0.608, 0.488, + 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, -1.588, -1.08, -0.512, -0.028, + 0.216, 0.268, 0.052, -0.38, -0.684, -0.788, -1.056, -1.768, -2.04, -2.04, + -1.344, -0.8, -0.436, -0.172, 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, + -1.544, -2.02, -2.016, -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, + -0.46, -0.816, -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, + 0.06, 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 + ], + "y": [ + 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, 0.456, 0.188, + 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, 0.32, 0.252, 0.208, 0.152, + 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, + 0.596, 0.644, 0.732, 0.58, 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, + 0.38, 0.284, 0.22, 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, + 0.348, 0.34, 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, + 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, 0.556, + 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, 0.328, 0.536, + 0.78, 0.7 + ], + "z": [ + -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, -0.596, -0.696, + -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, -0.888, -0.948, -0.972, -1.192, + -1.148, -0.808, -0.636, -0.42, 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, + -1.032, -0.972, -0.656, -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, + -0.364, -0.744, -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, + 1.116, 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, -0.7, + -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, -1.508, -1.86, + -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, 0.256, -0.24, -0.712, + -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, 0.18, 0.576, 1.292, 0.92 + ] + } + }, + { + "ID": 1705437987590, + "data": { + "x": [ + -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, 0.872, + 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, -0.312, -0.144, 0.076, + 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, 0.004, -1.244, -0.88, -0.372, -0.04, + 0.252, 0.616, 1.016, 1.332, 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, + -0.312, -0.052, 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, + -1.504, -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, + 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, 1.568, 1.692, + 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, 0.008, 0.68, 1.264, 1.68, + 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 + ], + "y": [ + 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, 0.068, + 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, 0.588, 0.452, + 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, 0.372, 0.344, 0.456, 0.368, + 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, 0.104, 0, 0.484, 0.416, 0.384, 0.356, + 0.32, 0.216, 0.244, 0.284, 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, + 0.336, 0.26, 0.224, 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, + 0.42, 0.396, 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, + 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, 0.044, 0.08, + 0.352, 0.632 + ], + "z": [ + -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, -0.384, + -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, -0.844, -0.48, + -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, -1.596, -1.952, -2.016, + -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, -0.06, -0.324, -0.676, -1.124, + -1.784, -2.04, -1.748, -1.408, -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, + -0.396, -0.836, -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, + 0.112, 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, + -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, -2.04, + -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, -0.472, -0.992, + -1.876, -2.04 + ] + } + }, + { + "ID": 1705437985014, + "data": { + "x": [ + -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, -1.04, -0.82, + -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, 1.188, 0.936, 0.456, 0.132, + -0.5, -1.94, -2.04, -2.04, -1.536, -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, + 0.812, 0.396, -0.2, -1.508, -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, + 0.776, 0.716, 0.46, -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, + 0.148, 0.316, 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, + -0.792, -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, + -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, 0.452, + 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, 0.076, 0.544, + 0.528 + ], + "y": [ + 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, 0.512, + 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, 0.164, 0.048, -0.22, + 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, 0.236, 0.132, 0.064, 0.124, 0.152, + 0.304, 0.248, 0.896, 1.34, 1.08, 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, + 0.236, 0.24, 0.66, 1.26, 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, + 0.124, 0.192, 0.244, 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, + 0.076, 0.092, 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, + 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, 0.672, + 0.496, 0.26, 0.232, 0.228 + ], + "z": [ + -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, -0.892, + -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, -1.144, -1.044, + -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, -0.476, -0.416, -0.764, + -0.892, -1.332, -1.872, -1.152, -0.744, -0.904, -0.704, -0.028, -0.296, + -0.256, -0.592, -0.848, -1, -1.404, -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, + -0.14, -0.3, -0.576, -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, + -0.492, -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, + -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, -1.328, + -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, -0.256, -0.28, -0.424, + -0.608, -0.652, -1.504, -1.412 + ] + } + }, + { + "ID": 1705437822437, + "data": { + "x": [ + -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, -0.896, -0.96, + -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, -0.204, -0.12, -0.164, + -0.248, -0.236, -0.088, -0.644, -1.068, -0.812, -0.644, -0.56, -0.372, -0.292, + -0.272, -0.076, -0.04, -0.08, -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, + -0.828, -0.72, -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, + -0.272, -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, -0.16, + -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, -0.872, -0.844, -0.72, + -0.624, -0.572, -0.48, -0.392, -0.356, -0.292, -0.244, -0.208, -0.288, -0.372, + -0.364, -0.216, -0.464, -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, + -0.468, -0.34, -0.272 + ], + "y": [ + 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, -1.216, + -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, 1.692, 1.224, + 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, -0.204, 0.568, 0.92, 1.304, + 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, -2.04, -2.04, -2.032, -1.288, -0.796, + 0.108, 1.112, 1.588, 1.832, 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, + -2.04, -1.68, -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, + -0.08, -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, + 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, -2.04, -2.004, + -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 + ], + "z": [ + -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, -0.764, + -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, -1.164, -1.144, + -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, -0.172, -0.32, -0.58, -0.672, + -0.952, -0.988, -1.092, -1.16, -1.104, -1.032, -0.924, -0.872, -0.736, -0.256, + 0.276, -0.392, -0.444, -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, + -0.884, -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, + -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, -0.372, + -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, -1.208, -0.992, -0.828, + -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, -0.084, -0.124, -0.208, -0.512, + -0.816, -1.18, -1.332 + ] + } + }, + { + "ID": 1705437819739, + "data": { + "x": [ + -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, -0.928, + -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, 0.14, -0.124, -0.264, + -0.324, -0.376, -0.636, -1.312, -1.216, -1.036, -0.744, -0.256, 0.188, 0.132, + 0.112, 0.164, 0.008, -0.132, -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, + -0.988, -0.648, -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, + -0.336, -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, -0.084, + 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, -0.468, -1.032, + -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, -0.132, -0.22, -0.088, -0.164, + -0.252, -0.32, -0.368, -0.432, -0.604, -0.876, -1.016, -1, -0.92, -0.848 + ], + "y": [ + -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, -1.256, + -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, 1.132, 0.848, + 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, -0.496, -0.092, 0.288, + 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, -1.436, -1.448, -1.124, -0.968, + -0.736, -0.62, 0.052, 0.148, 0.328, 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, + -0.828, -1.444, -1.42, -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, + 0.456, 0.816, 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, + -0.58, -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, + 0.036, -0.832, -1.244, -1.18, -0.9, -0.764, -0.8 + ], + "z": [ + -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, 1.76, 2.04, + 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, -2.04, -1.44, -1.12, -0.88, + -0.312, 0.328, 0.988, 1.404, 1.352, 0.476, -0.152, -1.276, -2.04, -2.04, + -2.04, -2.04, -1.668, -1.176, -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, + 0.636, -0.136, -1.344, -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, + -0.616, 0.18, 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, + -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, 1.32, 0.712, + 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, -1.692, -1.396, -1.04, + -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, 0.68 + ] + } + }, + { + "ID": 1705437816208, + "data": { + "x": [ + -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, 0.436, + 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, -1.112, -0.712, + -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, -0.164, -0.204, -0.144, + 0.252, -0.232, -1.312, -1.132, -0.92, -0.764, -0.432, -0.072, 0.096, 0.184, + 0.156, 0.052, -0.02, -0.068, 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, + -0.576, -0.328, -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, + -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, 0.052, 0.092, + 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, -1.376, -1.048, -0.872, + -0.776, -0.608, -0.324, -0.128, -0.004, 0.024, 0.024, -0.008, -0.12, -0.248, + -0.384, -0.356 + ], + "y": [ + -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, 1.008, + 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, -2.04, -2.04, -1.464, + -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, 1.88, 1.476, 0.836, -0.228, -2.04, + -2.04, -2.04, -2.024, -1.712, -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, + 1.084, 0.184, -2.04, -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, + 1.648, 1.532, 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, + -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, -0.748, + -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, 0.868, 1.392, + 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 + ], + "z": [ + -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, -0.072, + -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, 0.736, 0.544, 0.124, + -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, -1.136, -1, -0.92, -0.82, + -0.736, -0.34, 0.824, 0.26, -0.084, -0.072, -0.176, -0.42, -0.6, -0.852, + -1.132, -1.308, -1.16, -0.968, -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, + -0.364, -0.6, -0.932, -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, + 0.392, 0.376, -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, + -1.024, -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, + -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, -0.972, + -0.912, -0.944 + ] + } + }, + { + "ID": 1705437808407, + "data": { + "x": [ + -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, -0.464, -0.328, + -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, 0.116, 0.068, -0.084, -0.348, + -0.396, -0.628, -0.752, -0.744, -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, + -0.308, -0.324, -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, + -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, -0.94, -0.844, + -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, 0.284, 0.272, 0.288, 0.232, + 0.152, 0.004, -0.28, -0.408, -0.656, -0.836, -1.068, -1.036, -1.016, -0.952, + -0.844, -0.724, -0.68, -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, + 0.292, 0.42, 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 + ], + "y": [ + 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, 0.556, + 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, 0.144, 0.472, + 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, 0.524, 0.688, 0.664, + 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, 0.408, 0.18, 0.468, 0.744, + 0.724, 0.684, 0.88, 1.072, 1.108, 1, 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, + 0.5, 0.404, 0.364, 0.368, 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, + 0.908, 1.184, 1.192, 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, + 0.812, 0.78, 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, + 0.452, 0.732, 0.976 + ], + "z": [ + -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, -0.76, + -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, -0.492, -0.796, + -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, -1.488, -1.508, -1.12, + -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, -0.012, 0, 0.016, 0.028, -0.012, + -0.3, -0.692, -1.236, -1.444, -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, + -1.524, -1.288, -0.888, -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, + 0.144, 0.18, 0.116, -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, + -1.304, -1.312, -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, + -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, -1.076, -1.304 + ] + } + }, + { + "ID": 1705437804840, + "data": { + "x": [ + 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, -0.084, -0.284, + -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, 0.512, 0, -0.616, 0.156, + 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, 0.764, 0.416, 0.432, 0.444, 0.32, + 0.312, 0.348, 0.32, 0.472, 0.292, 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, + 0.708, 0.684, 0.516, 0.46, 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, + 0.032, 0.092, 0.136, 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, + 0.62, 0.572, 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, + 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, 0.72, 0.7, + 0.48, 0.304, 0.052 + ], + "y": [ + -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, 0.144, + 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, 1.212, 0.732, -1.24, + -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, -0.816, -0.044, 0.556, 1.332, + 1.496, 1.224, 1.184, 1.48, 1.644, 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, + -1.224, -1.252, -1.116, -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, + 1.844, 1.728, 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, + -0.976, -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, + 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, -0.936, + -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, 0.248 + ], + "z": [ + -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, -0.296, + -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, -0.284, -0.884, + -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, -0.732, -0.292, -0.34, + -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, -0.752, -0.66, -0.652, -0.716, + -0.632, -0.48, -0.452, -0.532, -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, + -1.24, -1, -0.872, -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, + -0.54, -0.552, -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, + -1.1, -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, + -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, -0.504, -0.652, + -0.704, -0.916 + ] + } + } + ], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + }, + { + "ID": 1705437833024, + "name": "Still", + "recordings": [ + { + "ID": 1705437969952, + "data": { + "x": [ + -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, -0.06, + -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, -0.064, -0.06, + -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, -0.06, -0.06, -0.068, + -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, -0.056, -0.056, -0.056, -0.056, + -0.06, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, + -0.064, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, + -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, -0.052, + -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, -0.06, -0.056, + -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, -0.056, -0.068, -0.06, + -0.06, -0.052, -0.06 + ], + "y": [ + 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, 0.772, + 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, 0.772, 0.76, 0.764, + 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, 0.764, 0.76, 0.76, 0.764, + 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, + 0.76, 0.756, 0.756, 0.772, 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, + 0.756, 0.76, 0.764, 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, + 0.764, 0.764, 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, + 0.76, 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, 0.76, + 0.764, 0.752 + ], + "z": [ + -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, -0.692, + -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, -0.688, -0.692, -0.684, + -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, -0.7, -0.7, -0.708, -0.7, -0.696, + -0.692, -0.696, -0.7, -0.7, -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, + -0.7, -0.692, -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, + -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, -0.696, + -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, -0.708, -0.7, -0.712, + -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, -0.696, -0.704, -0.696, -0.708, + -0.7, -0.7, -0.7, -0.704, -0.7, -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, + -0.7 + ] + } + }, + { + "ID": 1705437870493, + "data": { + "x": [ + -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, -0.7, -0.648, + -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, -0.752, -0.704, -0.68, + -0.692, -0.708, -0.72, -0.756, -0.764, -0.768, -0.772, -0.736, -0.748, -0.74, + -0.772, -0.764, -0.752, -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, + -0.748, -0.748, -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, + -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, -0.748, + -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, -0.756, -0.744, -0.732, + -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, -0.772, -0.74, -0.732, -0.74, + -0.764, -0.776, -0.772, -0.764, -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, + -0.812, -0.808 + ], + "y": [ + -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, -0.548, -0.552, + -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, -0.484, -0.5, -0.504, -0.508, + -0.484, -0.492, -0.496, -0.484, -0.48, -0.476, -0.484, -0.492, -0.468, -0.484, + -0.476, -0.484, -0.492, -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, + -0.492, -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, -0.468, + -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, -0.48, -0.492, -0.476, + -0.472, -0.476, -0.484, -0.484, -0.484, -0.484, -0.492, -0.488, -0.456, -0.48, + -0.484, -0.472, -0.476, -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, + -0.472, -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, -0.472, + -0.428, -0.436 + ], + "z": [ + -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, -0.504, + -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, -0.584, -0.56, + -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, -0.576, -0.596, -0.572, + -0.604, -0.592, -0.568, -0.572, -0.56, -0.584, -0.584, -0.612, -0.572, -0.568, + -0.576, -0.588, -0.6, -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, + -0.588, -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, -0.592, + -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, -0.592, -0.624, -0.572, + -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, -0.58, -0.564, -0.552, -0.564, + -0.58, -0.592, -0.588, -0.584, -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, + -0.636, -0.608 + ] + } + }, + { + "ID": 1705437864426, + "data": { + "x": [ + 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, 0.876, + 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, 0.98, 0.952, + 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, 0.956, 0.936, 0.948, + 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, 0.948, 0.94, 0.928, 0.928, 0.94, + 0.96, 0.94, 0.94, 0.94, 0.936, 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, + 0.956, 0.96, 0.952, 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, + 0.944, 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, + 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, 0.968, + 0.968, 0.944 + ], + "y": [ + -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, -0.076, + -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, -0.072, -0.072, -0.08, + -0.076, -0.084, -0.104, -0.088, -0.104, -0.088, -0.116, -0.116, -0.132, + -0.116, -0.108, -0.104, -0.12, -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, + -0.112, -0.1, -0.1, -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, + -0.116, -0.108, -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, + -0.096, -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, + -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, -0.096, + -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, -0.08, -0.1, + -0.096, -0.092, -0.092, -0.112, -0.088 + ], + "z": [ + 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, -0.196, + -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, -0.248, -0.172, -0.14, + -0.136, -0.188, -0.204, -0.14, -0.116, -0.104, -0.18, -0.216, -0.192, -0.168, + -0.132, -0.16, -0.188, -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, + -0.176, -0.152, -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, + -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, -0.168, -0.16, + -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, -0.172, -0.168, -0.168, + -0.18, -0.176, -0.168, -0.16, -0.148, -0.148, -0.164, -0.184, -0.184, -0.176, + -0.144, -0.156, -0.176, -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, + -0.176, -0.14, -0.144 + ] + } + }, + { + "ID": 1705437860385, + "data": { + "x": [ + 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, -0.052, + -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, -0.04, -0.056, -0.052, + -0.052, -0.056, -0.056, -0.044, -0.044, -0.056, -0.052, -0.048, -0.048, + -0.048, -0.048, -0.048, -0.06, -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, + -0.056, -0.056, -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, + -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, -0.056, + -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, -0.052, -0.056, + -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, -0.052, -0.048, -0.056, + -0.056, -0.048, -0.052, -0.056, -0.048, -0.056, -0.048, -0.048, -0.052, + -0.052, -0.048, -0.052, -0.052, -0.044 + ], + "y": [ + 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, 0.224, + 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, 0.228, 0.224, 0.224, + 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, 0.228, 0.228, 0.228, 0.232, 0.236, + 0.232, 0.232, 0.228, 0.22, 0.232, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, + 0.224, 0.224, 0.232, 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, + 0.228, 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, 0.228, + 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, 0.228, 0.228, 0.236, + 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, 0.228, 0.228, 0.228, 0.228, 0.224, + 0.232, 0.232, 0.232 + ], + "z": [ + 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, 0.996, 1.012, + 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, 1.012, 1.012, 1.012, 1, + 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, 1.008, 1.008, 0.996, 0.996, 1.008, + 1.012, 1.008, 1.012, 1.008, 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, + 1.008, 1.004, 1.004, 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, + 1.004, 1, 1, 1, 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, + 1.004, 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, + 0.996, 1.004, 1.008, 1.012, 1.008, 1.004, 1.008, 1.008, 1.008 + ] + } + }, + { + "ID": 1705437854703, + "data": { + "x": [ + -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, -0.104, + -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, -0.064, -0.068, + -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, -0.072, -0.068, -0.072, + -0.068, -0.056, -0.064, -0.068, -0.068, -0.068, -0.068, -0.06, -0.072, -0.076, + -0.072, -0.068, -0.064, -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, + -0.068, -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, + -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, -0.076, -0.06, + -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, -0.064, -0.064, -0.072, -0.068, + -0.064, -0.064, -0.088, -0.06, -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, + -0.064, -0.064, -0.064, -0.068, -0.072, -0.068, -0.06 + ], + "y": [ + 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, 0.96, 0.96, + 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, 0.948, 0.952, 0.944, + 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, 0.944, 0.952, 0.952, 0.952, + 0.952, 0.952, 0.948, 0.948, 0.948, 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, + 0.948, 0.952, 0.948, 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, + 0.948, 0.944, 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, + 0.952, 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, + 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, 0.952, + 0.948, 0.948, 0.948, 0.948 + ], + "z": [ + -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, -0.396, -0.42, + -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, -0.44, -0.432, -0.428, + -0.428, -0.432, -0.428, -0.428, -0.432, -0.428, -0.424, -0.432, -0.436, + -0.428, -0.428, -0.432, -0.432, -0.428, -0.424, -0.436, -0.428, -0.432, + -0.432, -0.436, -0.424, -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, + -0.436, -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, + -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, -0.42, -0.428, + -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, -0.428, -0.436, -0.432, -0.436, + -0.44, -0.428, -0.432, -0.432, -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, + -0.424, -0.424, -0.432, -0.424, -0.432, -0.436 + ] + } + }, + { + "ID": 1705437847993, + "data": { + "x": [ + 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, 0.768, + 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, 0.76, 0.748, + 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, 0.764, 0.76, 0.748, + 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, 0.756, 0.748, 0.752, 0.744, + 0.744, 0.752, 0.756, 0.756, 0.752, 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, + 0.756, 0.752, 0.748, 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, + 0.756, 0.752, 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, + 0.736, 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, + 0.736, 0.74, 0.752, 0.74 + ], + "y": [ + 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, 0.248, + 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, 0.268, 0.268, 0.272, + 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, 0.264, 0.264, 0.268, 0.268, + 0.276, 0.276, 0.256, 0.256, 0.264, 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, + 0.276, 0.276, 0.284, 0.268, 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, + 0.276, 0.28, 0.272, 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, + 0.268, 0.272, 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, + 0.3, 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, + 0.284, 0.276, 0.272, 0.276 + ], + "z": [ + -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, -0.564, -0.552, + -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, -0.568, -0.572, -0.552, -0.548, + -0.54, -0.544, -0.556, -0.56, -0.56, -0.544, -0.536, -0.54, -0.532, -0.556, + -0.564, -0.572, -0.556, -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, + -0.552, -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, -0.536, + -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, -0.54, -0.568, -0.56, + -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, -0.544, -0.548, -0.548, -0.56, + -0.556, -0.544, -0.552, -0.56, -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, + -0.556, -0.56, -0.568, -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, + -0.564, -0.56 + ] + } + } + ], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + }, + { + "ID": 1705437876740, + "name": "Circle", + "recordings": [ + { + "ID": 1705438034019, + "data": { + "x": [ + -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, -0.536, -0.456, + -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, -0.916, -0.956, -1.032, -1.1, + -1.228, -1.284, -1.312, -1.36, -1.356, -1.296, -1.224, -1.096, -0.844, -0.964, + -0.948, -0.984, -0.896, -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, + -0.392, -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, + -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, -0.36, -0.476, + -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, -0.532, -0.532, -0.608, + -0.652, -0.668, -0.652, -0.764, -0.892, -0.92, -0.864, -0.848, -0.884, -0.872, + -0.848, -0.888, -0.952, -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, + -0.732, -0.84 + ], + "y": [ + 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, 0.392, 0.376, + 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, 0.2, 0.152, 0.128, 0.16, + 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, 0.284, 0.372, 0.284, 0.348, 0.412, + 0.408, 0.376, 0.36, 0.396, 0.452, 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, + 0.312, 0.34, 0.356, 0.332, 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, + 0.324, 0.308, 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, + 0.348, 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, + 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, 0.292, + 0.324 + ], + "z": [ + -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, -0.388, + -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, -0.204, -0.272, -0.308, + -0.308, -0.388, -0.424, -0.512, -0.592, -0.66, -0.696, -0.772, -0.812, -0.968, + -0.948, -1.02, -1.168, -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, + -1.12, -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, + -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, -0.556, -0.532, + -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, -0.392, -0.356, -0.364, + -0.388, -0.376, -0.388, -0.42, -0.408, -0.416, -0.376, -0.372, -0.368, -0.412, + -0.464, -0.492, -0.516, -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, + -0.976, -0.972 + ] + } + }, + { + "ID": 1705438029559, + "data": { + "x": [ + -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, -0.644, -0.536, + -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, -0.94, -0.912, -0.888, -0.816, + -0.836, -0.872, -0.844, -0.848, -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, + -0.664, -0.636, -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, + -0.364, -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, 0.068, + -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, -0.172, -0.264, + -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, -0.612, -0.552, -0.576, -0.676, + -0.804, -0.876, -0.864, -0.856, -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, + -0.856, -0.792, -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, + -0.668, -0.588, -0.508 + ], + "y": [ + 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, 0.132, + 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, 0.232, 0.256, + 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, 0.296, 0.292, 0.304, + 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, 0.368, 0.352, 0.356, 0.328, + 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, + 0.24, 0.248, 0.232, 0.196, 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, + 0.176, 0.152, 0.144, 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, + 0.252, 0.188, 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, + 0.276, 0.248, 0.236 + ], + "z": [ + -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, -1.064, + -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, -0.672, -0.712, + -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, -0.524, -0.484, -0.468, -0.516, + -0.54, -0.596, -0.624, -0.6, -0.596, -0.6, -0.624, -0.632, -0.656, -0.632, + -0.7, -0.744, -0.76, -0.768, -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, + -0.78, -1.112, -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, + -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, -1.064, -0.996, + -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, -0.796, -0.808, -0.812, -0.74, + -0.76, -0.72, -0.744, -0.728, -0.704, -0.692, -0.636, -0.644, -0.656, -0.676, + -0.616 + ] + } + }, + { + "ID": 1705438027130, + "data": { + "x": [ + -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, -0.532, + -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, -1.056, -0.968, -0.9, + -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, -0.8, -0.788, -0.748, -0.728, + -0.636, -0.56, -0.532, -0.504, -0.492, -0.432, -0.424, -0.464, -0.428, -0.408, + -0.336, -0.296, -0.252, -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, + -0.168, -0.136, -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, + -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, -0.628, -0.624, + -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, -0.84, -0.864, -0.888, -0.924, + -0.936, -0.88, -0.848, -0.888, -0.88, -0.832, -0.768, -0.724, -0.68, -0.664, + -0.664, -0.62, -0.572 + ], + "y": [ + 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, 0.164, + 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, 0.208, 0.212, + 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, 0.324, 0.316, 0.32, + 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, 0.276, 0.248, 0.232, 0.252, + 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, 0.176, 0.156, 0.136, 0.16, 0.18, + 0.208, 0.224, 0.204, 0.18, 0.188, 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, + 0.104, 0.112, 0.084, 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, + 0.1, 0.14, 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, + 0.268, 0.276, 0.284, 0.296 + ], + "z": [ + -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, -1.176, + -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, -0.964, -0.904, -0.824, + -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, -0.568, -0.56, -0.528, -0.468, + -0.492, -0.488, -0.48, -0.492, -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, + -0.704, -0.732, -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, + -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, -1.208, -1.208, + -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, -1.076, -1.072, -1.028, + -1.028, -0.984, -0.944, -0.968, -0.96, -0.964, -0.964, -0.952, -0.904, -0.856, + -0.808, -0.788, -0.756, -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, + -0.604, -0.592, -0.588 + ] + } + }, + { + "ID": 1705437979405, + "data": { + "x": [ + 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, -0.204, + -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, -0.136, -0.28, -0.392, + -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, -0.464, -0.516, -0.616, -0.648, + -0.6, -0.52, -0.48, -0.468, -0.468, -0.46, -0.432, -0.404, -0.392, -0.304, + -0.256, -0.208, -0.204, -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, + -0.792, -0.776, -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, + -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, -0.104, + 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, 0.092, 0.04, 0, + -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, 0.312, 0.188, 0.036, -0.06, + -0.156, -0.248 + ], + "y": [ + 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, 0.364, + 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, 0.392, 0.364, + 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, 0.312, 0.296, 0.312, + 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, 0.376, 0.316, 0.292, 0.308, + 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, 0.708, 0.728, 0.74, 0.748, 0.716, + 0.68, 0.624, 0.592, 0.564, 0.536, 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, + 0.416, 0.408, 0.42, 0.408, 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, + 0.448, 0.44, 0.464, 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, + 0.348, 0.368, 0.412, 0.484, 0.5 + ], + "z": [ + -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, -0.652, + -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, -1.364, -1.312, + -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, -1.004, -0.996, -1.036, + -1.056, -1.032, -0.996, -0.932, -0.868, -0.864, -0.824, -0.808, -0.764, + -0.708, -0.652, -0.624, -0.632, -0.644, -0.628, -0.612, -0.616, -0.692, + -0.788, -0.884, -0.932, -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, + -0.784, -0.74, -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, + -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, -0.92, + -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, -1.092, -1.132, + -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 + ] + } + }, + { + "ID": 1705437977128, + "data": { + "x": [ + -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, 0.16, + 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, -0.42, -0.596, + -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, -0.8, -0.868, -0.968, + -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, -0.456, -0.396, -0.436, -0.476, + -0.416, -0.364, -0.296, -0.292, -0.304, -0.332, -0.324, -0.272, -0.208, + -0.156, -0.112, -0.08, -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, + 0.168, 0.176, 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, + 0.156, 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, -0.404, + -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, -0.58, -0.52, -0.428 + ], + "y": [ + 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, 0.316, 0.5, + 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, 0.52, 0.44, 0.44, 0.464, + 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, 0.604, 0.584, 0.62, 0.608, 0.58, + 0.56, 0.564, 0.576, 0.592, 0.58, 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, + 0.456, 0.44, 0.4, 0.376, 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, + 0.38, 0.392, 0.436, 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, + 0.244, 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, + 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, 0.428 + ], + "z": [ + -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, -0.816, + -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, -1.18, -1.196, -1.236, + -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, -1.04, -0.964, -0.888, -0.856, + -0.772, -0.696, -0.62, -0.564, -0.6, -0.592, -0.536, -0.48, -0.432, -0.448, + -0.48, -0.484, -0.516, -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, + -0.62, -0.668, -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, + -0.84, -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, + -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, -1.152, + -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, -1.044, -1, -1, -1.012 + ] + } + }, + { + "ID": 1705437922352, + "data": { + "x": [ + -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, 0.064, 0.064, + -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, -0.688, -0.728, -0.772, + -0.784, -0.768, -0.732, -0.696, -0.648, -0.576, -0.544, -0.52, -0.504, -0.492, + -0.492, -0.484, -0.436, -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, + -0.248, -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, + -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, -0.088, + -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, -0.176, -0.176, + -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, -0.312, -0.376, -0.46, -0.436, + -0.348, -0.26, -0.268, -0.376, -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, + -0.456, -0.436, -0.448, -0.46 + ], + "y": [ + 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, 0.884, 1.036, + 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, 1.236, 1.188, 1.176, + 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, 1.036, 1.004, 1.004, 1, 0.992, + 0.98, 0.94, 0.92, 0.896, 0.884, 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, + 0.76, 0.728, 0.712, 0.724, 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, + 0.696, 0.732, 0.752, 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, + 0.932, 0.892, 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, + 0.932, 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, 0.928, + 0.9 + ], + "z": [ + -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, -0.868, -0.848, + -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, -0.504, -0.444, -0.416, -0.38, + -0.36, -0.316, -0.26, -0.2, -0.152, -0.136, -0.116, -0.116, -0.108, -0.044, + -0.004, 0.036, 0.072, 0.072, 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, + 0.096, 0.092, 0.068, -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, + -0.324, -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, -0.56, + -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, -0.628, -0.656, -0.672, + -0.524, -0.46, -0.468, -0.532, -0.596, -0.604, -0.6, -0.644, -0.66, -0.748, + -0.752, -0.76, -0.676, -0.608, -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, + -0.372, -0.38 + ] + } + }, + { + "ID": 1705437913803, + "data": { + "x": [ + 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, 0.86, + 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, 0.204, 0.044, + -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, -0.536, -0.488, -0.404, + -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, -0.548, -0.376, -0.444, -0.496, + -0.496, -0.428, -0.344, -0.724, -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, + 0.372, 0.34, 0.296, 0.22, 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, + 0.456, 0.5, 0.516, 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, + 0.344, 0.252, 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, + -0.16, -0.212, -0.316, -0.412, -0.42, -0.38, -0.296, -0.244 + ], + "y": [ + 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, -0.192, + -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, -0.044, -0.004, + 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, 0.36, 0.396, 0.424, 0.4, + 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, 0.332, 0.344, 0.244, 0.068, -0.096, + -0.092, -0.06, -0.02, 0.452, -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, + 0.06, 0.072, 0.072, 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, + 0.076, 0.04, 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, + 0.304, 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, + 0.536, 0.528, 0.552, 0.604, 0.648, 0.696, 0.64, 0.596 + ], + "z": [ + -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, -0.576, + -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, -0.28, -0.18, -0.248, + -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, -0.524, -0.58, -0.66, -0.78, + -0.808, -0.784, -0.732, -0.76, -0.744, -0.744, -0.8, -0.904, -1.032, -1.076, + -1.14, -1.3, -1.504, -1.62, -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, + -1.08, -1.064, -1.1, -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, + -1.332, -1.292, -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, + -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, -0.736, -0.712, + -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, -0.64, -0.692, -0.724, + -0.752, -0.768, -0.772, -0.792 + ] + } + }, + { + "ID": 1705437908508, + "data": { + "x": [ + -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, -1.108, + -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, -0.868, -0.908, + -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, -0.484, -0.424, -0.42, -0.436, + -0.408, -0.352, -0.272, -0.216, -0.208, -0.236, -0.188, -0.148, -0.104, + -0.084, -0.08, -0.076, -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, + -0.212, -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, -0.76, + -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, -0.972, -0.992, -1, + -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, -0.796, -0.692, -0.564, -0.404, + -0.388, -0.312, -0.32, -0.324, -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, + -0.508, -0.508, -0.508, -0.496 + ], + "y": [ + 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, 0.712, + 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, 0.96, 0.972, 0.96, + 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, 0.876, 0.832, 0.824, 0.8, 0.74, + 0.716, 0.692, 0.672, 0.628, 0.572, 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, + 0.54, 0.528, 0.54, 0.544, 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, + 0.472, 0.492, 0.52, 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, + 0.872, 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, 1, + 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, 0.776, 0.772, + 0.772, 0.776 + ], + "z": [ + -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, -0.036, + -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, -0.464, -0.508, + -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, -0.664, -0.72, -0.704, -0.744, + -0.784, -0.856, -0.884, -0.94, -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, + -0.744, -0.744, -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, + -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, -0.012, + 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, 0.148, 0.088, + 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, -0.592, -0.684, -0.652, + -0.912, -0.672, -0.604, -0.6, -0.584, -0.564, -0.504, -0.504, -0.496, -0.484, + -0.472, -0.46, -0.456, -0.448 + ] + } + }, + { + "ID": 1705437905957, + "data": { + "x": [ + -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, -0.496, + -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, -0.884, -0.936, + -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, -0.964, -0.872, -0.824, -0.796, + -0.832, -0.868, -0.804, -0.716, -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, + -0.516, -0.5, -0.476, -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, + -0.228, -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, -0.244, + -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, -0.928, -0.972, -1.004, + -0.976, -0.928, -0.92, -0.94, -0.952, -0.904, -0.848, -0.828, -0.856, -0.844, + -0.804, -0.78, -0.764, -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, + -0.64, -0.6, -0.588 + ], + "y": [ + 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, 0.92, 0.992, + 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, 0.856, 0.848, 0.812, + 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, 0.724, 0.708, 0.7, 0.692, + 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, + 0.492, 0.472, 0.508, 0.524, 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, + 0.808, 0.812, 0.848, 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, + 0.916, 0.86, 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, + 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, 0.716, + 0.724, 0.712 + ], + "z": [ + -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, -0.612, + -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, -0.356, -0.328, + -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, -0.204, -0.176, -0.148, + -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, 0.188, 0.216, 0.204, 0.164, 0.088, + 0.032, 0.044, 0.04, 0.068, 0.06, 0.016, -0.048, -0.124, -0.208, -0.296, + -0.404, -0.428, -0.4, -0.38, -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, + -0.8, -0.872, -0.88, -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, + -0.272, -0.28, -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, + -0.376, -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, + -0.38, -0.356, -0.32, -0.328 + ] + } + }, + { + "ID": 1705437896254, + "data": { + "x": [ + -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, -0.712, + -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, -0.808, -0.84, -0.944, + -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, -1.076, -1.048, -1.048, -1.112, + -1.144, -1.188, -1.208, -1.228, -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, + -1.02, -0.956, -0.912, -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, + -0.732, -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, + -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, -0.496, -0.48, + -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, -0.404, -0.46, -0.532, + -0.568, -0.484, -0.38, -0.444, -0.572, -0.728, -0.728, -0.616, -0.644, -0.656, + -0.704, -0.688, -0.644, -0.652 + ], + "y": [ + 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, 0.472, + 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, 0.244, 0.232, + 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, 0.056, 0.076, 0.104, + 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, 0.28, 0.308, 0.324, 0.348, 0.38, + 0.404, 0.408, 0.408, 0.384, 0.372, 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, + 0.38, 0.364, 0.412, 0.32, 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, + 0.372, 0.368, 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, + 0.468, 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, 0.32, + 0.32, 0.316 + ], + "z": [ + -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, -0.556, -0.524, + -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, -0.644, -0.72, -0.756, -0.744, + -0.724, -0.7, -0.684, -0.716, -0.716, -0.68, -0.668, -0.656, -0.708, -0.736, + -0.696, -0.712, -0.684, -0.7, -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, + -0.524, -0.496, -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, + -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, -0.392, -0.444, + -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, -0.716, -0.76, -0.76, + -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, -0.728, -0.66, -0.7, -0.756, + -0.86, -0.776, -0.76, -0.812, -0.876, -0.872, -0.908, -0.96, -0.976, -0.996, + -1.032, -0.976, -0.96 + ] + } + } + ], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + } +] diff --git a/src/test-fixtures/test-data-shake-still.json b/src/test-fixtures/test-data-shake-still.json new file mode 100644 index 000000000..23acfa6c3 --- /dev/null +++ b/src/test-fixtures/test-data-shake-still.json @@ -0,0 +1,573 @@ +[ + { + "ID": 1705437793875, + "name": "Shake", + "recordings": [ + { + "ID": 1718706806260, + "data": { + "x": [ + 2.04, 2.04, 2.008, 1.516, 1.152, 0.612, 0.056, -0.528, -1.224, -1.716, -1.632, + -1.072, -0.512, 0.408, 1.4, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.68, 1.304, + 0.836, 0.248, -0.412, -1.152, -1.776, -1.868, -1.384, -0.744, 0.232, 1.376, + 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.584, 1.156, 0.656, 0.096, -0.6, -1.352, + -1.792, -1.644, -0.996, -0.316, 0.612, 1.648, 2.04, 2.04, 2.04, 2.04, 2.04, + 2.04, 1.5, 1.096, 0.6, -0.012, -0.656, -1.272, -1.596, -1.408, -0.872, -0.22, + 0.544, 1.488, 2.036, 2.04, 2.04, 2.04, 2.04, 2.04, 1.948, 1.42, 0.928, 0.428, + -0.216, -0.788, -1.272, -1.604, -1.396, -0.856, -0.268, 0.544, 1.488, 2.04, + 2.04, 2.04 + ], + "y": [ + 1.236, 0.976, 0.7, 0.58, 0.496, 0.316, 0.176, 0.004, -0.152, -0.248, -0.312, + -0.372, -0.356, -0.024, 0.372, 0.816, 1.24, 1.416, 1.416, 1.216, 0.932, 0.688, + 0.552, 0.388, 0.208, -0.028, -0.188, -0.292, -0.388, -0.464, -0.504, -0.268, + 0.248, 0.78, 1.28, 1.52, 1.492, 1.26, 0.924, 0.628, 0.48, 0.332, 0.164, + -0.032, -0.172, -0.288, -0.428, -0.416, -0.34, -0.036, 0.5, 0.996, 1.296, + 1.52, 1.42, 1.168, 0.804, 0.6, 0.484, 0.344, 0.164, -0.028, -0.168, -0.264, + -0.304, -0.3, -0.18, 0.084, 0.512, 0.832, 1.08, 1.228, 1.3, 1.268, 1.064, + 0.764, 0.62, 0.452, 0.312, 0.12, -0.088, -0.184, -0.324, -0.404, -0.392, + -0.308, -0.024, 0.408, 0.844, 1.252, 1.536 + ], + "z": [ + 2.04, 2.024, 1.264, 0.56, 0.044, -0.34, -0.664, -1.004, -1.416, -1.656, + -1.488, -0.916, -0.496, -0.2, 0.276, 0.928, 1.888, 2.04, 2.04, 2.04, 1.448, + 0.712, 0.192, -0.2, -0.576, -0.888, -1.384, -1.7, -1.568, -1.144, -0.636, + -0.208, 0.288, 0.952, 1.824, 2.04, 2.04, 2.04, 1.416, 0.68, 0.112, -0.328, + -0.676, -1, -1.424, -1.568, -1.324, -0.852, -0.404, 0.104, 0.492, 1.204, + 1.948, 2.04, 2.04, 1.98, 1.352, 0.632, 0.124, -0.352, -0.744, -0.988, -1.328, + -1.368, -1.192, -0.812, -0.388, -0.004, 0.368, 0.892, 1.448, 1.944, 2.04, + 2.036, 1.696, 1.1, 0.38, -0.032, -0.4, -0.732, -0.844, -1.196, -1.332, -1.12, + -0.764, -0.428, -0.068, 0.4, 0.708, 1.248, 1.852 + ] + } + }, + { + "ID": 1718706797873, + "data": { + "x": [ + -0.732, -0.276, 0.012, 0.188, 0.368, 0.704, 1.132, 1.32, 1.36, 1.184, 0.884, + 0.628, 0.396, 0.156, -0.036, -0.388, -0.748, -1.092, -0.984, -0.592, -0.212, + 0.056, 0.348, 0.612, 0.892, 1.228, 1.304, 1.272, 1.04, 0.792, 0.628, 0.452, + 0.168, -0.14, -0.5, -0.9, -1.008, -0.736, -0.308, 0.064, 0.284, 0.532, 0.84, + 1.216, 1.42, 1.384, 1.148, 0.928, 0.736, 0.536, 0.328, 0.092, -0.156, -0.504, + -0.872, -1.112, -0.776, -0.324, 0.036, 0.292, 0.472, 0.772, 1.248, 1.452, + 1.488, 1.3, 0.996, 0.736, 0.492, 0.268, 0.032, -0.268, -0.672, -1.088, -1.1, + -0.7, -0.228, 0.116, 0.44, 0.644, 0.928, 1.196, 1.392, 1.328, 1.088, 0.808, + 0.584, 0.368, 0.144, -0.108, -0.48 + ], + "y": [ + -1.64, -0.936, -0.288, 0.332, 1.16, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, + 1.276, 0.536, -0.084, -0.608, -1.136, -1.7, -1.756, -1.372, -0.9, -0.376, + 0.52, 1.464, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.84, 1.208, 0.46, -0.22, + -0.972, -1.644, -1.968, -1.796, -1.296, -0.66, 0.092, 1.008, 2.04, 2.04, 2.04, + 2.04, 2.04, 2.04, 2.036, 1.344, 0.704, 0.104, -0.516, -1.112, -1.672, -2.036, + -1.672, -1.064, -0.416, 0.46, 1.224, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2, + 1.332, 0.704, 0.156, -0.512, -1.228, -1.816, -1.864, -1.424, -0.768, -0.08, + 0.832, 1.788, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.7, 1.14, 0.524, -0.06, + -0.744 + ], + "z": [ + -0.536, -0.372, -0.476, -0.588, -0.716, -0.54, -0.004, 0.384, 0.452, 0.28, + -0.072, -0.48, -0.68, -0.752, -0.636, -0.74, -0.876, -0.952, -0.732, -0.408, + -0.192, -0.224, -0.34, -0.292, -0.12, 0.292, 0.46, 0.464, 0.156, -0.1, -0.376, + -0.652, -0.844, -0.932, -0.868, -0.864, -0.696, -0.424, -0.152, -0.156, -0.42, + -0.512, -0.488, -0.04, 0.272, 0.384, 0.16, -0.076, -0.376, -0.604, -0.668, + -0.616, -0.508, -0.54, -0.688, -0.748, -0.548, -0.404, -0.392, -0.56, -0.616, + -0.372, 0.196, 0.584, 0.764, 0.624, 0.16, -0.24, -0.54, -0.696, -0.78, -0.756, + -0.792, -0.904, -0.8, -0.5, -0.264, -0.352, -0.348, -0.312, 0.024, 0.396, + 0.696, 0.736, 0.476, 0.136, -0.236, -0.516, -0.62, -0.66, -0.684 + ] + } + }, + { + "ID": 1718706789113, + "data": { + "x": [ + 2.04, 2.04, 2.04, 2.04, 1.856, 1.276, 0.684, 0.096, -0.62, -1.172, -1.54, + -1.592, -1.34, -0.828, -0.212, 0.588, 1.496, 2.04, 2.04, 2.04, 2.04, 2.04, + 2.04, 2.04, 1.504, 0.88, 0.268, -0.368, -0.896, -1.464, -1.768, -1.608, + -1.104, -0.452, 0.248, 1.216, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.66, + 1.088, 0.532, -0.084, -0.672, -1.288, -1.596, -1.6, -1.316, -0.76, 0.004, + 0.784, 1.784, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.872, 1.344, 0.716, 0.168, + -0.436, -1.032, -1.548, -1.752, -1.528, -1.028, -0.1, 1.092, 2.04, 2.04, 2.04, + 2.04, 2.04, 2.04, 2.04, 1.616, 1.088, 0.408, -0.196, -0.88, -1.46, -1.552, + -1.448, -0.96 + ], + "y": [ + -0.248, -0.212, -0.224, -0.232, -0.228, -0.196, -0.132, -0.08, 0.036, 0.232, + 0.392, 0.332, 0.136, -0.052, -0.176, -0.232, -0.284, -0.268, -0.196, -0.208, + -0.192, -0.228, -0.276, -0.26, -0.232, -0.16, -0.1, -0.008, 0.184, 0.376, + 0.452, 0.26, -0.016, -0.26, -0.364, -0.356, -0.276, -0.244, -0.172, -0.108, + -0.144, -0.184, -0.232, -0.216, -0.156, -0.092, -0.032, 0.052, 0.232, 0.368, + 0.316, 0.124, -0.124, -0.272, -0.336, -0.368, -0.376, -0.396, -0.344, -0.328, + -0.336, -0.312, -0.264, -0.244, -0.176, -0.124, -0.076, 0.112, 0.404, 0.464, + 0.256, -0.044, -0.3, -0.504, -0.516, -0.52, -0.484, -0.448, -0.42, -0.368, + -0.312, -0.244, -0.184, -0.12, 0.028, 0.228, 0.44, 0.396, 0.216, -0.076 + ], + "z": [ + 1.396, 1.324, 1.044, 0.564, 0.164, -0.164, -0.396, -0.564, -0.652, -0.892, + -1.004, -0.796, -0.512, -0.328, -0.28, -0.176, 0.064, 0.496, 0.88, 1.22, + 1.208, 1.08, 0.8, 0.316, -0.084, -0.404, -0.544, -0.616, -0.844, -0.996, + -0.96, -0.632, -0.28, -0.096, 0.076, 0.12, 0.264, 0.576, 0.952, 1.128, 1.192, + 1.008, 0.608, 0.148, -0.164, -0.42, -0.572, -0.684, -0.852, -0.956, -0.76, + -0.596, -0.336, -0.22, -0.08, 0.104, 0.536, 1, 1.124, 1.12, 1.028, 0.716, + 0.288, -0.02, -0.288, -0.468, -0.68, -0.94, -1.148, -1.116, -0.692, -0.292, + -0.072, -0.052, 0.04, 0.336, 0.8, 1.06, 1.036, 0.748, 0.368, -0.02, -0.308, + -0.496, -0.672, -0.804, -0.984, -0.864, -0.544, -0.2 + ] + } + }, + { + "ID": 1718706779890, + "data": { + "x": [ + -1.548, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.484, -0.74, + -0.032, 0.68, 1.348, 1.836, 2.04, 1.992, 1.532, 0.892, 0.192, -0.668, -1.624, + -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.516, -0.944, -0.368, + 0.308, 0.94, 1.572, 2.04, 2.04, 1.688, 1.068, 0.444, -0.396, -1.288, -2.04, + -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.748, -1.164, -0.664, -0.16, + 0.368, 0.788, 1.16, 1.5, 1.608, 1.38, 0.828, 0.272, -0.352, -1.188, -2.04, + -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.684, -1.044, -0.56, 0.024, 0.768, + 1.476, 1.768, 1.728, 1.38, 0.768, 0.336, -0.392, -1.576, -2.04, -2.04, -2.04, + -2.04, -2.04, -2.04, -2.04, -1.476, -0.944 + ], + "y": [ + 0.588, 0.692, 0.696, 0.708, 0.804, 0.704, 0.58, 0.496, 0.36, 0.248, 0.096, + -0.12, -0.384, -0.58, -0.732, -0.7, -0.464, -0.124, 0.144, 0.32, 0.4, 0.536, + 0.652, 0.632, 0.628, 0.58, 0.508, 0.404, 0.3, 0.224, 0.108, -0.088, -0.232, + -0.436, -0.72, -0.72, -0.532, -0.236, 0.008, 0.272, 0.412, 0.396, 0.416, + 0.516, 0.54, 0.484, 0.404, 0.232, 0.104, 0.048, -0.024, -0.108, -0.216, + -0.264, -0.348, -0.472, -0.556, -0.496, -0.308, -0.168, -0.048, 0.056, 0.236, + 0.204, 0.292, 0.388, 0.356, 0.324, 0.176, 0.076, 0.012, 0.008, -0.032, -0.196, + -0.444, -0.572, -0.544, -0.408, -0.156, -0.044, 0.1, 0.352, 0.212, 0.1, 0.028, + 0.036, 0.092, 0.108, 0.012, -0.072, -0.112 + ], + "z": [ + -0.832, -0.824, -0.684, -0.528, -0.164, 0.232, 0.268, -0.156, -0.62, -0.94, + -0.916, -0.732, -0.428, -0.128, 0.06, 0.26, 0.38, 0.248, -0.052, -0.464, + -0.716, -0.932, -0.736, -0.372, 0.008, 0.252, 0.224, -0.156, -0.484, -0.712, + -0.764, -0.632, -0.392, -0.088, 0.016, 0.104, 0.208, 0.28, 0.14, -0.324, + -0.66, -0.78, -0.572, -0.18, 0.004, 0.04, 0.02, -0.1, -0.344, -0.54, -0.668, + -0.676, -0.508, -0.292, -0.084, 0.052, 0.024, 0.128, 0.148, 0.2, -0.116, + -0.52, -0.62, -0.408, 0.024, 0.328, 0.388, 0.232, -0.116, -0.444, -0.564, + -0.648, -0.664, -0.592, -0.464, -0.2, 0.148, 0.492, 0.664, 0.528, 0.188, + -0.332, -0.632, -0.64, -0.468, -0.252, -0.02, 0.132, 0.028, -0.18, -0.432 + ] + } + }, + { + "ID": 1718706773634, + "data": { + "x": [ + -0.892, -0.768, -0.66, -0.468, -0.236, -0.028, 0.168, 0.56, 1.188, 1.368, + 0.772, 0.044, -0.28, -0.448, -0.552, -0.8, -1.128, -1.216, -1.164, -0.964, + -0.672, -0.404, -0.156, 0.028, 0.276, 0.704, 1.336, 1.444, 0.784, 0.096, + -0.236, -0.408, -0.624, -1.048, -1.364, -1.356, -1.224, -1.016, -0.736, + -0.424, -0.188, 0.032, 0.216, 0.448, 0.804, 1.252, 1.068, 0.544, 0.1, -0.128, + -0.324, -0.712, -1.136, -1.492, -1.496, -1.224, -0.792, -0.436, -0.2, 0.048, + 0.268, 0.66, 1.092, 1.38, 1.036, 0.352, -0.088, -0.32, -0.516, -0.876, -1.232, + -1.28, -1.232, -1.016, -0.732, -0.432, -0.212, 0.036, 0.296, 0.788, 1.2, + 1.192, 0.668, 0.068, -0.224, -0.464, -0.712, -0.916, -1.096, -1.132 + ], + "y": [ + -2.04, -2.04, -2.04, -1.98, -1.152, -0.24, 0.632, 1.668, 2.04, 2.04, 2.04, + 1.408, 0.456, -0.696, -1.936, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, + -1.556, -0.8, -0.112, 0.664, 1.672, 2.04, 2.04, 2.04, 1.104, 0.228, -0.928, + -2.032, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.704, -1.04, -0.416, + 0.284, 1.172, 1.904, 2.04, 2.04, 1.616, 0.724, -0.028, -1.208, -2.04, -2.04, + -2.04, -2.04, -2.04, -2.04, -1.924, -1.132, -0.32, 0.432, 1.292, 2.04, 2.04, + 2.04, 1.516, 0.7, -0.328, -1.54, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, + -1.92, -1.044, -0.168, 0.744, 1.812, 2.04, 2.04, 2.024, 1.124, 0.34, -0.54, + -1.588, -2.04, -2.04, -2.04 + ], + "z": [ + 0.532, 0.264, -0.124, -0.568, -0.896, -0.932, -0.804, -0.712, -0.792, -0.724, + -0.376, -0.108, -0.356, -0.676, -0.76, -0.472, 0.228, 0.66, 0.732, 0.388, + -0.172, -0.628, -0.816, -0.8, -0.712, -0.732, -0.928, -0.856, -0.484, -0.288, + -0.428, -0.728, -0.696, -0.26, 0.312, 0.732, 0.744, 0.42, -0.092, -0.468, + -0.852, -0.928, -0.884, -0.712, -0.64, -0.588, -0.38, -0.18, -0.188, -0.476, + -0.748, -0.56, -0.016, 0.66, 1.004, 0.716, 0.012, -0.556, -0.824, -0.936, + -0.852, -0.768, -0.744, -0.676, -0.432, -0.096, -0.208, -0.584, -0.872, + -0.624, -0.108, 0.268, 0.432, 0.232, -0.232, -0.668, -0.916, -0.996, -0.944, + -0.828, -0.596, -0.304, 0.024, 0.108, -0.204, -0.544, -0.704, -0.704, -0.5, + -0.284 + ] + } + }, + { + "ID": 1718706765639, + "data": { + "x": [ + -0.228, -0.092, -0.24, -0.212, -0.136, -0.076, -0.08, -0.212, -0.316, -0.444, + -0.696, -0.808, -0.86, -0.704, -0.46, -0.368, -0.268, -0.216, -0.16, -0.108, + -0.136, -0.192, -0.272, -0.24, -0.212, -0.108, -0.108, -0.324, -0.548, -0.764, + -0.916, -0.948, -0.836, -0.648, -0.516, -0.46, -0.38, -0.308, -0.296, -0.252, + -0.172, -0.144, -0.072, 0.008, 0.052, 0.036, -0.096, -0.304, -0.596, -0.748, + -0.96, -0.884, -0.656, -0.42, -0.32, -0.308, -0.296, -0.208, -0.212, -0.196, + -0.156, -0.184, -0.232, -0.276, -0.304, -0.288, -0.412, -0.444, -0.424, + -0.548, -0.488, -0.436, -0.304, -0.252, -0.328, -0.312, -0.352, -0.312, -0.28, + -0.312, -0.268, -0.336, -0.292, -0.244, -0.188, -0.24, -0.32, -0.636, -0.564, + -0.672, -0.52 + ], + "y": [ + 0.156, 0.344, 0.556, 0.664, 0.652, 0.508, 0.236, -0.128, -0.548, -1.116, + -1.524, -1.8, -1.712, -1.388, -1.22, -1.036, -0.796, -0.548, -0.284, 0.016, + 0.416, 0.708, 0.864, 0.816, 0.612, 0.304, -0.028, -0.492, -1.044, -1.504, + -1.704, -1.764, -1.592, -1.328, -1.112, -0.9, -0.684, -0.488, -0.284, -0.04, + 0.284, 0.604, 0.804, 0.796, 0.588, 0.28, -0.128, -0.704, -1.212, -1.608, + -1.796, -1.776, -1.536, -1.248, -1.012, -0.748, -0.52, -0.324, -0.072, 0.232, + 0.472, 0.6, 0.672, 0.596, 0.42, 0.132, -0.296, -0.904, -1.492, -1.872, -2.008, + -1.804, -1.52, -1.284, -1.06, -0.864, -0.644, -0.448, -0.148, 0.208, 0.504, + 0.716, 0.788, 0.72, 0.46, 0.124, -0.38, -0.9, -1.348, -1.612, -1.752 + ], + "z": [ + 0.804, 1.304, 1.548, 1.548, 1.308, 0.928, 0.388, -0.352, -1.12, -1.984, -2.04, + -2.04, -2.04, -2.04, -2.04, -2.008, -1.324, -0.656, -0.016, 0.58, 1.032, 1.44, + 1.764, 1.648, 1.28, 0.764, 0.08, -0.788, -1.712, -2.04, -2.04, -2.04, -2.04, + -2.04, -2.04, -1.836, -1.172, -0.472, 0.104, 0.592, 1.116, 1.676, 1.86, 1.72, + 1.248, 0.568, -0.144, -1.124, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, + -1.976, -1.42, -0.844, -0.188, 0.432, 0.872, 1.264, 1.5, 1.46, 1.112, 0.66, + 0.164, -0.528, -1.348, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.852, + -1.204, -0.708, -0.148, 0.456, 1.052, 1.464, 1.624, 1.476, 1.176, 0.716, 0.12, + -0.54, -1.46, -2.04, -2.04, -2.04 + ] + } + }, + { + "ID": 1718706760274, + "data": { + "x": [ + -0.2, -0.024, 0.12, 0.308, 0.468, 0.468, 0.292, 0.004, -0.26, -0.404, -0.484, + -0.6, -0.68, -0.712, -0.712, -0.632, -0.524, -0.408, -0.18, 0.004, 0.176, + 0.304, 0.416, 0.404, 0.28, 0.092, -0.14, -0.296, -0.444, -0.596, -0.668, -0.7, + -0.652, -0.592, -0.52, -0.448, -0.256, -0.092, 0.056, 0.212, 0.432, 0.592, + 0.432, 0.084, -0.2, -0.376, -0.504, -0.628, -0.772, -0.752, -0.76, -0.676, + -0.612, -0.492, -0.3, -0.104, 0.084, 0.264, 0.464, 0.524, 0.336, 0.024, + -0.236, -0.372, -0.528, -0.636, -0.74, -0.784, -0.788, -0.672, -0.524, -0.476, + -0.276, -0.156, 0.036, 0.24, 0.392, 0.476, 0.276, 0.016, -0.236, -0.364, + -0.476, -0.624, -0.752, -0.812, -0.84, -0.72, -0.636, -0.524, -0.252 + ], + "y": [ + -0.528, -0.732, -0.76, -0.728, -0.64, -0.552, -0.476, -0.412, -0.332, -0.276, + -0.26, -0.136, 0.188, 0.536, 0.688, 0.544, 0.192, -0.172, -0.476, -0.664, + -0.712, -0.6, -0.492, -0.364, -0.32, -0.276, -0.232, -0.192, -0.16, -0.012, + 0.18, 0.444, 0.564, 0.516, 0.276, -0.068, -0.372, -0.516, -0.54, -0.512, + -0.48, -0.508, -0.444, -0.36, -0.284, -0.284, -0.28, -0.176, 0.052, 0.344, + 0.544, 0.444, 0.184, -0.16, -0.388, -0.516, -0.596, -0.552, -0.444, -0.42, + -0.316, -0.228, -0.188, -0.228, -0.172, -0.064, 0.076, 0.316, 0.536, 0.496, + 0.236, -0.092, -0.376, -0.516, -0.564, -0.524, -0.456, -0.488, -0.396, -0.328, + -0.344, -0.396, -0.332, -0.164, 0.08, 0.312, 0.44, 0.296, 0.004, -0.304, + -0.524 + ], + "z": [ + 1.668, 0.78, -0.188, -1.188, -1.884, -2.04, -1.928, -1.268, -0.472, 0.396, + 1.224, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.5, 0.64, -0.212, -0.976, + -1.52, -1.804, -1.704, -1.184, -0.496, 0.236, 1.08, 2.008, 2.04, 2.04, 2.04, + 2.04, 2.04, 2.04, 1.644, 0.88, 0.088, -0.884, -1.732, -2.04, -1.964, -1.328, + -0.496, 0.332, 1.204, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.472, 0.708, + -0.176, -0.996, -1.64, -1.944, -1.676, -1.064, -0.396, 0.332, 1.18, 2.04, + 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.556, 0.744, -0.108, -0.96, -1.608, + -1.956, -1.732, -1.116, -0.348, 0.552, 1.556, 2.04, 2.04, 2.04, 2.04, 2.04, + 2.04, 1.9, 1.18 + ] + } + } + ], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + }, + { + "ID": 1705437833024, + "name": "Still", + "recordings": [ + { + "ID": 1718706751151, + "data": { + "x": [ + 0.444, 0.416, 0.412, 0.416, 0.44, 0.428, 0.44, 0.404, 0.432, 0.432, 0.428, + 0.424, 0.416, 0.412, 0.416, 0.424, 0.436, 0.424, 0.412, 0.42, 0.42, 0.428, + 0.428, 0.416, 0.412, 0.456, 0.424, 0.412, 0.404, 0.412, 0.416, 0.428, 0.436, + 0.412, 0.412, 0.396, 0.42, 0.436, 0.42, 0.416, 0.408, 0.4, 0.388, 0.404, + 0.428, 0.408, 0.404, 0.408, 0.42, 0.44, 0.42, 0.4, 0.396, 0.432, 0.424, 0.396, + 0.4, 0.392, 0.392, 0.424, 0.416, 0.408, 0.412, 0.412, 0.412, 0.408, 0.404, + 0.42, 0.424, 0.404, 0.4, 0.416, 0.412, 0.408, 0.388, 0.396, 0.408, 0.392, + 0.396, 0.404, 0.416, 0.396, 0.408, 0.404, 0.42, 0.408, 0.396, 0.38, 0.392, + 0.4, 0.42 + ], + "y": [ + -0.828, -0.82, -0.82, -0.808, -0.804, -0.804, -0.804, -0.828, -0.816, -0.808, + -0.816, -0.808, -0.816, -0.82, -0.816, -0.82, -0.816, -0.82, -0.816, -0.816, + -0.808, -0.808, -0.824, -0.816, -0.824, -0.864, -0.808, -0.812, -0.824, + -0.824, -0.824, -0.812, -0.812, -0.82, -0.824, -0.836, -0.84, -0.816, -0.812, + -0.816, -0.812, -0.804, -0.804, -0.804, -0.836, -0.824, -0.824, -0.828, + -0.824, -0.828, -0.816, -0.816, -0.828, -0.824, -0.816, -0.804, -0.8, -0.804, + -0.812, -0.816, -0.82, -0.816, -0.824, -0.824, -0.824, -0.82, -0.824, -0.82, + -0.828, -0.824, -0.828, -0.836, -0.816, -0.836, -0.812, -0.816, -0.82, -0.816, + -0.812, -0.816, -0.816, -0.828, -0.828, -0.824, -0.824, -0.816, -0.816, + -0.812, -0.82, -0.812, -0.824 + ], + "z": [ + 0.384, 0.384, 0.384, 0.38, 0.392, 0.376, 0.376, 0.372, 0.368, 0.384, 0.384, + 0.392, 0.392, 0.384, 0.376, 0.372, 0.388, 0.4, 0.388, 0.376, 0.38, 0.384, + 0.392, 0.384, 0.376, 0.36, 0.408, 0.408, 0.38, 0.368, 0.364, 0.388, 0.4, + 0.392, 0.4, 0.38, 0.376, 0.392, 0.404, 0.4, 0.4, 0.372, 0.372, 0.38, 0.388, + 0.372, 0.376, 0.38, 0.392, 0.404, 0.404, 0.4, 0.376, 0.384, 0.4, 0.38, 0.388, + 0.372, 0.38, 0.4, 0.396, 0.4, 0.392, 0.396, 0.392, 0.376, 0.384, 0.404, 0.408, + 0.392, 0.384, 0.388, 0.388, 0.404, 0.396, 0.408, 0.396, 0.388, 0.388, 0.408, + 0.404, 0.4, 0.392, 0.396, 0.392, 0.408, 0.4, 0.392, 0.396, 0.392, 0.408 + ] + } + }, + { + "ID": 1718706742225, + "data": { + "x": [ + -1.024, -1.004, -0.976, -0.98, -1.024, -1.04, -1.02, -0.992, -0.98, -1.008, + -1.032, -1.024, -1.012, -1.004, -1.008, -1.016, -1.04, -1.012, -0.984, -0.988, + -1.008, -1.004, -0.996, -1.012, -0.988, -0.976, -0.996, -1.004, -1.016, + -0.984, -0.972, -0.984, -1.008, -1.032, -1.036, -1, -0.992, -1.032, -1.036, + -1.036, -0.996, -0.984, -0.996, -1.008, -1.032, -1.024, -0.984, -0.984, + -1.008, -1.024, -1.036, -1.012, -0.992, -1.024, -1.008, -0.976, -1.016, + -0.984, -0.984, -1.012, -1.036, -1.016, -1.016, -1, -1.016, -1.036, -1.004, + -0.996, -0.988, -1.004, -1.016, -1.012, -1.008, -0.984, -1, -1.016, -1.032, + -0.992, -0.984, -1.012, -1.024, -1.036, -1.004, -0.98, -0.992, -1.032, -1.016, + -0.996, -0.988, -1.016, -1.036 + ], + "y": [ + 0.028, 0.02, 0.008, 0, 0.016, 0.02, 0.032, 0.02, 0.016, 0.016, 0.032, 0.04, + 0.056, 0.056, 0.036, 0.024, 0.012, 0.004, 0.004, 0.004, 0.008, 0.012, 0.016, + 0.02, 0.02, 0.02, 0.02, 0.016, 0.008, 0.008, 0, 0, 0.016, 0.036, 0.032, 0.024, + 0.02, 0.028, 0.028, 0.028, 0.012, 0.02, 0.032, 0.012, 0.008, 0.012, 0.016, + 0.004, 0.012, 0.032, 0.028, 0.032, 0.008, 0.008, 0.012, 0.004, -0.004, 0.012, + 0.008, 0.012, 0.02, 0.028, 0.016, 0.02, 0.004, 0.02, 0.036, 0.048, 0.036, + 0.012, 0.024, 0.032, 0.028, 0.012, 0, 0.004, 0.012, 0.008, 0, 0.012, 0.02, + 0.036, 0.028, 0.016, 0.024, 0.02, 0.012, 0.02, 0.02, 0.032, 0.024 + ], + "z": [ + -0.22, -0.188, -0.172, -0.172, -0.192, -0.216, -0.204, -0.2, -0.188, -0.176, + -0.172, -0.2, -0.192, -0.164, -0.156, -0.184, -0.236, -0.232, -0.224, -0.208, + -0.2, -0.184, -0.196, -0.208, -0.192, -0.196, -0.196, -0.212, -0.204, -0.196, + -0.192, -0.204, -0.208, -0.196, -0.196, -0.164, -0.14, -0.16, -0.176, -0.204, + -0.196, -0.168, -0.144, -0.156, -0.22, -0.212, -0.184, -0.168, -0.204, -0.204, + -0.2, -0.184, -0.164, -0.196, -0.184, -0.172, -0.192, -0.176, -0.152, -0.16, + -0.204, -0.192, -0.204, -0.18, -0.188, -0.208, -0.204, -0.172, -0.144, -0.16, + -0.172, -0.184, -0.18, -0.18, -0.2, -0.224, -0.22, -0.18, -0.16, -0.176, + -0.188, -0.192, -0.188, -0.168, -0.14, -0.164, -0.188, -0.188, -0.18, -0.188, + -0.184 + ] + } + }, + { + "ID": 1718706735565, + "data": { + "x": [ + -0.06, -0.084, -0.088, -0.096, -0.104, -0.088, -0.076, -0.092, -0.084, -0.08, + -0.076, -0.08, -0.08, -0.084, -0.084, -0.068, -0.068, -0.084, -0.088, -0.08, + -0.08, -0.092, -0.088, -0.092, -0.088, -0.092, -0.096, -0.088, -0.088, -0.092, + -0.076, -0.068, -0.072, -0.076, -0.076, -0.072, -0.068, -0.096, -0.096, -0.08, + -0.084, -0.064, -0.068, -0.084, -0.076, -0.08, -0.064, -0.068, -0.084, -0.08, + -0.06, -0.056, -0.056, -0.06, -0.08, -0.068, -0.076, -0.092, -0.084, -0.076, + -0.068, -0.072, -0.088, -0.1, -0.1, -0.084, -0.08, -0.068, -0.08, -0.084, + -0.072, -0.06, -0.06, -0.068, -0.072, -0.08, -0.092, -0.092, -0.084, -0.084, + -0.076, -0.076, -0.08, -0.08, -0.076, -0.08, -0.096, -0.108, -0.096, -0.084 + ], + "y": [ + 1.012, 1.02, 1.02, 1.004, 1, 1.004, 1, 1.016, 1.02, 1.016, 1.02, 1.032, 1.048, + 1.032, 1.036, 1.008, 1.008, 1.02, 1.024, 1.02, 1.008, 1.016, 1.012, 1.012, + 1.016, 1.008, 1.012, 1.012, 1.016, 1.004, 1.004, 1.008, 1.016, 1.04, 1.02, + 1.004, 1.008, 1.056, 1.028, 1.024, 1.028, 1.012, 1.02, 1.02, 1.016, 1.008, + 0.992, 1.008, 1.02, 1.028, 1.016, 1.012, 1.02, 1.024, 1.024, 1.004, 1.012, + 1.028, 1.016, 1.008, 1.012, 1.016, 1.028, 1.024, 1.024, 0.996, 0.996, 1.02, + 1.02, 1.012, 1.004, 1.02, 1.024, 1.02, 1.012, 1.016, 1.02, 1.016, 1.028, + 1.012, 1.004, 1.012, 1.024, 1.016, 1.008, 1, 1.016, 1.028, 1.024, 1.008 + ], + "z": [ + 0.076, 0.076, 0.084, 0.084, 0.076, 0.088, 0.1, 0.064, 0.064, 0.064, 0.08, + 0.096, 0.1, 0.088, 0.072, 0.08, 0.088, 0.072, 0.076, 0.084, 0.092, 0.1, 0.076, + 0.072, 0.08, 0.088, 0.08, 0.068, 0.056, 0.068, 0.08, 0.084, 0.092, 0.088, + 0.088, 0.084, 0.084, 0.06, 0.084, 0.092, 0.108, 0.1, 0.084, 0.052, 0.04, + 0.056, 0.056, 0.072, 0.08, 0.096, 0.1, 0.092, 0.096, 0.088, 0.084, 0.1, 0.096, + 0.08, 0.08, 0.072, 0.076, 0.096, 0.108, 0.092, 0.08, 0.076, 0.092, 0.096, + 0.076, 0.056, 0.072, 0.088, 0.084, 0.1, 0.084, 0.088, 0.08, 0.1, 0.096, 0.088, + 0.088, 0.096, 0.1, 0.096, 0.08, 0.08, 0.088, 0.096, 0.076, 0.064 + ] + } + }, + { + "ID": 1718706729969, + "data": { + "x": [ + 0.992, 0.996, 0.988, 0.992, 1, 0.996, 0.956, 0.96, 0.972, 1, 0.996, 0.972, + 0.984, 1.004, 1.012, 1.02, 1, 0.984, 0.988, 0.98, 0.984, 0.972, 0.988, 0.968, + 1.028, 1.004, 1.028, 1, 0.98, 0.972, 1.012, 1.008, 0.992, 0.976, 0.992, 0.992, + 1.008, 0.988, 0.972, 0.972, 0.988, 1.008, 1, 0.956, 0.984, 1, 1, 0.996, 0.996, + 0.992, 0.996, 0.992, 0.984, 0.98, 0.988, 0.98, 1.004, 0.996, 0.964, 0.956, + 1.008, 1.016, 0.98, 0.984, 1, 1.004, 1.008, 0.984, 0.988, 0.984, 0.984, 1.016, + 0.984, 0.984, 0.984, 1, 0.992, 0.992, 0.976, 0.972, 1.016, 1, 0.992, 0.968, + 0.976, 1.012, 1.012, 0.988, 0.968, 0.968 + ], + "y": [ + 0.012, 0.02, 0.024, 0.024, 0.02, 0.032, 0.04, 0.044, 0.04, 0.004, 0.024, + 0.008, 0.004, -0.004, 0.004, 0, 0, 0, 0.008, 0.02, 0.024, 0.004, 0.004, 0.008, + 0.048, 0.004, 0, -0.008, 0, 0.004, -0.008, 0.004, -0.004, 0.012, 0, 0, 0, + -0.008, -0.004, 0.016, 0.004, -0.004, 0, 0, 0.004, 0.012, 0.008, -0.004, 0, 0, + 0.008, -0.008, -0.008, 0.004, 0.02, 0.008, -0.008, -0.008, -0.012, 0.004, + -0.004, -0.016, -0.012, -0.004, -0.004, -0.012, -0.016, -0.004, 0.004, 0.008, + 0, -0.02, -0.032, -0.016, -0.012, -0.008, -0.004, 0, -0.02, -0.008, -0.008, + -0.008, -0.008, -0.012, 0, -0.016, -0.008, -0.012, -0.008, 0 + ], + "z": [ + -0.16, -0.14, -0.152, -0.152, -0.156, -0.16, -0.172, -0.168, -0.16, -0.168, + -0.16, -0.164, -0.152, -0.148, -0.14, -0.14, -0.152, -0.164, -0.168, -0.164, + -0.164, -0.172, -0.16, -0.168, -0.152, -0.128, -0.14, -0.144, -0.148, -0.168, + -0.156, -0.152, -0.14, -0.164, -0.16, -0.152, -0.136, -0.156, -0.164, -0.148, + -0.144, -0.136, -0.132, -0.164, -0.168, -0.152, -0.152, -0.152, -0.144, + -0.148, -0.148, -0.16, -0.168, -0.164, -0.136, -0.144, -0.14, -0.148, -0.156, + -0.176, -0.156, -0.144, -0.156, -0.14, -0.148, -0.164, -0.148, -0.144, -0.136, + -0.14, -0.16, -0.136, -0.16, -0.152, -0.152, -0.148, -0.136, -0.144, -0.16, + -0.168, -0.144, -0.132, -0.144, -0.156, -0.168, -0.152, -0.14, -0.144, -0.16, + -0.156 + ] + } + }, + { + "ID": 1718706724105, + "data": { + "x": [ + -0.036, -0.056, -0.044, -0.028, -0.028, -0.044, -0.06, -0.036, -0.024, -0.02, + -0.056, -0.06, -0.032, -0.032, -0.032, -0.044, -0.04, -0.032, -0.032, -0.052, + -0.052, -0.036, -0.024, -0.028, -0.048, -0.072, -0.056, -0.056, -0.024, + -0.016, -0.052, -0.072, -0.032, 0.088, -0.036, -0.056, -0.048, -0.036, -0.028, + -0.036, -0.036, -0.04, -0.032, -0.032, -0.04, -0.04, -0.044, -0.052, -0.024, + -0.044, -0.048, -0.056, -0.04, -0.032, -0.032, -0.04, -0.04, -0.052, -0.044, + -0.028, -0.036, -0.048, -0.04, -0.052, -0.052, -0.032, -0.036, -0.052, -0.052, + -0.048, -0.052, -0.036, -0.036, -0.032, -0.044, -0.036, -0.06, -0.052, -0.04, + -0.04, -0.036, -0.028, -0.052, -0.048, -0.04, -0.04, -0.048, -0.076, -0.072, + -0.044 + ], + "y": [ + -0.984, -0.976, -0.98, -0.98, -0.984, -0.98, -0.992, -0.996, -0.992, -0.996, + -0.996, -1, -1.008, -0.992, -0.992, -1, -1, -0.988, -0.98, -0.964, -0.972, + -0.992, -1, -1, -0.996, -0.984, -0.98, -0.992, -1.008, -1.024, -1.016, -0.992, + -0.992, -1.072, -0.972, -0.988, -0.992, -0.988, -0.984, -0.984, -0.992, + -1.004, -0.992, -0.992, -0.996, -0.992, -0.984, -0.984, -0.988, -0.992, + -1.008, -0.996, -1, -1, -0.992, -0.988, -0.996, -0.996, -0.988, -0.984, + -0.992, -0.996, -0.996, -0.984, -0.98, -0.992, -0.988, -0.98, -0.988, -0.996, + -0.996, -0.996, -0.988, -0.992, -0.992, -0.98, -0.98, -0.984, -0.996, -1.004, + -1, -0.996, -0.996, -0.988, -0.984, -0.984, -0.988, -0.984, -0.988, -0.976 + ], + "z": [ + -0.228, -0.228, -0.216, -0.192, -0.184, -0.204, -0.216, -0.212, -0.188, + -0.184, -0.2, -0.216, -0.22, -0.204, -0.216, -0.212, -0.228, -0.224, -0.2, + -0.208, -0.208, -0.2, -0.2, -0.2, -0.204, -0.208, -0.204, -0.204, -0.196, + -0.196, -0.2, -0.236, -0.22, -0.22, -0.188, -0.208, -0.216, -0.22, -0.2, + -0.204, -0.224, -0.232, -0.212, -0.2, -0.208, -0.216, -0.208, -0.212, -0.212, + -0.22, -0.212, -0.228, -0.204, -0.216, -0.208, -0.212, -0.216, -0.228, -0.228, + -0.22, -0.2, -0.216, -0.232, -0.212, -0.208, -0.22, -0.208, -0.196, -0.208, + -0.22, -0.22, -0.216, -0.22, -0.212, -0.212, -0.2, -0.212, -0.228, -0.232, + -0.216, -0.212, -0.208, -0.208, -0.216, -0.204, -0.196, -0.204, -0.228, + -0.228, -0.212 + ] + } + }, + { + "ID": 1718706718209, + "data": { + "x": [ + -0.028, -0.044, -0.068, -0.068, -0.056, -0.028, -0.036, -0.064, -0.064, + -0.036, -0.04, -0.06, -0.06, -0.052, -0.032, -0.04, -0.048, -0.076, -0.064, + -0.036, -0.032, -0.048, -0.044, -0.052, -0.04, -0.032, -0.052, -0.064, -0.052, + -0.04, -0.044, -0.056, -0.052, -0.04, -0.048, -0.064, -0.048, -0.052, -0.04, + -0.056, -0.048, -0.056, -0.048, -0.08, -0.04, 0.04, -0.036, -0.036, -0.044, + -0.044, -0.04, -0.044, -0.048, -0.044, -0.048, -0.036, -0.044, -0.052, -0.044, + -0.044, -0.032, -0.048, -0.06, -0.044, -0.036, -0.048, -0.06, -0.056, -0.052, + -0.04, -0.04, -0.048, -0.056, -0.064, -0.06, -0.044, -0.048, -0.076, -0.052, + -0.044, -0.048, -0.056, -0.064, -0.06, -0.06, -0.06, -0.068, -0.06, -0.048, + -0.044 + ], + "y": [ + -0.024, -0.028, -0.024, -0.024, -0.016, -0.024, -0.028, -0.032, -0.02, -0.028, + -0.032, -0.024, -0.024, -0.016, -0.02, -0.02, -0.024, -0.016, -0.016, -0.012, + -0.032, -0.036, -0.036, -0.032, -0.008, -0.016, -0.024, -0.02, -0.02, -0.028, + -0.02, -0.02, -0.028, -0.02, -0.032, -0.028, -0.032, -0.028, -0.028, -0.02, + -0.016, -0.024, -0.024, -0.028, -0.028, -0.072, -0.044, -0.04, -0.04, -0.024, + -0.024, -0.024, -0.044, -0.024, -0.02, -0.028, -0.04, -0.04, -0.044, -0.028, + -0.028, -0.032, -0.04, -0.04, -0.02, -0.016, -0.024, -0.036, -0.032, -0.036, + -0.036, -0.04, -0.04, -0.032, -0.028, -0.028, -0.04, -0.032, -0.04, -0.036, + -0.036, -0.04, -0.032, -0.028, -0.036, -0.032, -0.032, -0.032, -0.036, -0.048 + ], + "z": [ + 1.016, 1.016, 1.008, 1.012, 1, 1.028, 1.028, 1.02, 1.008, 1.008, 1.024, 1.024, + 1.016, 1.016, 1.028, 1.016, 1.02, 1.02, 0.98, 1.028, 1.04, 1.02, 1.032, 1.012, + 1.02, 1.016, 1.012, 1.016, 1.044, 1.016, 1.02, 0.972, 1.004, 1.036, 1.048, + 1.004, 1.008, 1.004, 1, 0.988, 1.012, 1.032, 1.016, 1.032, 1.008, 0.92, 1.036, + 1.016, 1.032, 1.024, 1.036, 1.012, 1.008, 1.024, 1.012, 1.024, 1.012, 1, 1, + 1.008, 1.056, 1.032, 0.988, 1.008, 1.032, 1.02, 1.024, 1.004, 1.004, 1.012, + 1.044, 1.028, 1.016, 1.036, 1.02, 1.024, 1.028, 1.008, 1.02, 1.004, 1.008, + 1.012, 1.004, 1.012, 1.032, 1.036, 1.036, 1.02, 1.02, 1.02 + ] + } + }, + { + "ID": 1718706711804, + "data": { + "x": [ + 0.004, -0.004, 0, 0, 0, 0.024, 0.012, 0, 0.004, -0.008, 0.004, 0.024, 0.012, + 0.004, 0, 0.004, 0.008, -0.008, 0.004, -0.012, 0.016, 0.02, 0.012, 0.004, 0, + 0.008, 0.024, 0.02, 0.012, 0, 0.004, 0.004, 0.004, 0.008, 0.008, 0.004, 0.004, + 0, 0, 0.016, 0.016, 0.012, 0.008, -0.004, -0.004, 0.016, 0.004, 0.008, -0.008, + 0, 0.012, 0.012, 0, 0, 0.004, 0.008, 0.004, 0.008, 0.008, 0.028, 0.012, + -0.004, -0.02, -0.004, 0, -0.004, -0.008, -0.004, 0.004, 0, 0.004, -0.004, + 0.004, 0.008, 0.004, -0.004, 0, -0.008, -0.004, 0.004, -0.004, -0.004, -0.004, + 0.004, -0.004, 0.004, 0.004, 0, -0.004, -0.004, 0.008 + ], + "y": [ + -0.04, -0.048, -0.04, -0.056, -0.036, -0.048, -0.048, -0.048, -0.048, -0.056, + -0.044, -0.048, -0.048, -0.044, -0.048, -0.048, -0.044, -0.028, -0.032, + -0.032, -0.04, -0.068, -0.036, -0.044, -0.044, -0.044, -0.052, -0.04, -0.044, + -0.036, -0.036, -0.044, -0.028, -0.04, -0.032, -0.04, -0.052, -0.04, -0.044, + -0.044, -0.044, -0.04, -0.04, -0.04, -0.048, -0.052, -0.048, -0.044, -0.036, + -0.036, -0.04, -0.04, -0.04, -0.044, -0.044, -0.048, -0.044, -0.036, -0.036, + -0.044, -0.044, -0.044, -0.056, -0.06, -0.052, -0.044, -0.04, -0.048, -0.06, + -0.052, -0.056, -0.036, -0.048, -0.044, -0.052, -0.044, -0.044, -0.036, + -0.048, -0.048, -0.04, -0.044, -0.04, -0.052, -0.048, -0.052, -0.044, -0.044, + -0.052, -0.048, -0.048 + ], + "z": [ + -1.06, -1.06, -1.068, -1.068, -1.052, -1.028, -1.032, -1.06, -1.096, -1.092, + -1.068, -1.064, -1.072, -1.072, -1.084, -1.068, -1.056, -1.064, -1.056, + -1.056, -1.048, -1.048, -1.064, -1.072, -1.08, -1.076, -1.052, -1.056, -1.06, + -1.056, -1.084, -1.064, -1.06, -1.064, -1.072, -1.072, -1.068, -1.08, -1.076, + -1.056, -1.056, -1.064, -1.06, -1.076, -1.08, -1.052, -1.072, -1.064, -1.06, + -1.052, -1.064, -1.064, -1.072, -1.076, -1.064, -1.064, -1.068, -1.064, + -1.056, -1.052, -1.044, -1.068, -1.092, -1.072, -1.064, -1.064, -1.064, -1.06, + -1.068, -1.068, -1.076, -1.064, -1.068, -1.06, -1.068, -1.064, -1.064, -1.056, + -1.064, -1.064, -1.068, -1.068, -1.068, -1.064, -1.056, -1.064, -1.056, + -1.068, -1.084, -1.064, -1.068 + ] + } + } + ], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + }, + { + "ID": 1705437876740, + "name": "Circle", + "recordings": [], + "output": {}, + "confidence": { + "currentConfidence": 0, + "requiredConfidence": 0.8, + "isConfident": false + } + } +] From a2adb5bdba6613db36f18be82325b0845d1bef46 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 14:04:25 +0100 Subject: [PATCH 034/172] Training status view and error dialog --- src/components/TrainingErrorDialog.tsx | 51 ++++++++++++++ src/components/TrainingStatusView.tsx | 92 +++++++++++++++++++++++--- 2 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 src/components/TrainingErrorDialog.tsx diff --git a/src/components/TrainingErrorDialog.tsx b/src/components/TrainingErrorDialog.tsx new file mode 100644 index 000000000..c4a491d11 --- /dev/null +++ b/src/components/TrainingErrorDialog.tsx @@ -0,0 +1,51 @@ +import { + Heading, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; + +interface TrainingErrorDialogProps { + isOpen: boolean; + onClose: () => void; +} + +const TrainingErrorDialog = ({ isOpen, onClose }: TrainingErrorDialogProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default TrainingErrorDialog; diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx index e291403a3..f651e6139 100644 --- a/src/components/TrainingStatusView.tsx +++ b/src/components/TrainingStatusView.tsx @@ -1,22 +1,60 @@ -import { Button, Heading, Text } from "@chakra-ui/react"; -import { ReactNode, useCallback } from "react"; +import { + Button, + HStack, + Heading, + Progress, + Text, + useDisclosure, +} from "@chakra-ui/react"; +import { ReactNode, useCallback, useState } from "react"; import { FormattedMessage } from "react-intl"; -import { useGestureActions } from "../gestures-hooks"; +import { useGestureActions, useGestureData } from "../gestures-hooks"; import { useNavigate } from "react-router"; import { createStepPageUrl } from "../urls"; import TrainingButton from "./TrainingButton"; +import { trainModel } from "../ml"; +import TrainingErrorDialog from "./TrainingErrorDialog"; + +enum TrainingStatus { + NotStarted, + InProgress, + Complete, +} const TrainingStatusView = () => { const navigate = useNavigate(); + const [gestureData] = useGestureData(); const actions = useGestureActions(); const isSufficientData = actions.isSufficientForTraining(); + const [trainStatus, setTrainStatus] = useState( + TrainingStatus.NotStarted + ); + const [trainProgress, setTrainProgress] = useState(0); + const trainErrorDialog = useDisclosure(); const navigateToDataPage = useCallback(() => { navigate(createStepPageUrl("add-data")); }, [navigate]); - // TODO: Train - const handleTrain = useCallback(() => {}, []); + const navigateToTestModelPage = useCallback(() => { + navigate(createStepPageUrl("test-model")); + }, [navigate]); + + const handleTrain = useCallback(async () => { + setTrainStatus(TrainingStatus.InProgress); + await trainModel({ + data: gestureData.data, + onTraining: (progress) => { + setTrainProgress(progress); + }, + onTrainEnd: () => { + setTrainStatus(TrainingStatus.Complete); + }, + onError: () => { + setTrainStatus(TrainingStatus.NotStarted); + }, + }); + }, [gestureData.data]); if (!isSufficientData) { return ( @@ -30,11 +68,45 @@ const TrainingStatusView = () => { ); } - return ( - - - - ); + + switch (trainStatus) { + case TrainingStatus.NotStarted: + return ( + <> + + + + + + ); + case TrainingStatus.InProgress: + return ( + + + + ); + case TrainingStatus.Complete: + return ( + + + + + + + ); + } }; interface TrainingStatusSectionProps { From a7a74850cc9a8649a87e7fd896619ec27d960240 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 14:10:53 +0100 Subject: [PATCH 035/172] Fix lint for ml.test.ts --- src/ml.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ml.test.ts b/src/ml.test.ts index 8dd666e24..b87e596a1 100644 --- a/src/ml.test.ts +++ b/src/ml.test.ts @@ -8,6 +8,7 @@ */ import * as tf from "@tensorflow/tfjs"; +import { vi } from "vitest"; import { GestureData } from "./gestures-hooks"; import { prepareFeaturesAndLabels, trainModel } from "./ml"; import gestureDataBadLabels from "./test-fixtures/gesture-data-bad-labels.json"; @@ -17,7 +18,7 @@ import testdataShakeStill from "./test-fixtures/test-data-shake-still.json"; let tensorFlowModel: tf.LayersModel | void; beforeAll(async () => { // No webgl in tests running in node. - tf.setBackend("cpu"); + await tf.setBackend("cpu"); // This creates determinism in the model training step. const randomSpy = vi.spyOn(Math, "random"); @@ -51,7 +52,7 @@ const getModelResults = (data: GestureData[]) => { }; describe("Model tests", () => { - test("returns acceptable results on training data", async () => { + test("returns acceptable results on training data", () => { const { tensorFlowResultAccuracy, tensorflowPredictionResult, labels } = getModelResults(gestureData); const d = labels[0].length; // dimensions @@ -66,7 +67,7 @@ describe("Model tests", () => { // The action names don't matter, the order of the actions in the data.json file does. // Training data is shake, still, circle. This data is still, circle, shake. - test("returns incorrect results on wrongly labelled training data", async () => { + test("returns incorrect results on wrongly labelled training data", () => { const { tensorFlowResultAccuracy, tensorflowPredictionResult, labels } = getModelResults(gestureDataBadLabels); const d = labels[0].length; // dimensions @@ -79,7 +80,7 @@ describe("Model tests", () => { expect(tensorFlowResultAccuracy).toBe("0.0000"); }); - test("returns correct results on testing data", async () => { + test("returns correct results on testing data", () => { const { tensorFlowResultAccuracy } = getModelResults(testdataShakeStill); // The model thinks two samples of still are circle. // 14 samples; 1.0 / 14 = 0.0714; 0.0714 * 12 correct inferences = 0.8571 From 87a4388f11e2e804dd9b6935bb5d24369989fa54 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 14:13:07 +0100 Subject: [PATCH 036/172] Run prettier on test fixtures --- .../gesture-data-bad-labels.json | 1499 +++++++++-------- src/test-fixtures/gesture-data.json | 1499 +++++++++-------- src/test-fixtures/test-data-shake-still.json | 805 ++++----- 3 files changed, 2025 insertions(+), 1778 deletions(-) diff --git a/src/test-fixtures/gesture-data-bad-labels.json b/src/test-fixtures/gesture-data-bad-labels.json index 79f01fc5a..05fe7c905 100644 --- a/src/test-fixtures/gesture-data-bad-labels.json +++ b/src/test-fixtures/gesture-data-bad-labels.json @@ -7,39 +7,42 @@ "ID": 1705437969952, "data": { "x": [ - -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, -0.06, - -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, -0.064, -0.06, - -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, -0.06, -0.06, -0.068, - -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, -0.056, -0.056, -0.056, -0.056, - -0.06, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, - -0.064, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, - -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, -0.052, - -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, -0.06, -0.056, - -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, -0.056, -0.068, -0.06, - -0.06, -0.052, -0.06 + -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, + -0.06, -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, + -0.064, -0.06, -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, + -0.06, -0.06, -0.068, -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, + -0.056, -0.056, -0.056, -0.056, -0.06, -0.06, -0.056, -0.064, + -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, -0.064, -0.06, + -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, + -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, + -0.052, -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, + -0.06, -0.056, -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, + -0.056, -0.068, -0.06, -0.06, -0.052, -0.06 ], "y": [ - 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, 0.772, - 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, 0.772, 0.76, 0.764, - 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, 0.764, 0.76, 0.76, 0.764, - 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, - 0.76, 0.756, 0.756, 0.772, 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, - 0.756, 0.76, 0.764, 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, - 0.764, 0.764, 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, - 0.76, 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, 0.76, - 0.764, 0.752 + 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, + 0.772, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, + 0.772, 0.76, 0.764, 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, + 0.764, 0.76, 0.76, 0.764, 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, + 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, 0.76, 0.756, 0.756, 0.772, + 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.756, 0.76, 0.764, + 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, 0.764, 0.764, + 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, 0.76, + 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, + 0.76, 0.764, 0.752 ], "z": [ - -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, -0.692, - -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, -0.688, -0.692, -0.684, - -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, -0.7, -0.7, -0.708, -0.7, -0.696, - -0.692, -0.696, -0.7, -0.7, -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, - -0.7, -0.692, -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, - -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, -0.696, - -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, -0.708, -0.7, -0.712, - -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, -0.696, -0.704, -0.696, -0.708, - -0.7, -0.7, -0.7, -0.704, -0.7, -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, - -0.7 + -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, + -0.692, -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, + -0.688, -0.692, -0.684, -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, + -0.7, -0.7, -0.708, -0.7, -0.696, -0.692, -0.696, -0.7, -0.7, + -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, -0.7, -0.692, + -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, + -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, + -0.696, -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, + -0.708, -0.7, -0.712, -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, + -0.696, -0.704, -0.696, -0.708, -0.7, -0.7, -0.7, -0.704, -0.7, + -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, -0.7 ] } }, @@ -47,40 +50,44 @@ "ID": 1705437870493, "data": { "x": [ - -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, -0.7, -0.648, - -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, -0.752, -0.704, -0.68, - -0.692, -0.708, -0.72, -0.756, -0.764, -0.768, -0.772, -0.736, -0.748, -0.74, - -0.772, -0.764, -0.752, -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, - -0.748, -0.748, -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, - -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, -0.748, - -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, -0.756, -0.744, -0.732, - -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, -0.772, -0.74, -0.732, -0.74, - -0.764, -0.776, -0.772, -0.764, -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, - -0.812, -0.808 + -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, + -0.7, -0.648, -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, + -0.752, -0.704, -0.68, -0.692, -0.708, -0.72, -0.756, -0.764, + -0.768, -0.772, -0.736, -0.748, -0.74, -0.772, -0.764, -0.752, + -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, -0.748, -0.748, + -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, + -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, + -0.748, -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, + -0.756, -0.744, -0.732, -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, + -0.772, -0.74, -0.732, -0.74, -0.764, -0.776, -0.772, -0.764, + -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, -0.812, -0.808 ], "y": [ - -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, -0.548, -0.552, - -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, -0.484, -0.5, -0.504, -0.508, - -0.484, -0.492, -0.496, -0.484, -0.48, -0.476, -0.484, -0.492, -0.468, -0.484, - -0.476, -0.484, -0.492, -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, - -0.492, -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, -0.468, - -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, -0.48, -0.492, -0.476, - -0.472, -0.476, -0.484, -0.484, -0.484, -0.484, -0.492, -0.488, -0.456, -0.48, - -0.484, -0.472, -0.476, -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, - -0.472, -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, -0.472, - -0.428, -0.436 + -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, + -0.548, -0.552, -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, + -0.484, -0.5, -0.504, -0.508, -0.484, -0.492, -0.496, -0.484, -0.48, + -0.476, -0.484, -0.492, -0.468, -0.484, -0.476, -0.484, -0.492, + -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, -0.492, + -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, + -0.468, -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, + -0.48, -0.492, -0.476, -0.472, -0.476, -0.484, -0.484, -0.484, + -0.484, -0.492, -0.488, -0.456, -0.48, -0.484, -0.472, -0.476, + -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, -0.472, + -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, + -0.472, -0.428, -0.436 ], "z": [ - -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, -0.504, - -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, -0.584, -0.56, - -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, -0.576, -0.596, -0.572, - -0.604, -0.592, -0.568, -0.572, -0.56, -0.584, -0.584, -0.612, -0.572, -0.568, - -0.576, -0.588, -0.6, -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, - -0.588, -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, -0.592, - -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, -0.592, -0.624, -0.572, - -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, -0.58, -0.564, -0.552, -0.564, - -0.58, -0.592, -0.588, -0.584, -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, - -0.636, -0.608 + -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, + -0.504, -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, + -0.584, -0.56, -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, + -0.576, -0.596, -0.572, -0.604, -0.592, -0.568, -0.572, -0.56, + -0.584, -0.584, -0.612, -0.572, -0.568, -0.576, -0.588, -0.6, + -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, -0.588, + -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, + -0.592, -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, + -0.592, -0.624, -0.572, -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, + -0.58, -0.564, -0.552, -0.564, -0.58, -0.592, -0.588, -0.584, + -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, -0.636, -0.608 ] } }, @@ -88,39 +95,43 @@ "ID": 1705437864426, "data": { "x": [ - 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, 0.876, - 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, 0.98, 0.952, - 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, 0.956, 0.936, 0.948, - 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, 0.948, 0.94, 0.928, 0.928, 0.94, - 0.96, 0.94, 0.94, 0.94, 0.936, 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, - 0.956, 0.96, 0.952, 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, - 0.944, 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, - 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, 0.968, - 0.968, 0.944 + 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, + 0.876, 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, + 0.98, 0.952, 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, + 0.956, 0.936, 0.948, 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, + 0.948, 0.94, 0.928, 0.928, 0.94, 0.96, 0.94, 0.94, 0.94, 0.936, + 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, 0.956, 0.96, 0.952, + 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, 0.944, + 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, + 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, + 0.968, 0.968, 0.944 ], "y": [ - -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, -0.076, - -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, -0.072, -0.072, -0.08, - -0.076, -0.084, -0.104, -0.088, -0.104, -0.088, -0.116, -0.116, -0.132, - -0.116, -0.108, -0.104, -0.12, -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, - -0.112, -0.1, -0.1, -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, - -0.116, -0.108, -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, - -0.096, -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, - -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, -0.096, - -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, -0.08, -0.1, - -0.096, -0.092, -0.092, -0.112, -0.088 + -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, + -0.076, -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, + -0.072, -0.072, -0.08, -0.076, -0.084, -0.104, -0.088, -0.104, + -0.088, -0.116, -0.116, -0.132, -0.116, -0.108, -0.104, -0.12, + -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, -0.112, -0.1, -0.1, + -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, -0.116, -0.108, + -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, -0.096, + -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, + -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, + -0.096, -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, + -0.08, -0.1, -0.096, -0.092, -0.092, -0.112, -0.088 ], "z": [ - 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, -0.196, - -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, -0.248, -0.172, -0.14, - -0.136, -0.188, -0.204, -0.14, -0.116, -0.104, -0.18, -0.216, -0.192, -0.168, - -0.132, -0.16, -0.188, -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, - -0.176, -0.152, -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, - -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, -0.168, -0.16, - -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, -0.172, -0.168, -0.168, - -0.18, -0.176, -0.168, -0.16, -0.148, -0.148, -0.164, -0.184, -0.184, -0.176, - -0.144, -0.156, -0.176, -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, - -0.176, -0.14, -0.144 + 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, + -0.196, -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, + -0.248, -0.172, -0.14, -0.136, -0.188, -0.204, -0.14, -0.116, + -0.104, -0.18, -0.216, -0.192, -0.168, -0.132, -0.16, -0.188, + -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, -0.176, -0.152, + -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, + -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, + -0.168, -0.16, -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, + -0.172, -0.168, -0.168, -0.18, -0.176, -0.168, -0.16, -0.148, + -0.148, -0.164, -0.184, -0.184, -0.176, -0.144, -0.156, -0.176, + -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, -0.176, + -0.14, -0.144 ] } }, @@ -128,36 +139,40 @@ "ID": 1705437860385, "data": { "x": [ - 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, -0.052, - -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, -0.04, -0.056, -0.052, - -0.052, -0.056, -0.056, -0.044, -0.044, -0.056, -0.052, -0.048, -0.048, - -0.048, -0.048, -0.048, -0.06, -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, - -0.056, -0.056, -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, - -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, -0.056, - -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, -0.052, -0.056, - -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, -0.052, -0.048, -0.056, - -0.056, -0.048, -0.052, -0.056, -0.048, -0.056, -0.048, -0.048, -0.052, - -0.052, -0.048, -0.052, -0.052, -0.044 + 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, + -0.052, -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, + -0.04, -0.056, -0.052, -0.052, -0.056, -0.056, -0.044, -0.044, + -0.056, -0.052, -0.048, -0.048, -0.048, -0.048, -0.048, -0.06, + -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, -0.056, -0.056, + -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, + -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, + -0.056, -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, + -0.052, -0.056, -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, + -0.052, -0.048, -0.056, -0.056, -0.048, -0.052, -0.056, -0.048, + -0.056, -0.048, -0.048, -0.052, -0.052, -0.048, -0.052, -0.052, + -0.044 ], "y": [ - 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, 0.224, - 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, 0.228, 0.224, 0.224, - 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, 0.228, 0.228, 0.228, 0.232, 0.236, - 0.232, 0.232, 0.228, 0.22, 0.232, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, - 0.224, 0.224, 0.232, 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, - 0.228, 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, 0.228, - 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, 0.228, 0.228, 0.236, - 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, 0.228, 0.228, 0.228, 0.228, 0.224, - 0.232, 0.232, 0.232 + 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, + 0.224, 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, + 0.228, 0.224, 0.224, 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, + 0.228, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, 0.228, 0.22, 0.232, + 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, 0.224, 0.224, 0.232, + 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, 0.228, + 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, + 0.228, 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, + 0.228, 0.228, 0.236, 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, + 0.228, 0.228, 0.228, 0.228, 0.224, 0.232, 0.232, 0.232 ], "z": [ - 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, 0.996, 1.012, - 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, 1.012, 1.012, 1.012, 1, - 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, 1.008, 1.008, 0.996, 0.996, 1.008, - 1.012, 1.008, 1.012, 1.008, 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, - 1.008, 1.004, 1.004, 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, - 1.004, 1, 1, 1, 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, - 1.004, 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, + 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, + 0.996, 1.012, 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, + 1.012, 1.012, 1.012, 1, 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, + 1.008, 1.008, 0.996, 0.996, 1.008, 1.012, 1.008, 1.012, 1.008, + 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, 1.008, 1.004, 1.004, + 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, 1.004, 1, 1, 1, + 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, 1.004, + 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, 0.996, 1.004, 1.008, 1.012, 1.008, 1.004, 1.008, 1.008, 1.008 ] } @@ -166,39 +181,44 @@ "ID": 1705437854703, "data": { "x": [ - -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, -0.104, - -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, -0.064, -0.068, - -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, -0.072, -0.068, -0.072, - -0.068, -0.056, -0.064, -0.068, -0.068, -0.068, -0.068, -0.06, -0.072, -0.076, - -0.072, -0.068, -0.064, -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, - -0.068, -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, - -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, -0.076, -0.06, - -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, -0.064, -0.064, -0.072, -0.068, - -0.064, -0.064, -0.088, -0.06, -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, - -0.064, -0.064, -0.064, -0.068, -0.072, -0.068, -0.06 + -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, + -0.104, -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, + -0.064, -0.068, -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, + -0.072, -0.068, -0.072, -0.068, -0.056, -0.064, -0.068, -0.068, + -0.068, -0.068, -0.06, -0.072, -0.076, -0.072, -0.068, -0.064, + -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, -0.068, + -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, + -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, + -0.076, -0.06, -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, + -0.064, -0.064, -0.072, -0.068, -0.064, -0.064, -0.088, -0.06, + -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, -0.064, -0.064, -0.064, + -0.068, -0.072, -0.068, -0.06 ], "y": [ - 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, 0.96, 0.96, - 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, 0.948, 0.952, 0.944, - 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, 0.944, 0.952, 0.952, 0.952, - 0.952, 0.952, 0.948, 0.948, 0.948, 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, - 0.948, 0.952, 0.948, 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, - 0.948, 0.944, 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, - 0.952, 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, - 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, 0.952, - 0.948, 0.948, 0.948, 0.948 + 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, + 0.96, 0.96, 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, + 0.948, 0.952, 0.944, 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, + 0.944, 0.952, 0.952, 0.952, 0.952, 0.952, 0.948, 0.948, 0.948, + 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, 0.948, 0.952, 0.948, + 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, 0.948, 0.944, + 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, 0.952, + 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, + 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, + 0.952, 0.948, 0.948, 0.948, 0.948 ], "z": [ - -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, -0.396, -0.42, - -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, -0.44, -0.432, -0.428, - -0.428, -0.432, -0.428, -0.428, -0.432, -0.428, -0.424, -0.432, -0.436, - -0.428, -0.428, -0.432, -0.432, -0.428, -0.424, -0.436, -0.428, -0.432, - -0.432, -0.436, -0.424, -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, - -0.436, -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, - -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, -0.42, -0.428, - -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, -0.428, -0.436, -0.432, -0.436, - -0.44, -0.428, -0.432, -0.432, -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, - -0.424, -0.424, -0.432, -0.424, -0.432, -0.436 + -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, + -0.396, -0.42, -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, + -0.44, -0.432, -0.428, -0.428, -0.432, -0.428, -0.428, -0.432, + -0.428, -0.424, -0.432, -0.436, -0.428, -0.428, -0.432, -0.432, + -0.428, -0.424, -0.436, -0.428, -0.432, -0.432, -0.436, -0.424, + -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, -0.436, + -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, + -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, + -0.42, -0.428, -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, + -0.428, -0.436, -0.432, -0.436, -0.44, -0.428, -0.432, -0.432, + -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, -0.424, -0.424, + -0.432, -0.424, -0.432, -0.436 ] } }, @@ -206,38 +226,41 @@ "ID": 1705437847993, "data": { "x": [ - 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, 0.768, - 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, 0.76, 0.748, - 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, 0.764, 0.76, 0.748, - 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, 0.756, 0.748, 0.752, 0.744, - 0.744, 0.752, 0.756, 0.756, 0.752, 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, - 0.756, 0.752, 0.748, 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, - 0.756, 0.752, 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, - 0.736, 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, + 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, + 0.768, 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, + 0.76, 0.748, 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, + 0.764, 0.76, 0.748, 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, + 0.756, 0.748, 0.752, 0.744, 0.744, 0.752, 0.756, 0.756, 0.752, + 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, 0.756, 0.752, 0.748, + 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, 0.756, 0.752, + 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, 0.736, + 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, 0.736, 0.74, 0.752, 0.74 ], "y": [ - 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, 0.248, - 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, 0.268, 0.268, 0.272, - 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, 0.264, 0.264, 0.268, 0.268, - 0.276, 0.276, 0.256, 0.256, 0.264, 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, - 0.276, 0.276, 0.284, 0.268, 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, - 0.276, 0.28, 0.272, 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, - 0.268, 0.272, 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, - 0.3, 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, + 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, + 0.248, 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, + 0.268, 0.268, 0.272, 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, + 0.264, 0.264, 0.268, 0.268, 0.276, 0.276, 0.256, 0.256, 0.264, + 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, 0.276, 0.276, 0.284, 0.268, + 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, 0.276, 0.28, 0.272, + 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, 0.268, 0.272, + 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, 0.3, + 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, 0.284, 0.276, 0.272, 0.276 ], "z": [ - -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, -0.564, -0.552, - -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, -0.568, -0.572, -0.552, -0.548, - -0.54, -0.544, -0.556, -0.56, -0.56, -0.544, -0.536, -0.54, -0.532, -0.556, - -0.564, -0.572, -0.556, -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, - -0.552, -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, -0.536, - -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, -0.54, -0.568, -0.56, - -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, -0.544, -0.548, -0.548, -0.56, - -0.556, -0.544, -0.552, -0.56, -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, - -0.556, -0.56, -0.568, -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, - -0.564, -0.56 + -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, + -0.564, -0.552, -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, + -0.568, -0.572, -0.552, -0.548, -0.54, -0.544, -0.556, -0.56, -0.56, + -0.544, -0.536, -0.54, -0.532, -0.556, -0.564, -0.572, -0.556, + -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, -0.552, + -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, + -0.536, -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, + -0.54, -0.568, -0.56, -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, + -0.544, -0.548, -0.548, -0.56, -0.556, -0.544, -0.552, -0.56, + -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, -0.556, -0.56, -0.568, + -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, -0.564, -0.56 ] } } @@ -257,39 +280,42 @@ "ID": 1705438034019, "data": { "x": [ - -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, -0.536, -0.456, - -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, -0.916, -0.956, -1.032, -1.1, - -1.228, -1.284, -1.312, -1.36, -1.356, -1.296, -1.224, -1.096, -0.844, -0.964, - -0.948, -0.984, -0.896, -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, - -0.392, -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, - -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, -0.36, -0.476, - -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, -0.532, -0.532, -0.608, - -0.652, -0.668, -0.652, -0.764, -0.892, -0.92, -0.864, -0.848, -0.884, -0.872, - -0.848, -0.888, -0.952, -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, - -0.732, -0.84 + -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, + -0.536, -0.456, -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, + -0.916, -0.956, -1.032, -1.1, -1.228, -1.284, -1.312, -1.36, -1.356, + -1.296, -1.224, -1.096, -0.844, -0.964, -0.948, -0.984, -0.896, + -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, -0.392, + -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, + -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, + -0.36, -0.476, -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, + -0.532, -0.532, -0.608, -0.652, -0.668, -0.652, -0.764, -0.892, + -0.92, -0.864, -0.848, -0.884, -0.872, -0.848, -0.888, -0.952, + -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, -0.732, -0.84 ], "y": [ - 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, 0.392, 0.376, - 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, 0.2, 0.152, 0.128, 0.16, - 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, 0.284, 0.372, 0.284, 0.348, 0.412, - 0.408, 0.376, 0.36, 0.396, 0.452, 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, - 0.312, 0.34, 0.356, 0.332, 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, - 0.324, 0.308, 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, - 0.348, 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, - 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, 0.292, - 0.324 + 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, + 0.392, 0.376, 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, + 0.2, 0.152, 0.128, 0.16, 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, + 0.284, 0.372, 0.284, 0.348, 0.412, 0.408, 0.376, 0.36, 0.396, 0.452, + 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, 0.312, 0.34, 0.356, 0.332, + 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, 0.324, 0.308, + 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, 0.348, + 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, + 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, + 0.292, 0.324 ], "z": [ - -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, -0.388, - -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, -0.204, -0.272, -0.308, - -0.308, -0.388, -0.424, -0.512, -0.592, -0.66, -0.696, -0.772, -0.812, -0.968, - -0.948, -1.02, -1.168, -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, - -1.12, -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, - -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, -0.556, -0.532, - -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, -0.392, -0.356, -0.364, - -0.388, -0.376, -0.388, -0.42, -0.408, -0.416, -0.376, -0.372, -0.368, -0.412, - -0.464, -0.492, -0.516, -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, - -0.976, -0.972 + -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, + -0.388, -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, + -0.204, -0.272, -0.308, -0.308, -0.388, -0.424, -0.512, -0.592, + -0.66, -0.696, -0.772, -0.812, -0.968, -0.948, -1.02, -1.168, + -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, -1.12, + -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, + -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, + -0.556, -0.532, -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, + -0.392, -0.356, -0.364, -0.388, -0.376, -0.388, -0.42, -0.408, + -0.416, -0.376, -0.372, -0.368, -0.412, -0.464, -0.492, -0.516, + -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, -0.976, -0.972 ] } }, @@ -297,39 +323,43 @@ "ID": 1705438029559, "data": { "x": [ - -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, -0.644, -0.536, - -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, -0.94, -0.912, -0.888, -0.816, - -0.836, -0.872, -0.844, -0.848, -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, - -0.664, -0.636, -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, - -0.364, -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, 0.068, - -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, -0.172, -0.264, - -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, -0.612, -0.552, -0.576, -0.676, - -0.804, -0.876, -0.864, -0.856, -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, - -0.856, -0.792, -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, + -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, + -0.644, -0.536, -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, + -0.94, -0.912, -0.888, -0.816, -0.836, -0.872, -0.844, -0.848, + -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, -0.664, -0.636, + -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, -0.364, + -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, + 0.068, -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, + -0.172, -0.264, -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, + -0.612, -0.552, -0.576, -0.676, -0.804, -0.876, -0.864, -0.856, + -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, -0.856, -0.792, + -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, -0.668, -0.588, -0.508 ], "y": [ - 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, 0.132, - 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, 0.232, 0.256, - 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, 0.296, 0.292, 0.304, - 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, 0.368, 0.352, 0.356, 0.328, - 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, - 0.24, 0.248, 0.232, 0.196, 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, - 0.176, 0.152, 0.144, 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, - 0.252, 0.188, 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, - 0.276, 0.248, 0.236 + 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, + 0.132, 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, + 0.232, 0.256, 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, + 0.296, 0.292, 0.304, 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, + 0.368, 0.352, 0.356, 0.328, 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, + 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, 0.24, 0.248, 0.232, 0.196, + 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, 0.176, 0.152, 0.144, + 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, 0.252, 0.188, + 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, 0.276, + 0.248, 0.236 ], "z": [ - -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, -1.064, - -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, -0.672, -0.712, - -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, -0.524, -0.484, -0.468, -0.516, - -0.54, -0.596, -0.624, -0.6, -0.596, -0.6, -0.624, -0.632, -0.656, -0.632, - -0.7, -0.744, -0.76, -0.768, -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, - -0.78, -1.112, -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, - -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, -1.064, -0.996, - -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, -0.796, -0.808, -0.812, -0.74, - -0.76, -0.72, -0.744, -0.728, -0.704, -0.692, -0.636, -0.644, -0.656, -0.676, - -0.616 + -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, + -1.064, -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, + -0.672, -0.712, -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, + -0.524, -0.484, -0.468, -0.516, -0.54, -0.596, -0.624, -0.6, -0.596, + -0.6, -0.624, -0.632, -0.656, -0.632, -0.7, -0.744, -0.76, -0.768, + -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, -0.78, -1.112, + -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, + -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, + -1.064, -0.996, -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, + -0.796, -0.808, -0.812, -0.74, -0.76, -0.72, -0.744, -0.728, -0.704, + -0.692, -0.636, -0.644, -0.656, -0.676, -0.616 ] } }, @@ -337,39 +367,43 @@ "ID": 1705438027130, "data": { "x": [ - -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, -0.532, - -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, -1.056, -0.968, -0.9, - -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, -0.8, -0.788, -0.748, -0.728, - -0.636, -0.56, -0.532, -0.504, -0.492, -0.432, -0.424, -0.464, -0.428, -0.408, - -0.336, -0.296, -0.252, -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, - -0.168, -0.136, -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, - -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, -0.628, -0.624, - -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, -0.84, -0.864, -0.888, -0.924, - -0.936, -0.88, -0.848, -0.888, -0.88, -0.832, -0.768, -0.724, -0.68, -0.664, - -0.664, -0.62, -0.572 + -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, + -0.532, -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, + -1.056, -0.968, -0.9, -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, + -0.8, -0.788, -0.748, -0.728, -0.636, -0.56, -0.532, -0.504, -0.492, + -0.432, -0.424, -0.464, -0.428, -0.408, -0.336, -0.296, -0.252, + -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, -0.168, -0.136, + -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, + -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, + -0.628, -0.624, -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, + -0.84, -0.864, -0.888, -0.924, -0.936, -0.88, -0.848, -0.888, -0.88, + -0.832, -0.768, -0.724, -0.68, -0.664, -0.664, -0.62, -0.572 ], "y": [ - 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, 0.164, - 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, 0.208, 0.212, - 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, 0.324, 0.316, 0.32, - 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, 0.276, 0.248, 0.232, 0.252, - 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, 0.176, 0.156, 0.136, 0.16, 0.18, - 0.208, 0.224, 0.204, 0.18, 0.188, 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, - 0.104, 0.112, 0.084, 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, - 0.1, 0.14, 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, + 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, + 0.164, 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, + 0.208, 0.212, 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, + 0.324, 0.316, 0.32, 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, + 0.276, 0.248, 0.232, 0.252, 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, + 0.176, 0.156, 0.136, 0.16, 0.18, 0.208, 0.224, 0.204, 0.18, 0.188, + 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, 0.104, 0.112, 0.084, + 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, 0.1, 0.14, + 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, 0.268, 0.276, 0.284, 0.296 ], "z": [ - -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, -1.176, - -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, -0.964, -0.904, -0.824, - -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, -0.568, -0.56, -0.528, -0.468, - -0.492, -0.488, -0.48, -0.492, -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, - -0.704, -0.732, -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, - -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, -1.208, -1.208, - -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, -1.076, -1.072, -1.028, - -1.028, -0.984, -0.944, -0.968, -0.96, -0.964, -0.964, -0.952, -0.904, -0.856, - -0.808, -0.788, -0.756, -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, - -0.604, -0.592, -0.588 + -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, + -1.176, -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, + -0.964, -0.904, -0.824, -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, + -0.568, -0.56, -0.528, -0.468, -0.492, -0.488, -0.48, -0.492, + -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, -0.704, -0.732, + -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, + -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, + -1.208, -1.208, -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, + -1.076, -1.072, -1.028, -1.028, -0.984, -0.944, -0.968, -0.96, + -0.964, -0.964, -0.952, -0.904, -0.856, -0.808, -0.788, -0.756, + -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, -0.604, + -0.592, -0.588 ] } }, @@ -377,39 +411,42 @@ "ID": 1705437979405, "data": { "x": [ - 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, -0.204, - -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, -0.136, -0.28, -0.392, - -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, -0.464, -0.516, -0.616, -0.648, - -0.6, -0.52, -0.48, -0.468, -0.468, -0.46, -0.432, -0.404, -0.392, -0.304, - -0.256, -0.208, -0.204, -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, - -0.792, -0.776, -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, - -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, -0.104, - 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, 0.092, 0.04, 0, - -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, 0.312, 0.188, 0.036, -0.06, - -0.156, -0.248 + 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, + -0.204, -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, + -0.136, -0.28, -0.392, -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, + -0.464, -0.516, -0.616, -0.648, -0.6, -0.52, -0.48, -0.468, -0.468, + -0.46, -0.432, -0.404, -0.392, -0.304, -0.256, -0.208, -0.204, + -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, -0.792, -0.776, + -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, + -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, + -0.104, 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, + 0.092, 0.04, 0, -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, + 0.312, 0.188, 0.036, -0.06, -0.156, -0.248 ], "y": [ - 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, 0.364, - 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, 0.392, 0.364, - 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, 0.312, 0.296, 0.312, - 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, 0.376, 0.316, 0.292, 0.308, - 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, 0.708, 0.728, 0.74, 0.748, 0.716, - 0.68, 0.624, 0.592, 0.564, 0.536, 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, - 0.416, 0.408, 0.42, 0.408, 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, - 0.448, 0.44, 0.464, 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, - 0.348, 0.368, 0.412, 0.484, 0.5 + 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, + 0.364, 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, + 0.392, 0.364, 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, + 0.312, 0.296, 0.312, 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, + 0.376, 0.316, 0.292, 0.308, 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, + 0.708, 0.728, 0.74, 0.748, 0.716, 0.68, 0.624, 0.592, 0.564, 0.536, + 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, 0.416, 0.408, 0.42, 0.408, + 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, 0.448, 0.44, 0.464, + 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, 0.348, 0.368, + 0.412, 0.484, 0.5 ], "z": [ - -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, -0.652, - -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, -1.364, -1.312, - -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, -1.004, -0.996, -1.036, - -1.056, -1.032, -0.996, -0.932, -0.868, -0.864, -0.824, -0.808, -0.764, - -0.708, -0.652, -0.624, -0.632, -0.644, -0.628, -0.612, -0.616, -0.692, - -0.788, -0.884, -0.932, -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, - -0.784, -0.74, -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, - -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, -0.92, - -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, -1.092, -1.132, - -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 + -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, + -0.652, -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, + -1.364, -1.312, -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, + -1.004, -0.996, -1.036, -1.056, -1.032, -0.996, -0.932, -0.868, + -0.864, -0.824, -0.808, -0.764, -0.708, -0.652, -0.624, -0.632, + -0.644, -0.628, -0.612, -0.616, -0.692, -0.788, -0.884, -0.932, + -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, -0.784, -0.74, + -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, + -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, + -0.92, -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, + -1.092, -1.132, -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 ] } }, @@ -417,36 +454,42 @@ "ID": 1705437977128, "data": { "x": [ - -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, 0.16, - 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, -0.42, -0.596, - -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, -0.8, -0.868, -0.968, - -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, -0.456, -0.396, -0.436, -0.476, - -0.416, -0.364, -0.296, -0.292, -0.304, -0.332, -0.324, -0.272, -0.208, - -0.156, -0.112, -0.08, -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, - 0.168, 0.176, 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, - 0.156, 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, -0.404, - -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, -0.58, -0.52, -0.428 + -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, + 0.16, 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, + -0.42, -0.596, -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, + -0.8, -0.868, -0.968, -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, + -0.456, -0.396, -0.436, -0.476, -0.416, -0.364, -0.296, -0.292, + -0.304, -0.332, -0.324, -0.272, -0.208, -0.156, -0.112, -0.08, + -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, 0.168, 0.176, + 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, 0.156, + 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, + -0.404, -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, + -0.58, -0.52, -0.428 ], "y": [ - 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, 0.316, 0.5, - 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, 0.52, 0.44, 0.44, 0.464, - 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, 0.604, 0.584, 0.62, 0.608, 0.58, - 0.56, 0.564, 0.576, 0.592, 0.58, 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, - 0.456, 0.44, 0.4, 0.376, 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, - 0.38, 0.392, 0.436, 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, - 0.244, 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, - 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, 0.428 + 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, + 0.316, 0.5, 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, + 0.52, 0.44, 0.44, 0.464, 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, + 0.604, 0.584, 0.62, 0.608, 0.58, 0.56, 0.564, 0.576, 0.592, 0.58, + 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, 0.456, 0.44, 0.4, 0.376, + 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, 0.38, 0.392, 0.436, + 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, 0.244, + 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, + 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, + 0.428 ], "z": [ - -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, -0.816, - -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, -1.18, -1.196, -1.236, - -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, -1.04, -0.964, -0.888, -0.856, - -0.772, -0.696, -0.62, -0.564, -0.6, -0.592, -0.536, -0.48, -0.432, -0.448, - -0.48, -0.484, -0.516, -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, - -0.62, -0.668, -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, - -0.84, -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, - -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, -1.152, - -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, -1.044, -1, -1, -1.012 + -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, + -0.816, -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, + -1.18, -1.196, -1.236, -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, + -1.04, -0.964, -0.888, -0.856, -0.772, -0.696, -0.62, -0.564, -0.6, + -0.592, -0.536, -0.48, -0.432, -0.448, -0.48, -0.484, -0.516, + -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, -0.62, -0.668, + -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, -0.84, + -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, + -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, + -1.152, -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, + -1.044, -1, -1, -1.012 ] } }, @@ -454,39 +497,43 @@ "ID": 1705437922352, "data": { "x": [ - -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, 0.064, 0.064, - -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, -0.688, -0.728, -0.772, - -0.784, -0.768, -0.732, -0.696, -0.648, -0.576, -0.544, -0.52, -0.504, -0.492, - -0.492, -0.484, -0.436, -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, - -0.248, -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, - -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, -0.088, - -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, -0.176, -0.176, - -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, -0.312, -0.376, -0.46, -0.436, - -0.348, -0.26, -0.268, -0.376, -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, - -0.456, -0.436, -0.448, -0.46 + -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, + 0.064, 0.064, -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, + -0.688, -0.728, -0.772, -0.784, -0.768, -0.732, -0.696, -0.648, + -0.576, -0.544, -0.52, -0.504, -0.492, -0.492, -0.484, -0.436, + -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, -0.248, + -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, + -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, + -0.088, -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, + -0.176, -0.176, -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, + -0.312, -0.376, -0.46, -0.436, -0.348, -0.26, -0.268, -0.376, + -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, -0.456, -0.436, + -0.448, -0.46 ], "y": [ - 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, 0.884, 1.036, - 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, 1.236, 1.188, 1.176, - 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, 1.036, 1.004, 1.004, 1, 0.992, - 0.98, 0.94, 0.92, 0.896, 0.884, 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, - 0.76, 0.728, 0.712, 0.724, 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, - 0.696, 0.732, 0.752, 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, - 0.932, 0.892, 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, - 0.932, 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, 0.928, - 0.9 + 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, + 0.884, 1.036, 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, + 1.236, 1.188, 1.176, 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, + 1.036, 1.004, 1.004, 1, 0.992, 0.98, 0.94, 0.92, 0.896, 0.884, + 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, 0.76, 0.728, 0.712, 0.724, + 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, 0.696, 0.732, 0.752, + 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, 0.932, 0.892, + 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, 0.932, + 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, + 0.928, 0.9 ], "z": [ - -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, -0.868, -0.848, - -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, -0.504, -0.444, -0.416, -0.38, - -0.36, -0.316, -0.26, -0.2, -0.152, -0.136, -0.116, -0.116, -0.108, -0.044, - -0.004, 0.036, 0.072, 0.072, 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, - 0.096, 0.092, 0.068, -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, - -0.324, -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, -0.56, - -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, -0.628, -0.656, -0.672, - -0.524, -0.46, -0.468, -0.532, -0.596, -0.604, -0.6, -0.644, -0.66, -0.748, - -0.752, -0.76, -0.676, -0.608, -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, - -0.372, -0.38 + -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, + -0.868, -0.848, -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, + -0.504, -0.444, -0.416, -0.38, -0.36, -0.316, -0.26, -0.2, -0.152, + -0.136, -0.116, -0.116, -0.108, -0.044, -0.004, 0.036, 0.072, 0.072, + 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, 0.096, 0.092, 0.068, + -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, -0.324, + -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, + -0.56, -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, + -0.628, -0.656, -0.672, -0.524, -0.46, -0.468, -0.532, -0.596, + -0.604, -0.6, -0.644, -0.66, -0.748, -0.752, -0.76, -0.676, -0.608, + -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, -0.372, -0.38 ] } }, @@ -494,38 +541,41 @@ "ID": 1705437913803, "data": { "x": [ - 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, 0.86, - 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, 0.204, 0.044, - -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, -0.536, -0.488, -0.404, - -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, -0.548, -0.376, -0.444, -0.496, - -0.496, -0.428, -0.344, -0.724, -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, - 0.372, 0.34, 0.296, 0.22, 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, - 0.456, 0.5, 0.516, 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, - 0.344, 0.252, 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, + 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, + 0.86, 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, + 0.204, 0.044, -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, + -0.536, -0.488, -0.404, -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, + -0.548, -0.376, -0.444, -0.496, -0.496, -0.428, -0.344, -0.724, + -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, 0.372, 0.34, 0.296, 0.22, + 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, 0.456, 0.5, 0.516, + 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, 0.344, 0.252, + 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, -0.16, -0.212, -0.316, -0.412, -0.42, -0.38, -0.296, -0.244 ], "y": [ - 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, -0.192, - -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, -0.044, -0.004, - 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, 0.36, 0.396, 0.424, 0.4, - 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, 0.332, 0.344, 0.244, 0.068, -0.096, - -0.092, -0.06, -0.02, 0.452, -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, - 0.06, 0.072, 0.072, 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, - 0.076, 0.04, 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, - 0.304, 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, + 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, + -0.192, -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, + -0.044, -0.004, 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, + 0.36, 0.396, 0.424, 0.4, 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, + 0.332, 0.344, 0.244, 0.068, -0.096, -0.092, -0.06, -0.02, 0.452, + -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, 0.06, 0.072, 0.072, + 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, 0.076, 0.04, + 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, 0.304, + 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, 0.536, 0.528, 0.552, 0.604, 0.648, 0.696, 0.64, 0.596 ], "z": [ - -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, -0.576, - -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, -0.28, -0.18, -0.248, - -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, -0.524, -0.58, -0.66, -0.78, - -0.808, -0.784, -0.732, -0.76, -0.744, -0.744, -0.8, -0.904, -1.032, -1.076, - -1.14, -1.3, -1.504, -1.62, -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, - -1.08, -1.064, -1.1, -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, - -1.332, -1.292, -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, - -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, -0.736, -0.712, - -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, -0.64, -0.692, -0.724, - -0.752, -0.768, -0.772, -0.792 + -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, + -0.576, -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, + -0.28, -0.18, -0.248, -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, + -0.524, -0.58, -0.66, -0.78, -0.808, -0.784, -0.732, -0.76, -0.744, + -0.744, -0.8, -0.904, -1.032, -1.076, -1.14, -1.3, -1.504, -1.62, + -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, -1.08, -1.064, -1.1, + -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, -1.332, -1.292, + -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, + -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, + -0.736, -0.712, -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, + -0.64, -0.692, -0.724, -0.752, -0.768, -0.772, -0.792 ] } }, @@ -533,39 +583,44 @@ "ID": 1705437908508, "data": { "x": [ - -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, -1.108, - -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, -0.868, -0.908, - -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, -0.484, -0.424, -0.42, -0.436, - -0.408, -0.352, -0.272, -0.216, -0.208, -0.236, -0.188, -0.148, -0.104, - -0.084, -0.08, -0.076, -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, - -0.212, -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, -0.76, - -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, -0.972, -0.992, -1, - -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, -0.796, -0.692, -0.564, -0.404, - -0.388, -0.312, -0.32, -0.324, -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, - -0.508, -0.508, -0.508, -0.496 + -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, + -1.108, -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, + -0.868, -0.908, -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, + -0.484, -0.424, -0.42, -0.436, -0.408, -0.352, -0.272, -0.216, + -0.208, -0.236, -0.188, -0.148, -0.104, -0.084, -0.08, -0.076, + -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, -0.212, + -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, + -0.76, -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, + -0.972, -0.992, -1, -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, + -0.796, -0.692, -0.564, -0.404, -0.388, -0.312, -0.32, -0.324, + -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, -0.508, -0.508, + -0.508, -0.496 ], "y": [ - 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, 0.712, - 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, 0.96, 0.972, 0.96, - 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, 0.876, 0.832, 0.824, 0.8, 0.74, - 0.716, 0.692, 0.672, 0.628, 0.572, 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, - 0.54, 0.528, 0.54, 0.544, 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, - 0.472, 0.492, 0.52, 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, - 0.872, 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, 1, - 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, 0.776, 0.772, - 0.772, 0.776 + 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, + 0.712, 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, + 0.96, 0.972, 0.96, 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, + 0.876, 0.832, 0.824, 0.8, 0.74, 0.716, 0.692, 0.672, 0.628, 0.572, + 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, 0.54, 0.528, 0.54, 0.544, + 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, 0.472, 0.492, 0.52, + 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, 0.872, + 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, + 1, 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, + 0.776, 0.772, 0.772, 0.776 ], "z": [ - -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, -0.036, - -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, -0.464, -0.508, - -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, -0.664, -0.72, -0.704, -0.744, - -0.784, -0.856, -0.884, -0.94, -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, - -0.744, -0.744, -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, - -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, -0.012, - 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, 0.148, 0.088, - 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, -0.592, -0.684, -0.652, - -0.912, -0.672, -0.604, -0.6, -0.584, -0.564, -0.504, -0.504, -0.496, -0.484, - -0.472, -0.46, -0.456, -0.448 + -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, + -0.036, -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, + -0.464, -0.508, -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, + -0.664, -0.72, -0.704, -0.744, -0.784, -0.856, -0.884, -0.94, + -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, -0.744, -0.744, + -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, + -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, + -0.012, 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, + 0.148, 0.088, 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, + -0.592, -0.684, -0.652, -0.912, -0.672, -0.604, -0.6, -0.584, + -0.564, -0.504, -0.504, -0.496, -0.484, -0.472, -0.46, -0.456, + -0.448 ] } }, @@ -573,38 +628,42 @@ "ID": 1705437905957, "data": { "x": [ - -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, -0.496, - -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, -0.884, -0.936, - -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, -0.964, -0.872, -0.824, -0.796, - -0.832, -0.868, -0.804, -0.716, -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, - -0.516, -0.5, -0.476, -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, - -0.228, -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, -0.244, - -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, -0.928, -0.972, -1.004, - -0.976, -0.928, -0.92, -0.94, -0.952, -0.904, -0.848, -0.828, -0.856, -0.844, - -0.804, -0.78, -0.764, -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, - -0.64, -0.6, -0.588 + -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, + -0.496, -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, + -0.884, -0.936, -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, + -0.964, -0.872, -0.824, -0.796, -0.832, -0.868, -0.804, -0.716, + -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, -0.516, -0.5, -0.476, + -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, -0.228, + -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, + -0.244, -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, + -0.928, -0.972, -1.004, -0.976, -0.928, -0.92, -0.94, -0.952, + -0.904, -0.848, -0.828, -0.856, -0.844, -0.804, -0.78, -0.764, + -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, -0.64, -0.6, + -0.588 ], "y": [ - 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, 0.92, 0.992, - 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, 0.856, 0.848, 0.812, - 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, 0.724, 0.708, 0.7, 0.692, - 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, - 0.492, 0.472, 0.508, 0.524, 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, - 0.808, 0.812, 0.848, 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, - 0.916, 0.86, 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, - 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, 0.716, - 0.724, 0.712 + 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, + 0.92, 0.992, 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, + 0.856, 0.848, 0.812, 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, + 0.724, 0.708, 0.7, 0.692, 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, + 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, 0.492, 0.472, 0.508, 0.524, + 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, 0.808, 0.812, 0.848, + 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, 0.916, 0.86, + 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, + 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, + 0.716, 0.724, 0.712 ], "z": [ - -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, -0.612, - -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, -0.356, -0.328, - -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, -0.204, -0.176, -0.148, - -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, 0.188, 0.216, 0.204, 0.164, 0.088, - 0.032, 0.044, 0.04, 0.068, 0.06, 0.016, -0.048, -0.124, -0.208, -0.296, - -0.404, -0.428, -0.4, -0.38, -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, - -0.8, -0.872, -0.88, -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, - -0.272, -0.28, -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, - -0.376, -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, + -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, + -0.612, -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, + -0.356, -0.328, -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, + -0.204, -0.176, -0.148, -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, + 0.188, 0.216, 0.204, 0.164, 0.088, 0.032, 0.044, 0.04, 0.068, 0.06, + 0.016, -0.048, -0.124, -0.208, -0.296, -0.404, -0.428, -0.4, -0.38, + -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, -0.8, -0.872, -0.88, + -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, -0.272, -0.28, + -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, -0.376, + -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, -0.38, -0.356, -0.32, -0.328 ] } @@ -613,39 +672,43 @@ "ID": 1705437896254, "data": { "x": [ - -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, -0.712, - -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, -0.808, -0.84, -0.944, - -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, -1.076, -1.048, -1.048, -1.112, - -1.144, -1.188, -1.208, -1.228, -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, - -1.02, -0.956, -0.912, -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, - -0.732, -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, - -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, -0.496, -0.48, - -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, -0.404, -0.46, -0.532, - -0.568, -0.484, -0.38, -0.444, -0.572, -0.728, -0.728, -0.616, -0.644, -0.656, - -0.704, -0.688, -0.644, -0.652 + -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, + -0.712, -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, + -0.808, -0.84, -0.944, -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, + -1.076, -1.048, -1.048, -1.112, -1.144, -1.188, -1.208, -1.228, + -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, -1.02, -0.956, -0.912, + -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, -0.732, + -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, + -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, + -0.496, -0.48, -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, + -0.404, -0.46, -0.532, -0.568, -0.484, -0.38, -0.444, -0.572, + -0.728, -0.728, -0.616, -0.644, -0.656, -0.704, -0.688, -0.644, + -0.652 ], "y": [ - 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, 0.472, - 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, 0.244, 0.232, - 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, 0.056, 0.076, 0.104, - 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, 0.28, 0.308, 0.324, 0.348, 0.38, - 0.404, 0.408, 0.408, 0.384, 0.372, 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, - 0.38, 0.364, 0.412, 0.32, 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, - 0.372, 0.368, 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, - 0.468, 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, 0.32, - 0.32, 0.316 + 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, + 0.472, 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, + 0.244, 0.232, 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, + 0.056, 0.076, 0.104, 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, + 0.28, 0.308, 0.324, 0.348, 0.38, 0.404, 0.408, 0.408, 0.384, 0.372, + 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, 0.38, 0.364, 0.412, 0.32, + 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, 0.372, 0.368, + 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, 0.468, + 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, + 0.32, 0.32, 0.316 ], "z": [ - -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, -0.556, -0.524, - -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, -0.644, -0.72, -0.756, -0.744, - -0.724, -0.7, -0.684, -0.716, -0.716, -0.68, -0.668, -0.656, -0.708, -0.736, - -0.696, -0.712, -0.684, -0.7, -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, - -0.524, -0.496, -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, - -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, -0.392, -0.444, - -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, -0.716, -0.76, -0.76, - -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, -0.728, -0.66, -0.7, -0.756, - -0.86, -0.776, -0.76, -0.812, -0.876, -0.872, -0.908, -0.96, -0.976, -0.996, - -1.032, -0.976, -0.96 + -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, + -0.556, -0.524, -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, + -0.644, -0.72, -0.756, -0.744, -0.724, -0.7, -0.684, -0.716, -0.716, + -0.68, -0.668, -0.656, -0.708, -0.736, -0.696, -0.712, -0.684, -0.7, + -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, -0.524, -0.496, + -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, + -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, + -0.392, -0.444, -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, + -0.716, -0.76, -0.76, -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, + -0.728, -0.66, -0.7, -0.756, -0.86, -0.776, -0.76, -0.812, -0.876, + -0.872, -0.908, -0.96, -0.976, -0.996, -1.032, -0.976, -0.96 ] } } @@ -665,39 +728,42 @@ "ID": 1705437992143, "data": { "x": [ - -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, -1.3, - -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, -2.04, -2.008, - -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, -0.232, -1.132, -2.04, - -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, 0.608, 0.332, -0.004, -0.876, - -2.04, -2.04, -2.04, -1.688, -1.396, -0.828, -0.032, 0.36, 0.492, 0.288, - 0.008, -0.492, -1.368, -2.04, -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, - 0.664, 0.544, 0.248, -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, - -0.612, 0.16, 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, - -1.668, -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, + -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, + -1.3, -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, + -2.04, -2.008, -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, + -0.232, -1.132, -2.04, -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, + 0.608, 0.332, -0.004, -0.876, -2.04, -2.04, -2.04, -1.688, -1.396, + -0.828, -0.032, 0.36, 0.492, 0.288, 0.008, -0.492, -1.368, -2.04, + -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, 0.664, 0.544, 0.248, + -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, -0.612, 0.16, + 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, -1.668, + -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, -2.04, -2.04, -1.992 ], "y": [ - 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, 0.324, - 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, 0.328, 0.364, - 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, 1.076, -0.116, 0.048, - 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, 0.524, 0.416, 0.252, 0.308, - 0.376, 0.316, 0.356, 0.644, 0.672, 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, - 0.256, 0.272, 0.404, 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, - 0.284, 0.28, 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, - 0.452, 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, 0.288, - 0.204, 0.476, 1.112, 0.2 + 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, + 0.324, 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, + 0.328, 0.364, 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, + 1.076, -0.116, 0.048, 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, + 0.524, 0.416, 0.252, 0.308, 0.376, 0.316, 0.356, 0.644, 0.672, + 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, 0.256, 0.272, 0.404, + 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, 0.284, 0.28, + 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, 0.452, + 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, + 0.288, 0.204, 0.476, 1.112, 0.2 ], "z": [ - -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, -0.096, - -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, -0.108, 0.196, 0.268, - 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, -0.26, -0.092, -0.204, 0.304, 0.78, - 0.364, 0.108, -0.24, -0.528, -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, - 0.552, 0.42, 0.128, 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, - -0.004, -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, - -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, -0.288, - -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, 0.504, 0.196, - -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, -0.072, -0.176, 0.136, - 0.34, 0.7 + -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, + -0.096, -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, + -0.108, 0.196, 0.268, 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, + -0.26, -0.092, -0.204, 0.304, 0.78, 0.364, 0.108, -0.24, -0.528, + -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, 0.552, 0.42, 0.128, + 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, -0.004, + -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, + -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, + -0.288, -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, + 0.504, 0.196, -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, + -0.072, -0.176, 0.136, 0.34, 0.7 ] } }, @@ -705,37 +771,41 @@ "ID": 1705437989960, "data": { "x": [ - -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, -1.496, - -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, 0.864, 0.256, - -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, 0.42, 0.608, 0.488, - 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, -1.588, -1.08, -0.512, -0.028, - 0.216, 0.268, 0.052, -0.38, -0.684, -0.788, -1.056, -1.768, -2.04, -2.04, - -1.344, -0.8, -0.436, -0.172, 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, - -1.544, -2.02, -2.016, -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, - -0.46, -0.816, -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, - 0.06, 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 + -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, + -1.496, -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, + 0.864, 0.256, -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, + 0.42, 0.608, 0.488, 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, + -1.588, -1.08, -0.512, -0.028, 0.216, 0.268, 0.052, -0.38, -0.684, + -0.788, -1.056, -1.768, -2.04, -2.04, -1.344, -0.8, -0.436, -0.172, + 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, -1.544, -2.02, -2.016, + -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, -0.46, -0.816, + -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, 0.06, + 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 ], "y": [ - 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, 0.456, 0.188, - 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, 0.32, 0.252, 0.208, 0.152, - 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, - 0.596, 0.644, 0.732, 0.58, 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, - 0.38, 0.284, 0.22, 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, - 0.348, 0.34, 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, - 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, 0.556, - 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, 0.328, 0.536, - 0.78, 0.7 + 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, + 0.456, 0.188, 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, + 0.32, 0.252, 0.208, 0.152, 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, + 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, 0.596, 0.644, 0.732, 0.58, + 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, 0.38, 0.284, 0.22, + 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, 0.348, 0.34, + 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, + 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, + 0.556, 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, + 0.328, 0.536, 0.78, 0.7 ], "z": [ - -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, -0.596, -0.696, - -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, -0.888, -0.948, -0.972, -1.192, - -1.148, -0.808, -0.636, -0.42, 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, - -1.032, -0.972, -0.656, -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, - -0.364, -0.744, -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, - 1.116, 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, -0.7, - -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, -1.508, -1.86, - -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, 0.256, -0.24, -0.712, - -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, 0.18, 0.576, 1.292, 0.92 + -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, + -0.596, -0.696, -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, + -0.888, -0.948, -0.972, -1.192, -1.148, -0.808, -0.636, -0.42, + 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, -1.032, -0.972, -0.656, + -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, -0.364, -0.744, + -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, 1.116, + 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, + -0.7, -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, + -1.508, -1.86, -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, + 0.256, -0.24, -0.712, -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, + 0.18, 0.576, 1.292, 0.92 ] } }, @@ -743,38 +813,41 @@ "ID": 1705437987590, "data": { "x": [ - -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, 0.872, - 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, -0.312, -0.144, 0.076, - 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, 0.004, -1.244, -0.88, -0.372, -0.04, - 0.252, 0.616, 1.016, 1.332, 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, - -0.312, -0.052, 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, - -1.504, -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, - 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, 1.568, 1.692, - 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, 0.008, 0.68, 1.264, 1.68, - 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 + -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, + 0.872, 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, + -0.312, -0.144, 0.076, 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, + 0.004, -1.244, -0.88, -0.372, -0.04, 0.252, 0.616, 1.016, 1.332, + 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, -0.312, -0.052, + 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, -1.504, + -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, + 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, + 1.568, 1.692, 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, + 0.008, 0.68, 1.264, 1.68, 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 ], "y": [ - 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, 0.068, - 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, 0.588, 0.452, - 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, 0.372, 0.344, 0.456, 0.368, - 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, 0.104, 0, 0.484, 0.416, 0.384, 0.356, - 0.32, 0.216, 0.244, 0.284, 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, - 0.336, 0.26, 0.224, 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, - 0.42, 0.396, 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, - 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, 0.044, 0.08, - 0.352, 0.632 + 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, + 0.068, 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, + 0.588, 0.452, 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, + 0.372, 0.344, 0.456, 0.368, 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, + 0.104, 0, 0.484, 0.416, 0.384, 0.356, 0.32, 0.216, 0.244, 0.284, + 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, 0.336, 0.26, 0.224, + 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, 0.42, 0.396, + 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, + 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, + 0.044, 0.08, 0.352, 0.632 ], "z": [ - -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, -0.384, - -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, -0.844, -0.48, - -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, -1.596, -1.952, -2.016, - -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, -0.06, -0.324, -0.676, -1.124, - -1.784, -2.04, -1.748, -1.408, -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, - -0.396, -0.836, -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, - 0.112, 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, - -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, -2.04, - -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, -0.472, -0.992, - -1.876, -2.04 + -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, + -0.384, -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, + -0.844, -0.48, -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, + -1.596, -1.952, -2.016, -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, + -0.06, -0.324, -0.676, -1.124, -1.784, -2.04, -1.748, -1.408, + -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, -0.396, -0.836, + -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, 0.112, + 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, + -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, + -2.04, -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, + -0.472, -0.992, -1.876, -2.04 ] } }, @@ -782,39 +855,42 @@ "ID": 1705437985014, "data": { "x": [ - -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, -1.04, -0.82, - -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, 1.188, 0.936, 0.456, 0.132, - -0.5, -1.94, -2.04, -2.04, -1.536, -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, - 0.812, 0.396, -0.2, -1.508, -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, - 0.776, 0.716, 0.46, -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, - 0.148, 0.316, 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, - -0.792, -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, - -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, 0.452, - 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, 0.076, 0.544, - 0.528 + -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, + -1.04, -0.82, -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, + 1.188, 0.936, 0.456, 0.132, -0.5, -1.94, -2.04, -2.04, -1.536, + -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, 0.812, 0.396, -0.2, -1.508, + -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, 0.776, 0.716, 0.46, + -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, 0.148, 0.316, + 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, -0.792, + -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, + -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, + 0.452, 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, + 0.076, 0.544, 0.528 ], "y": [ - 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, 0.512, - 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, 0.164, 0.048, -0.22, - 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, 0.236, 0.132, 0.064, 0.124, 0.152, - 0.304, 0.248, 0.896, 1.34, 1.08, 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, - 0.236, 0.24, 0.66, 1.26, 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, - 0.124, 0.192, 0.244, 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, - 0.076, 0.092, 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, - 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, 0.672, - 0.496, 0.26, 0.232, 0.228 + 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, + 0.512, 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, + 0.164, 0.048, -0.22, 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, + 0.236, 0.132, 0.064, 0.124, 0.152, 0.304, 0.248, 0.896, 1.34, 1.08, + 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, 0.236, 0.24, 0.66, 1.26, + 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, 0.124, 0.192, 0.244, + 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, 0.076, 0.092, + 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, + 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, + 0.672, 0.496, 0.26, 0.232, 0.228 ], "z": [ - -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, -0.892, - -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, -1.144, -1.044, - -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, -0.476, -0.416, -0.764, - -0.892, -1.332, -1.872, -1.152, -0.744, -0.904, -0.704, -0.028, -0.296, - -0.256, -0.592, -0.848, -1, -1.404, -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, - -0.14, -0.3, -0.576, -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, - -0.492, -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, - -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, -1.328, - -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, -0.256, -0.28, -0.424, - -0.608, -0.652, -1.504, -1.412 + -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, + -0.892, -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, + -1.144, -1.044, -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, + -0.476, -0.416, -0.764, -0.892, -1.332, -1.872, -1.152, -0.744, + -0.904, -0.704, -0.028, -0.296, -0.256, -0.592, -0.848, -1, -1.404, + -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, -0.14, -0.3, -0.576, + -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, -0.492, + -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, + -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, + -1.328, -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, + -0.256, -0.28, -0.424, -0.608, -0.652, -1.504, -1.412 ] } }, @@ -822,39 +898,43 @@ "ID": 1705437822437, "data": { "x": [ - -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, -0.896, -0.96, - -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, -0.204, -0.12, -0.164, - -0.248, -0.236, -0.088, -0.644, -1.068, -0.812, -0.644, -0.56, -0.372, -0.292, - -0.272, -0.076, -0.04, -0.08, -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, - -0.828, -0.72, -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, - -0.272, -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, -0.16, - -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, -0.872, -0.844, -0.72, - -0.624, -0.572, -0.48, -0.392, -0.356, -0.292, -0.244, -0.208, -0.288, -0.372, - -0.364, -0.216, -0.464, -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, - -0.468, -0.34, -0.272 + -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, + -0.896, -0.96, -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, + -0.204, -0.12, -0.164, -0.248, -0.236, -0.088, -0.644, -1.068, + -0.812, -0.644, -0.56, -0.372, -0.292, -0.272, -0.076, -0.04, -0.08, + -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, -0.828, -0.72, + -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, -0.272, + -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, + -0.16, -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, + -0.872, -0.844, -0.72, -0.624, -0.572, -0.48, -0.392, -0.356, + -0.292, -0.244, -0.208, -0.288, -0.372, -0.364, -0.216, -0.464, + -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, -0.468, -0.34, + -0.272 ], "y": [ - 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, -1.216, - -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, 1.692, 1.224, - 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, -0.204, 0.568, 0.92, 1.304, - 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, -2.04, -2.04, -2.032, -1.288, -0.796, - 0.108, 1.112, 1.588, 1.832, 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, - -2.04, -1.68, -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, - -0.08, -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, - 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, -2.04, -2.004, - -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 + 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, + -1.216, -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, + 1.692, 1.224, 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, + -0.204, 0.568, 0.92, 1.304, 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, + -2.04, -2.04, -2.032, -1.288, -0.796, 0.108, 1.112, 1.588, 1.832, + 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, -2.04, -1.68, + -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, -0.08, + -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, + 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, + -2.04, -2.004, -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 ], "z": [ - -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, -0.764, - -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, -1.164, -1.144, - -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, -0.172, -0.32, -0.58, -0.672, - -0.952, -0.988, -1.092, -1.16, -1.104, -1.032, -0.924, -0.872, -0.736, -0.256, - 0.276, -0.392, -0.444, -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, - -0.884, -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, - -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, -0.372, - -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, -1.208, -0.992, -0.828, - -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, -0.084, -0.124, -0.208, -0.512, - -0.816, -1.18, -1.332 + -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, + -0.764, -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, + -1.164, -1.144, -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, + -0.172, -0.32, -0.58, -0.672, -0.952, -0.988, -1.092, -1.16, -1.104, + -1.032, -0.924, -0.872, -0.736, -0.256, 0.276, -0.392, -0.444, + -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, -0.884, + -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, + -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, + -0.372, -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, + -1.208, -0.992, -0.828, -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, + -0.084, -0.124, -0.208, -0.512, -0.816, -1.18, -1.332 ] } }, @@ -862,37 +942,42 @@ "ID": 1705437819739, "data": { "x": [ - -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, -0.928, - -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, 0.14, -0.124, -0.264, - -0.324, -0.376, -0.636, -1.312, -1.216, -1.036, -0.744, -0.256, 0.188, 0.132, - 0.112, 0.164, 0.008, -0.132, -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, - -0.988, -0.648, -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, - -0.336, -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, -0.084, - 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, -0.468, -1.032, - -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, -0.132, -0.22, -0.088, -0.164, - -0.252, -0.32, -0.368, -0.432, -0.604, -0.876, -1.016, -1, -0.92, -0.848 + -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, + -0.928, -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, + 0.14, -0.124, -0.264, -0.324, -0.376, -0.636, -1.312, -1.216, + -1.036, -0.744, -0.256, 0.188, 0.132, 0.112, 0.164, 0.008, -0.132, + -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, -0.988, -0.648, + -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, -0.336, + -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, + -0.084, 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, + -0.468, -1.032, -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, + -0.132, -0.22, -0.088, -0.164, -0.252, -0.32, -0.368, -0.432, + -0.604, -0.876, -1.016, -1, -0.92, -0.848 ], "y": [ - -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, -1.256, - -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, 1.132, 0.848, - 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, -0.496, -0.092, 0.288, - 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, -1.436, -1.448, -1.124, -0.968, - -0.736, -0.62, 0.052, 0.148, 0.328, 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, - -0.828, -1.444, -1.42, -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, - 0.456, 0.816, 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, - -0.58, -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, + -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, + -1.256, -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, + 1.132, 0.848, 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, + -0.496, -0.092, 0.288, 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, + -1.436, -1.448, -1.124, -0.968, -0.736, -0.62, 0.052, 0.148, 0.328, + 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, -0.828, -1.444, -1.42, + -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, 0.456, 0.816, + 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, -0.58, + -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, 0.036, -0.832, -1.244, -1.18, -0.9, -0.764, -0.8 ], "z": [ - -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, 1.76, 2.04, - 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, -2.04, -1.44, -1.12, -0.88, - -0.312, 0.328, 0.988, 1.404, 1.352, 0.476, -0.152, -1.276, -2.04, -2.04, - -2.04, -2.04, -1.668, -1.176, -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, - 0.636, -0.136, -1.344, -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, - -0.616, 0.18, 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, - -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, 1.32, 0.712, - 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, -1.692, -1.396, -1.04, - -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, 0.68 + -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, + 1.76, 2.04, 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, + -2.04, -1.44, -1.12, -0.88, -0.312, 0.328, 0.988, 1.404, 1.352, + 0.476, -0.152, -1.276, -2.04, -2.04, -2.04, -2.04, -1.668, -1.176, + -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, 0.636, -0.136, -1.344, + -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, -0.616, 0.18, + 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, + -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, + 1.32, 0.712, 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, + -1.692, -1.396, -1.04, -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, + 0.68 ] } }, @@ -900,39 +985,42 @@ "ID": 1705437816208, "data": { "x": [ - -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, 0.436, - 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, -1.112, -0.712, - -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, -0.164, -0.204, -0.144, - 0.252, -0.232, -1.312, -1.132, -0.92, -0.764, -0.432, -0.072, 0.096, 0.184, - 0.156, 0.052, -0.02, -0.068, 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, - -0.576, -0.328, -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, - -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, 0.052, 0.092, - 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, -1.376, -1.048, -0.872, - -0.776, -0.608, -0.324, -0.128, -0.004, 0.024, 0.024, -0.008, -0.12, -0.248, - -0.384, -0.356 + -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, + 0.436, 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, + -1.112, -0.712, -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, + -0.164, -0.204, -0.144, 0.252, -0.232, -1.312, -1.132, -0.92, + -0.764, -0.432, -0.072, 0.096, 0.184, 0.156, 0.052, -0.02, -0.068, + 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, -0.576, -0.328, + -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, + -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, + 0.052, 0.092, 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, + -1.376, -1.048, -0.872, -0.776, -0.608, -0.324, -0.128, -0.004, + 0.024, 0.024, -0.008, -0.12, -0.248, -0.384, -0.356 ], "y": [ - -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, 1.008, - 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, -2.04, -2.04, -1.464, - -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, 1.88, 1.476, 0.836, -0.228, -2.04, - -2.04, -2.04, -2.024, -1.712, -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, - 1.084, 0.184, -2.04, -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, - 1.648, 1.532, 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, - -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, -0.748, - -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, 0.868, 1.392, - 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 + -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, + 1.008, 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, + -2.04, -2.04, -1.464, -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, + 1.88, 1.476, 0.836, -0.228, -2.04, -2.04, -2.04, -2.024, -1.712, + -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, 1.084, 0.184, -2.04, + -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, 1.648, 1.532, + 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, + -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, + -0.748, -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, + 0.868, 1.392, 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 ], "z": [ - -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, -0.072, - -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, 0.736, 0.544, 0.124, - -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, -1.136, -1, -0.92, -0.82, - -0.736, -0.34, 0.824, 0.26, -0.084, -0.072, -0.176, -0.42, -0.6, -0.852, - -1.132, -1.308, -1.16, -0.968, -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, - -0.364, -0.6, -0.932, -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, - 0.392, 0.376, -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, - -1.024, -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, - -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, -0.972, - -0.912, -0.944 + -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, + -0.072, -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, + 0.736, 0.544, 0.124, -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, + -1.136, -1, -0.92, -0.82, -0.736, -0.34, 0.824, 0.26, -0.084, + -0.072, -0.176, -0.42, -0.6, -0.852, -1.132, -1.308, -1.16, -0.968, + -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, -0.364, -0.6, -0.932, + -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, 0.392, 0.376, + -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, -1.024, + -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, + -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, + -0.972, -0.912, -0.944 ] } }, @@ -940,37 +1028,42 @@ "ID": 1705437808407, "data": { "x": [ - -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, -0.464, -0.328, - -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, 0.116, 0.068, -0.084, -0.348, - -0.396, -0.628, -0.752, -0.744, -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, - -0.308, -0.324, -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, - -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, -0.94, -0.844, - -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, 0.284, 0.272, 0.288, 0.232, - 0.152, 0.004, -0.28, -0.408, -0.656, -0.836, -1.068, -1.036, -1.016, -0.952, - -0.844, -0.724, -0.68, -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, - 0.292, 0.42, 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 + -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, + -0.464, -0.328, -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, + 0.116, 0.068, -0.084, -0.348, -0.396, -0.628, -0.752, -0.744, + -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, -0.308, -0.324, + -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, + -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, + -0.94, -0.844, -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, + 0.284, 0.272, 0.288, 0.232, 0.152, 0.004, -0.28, -0.408, -0.656, + -0.836, -1.068, -1.036, -1.016, -0.952, -0.844, -0.724, -0.68, + -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, 0.292, 0.42, + 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 ], "y": [ - 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, 0.556, - 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, 0.144, 0.472, - 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, 0.524, 0.688, 0.664, - 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, 0.408, 0.18, 0.468, 0.744, - 0.724, 0.684, 0.88, 1.072, 1.108, 1, 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, - 0.5, 0.404, 0.364, 0.368, 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, - 0.908, 1.184, 1.192, 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, - 0.812, 0.78, 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, - 0.452, 0.732, 0.976 + 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, + 0.556, 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, + 0.144, 0.472, 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, + 0.524, 0.688, 0.664, 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, + 0.408, 0.18, 0.468, 0.744, 0.724, 0.684, 0.88, 1.072, 1.108, 1, + 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, 0.5, 0.404, 0.364, 0.368, + 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, 0.908, 1.184, 1.192, + 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, 0.812, 0.78, + 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, 0.452, + 0.732, 0.976 ], "z": [ - -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, -0.76, - -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, -0.492, -0.796, - -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, -1.488, -1.508, -1.12, - -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, -0.012, 0, 0.016, 0.028, -0.012, - -0.3, -0.692, -1.236, -1.444, -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, - -1.524, -1.288, -0.888, -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, - 0.144, 0.18, 0.116, -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, - -1.304, -1.312, -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, - -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, -1.076, -1.304 + -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, + -0.76, -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, + -0.492, -0.796, -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, + -1.488, -1.508, -1.12, -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, + -0.012, 0, 0.016, 0.028, -0.012, -0.3, -0.692, -1.236, -1.444, + -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, -1.524, -1.288, -0.888, + -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, 0.144, 0.18, 0.116, + -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, -1.304, -1.312, + -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, + -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, + -1.076, -1.304 ] } }, @@ -978,38 +1071,42 @@ "ID": 1705437804840, "data": { "x": [ - 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, -0.084, -0.284, - -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, 0.512, 0, -0.616, 0.156, - 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, 0.764, 0.416, 0.432, 0.444, 0.32, - 0.312, 0.348, 0.32, 0.472, 0.292, 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, - 0.708, 0.684, 0.516, 0.46, 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, - 0.032, 0.092, 0.136, 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, - 0.62, 0.572, 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, - 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, 0.72, 0.7, - 0.48, 0.304, 0.052 + 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, + -0.084, -0.284, -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, + 0.512, 0, -0.616, 0.156, 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, + 0.764, 0.416, 0.432, 0.444, 0.32, 0.312, 0.348, 0.32, 0.472, 0.292, + 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, 0.708, 0.684, 0.516, 0.46, + 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, 0.032, 0.092, 0.136, + 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, 0.62, 0.572, + 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, + 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, + 0.72, 0.7, 0.48, 0.304, 0.052 ], "y": [ - -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, 0.144, - 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, 1.212, 0.732, -1.24, - -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, -0.816, -0.044, 0.556, 1.332, - 1.496, 1.224, 1.184, 1.48, 1.644, 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, - -1.224, -1.252, -1.116, -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, - 1.844, 1.728, 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, - -0.976, -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, - 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, -0.936, - -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, 0.248 + -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, + 0.144, 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, + 1.212, 0.732, -1.24, -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, + -0.816, -0.044, 0.556, 1.332, 1.496, 1.224, 1.184, 1.48, 1.644, + 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, -1.224, -1.252, -1.116, + -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, 1.844, 1.728, + 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, -0.976, + -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, + 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, + -0.936, -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, + 0.248 ], "z": [ - -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, -0.296, - -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, -0.284, -0.884, - -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, -0.732, -0.292, -0.34, - -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, -0.752, -0.66, -0.652, -0.716, - -0.632, -0.48, -0.452, -0.532, -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, - -1.24, -1, -0.872, -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, - -0.54, -0.552, -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, - -1.1, -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, - -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, -0.504, -0.652, - -0.704, -0.916 + -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, + -0.296, -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, + -0.284, -0.884, -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, + -0.732, -0.292, -0.34, -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, + -0.752, -0.66, -0.652, -0.716, -0.632, -0.48, -0.452, -0.532, + -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, -1.24, -1, -0.872, + -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, -0.54, -0.552, + -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, -1.1, + -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, + -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, + -0.504, -0.652, -0.704, -0.916 ] } } diff --git a/src/test-fixtures/gesture-data.json b/src/test-fixtures/gesture-data.json index a8f9852be..9304dbd14 100644 --- a/src/test-fixtures/gesture-data.json +++ b/src/test-fixtures/gesture-data.json @@ -7,39 +7,42 @@ "ID": 1705437992143, "data": { "x": [ - -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, -1.3, - -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, -2.04, -2.008, - -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, -0.232, -1.132, -2.04, - -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, 0.608, 0.332, -0.004, -0.876, - -2.04, -2.04, -2.04, -1.688, -1.396, -0.828, -0.032, 0.36, 0.492, 0.288, - 0.008, -0.492, -1.368, -2.04, -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, - 0.664, 0.544, 0.248, -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, - -0.612, 0.16, 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, - -1.668, -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, + -0.112, -0.356, -0.62, -0.892, -1.508, -1.848, -2.04, -2.04, -1.868, + -1.3, -0.824, -0.48, 0.256, 0.752, 0.864, -0.044, -0.908, -1.716, + -2.04, -2.008, -1.856, -1.336, -0.84, 0.192, 0.58, 0.504, -0.008, + -0.232, -1.132, -2.04, -2.04, -2.04, -1.012, -0.48, 0.028, 0.256, + 0.608, 0.332, -0.004, -0.876, -2.04, -2.04, -2.04, -1.688, -1.396, + -0.828, -0.032, 0.36, 0.492, 0.288, 0.008, -0.492, -1.368, -2.04, + -2.04, -1.828, -1.368, -0.668, -0.248, 0.108, 0.664, 0.544, 0.248, + -0.404, -1.388, -2.04, -2.04, -1.86, -1.372, -1.072, -0.612, 0.16, + 0.52, 0.488, 0.256, -0.144, -0.752, -1.732, -2.04, -2.04, -1.668, + -1.208, -0.884, -0.556, 0.024, 0.48, 0.556, 0.248, -0.096, -0.9, -2.04, -2.04, -1.992 ], "y": [ - 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, 0.324, - 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, 0.328, 0.364, - 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, 1.076, -0.116, 0.048, - 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, 0.524, 0.416, 0.252, 0.308, - 0.376, 0.316, 0.356, 0.644, 0.672, 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, - 0.256, 0.272, 0.404, 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, - 0.284, 0.28, 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, - 0.452, 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, 0.288, - 0.204, 0.476, 1.112, 0.2 + 0.436, 0.416, 0.308, 0.168, 0.148, -0.056, 0.352, 0.356, 0.3, 0.244, + 0.324, 0.264, 0.16, 0.396, 0.456, 0.208, 0.244, 0.252, 0.312, 0.396, + 0.328, 0.364, 0.204, 0.188, 0.548, 0.536, 0.388, 0.22, 0.052, 0.54, + 1.076, -0.116, 0.048, 0.132, 0.448, 0.528, 0.608, 0.6, 0.412, 0.152, + 0.524, 0.416, 0.252, 0.308, 0.376, 0.316, 0.356, 0.644, 0.672, + 0.544, 0.404, 0.164, 0.208, 0.624, 0.536, 0.256, 0.272, 0.404, + 0.492, 0.5, 0.712, 0.656, 0.568, 0.264, 0.296, 0.496, 0.284, 0.28, + 0.288, 0.42, 0.492, 0.488, 0.616, 0.684, 0.588, 0.396, 0.232, 0.452, + 0.98, 0.484, 0.36, 0.372, 0.544, 0.48, 0.496, 0.6, 0.692, 0.528, + 0.288, 0.204, 0.476, 1.112, 0.2 ], "z": [ - -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, -0.096, - -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, -0.108, 0.196, 0.268, - 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, -0.26, -0.092, -0.204, 0.304, 0.78, - 0.364, 0.108, -0.24, -0.528, -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, - 0.552, 0.42, 0.128, 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, - -0.004, -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, - -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, -0.288, - -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, 0.504, 0.196, - -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, -0.072, -0.176, 0.136, - 0.34, 0.7 + -0.576, -0.592, -0.516, -0.38, -0.368, 0.208, -0.028, 0.048, 0.008, + -0.096, -0.288, -0.396, -0.516, -0.556, -0.312, -0.056, -0.168, + -0.108, 0.196, 0.268, 0.1, -0.16, -0.32, -0.64, -0.692, -0.56, + -0.26, -0.092, -0.204, 0.304, 0.78, 0.364, 0.108, -0.24, -0.528, + -0.608, -0.552, -0.208, -0.048, -0.14, 0.42, 0.552, 0.42, 0.128, + 0.276, -0.332, -0.648, -0.648, -0.544, -0.212, 0.036, -0.004, + -0.148, 0.304, 0.212, 0.344, 0.352, 0.064, -0.572, -0.628, -0.616, + -0.276, -0.008, -0.18, -0.044, 0.012, 0.368, 0.32, 0.372, -0.092, + -0.288, -0.608, -0.712, -0.432, -0.252, -0.024, 0.044, -0.052, 0.4, + 0.504, 0.196, -0.016, -0.172, -0.308, -0.66, -0.72, -0.52, -0.268, + -0.072, -0.176, 0.136, 0.34, 0.7 ] } }, @@ -47,37 +50,41 @@ "ID": 1705437989960, "data": { "x": [ - -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, -1.496, - -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, 0.864, 0.256, - -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, 0.42, 0.608, 0.488, - 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, -1.588, -1.08, -0.512, -0.028, - 0.216, 0.268, 0.052, -0.38, -0.684, -0.788, -1.056, -1.768, -2.04, -2.04, - -1.344, -0.8, -0.436, -0.172, 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, - -1.544, -2.02, -2.016, -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, - -0.46, -0.816, -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, - 0.06, 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 + -0.4, 0.232, 0.708, 1.068, 1.284, 1.36, 1.02, 0.816, 0.332, -0.152, + -1.496, -2.04, -2.04, -1.14, -0.968, -0.648, 0.232, 1, 1.34, 1.32, + 0.864, 0.256, -0.696, -1.884, -2.04, -2.04, -1.62, -1.028, -0.196, + 0.42, 0.608, 0.488, 0.168, -0.528, -1.488, -2.028, -2.04, -2.04, + -1.588, -1.08, -0.512, -0.028, 0.216, 0.268, 0.052, -0.38, -0.684, + -0.788, -1.056, -1.768, -2.04, -2.04, -1.344, -0.8, -0.436, -0.172, + 0.22, 0.456, -0.124, -0.504, -0.864, -1.14, -1.544, -2.02, -2.016, + -1.52, -1.004, -0.456, -0.156, 0.064, 0.128, -0.204, -0.46, -0.816, + -1.22, -1.596, -1.884, -1.816, -1.44, -0.948, -0.516, -0.196, 0.06, + 0.124, -0.16, -0.504, -0.72, -0.928, -1.42, -1.8, -2.032, -1.76 ], "y": [ - 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, 0.456, 0.188, - 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, 0.32, 0.252, 0.208, 0.152, - 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, - 0.596, 0.644, 0.732, 0.58, 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, - 0.38, 0.284, 0.22, 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, - 0.348, 0.34, 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, - 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, 0.556, - 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, 0.328, 0.536, - 0.78, 0.7 + 0.592, 0.364, 0.128, -0.004, -0.06, -0.156, -0.04, 0.08, 0.284, + 0.456, 0.188, 1.104, 0.9, 0.6, 0.536, 0.388, 0.34, 0.316, 0.292, + 0.32, 0.252, 0.208, 0.152, 0.3, 0.792, 0.776, 0.748, 0.588, 0.44, + 0.372, 0.4, 0.424, 0.376, 0.296, 0.448, 0.596, 0.644, 0.732, 0.58, + 0.496, 0.46, 0.444, 0.568, 0.56, 0.456, 0.408, 0.38, 0.284, 0.22, + 0.352, 0.792, 0.636, 0.452, 0.456, 0.468, 0.392, 0.376, 0.348, 0.34, + 0.396, 0.456, 0.364, 0.432, 0.704, 0.728, 0.584, 0.416, 0.424, + 0.428, 0.432, 0.288, 0.3, 0.368, 0.392, 0.436, 0.512, 0.684, 0.664, + 0.556, 0.496, 0.472, 0.456, 0.444, 0.7, 0.304, 0.36, 0.292, 0.276, + 0.328, 0.536, 0.78, 0.7 ], "z": [ - -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, -0.596, -0.696, - -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, -0.888, -0.948, -0.972, -1.192, - -1.148, -0.808, -0.636, -0.42, 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, - -1.032, -0.972, -0.656, -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, - -0.364, -0.744, -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, - 1.116, 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, -0.7, - -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, -1.508, -1.86, - -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, 0.256, -0.24, -0.712, - -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, 0.18, 0.576, 1.292, 0.92 + -1.196, -0.908, -0.712, -0.556, -0.44, -0.42, -0.548, -0.512, + -0.596, -0.696, -1.364, -0.26, -0.592, -0.788, -0.876, -0.84, + -0.888, -0.948, -0.972, -1.192, -1.148, -0.808, -0.636, -0.42, + 0.076, 0.42, 0.124, -0.224, -0.572, -0.924, -1.032, -0.972, -0.656, + -0.708, -0.492, -0.096, 0.428, 0.548, 0.196, -0.012, -0.364, -0.744, + -0.996, -1.08, -1.448, -1.444, -1.136, -0.652, -0.104, 0.528, 1.116, + 0.88, 0.384, 0.048, -0.348, -0.804, -1.34, -1.604, -1.624, -1.3, + -0.7, -0.116, 0.544, 0.996, 0.784, 0.448, 0.084, -0.468, -0.964, + -1.508, -1.86, -1.5, -1.08, -0.54, 0.112, 0.68, 1.224, 1.028, 0.66, + 0.256, -0.24, -0.712, -1.24, -1.232, -1.604, -1.456, -1.004, -0.428, + 0.18, 0.576, 1.292, 0.92 ] } }, @@ -85,38 +92,41 @@ "ID": 1705437987590, "data": { "x": [ - -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, 0.872, - 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, -0.312, -0.144, 0.076, - 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, 0.004, -1.244, -0.88, -0.372, -0.04, - 0.252, 0.616, 1.016, 1.332, 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, - -0.312, -0.052, 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, - -1.504, -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, - 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, 1.568, 1.692, - 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, 0.008, 0.68, 1.264, 1.68, - 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 + -1.464, -1.144, -0.92, -0.696, -0.22, -0.016, 0.168, 0.384, 0.552, + 0.872, 1.164, 1.284, 1.192, 0.904, 0.38, -1.068, -1.9, -0.784, + -0.312, -0.144, 0.076, 0.36, 0.812, 1.116, 1.18, 1.072, 0.752, + 0.004, -1.244, -0.88, -0.372, -0.04, 0.252, 0.616, 1.016, 1.332, + 1.296, 1.068, 0.748, 0.384, -0.748, -1.776, -0.68, -0.312, -0.052, + 0.396, 0.932, 1.264, 1.364, 1.144, 0.84, 0.328, -0.696, -1.504, + -0.78, -0.38, -0.1, 0.244, 0.608, 0.86, 1.172, 1.436, 1.492, 1.268, + 0.784, -0.5, -1.484, -0.62, -0.272, -0.128, 0.324, 0.716, 1.168, + 1.568, 1.692, 1.452, 0.88, -0.108, -1.46, -1.128, -0.616, -0.432, + 0.008, 0.68, 1.264, 1.68, 1.804, 1.496, 0.884, -0.14, -1.08, -0.852 ], "y": [ - 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, 0.068, - 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, 0.588, 0.452, - 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, 0.372, 0.344, 0.456, 0.368, - 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, 0.104, 0, 0.484, 0.416, 0.384, 0.356, - 0.32, 0.216, 0.244, 0.284, 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, - 0.336, 0.26, 0.224, 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, - 0.42, 0.396, 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, - 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, 0.044, 0.08, - 0.352, 0.632 + 0.668, 0.576, 0.556, 0.508, 0.36, 0.472, 0.384, 0.34, 0.24, 0.124, + 0.068, 0.052, 0.052, 0.12, 0.02, 0.124, 0.836, 0.556, 0.612, 0.62, + 0.588, 0.452, 0.304, 0.26, 0.108, 0.096, 0.108, -0.108, 0.072, + 0.372, 0.344, 0.456, 0.368, 0.3, 0.316, 0.332, 0.272, 0.204, 0.196, + 0.104, 0, 0.484, 0.416, 0.384, 0.356, 0.32, 0.216, 0.244, 0.284, + 0.208, 0.196, 0.136, -0.06, 0.52, 0.34, 0.332, 0.336, 0.26, 0.224, + 0.184, 0.104, 0.116, 0.132, 0.116, 0.084, 0.068, 0.588, 0.42, 0.396, + 0.364, 0.272, 0.196, 0.044, -0.008, 0.02, 0, 0.048, -0.02, 0.428, + 0.592, 0.552, 0.524, 0.324, 0.128, -0.1, -0.128, -0.068, -0.052, + 0.044, 0.08, 0.352, 0.632 ], "z": [ - -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, -0.384, - -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, -0.844, -0.48, - -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, -1.596, -1.952, -2.016, - -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, -0.06, -0.324, -0.676, -1.124, - -1.784, -2.04, -1.748, -1.408, -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, - -0.396, -0.836, -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, - 0.112, 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, - -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, -2.04, - -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, -0.472, -0.992, - -1.876, -2.04 + -0.148, -0.472, -0.476, -0.388, -0.684, -0.404, -0.46, -0.38, -0.36, + -0.384, -0.572, -0.792, -1.084, -1.36, -1.62, -2.04, -1.748, -1.216, + -0.844, -0.48, -0.168, 0.036, 0.048, -0.032, -0.232, -0.596, -1.076, + -1.596, -1.952, -2.016, -1.496, -1.008, -0.52, -0.216, -0.04, 0.04, + -0.06, -0.324, -0.676, -1.124, -1.784, -2.04, -1.748, -1.408, + -0.936, -0.444, -0.108, 0.104, 0.088, -0.116, -0.396, -0.836, + -1.608, -2.04, -1.964, -1.612, -1.184, -0.764, -0.46, -0.156, 0.112, + 0.204, 0.056, -0.304, -0.76, -1.54, -2.04, -2.04, -1.768, -1.204, + -0.708, -0.244, 0.152, 0.332, 0.264, -0.024, -0.424, -0.984, -1.7, + -2.04, -1.984, -1.372, -0.764, -0.296, 0.084, 0.216, 0.12, -0.136, + -0.472, -0.992, -1.876, -2.04 ] } }, @@ -124,39 +134,42 @@ "ID": 1705437985014, "data": { "x": [ - -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, -1.04, -0.82, - -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, 1.188, 0.936, 0.456, 0.132, - -0.5, -1.94, -2.04, -2.04, -1.536, -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, - 0.812, 0.396, -0.2, -1.508, -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, - 0.776, 0.716, 0.46, -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, - 0.148, 0.316, 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, - -0.792, -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, - -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, 0.452, - 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, 0.076, 0.544, - 0.528 + -0.392, -0.532, -0.452, -0.576, -0.748, -0.908, -0.984, -1.12, + -1.04, -0.82, -0.668, -0.184, 0.18, 0.428, 0.816, 1.248, 1.38, + 1.188, 0.936, 0.456, 0.132, -0.5, -1.94, -2.04, -2.04, -1.536, + -0.74, -0.38, 0.056, 0.4, 0.984, 1.208, 0.812, 0.396, -0.2, -1.508, + -2.024, -1.576, -0.732, -0.212, 0.252, 0.536, 0.776, 0.716, 0.46, + -0.036, -1.18, -1.752, -1.272, -0.952, -0.592, -0.348, 0.148, 0.316, + 0.884, 0.832, 0.688, 0.452, -0.216, -1.084, -1.94, -1.252, -0.792, + -0.544, -0.16, 0.284, 0.676, 0.672, 0.56, 0.34, -0.036, -0.916, + -2.036, -1.516, -0.892, -0.536, -0.256, -0.068, 0.256, 0.528, 0.584, + 0.452, 0.124, -0.424, -1.404, -1.912, -1.208, -0.78, -0.54, -0.264, + 0.076, 0.544, 0.528 ], "y": [ - 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, 0.512, - 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, 0.164, 0.048, -0.22, - 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, 0.236, 0.132, 0.064, 0.124, 0.152, - 0.304, 0.248, 0.896, 1.34, 1.08, 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, - 0.236, 0.24, 0.66, 1.26, 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, - 0.124, 0.192, 0.244, 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, - 0.076, 0.092, 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, - 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, 0.672, - 0.496, 0.26, 0.232, 0.228 + 0.328, 0.336, 0.336, 0.304, 0.328, 0.38, 0.396, 0.384, 0.348, 0.46, + 0.512, 0.408, 0.348, 0.18, -0.028, -0.08, -0.036, 0.02, 0.136, + 0.164, 0.048, -0.22, 0.464, 1.092, 1.044, 0.812, 0.496, 0.348, + 0.236, 0.132, 0.064, 0.124, 0.152, 0.304, 0.248, 0.896, 1.34, 1.08, + 0.76, 0.636, 0.436, 0.244, 0.224, 0.176, 0.236, 0.24, 0.66, 1.26, + 1.124, 1.04, 0.784, 0.572, 0.008, 0.272, 0.068, 0.124, 0.192, 0.244, + 0.564, 0.636, 1.372, 0.976, 0.904, 0.68, 0.416, 0.036, 0.076, 0.092, + 0.208, 0.348, 0.412, 0.768, 1.088, 1, 0.844, 0.708, 0.54, 0.424, + 0.228, 0.196, 0.116, 0.232, 0.34, 0.492, 0.84, 1.196, 0.956, 0.844, + 0.672, 0.496, 0.26, 0.232, 0.228 ], "z": [ - -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, -0.892, - -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, -1.144, -1.044, - -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, -0.476, -0.416, -0.764, - -0.892, -1.332, -1.872, -1.152, -0.744, -0.904, -0.704, -0.028, -0.296, - -0.256, -0.592, -0.848, -1, -1.404, -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, - -0.14, -0.3, -0.576, -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, - -0.492, -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, - -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, -1.328, - -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, -0.256, -0.28, -0.424, - -0.608, -0.652, -1.504, -1.412 + -0.932, -0.884, -1.076, -0.972, -0.776, -0.744, -0.856, -0.884, + -0.892, -0.844, -0.744, -0.572, -0.504, -0.576, -0.668, -1, -1.204, + -1.144, -1.044, -0.832, -0.664, -1.024, -0.46, 0.276, -0.064, -0.16, + -0.476, -0.416, -0.764, -0.892, -1.332, -1.872, -1.152, -0.744, + -0.904, -0.704, -0.028, -0.296, -0.256, -0.592, -0.848, -1, -1.404, + -1.22, -1.3, -0.76, -0.848, 0.028, -0.14, -0.14, -0.3, -0.576, + -1.052, -0.276, -2.04, -1.556, -1.308, -1.328, -0.572, -0.492, + -0.212, 0.076, -0.28, -0.44, -0.688, -0.856, -1.768, -1.464, -1.268, + -0.9, -0.588, -0.46, -0.352, -0.18, -0.328, -0.404, -0.576, -0.9, + -1.328, -1.48, -1.444, -1.12, -0.704, -0.876, -0.452, -0.156, + -0.256, -0.28, -0.424, -0.608, -0.652, -1.504, -1.412 ] } }, @@ -164,39 +177,43 @@ "ID": 1705437822437, "data": { "x": [ - -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, -0.896, -0.96, - -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, -0.204, -0.12, -0.164, - -0.248, -0.236, -0.088, -0.644, -1.068, -0.812, -0.644, -0.56, -0.372, -0.292, - -0.272, -0.076, -0.04, -0.08, -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, - -0.828, -0.72, -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, - -0.272, -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, -0.16, - -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, -0.872, -0.844, -0.72, - -0.624, -0.572, -0.48, -0.392, -0.356, -0.292, -0.244, -0.208, -0.288, -0.372, - -0.364, -0.216, -0.464, -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, - -0.468, -0.34, -0.272 + -0.684, -0.688, -0.672, -0.668, -0.628, -0.564, -0.576, -0.7, + -0.896, -0.96, -1.016, -0.888, -0.752, -0.564, -0.46, -0.36, -0.288, + -0.204, -0.12, -0.164, -0.248, -0.236, -0.088, -0.644, -1.068, + -0.812, -0.644, -0.56, -0.372, -0.292, -0.272, -0.076, -0.04, -0.08, + -0.172, -0.288, -0.276, -0.06, -0.532, -1.132, -0.828, -0.72, + -0.712, -0.368, -0.296, -0.34, -0.28, -0.216, -0.164, -0.18, -0.272, + -0.408, -0.644, -0.764, -0.732, -0.636, -0.62, -0.524, -0.344, + -0.16, -0.032, -0.088, -0.136, -0.224, -0.252, -0.18, -0.484, + -0.872, -0.844, -0.72, -0.624, -0.572, -0.48, -0.392, -0.356, + -0.292, -0.244, -0.208, -0.288, -0.372, -0.364, -0.216, -0.464, + -1.016, -0.904, -0.956, -0.812, -0.564, -0.632, -0.468, -0.34, + -0.272 ], "y": [ - 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, -1.216, - -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, 1.692, 1.224, - 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, -0.204, 0.568, 0.92, 1.304, - 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, -2.04, -2.04, -2.032, -1.288, -0.796, - 0.108, 1.112, 1.588, 1.832, 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, - -2.04, -1.68, -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, - -0.08, -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, - 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, -2.04, -2.004, - -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 + 0.1, 0.048, 0.028, 0.24, 0.336, 0.376, 0.208, -0.328, -1.1, -1.368, + -1.216, -0.936, -0.488, -0.032, 0.468, 1.056, 1.708, 1.856, 1.896, + 1.692, 1.224, 0.592, -0.744, -2.04, -2.04, -2.04, -1.404, -0.66, + -0.204, 0.568, 0.92, 1.304, 1.768, 1.876, 1.5, 1.056, 0.596, -0.508, + -2.04, -2.04, -2.032, -1.288, -0.796, 0.108, 1.112, 1.588, 1.832, + 1.832, 1.588, 1.176, 0.232, -0.968, -2.04, -2.04, -2.04, -1.68, + -1.08, -0.388, 0.248, 1.02, 1.704, 2.04, 1.98, 1.488, 0.868, -0.08, + -1.912, -2.04, -2.04, -1.916, -1.296, -0.684, 0.088, 0.532, 0.92, + 1.156, 1.348, 1.58, 1.616, 1.312, 0.816, -0.012, -2.04, -2.04, + -2.04, -2.004, -1.492, -0.904, 0.028, 0.564, 0.976, 1.292 ], "z": [ - -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, -0.764, - -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, -1.164, -1.144, - -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, -0.172, -0.32, -0.58, -0.672, - -0.952, -0.988, -1.092, -1.16, -1.104, -1.032, -0.924, -0.872, -0.736, -0.256, - 0.276, -0.392, -0.444, -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, - -0.884, -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, - -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, -0.372, - -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, -1.208, -0.992, -0.828, - -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, -0.084, -0.124, -0.208, -0.512, - -0.816, -1.18, -1.332 + -0.736, -0.788, -0.756, -0.768, -0.796, -0.908, -0.936, -1.024, + -0.764, -0.412, -0.34, -0.408, -0.56, -0.62, -0.624, -0.896, -1.092, + -1.164, -1.144, -0.992, -0.844, -0.808, -0.736, -0.016, 0.356, + -0.172, -0.32, -0.58, -0.672, -0.952, -0.988, -1.092, -1.16, -1.104, + -1.032, -0.924, -0.872, -0.736, -0.256, 0.276, -0.392, -0.444, + -0.476, -0.656, -1.124, -1.136, -1.156, -1.18, -1.004, -0.884, + -0.812, -0.664, -0.508, -0.284, -0.312, -0.392, -0.5, -0.64, -0.84, + -1.172, -1.3, -1.352, -1.128, -0.968, -0.836, -0.764, -0.5, -0.152, + -0.372, -0.372, -0.432, -0.612, -0.892, -1.08, -1.212, -1.324, + -1.208, -0.992, -0.828, -0.744, -0.86, -0.872, -0.732, 0.012, 0.256, + -0.084, -0.124, -0.208, -0.512, -0.816, -1.18, -1.332 ] } }, @@ -204,37 +221,42 @@ "ID": 1705437819739, "data": { "x": [ - -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, -0.928, - -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, 0.14, -0.124, -0.264, - -0.324, -0.376, -0.636, -1.312, -1.216, -1.036, -0.744, -0.256, 0.188, 0.132, - 0.112, 0.164, 0.008, -0.132, -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, - -0.988, -0.648, -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, - -0.336, -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, -0.084, - 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, -0.468, -1.032, - -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, -0.132, -0.22, -0.088, -0.164, - -0.252, -0.32, -0.368, -0.432, -0.604, -0.876, -1.016, -1, -0.92, -0.848 + -0.268, -0.048, 0.06, 0.028, -0.108, -0.3, -0.412, -0.536, -0.668, + -0.928, -1.18, -1.26, -0.86, -0.372, 0.344, 0.564, 0.548, 0.392, + 0.14, -0.124, -0.264, -0.324, -0.376, -0.636, -1.312, -1.216, + -1.036, -0.744, -0.256, 0.188, 0.132, 0.112, 0.164, 0.008, -0.132, + -0.22, -0.28, -0.504, -0.924, -1.192, -1.176, -0.988, -0.648, + -0.088, 0.08, 0.212, 0.104, 0.144, 0.008, -0.112, -0.264, -0.336, + -0.312, -0.468, -0.856, -1.132, -1.12, -0.932, -0.692, -0.384, + -0.084, 0.132, 0.064, 0.02, 0.016, -0.116, -0.212, -0.288, -0.28, + -0.468, -1.032, -1.404, -1.112, -0.844, -0.616, -0.22, -0.044, + -0.132, -0.22, -0.088, -0.164, -0.252, -0.32, -0.368, -0.432, + -0.604, -0.876, -1.016, -1, -0.92, -0.848 ], "y": [ - -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, -1.256, - -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, 1.132, 0.848, - 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, -0.496, -0.092, 0.288, - 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, -1.436, -1.448, -1.124, -0.968, - -0.736, -0.62, 0.052, 0.148, 0.328, 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, - -0.828, -1.444, -1.42, -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, - 0.456, 0.816, 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, - -0.58, -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, + -0.22, -0.116, -0.016, 0.048, 0.056, 0, -0.02, 0.036, 0.056, -0.256, + -1.256, -1.636, -1.248, -0.856, -0.344, 0.048, 0.54, 1.044, 1.216, + 1.132, 0.848, 0.468, -0.156, -1.38, -1.892, -1.296, -0.94, -0.836, + -0.496, -0.092, 0.288, 0.664, 0.7, 0.648, 0.716, 0.6, 0.232, -0.704, + -1.436, -1.448, -1.124, -0.968, -0.736, -0.62, 0.052, 0.148, 0.328, + 0.54, 0.572, 0.564, 0.544, 0.32, -0.064, -0.828, -1.444, -1.42, + -1.224, -0.952, -0.748, -0.524, -0.44, -0.744, 0.068, 0.456, 0.816, + 0.788, 0.824, 0.536, 0.124, -0.688, -1.372, -1.516, -0.964, -0.58, + -0.488, -0.46, -0.132, 0.264, 0.432, 0.58, 0.7, 0.624, 0.556, 0.312, 0.036, -0.832, -1.244, -1.18, -0.9, -0.764, -0.8 ], "z": [ - -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, 1.76, 2.04, - 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, -2.04, -1.44, -1.12, -0.88, - -0.312, 0.328, 0.988, 1.404, 1.352, 0.476, -0.152, -1.276, -2.04, -2.04, - -2.04, -2.04, -1.668, -1.176, -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, - 0.636, -0.136, -1.344, -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, - -0.616, 0.18, 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, - -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, 1.32, 0.712, - 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, -1.692, -1.396, -1.04, - -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, 0.68 + -1.448, -1.808, -2.04, -2.04, -1.684, -1.22, -0.936, -0.54, 0.412, + 1.76, 2.04, 1.732, 0.708, -0.084, -1.284, -2.04, -2.04, -2.04, + -2.04, -1.44, -1.12, -0.88, -0.312, 0.328, 0.988, 1.404, 1.352, + 0.476, -0.152, -1.276, -2.04, -2.04, -2.04, -2.04, -1.668, -1.176, + -0.596, -0.04, 0.844, 1.492, 1.636, 1.064, 0.636, -0.136, -1.344, + -2.04, -2.04, -2.04, -2.04, -1.82, -1.36, -1.044, -0.616, 0.18, + 0.912, 1.432, 1.756, 1.2, 0.528, 0.064, -0.612, -1.708, -2.04, + -2.04, -2.04, -1.932, -1.668, -1.3, -0.744, -0.032, 0.692, 1.284, + 1.32, 0.712, 0.16, -0.344, -1.06, -2.04, -2.04, -2.04, -2.04, + -1.692, -1.396, -1.04, -0.636, 0.04, 0.568, 0.784, 0.948, 1.012, + 0.68 ] } }, @@ -242,39 +264,42 @@ "ID": 1705437816208, "data": { "x": [ - -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, 0.436, - 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, -1.112, -0.712, - -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, -0.164, -0.204, -0.144, - 0.252, -0.232, -1.312, -1.132, -0.92, -0.764, -0.432, -0.072, 0.096, 0.184, - 0.156, 0.052, -0.02, -0.068, 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, - -0.576, -0.328, -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, - -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, 0.052, 0.092, - 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, -1.376, -1.048, -0.872, - -0.776, -0.608, -0.324, -0.128, -0.004, 0.024, 0.024, -0.008, -0.12, -0.248, - -0.384, -0.356 + -0.692, -0.644, -0.548, -0.516, -0.74, -0.872, -0.26, 0.108, 0.268, + 0.436, 0.248, -0.04, -0.224, -0.272, -0.176, 0.316, -0.408, -1.336, + -1.112, -0.712, -0.52, -0.432, -0.188, 0.088, 0.148, 0.028, -0.072, + -0.164, -0.204, -0.144, 0.252, -0.232, -1.312, -1.132, -0.92, + -0.764, -0.432, -0.072, 0.096, 0.184, 0.156, 0.052, -0.02, -0.068, + 0.136, -0.276, -1.244, -1.136, -0.996, -0.872, -0.576, -0.328, + -0.172, -0.084, -0.072, -0.08, -0.168, -0.22, -0.128, -0.112, + -1.036, -1.22, -1.008, -0.856, -0.688, -0.368, -0.196, -0.036, + 0.052, 0.092, 0.076, 0.04, -0.084, -0.212, -0.168, 0.124, -0.724, + -1.376, -1.048, -0.872, -0.776, -0.608, -0.324, -0.128, -0.004, + 0.024, 0.024, -0.008, -0.12, -0.248, -0.384, -0.356 ], "y": [ - -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, 1.008, - 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, -2.04, -2.04, -1.464, - -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, 1.88, 1.476, 0.836, -0.228, -2.04, - -2.04, -2.04, -2.024, -1.712, -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, - 1.084, 0.184, -2.04, -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, - 1.648, 1.532, 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, - -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, -0.748, - -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, 0.868, 1.392, - 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 + -1.164, -1.268, -1.532, -1.984, -2.04, -2.04, -1.672, -0.532, 0.236, + 1.008, 1.736, 2.012, 1.868, 1.056, 0.692, -0.156, -2.04, -2.04, + -2.04, -2.04, -1.464, -0.956, -0.604, 0.364, 1.252, 1.812, 2.04, + 1.88, 1.476, 0.836, -0.228, -2.04, -2.04, -2.04, -2.024, -1.712, + -1.132, -0.2, 0.816, 1.624, 2.04, 2.04, 1.904, 1.084, 0.184, -2.04, + -2.04, -2.04, -1.536, -1.236, -0.616, 0, 0.796, 1.36, 1.648, 1.532, + 1.3, 0.904, 0.28, -1.288, -2.04, -2.04, -1.864, -1.692, -1.2, + -0.572, 0.008, 0.548, 1.228, 1.792, 2.02, 1.8, 1.408, 1.012, 0.528, + -0.748, -2.04, -2.04, -1.896, -1.128, -1.06, -0.936, -0.468, 0.244, + 0.868, 1.392, 1.772, 1.712, 1.304, 0.924, 0.544, 0.144 ], "z": [ - -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, -0.072, - -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, 0.736, 0.544, 0.124, - -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, -1.136, -1, -0.92, -0.82, - -0.736, -0.34, 0.824, 0.26, -0.084, -0.072, -0.176, -0.42, -0.6, -0.852, - -1.132, -1.308, -1.16, -0.968, -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, - -0.364, -0.6, -0.932, -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, - 0.392, 0.376, -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, - -1.024, -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, - -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, -0.972, - -0.912, -0.944 + -0.116, -0.136, -0.024, 0.108, 0.696, 0.724, 0.424, 0.18, 0.196, + -0.072, -0.788, -1.488, -1.564, -0.98, -0.712, -0.504, -0.528, + 0.736, 0.544, 0.124, -0.104, -0.204, -0.084, -0.356, -0.752, -1.048, + -1.136, -1, -0.92, -0.82, -0.736, -0.34, 0.824, 0.26, -0.084, + -0.072, -0.176, -0.42, -0.6, -0.852, -1.132, -1.308, -1.16, -0.968, + -0.868, -0.744, 0.668, 0.048, -0.156, -0.248, -0.364, -0.6, -0.932, + -1.116, -1.224, -1.196, -1.16, -1.044, -0.884, -0.748, 0.392, 0.376, + -0.156, -0.228, -0.268, -0.54, -0.74, -0.784, -0.816, -0.92, -1.024, + -0.984, -0.904, -0.868, -0.86, -1.072, 0.016, 0.704, -0.076, -0.348, + -0.336, -0.372, -0.38, -0.628, -0.84, -0.964, -1.06, -1.116, -1.044, + -0.972, -0.912, -0.944 ] } }, @@ -282,37 +307,42 @@ "ID": 1705437808407, "data": { "x": [ - -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, -0.464, -0.328, - -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, 0.116, 0.068, -0.084, -0.348, - -0.396, -0.628, -0.752, -0.744, -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, - -0.308, -0.324, -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, - -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, -0.94, -0.844, - -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, 0.284, 0.272, 0.288, 0.232, - 0.152, 0.004, -0.28, -0.408, -0.656, -0.836, -1.068, -1.036, -1.016, -0.952, - -0.844, -0.724, -0.68, -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, - 0.292, 0.42, 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 + -0.756, -0.972, -0.836, -0.82, -0.832, -0.728, -0.904, -0.376, + -0.464, -0.328, -0.112, 0.084, 0.172, 0.292, 0.404, 0.356, 0.3, + 0.116, 0.068, -0.084, -0.348, -0.396, -0.628, -0.752, -0.744, + -0.856, -0.82, -0.808, -0.688, -0.572, -0.436, -0.308, -0.324, + -0.068, 0.184, 0.24, 0.276, 0.268, 0.284, 0.26, 0.028, 0.016, + -0.224, -0.472, -0.54, -0.612, -0.764, -0.916, -0.976, -0.984, + -0.94, -0.844, -0.8, -0.524, -0.432, -0.256, -0.1, 0.124, 0.208, + 0.284, 0.272, 0.288, 0.232, 0.152, 0.004, -0.28, -0.408, -0.656, + -0.836, -1.068, -1.036, -1.016, -0.952, -0.844, -0.724, -0.68, + -0.796, -0.564, -0.34, -0.208, -0.104, 0.052, 0.168, 0.292, 0.42, + 0.408, 0.436, 0.352, 0.164, -0.04, -0.352, -0.488 ], "y": [ - 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, 0.556, - 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, 0.144, 0.472, - 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, 0.524, 0.688, 0.664, - 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, 0.408, 0.18, 0.468, 0.744, - 0.724, 0.684, 0.88, 1.072, 1.108, 1, 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, - 0.5, 0.404, 0.364, 0.368, 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, - 0.908, 1.184, 1.192, 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, - 0.812, 0.78, 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, - 0.452, 0.732, 0.976 + 0.668, 0.968, 0.696, 0.572, 0.688, 0.628, 0.932, 0.596, 0.836, 0.78, + 0.556, 0.52, 0.492, 0.38, 0.452, 0.304, 0.08, 0.144, 0.264, 0.184, + 0.144, 0.472, 0.904, 1.004, 0.752, 0.7, 0.808, 1.032, 0.988, 0.676, + 0.524, 0.688, 0.664, 0.536, 0.4, 0.304, 0.256, 0.276, 0.32, 0.296, + 0.408, 0.18, 0.468, 0.744, 0.724, 0.684, 0.88, 1.072, 1.108, 1, + 0.912, 0.816, 0.912, 0.756, 0.728, 0.668, 0.5, 0.404, 0.364, 0.368, + 0.372, 0.364, 0.424, 0.356, 0.316, 0.532, 0.62, 0.908, 1.184, 1.192, + 1.028, 0.988, 1.188, 1.344, 1.268, 0.952, 0.816, 0.824, 0.812, 0.78, + 0.704, 0.56, 0.428, 0.328, 0.26, 0.256, 0.104, 0.172, 0.096, 0.452, + 0.732, 0.976 ], "z": [ - -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, -0.76, - -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, -0.492, -0.796, - -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, -1.488, -1.508, -1.12, - -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, -0.012, 0, 0.016, 0.028, -0.012, - -0.3, -0.692, -1.236, -1.444, -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, - -1.524, -1.288, -0.888, -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, - 0.144, 0.18, 0.116, -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, - -1.304, -1.312, -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, - -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, -1.076, -1.304 + -1.704, -1.86, -1.568, -1.4, -1.512, -1.552, -1.24, -1.304, -1.22, + -0.76, -0.304, -0.084, 0.088, 0.2, 0.396, 0.468, 0.192, -0.008, + -0.492, -0.796, -0.96, -1.336, -1.768, -1.676, -1.424, -1.296, -1.3, + -1.488, -1.508, -1.12, -1.056, -0.924, -0.6, -0.18, -0.084, -0.008, + -0.012, 0, 0.016, 0.028, -0.012, -0.3, -0.692, -1.236, -1.444, + -1.212, -1.236, -1.4, -1.524, -1.456, -1.54, -1.524, -1.288, -0.888, + -0.58, -0.268, -0.092, -0.04, 0.02, 0.104, 0.14, 0.144, 0.18, 0.116, + -0.116, -0.504, -0.78, -0.996, -1.284, -1.42, -1.38, -1.304, -1.312, + -1.304, -1.288, -1.24, -1.036, -0.704, -0.484, -0.292, -0.132, + -0.004, 0.032, 0.156, 0.248, 0.28, 0.22, 0.092, -0.144, -0.56, + -1.076, -1.304 ] } }, @@ -320,38 +350,42 @@ "ID": 1705437804840, "data": { "x": [ - 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, -0.084, -0.284, - -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, 0.512, 0, -0.616, 0.156, - 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, 0.764, 0.416, 0.432, 0.444, 0.32, - 0.312, 0.348, 0.32, 0.472, 0.292, 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, - 0.708, 0.684, 0.516, 0.46, 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, - 0.032, 0.092, 0.136, 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, - 0.62, 0.572, 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, - 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, 0.72, 0.7, - 0.48, 0.304, 0.052 + 0.42, 0.624, 0.688, 0.412, 0.552, 0.592, 0.532, 0.592, 0.292, + -0.084, -0.284, -0.044, -0.428, 0.824, 1.172, 0.508, 0.428, 0.344, + 0.512, 0, -0.616, 0.156, 0.232, 0.268, 0.78, 0.636, 0.428, 0.688, + 0.764, 0.416, 0.432, 0.444, 0.32, 0.312, 0.348, 0.32, 0.472, 0.292, + 0.092, 0.268, 0.348, 0.424, 0.508, 0.716, 0.708, 0.684, 0.516, 0.46, + 0.3, 0.084, 0.08, 0.108, -0.036, -0.012, 0.088, 0.032, 0.092, 0.136, + 0.252, 0.032, 0.36, 0.476, 0.656, 0.744, 0.72, 0.692, 0.62, 0.572, + 0.464, 0.248, -0.016, 0.096, -0.18, -0.176, -0.004, -0.04, 0.112, + 0.22, 0.024, 0.02, 0.136, 0.208, 0.136, 0.448, 0.764, 0.808, 0.752, + 0.72, 0.7, 0.48, 0.304, 0.052 ], "y": [ - -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, 0.144, - 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, 1.212, 0.732, -1.24, - -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, -0.816, -0.044, 0.556, 1.332, - 1.496, 1.224, 1.184, 1.48, 1.644, 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, - -1.224, -1.252, -1.116, -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, - 1.844, 1.728, 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, - -0.976, -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, - 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, -0.936, - -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, 0.248 + -1.136, -1.324, -0.948, -1, -1.404, -1.46, -1.192, -0.868, -0.432, + 0.144, 0.528, 0.496, 1.672, 2.04, 2.04, 2.04, 2.04, 1.416, 1.6, + 1.212, 0.732, -1.24, -0.648, -0.616, -1.356, -1.124, -0.792, -0.728, + -0.816, -0.044, 0.556, 1.332, 1.496, 1.224, 1.184, 1.48, 1.644, + 1.06, 0.524, 0.012, -0.1, -0.472, -0.9, -1.224, -1.252, -1.116, + -1.024, -0.592, -0.216, 0.372, 1.112, 1.648, 2.008, 1.844, 1.728, + 1.544, 1.108, 0.768, 0.512, 0.396, -0.436, -0.692, -0.892, -0.976, + -0.912, -0.756, -0.58, -0.432, -0.296, -0.108, 0.472, 1.108, 1.772, + 1.7, 1.284, 1.516, 1.564, 1.552, 0.992, 0.436, 0.384, 0.356, -0.376, + -0.936, -1.132, -1.172, -0.964, -0.828, -0.592, -0.408, -0.208, + 0.248 ], "z": [ - -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, -0.296, - -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, -0.284, -0.884, - -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, -0.732, -0.292, -0.34, - -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, -0.752, -0.66, -0.652, -0.716, - -0.632, -0.48, -0.452, -0.532, -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, - -1.24, -1, -0.872, -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, - -0.54, -0.552, -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, - -1.1, -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, - -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, -0.504, -0.652, - -0.704, -0.916 + -0.468, -0.332, -0.256, 0.184, -0.152, -0.46, -0.5, -0.568, -0.408, + -0.296, -0.144, -0.636, -1.18, -1.696, -0.276, -0.22, 0.472, 0.252, + -0.284, -0.884, -0.368, -0.308, -0.66, -0.488, -1.112, -1.176, -0.8, + -0.732, -0.292, -0.34, -0.56, -0.452, -0.856, -0.984, -1.1, -0.888, + -0.752, -0.66, -0.652, -0.716, -0.632, -0.48, -0.452, -0.532, + -0.684, -0.684, -0.568, -0.404, -0.608, -0.8, -1.24, -1, -0.872, + -0.684, -0.516, -0.828, -0.7, -0.732, -0.432, -0.452, -0.54, -0.552, + -0.868, -0.7, -0.56, -0.56, -0.772, -0.772, -0.792, -0.796, -1.1, + -0.88, -0.996, -0.608, -0.492, -0.436, -0.648, -0.608, -0.6, -0.716, + -0.628, -0.592, -0.848, -0.792, -0.816, -0.768, -0.604, -0.56, + -0.504, -0.652, -0.704, -0.916 ] } } @@ -371,39 +405,42 @@ "ID": 1705437969952, "data": { "x": [ - -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, -0.06, - -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, -0.064, -0.06, - -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, -0.06, -0.06, -0.068, - -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, -0.056, -0.056, -0.056, -0.056, - -0.06, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, - -0.064, -0.06, -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, - -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, -0.052, - -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, -0.06, -0.056, - -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, -0.056, -0.068, -0.06, - -0.06, -0.052, -0.06 + -0.064, -0.06, -0.06, -0.06, -0.06, -0.064, -0.056, -0.056, -0.06, + -0.06, -0.064, -0.056, -0.056, -0.056, -0.06, -0.052, -0.064, -0.06, + -0.064, -0.06, -0.06, -0.052, -0.056, -0.06, -0.064, -0.06, -0.064, + -0.06, -0.06, -0.068, -0.056, -0.056, -0.048, -0.064, -0.056, -0.06, + -0.056, -0.056, -0.056, -0.056, -0.06, -0.06, -0.056, -0.064, + -0.064, -0.056, -0.056, -0.056, -0.06, -0.056, -0.064, -0.06, + -0.056, -0.064, -0.064, -0.056, -0.056, -0.064, -0.056, -0.06, + -0.052, -0.06, -0.056, -0.06, -0.06, -0.06, -0.06, -0.056, -0.06, + -0.052, -0.048, -0.06, -0.06, -0.056, -0.064, -0.06, -0.056, -0.056, + -0.06, -0.056, -0.064, -0.06, -0.056, -0.06, -0.06, -0.052, -0.064, + -0.056, -0.068, -0.06, -0.06, -0.052, -0.06 ], "y": [ - 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, 0.772, - 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, 0.772, 0.76, 0.764, - 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, 0.764, 0.76, 0.76, 0.764, - 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, - 0.76, 0.756, 0.756, 0.772, 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, - 0.756, 0.76, 0.764, 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, - 0.764, 0.764, 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, - 0.76, 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, 0.76, - 0.764, 0.752 + 0.768, 0.768, 0.772, 0.76, 0.76, 0.772, 0.764, 0.76, 0.764, 0.764, + 0.772, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.76, 0.764, 0.76, + 0.772, 0.76, 0.764, 0.768, 0.764, 0.764, 0.76, 0.764, 0.756, 0.764, + 0.764, 0.76, 0.76, 0.764, 0.764, 0.76, 0.756, 0.76, 0.76, 0.756, + 0.76, 0.764, 0.768, 0.764, 0.752, 0.76, 0.76, 0.756, 0.756, 0.772, + 0.76, 0.764, 0.76, 0.764, 0.76, 0.768, 0.764, 0.756, 0.76, 0.764, + 0.764, 0.764, 0.76, 0.756, 0.768, 0.764, 0.756, 0.76, 0.764, 0.764, + 0.764, 0.764, 0.764, 0.756, 0.76, 0.76, 0.772, 0.76, 0.764, 0.76, + 0.772, 0.764, 0.756, 0.76, 0.768, 0.752, 0.76, 0.76, 0.76, 0.756, + 0.76, 0.764, 0.752 ], "z": [ - -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, -0.692, - -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, -0.688, -0.692, -0.684, - -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, -0.7, -0.7, -0.708, -0.7, -0.696, - -0.692, -0.696, -0.7, -0.7, -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, - -0.7, -0.692, -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, - -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, -0.696, - -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, -0.708, -0.7, -0.712, - -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, -0.696, -0.704, -0.696, -0.708, - -0.7, -0.7, -0.7, -0.704, -0.7, -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, - -0.7 + -0.7, -0.684, -0.7, -0.688, -0.696, -0.696, -0.7, -0.696, -0.7, + -0.692, -0.696, -0.704, -0.696, -0.692, -0.7, -0.696, -0.696, + -0.688, -0.692, -0.684, -0.7, -0.696, -0.704, -0.692, -0.704, -0.7, + -0.7, -0.7, -0.708, -0.7, -0.696, -0.692, -0.696, -0.7, -0.7, + -0.692, -0.704, -0.692, -0.708, -0.696, -0.704, -0.7, -0.692, + -0.692, -0.708, -0.704, -0.696, -0.704, -0.704, -0.704, -0.704, + -0.704, -0.7, -0.696, -0.708, -0.7, -0.696, -0.696, -0.692, -0.704, + -0.696, -0.696, -0.688, -0.7, -0.708, -0.696, -0.704, -0.696, + -0.708, -0.7, -0.712, -0.708, -0.696, -0.7, -0.696, -0.696, -0.7, + -0.696, -0.704, -0.696, -0.708, -0.7, -0.7, -0.7, -0.704, -0.7, + -0.684, -0.692, -0.696, -0.704, -0.704, -0.7, -0.7 ] } }, @@ -411,40 +448,44 @@ "ID": 1705437870493, "data": { "x": [ - -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, -0.7, -0.648, - -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, -0.752, -0.704, -0.68, - -0.692, -0.708, -0.72, -0.756, -0.764, -0.768, -0.772, -0.736, -0.748, -0.74, - -0.772, -0.764, -0.752, -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, - -0.748, -0.748, -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, - -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, -0.748, - -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, -0.756, -0.744, -0.732, - -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, -0.772, -0.74, -0.732, -0.74, - -0.764, -0.776, -0.772, -0.764, -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, - -0.812, -0.808 + -0.804, -0.836, -0.848, -0.812, -0.768, -0.784, -0.808, -0.776, + -0.7, -0.648, -0.636, -0.664, -0.672, -0.704, -0.712, -0.72, -0.756, + -0.752, -0.704, -0.68, -0.692, -0.708, -0.72, -0.756, -0.764, + -0.768, -0.772, -0.736, -0.748, -0.74, -0.772, -0.764, -0.752, + -0.736, -0.732, -0.756, -0.76, -0.772, -0.74, -0.74, -0.748, -0.748, + -0.752, -0.772, -0.756, -0.748, -0.732, -0.74, -0.752, -0.756, + -0.748, -0.732, -0.74, -0.76, -0.752, -0.772, -0.764, -0.74, -0.736, + -0.748, -0.764, -0.756, -0.728, -0.74, -0.756, -0.752, -0.744, + -0.756, -0.744, -0.732, -0.728, -0.74, -0.752, -0.76, -0.776, -0.78, + -0.772, -0.74, -0.732, -0.74, -0.764, -0.776, -0.772, -0.764, + -0.748, -0.76, -0.736, -0.748, -0.744, -0.768, -0.812, -0.808 ], "y": [ - -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, -0.548, -0.552, - -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, -0.484, -0.5, -0.504, -0.508, - -0.484, -0.492, -0.496, -0.484, -0.48, -0.476, -0.484, -0.492, -0.468, -0.484, - -0.476, -0.484, -0.492, -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, - -0.492, -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, -0.468, - -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, -0.48, -0.492, -0.476, - -0.472, -0.476, -0.484, -0.484, -0.484, -0.484, -0.492, -0.488, -0.456, -0.48, - -0.484, -0.472, -0.476, -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, - -0.472, -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, -0.472, - -0.428, -0.436 + -0.48, -0.472, -0.48, -0.492, -0.476, -0.432, -0.428, -0.476, + -0.548, -0.552, -0.536, -0.516, -0.496, -0.484, -0.476, -0.484, + -0.484, -0.5, -0.504, -0.508, -0.484, -0.492, -0.496, -0.484, -0.48, + -0.476, -0.484, -0.492, -0.468, -0.484, -0.476, -0.484, -0.492, + -0.5, -0.488, -0.484, -0.476, -0.476, -0.488, -0.496, -0.492, + -0.472, -0.464, -0.464, -0.476, -0.48, -0.488, -0.484, -0.468, + -0.468, -0.484, -0.488, -0.484, -0.488, -0.472, -0.476, -0.476, + -0.48, -0.492, -0.476, -0.472, -0.476, -0.484, -0.484, -0.484, + -0.484, -0.492, -0.488, -0.456, -0.48, -0.484, -0.472, -0.476, + -0.46, -0.472, -0.472, -0.46, -0.472, -0.488, -0.476, -0.472, + -0.488, -0.492, -0.5, -0.492, -0.488, -0.484, -0.492, -0.488, + -0.472, -0.428, -0.436 ], "z": [ - -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, -0.504, - -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, -0.584, -0.56, - -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, -0.576, -0.596, -0.572, - -0.604, -0.592, -0.568, -0.572, -0.56, -0.584, -0.584, -0.612, -0.572, -0.568, - -0.576, -0.588, -0.6, -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, - -0.588, -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, -0.592, - -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, -0.592, -0.624, -0.572, - -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, -0.58, -0.564, -0.552, -0.564, - -0.58, -0.592, -0.588, -0.584, -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, - -0.636, -0.608 + -0.56, -0.592, -0.572, -0.52, -0.512, -0.568, -0.656, -0.656, -0.56, + -0.504, -0.54, -0.608, -0.636, -0.652, -0.66, -0.64, -0.66, -0.66, + -0.584, -0.56, -0.576, -0.6, -0.592, -0.58, -0.62, -0.608, -0.616, + -0.576, -0.596, -0.572, -0.604, -0.592, -0.568, -0.572, -0.56, + -0.584, -0.584, -0.612, -0.572, -0.568, -0.576, -0.588, -0.6, + -0.596, -0.572, -0.564, -0.568, -0.592, -0.6, -0.604, -0.588, + -0.572, -0.58, -0.584, -0.588, -0.596, -0.584, -0.564, -0.568, + -0.592, -0.596, -0.592, -0.588, -0.572, -0.588, -0.6, -0.576, + -0.592, -0.624, -0.572, -0.564, -0.584, -0.588, -0.6, -0.6, -0.592, + -0.58, -0.564, -0.552, -0.564, -0.58, -0.592, -0.588, -0.584, + -0.568, -0.58, -0.56, -0.568, -0.548, -0.58, -0.636, -0.608 ] } }, @@ -452,39 +493,43 @@ "ID": 1705437864426, "data": { "x": [ - 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, 0.876, - 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, 0.98, 0.952, - 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, 0.956, 0.936, 0.948, - 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, 0.948, 0.94, 0.928, 0.928, 0.94, - 0.96, 0.94, 0.94, 0.94, 0.936, 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, - 0.956, 0.96, 0.952, 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, - 0.944, 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, - 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, 0.968, - 0.968, 0.944 + 0.892, 0.972, 0.972, 0.92, 0.916, 0.964, 0.924, 0.932, 0.984, 0.932, + 0.876, 0.88, 0.904, 0.92, 0.948, 0.984, 0.992, 0.948, 0.948, 0.992, + 0.98, 0.952, 0.948, 0.928, 0.964, 0.94, 0.94, 0.948, 0.976, 0.964, + 0.956, 0.936, 0.948, 0.94, 0.932, 0.96, 0.952, 0.936, 0.98, 0.984, + 0.948, 0.94, 0.928, 0.928, 0.94, 0.96, 0.94, 0.94, 0.94, 0.936, + 0.948, 0.948, 0.952, 0.952, 0.936, 0.96, 0.96, 0.956, 0.96, 0.952, + 0.94, 0.956, 0.94, 0.94, 0.948, 0.94, 0.952, 0.952, 0.952, 0.944, + 0.944, 0.96, 0.976, 0.968, 0.944, 0.928, 0.94, 0.956, 0.952, 0.952, + 0.948, 0.952, 0.948, 0.944, 0.944, 0.94, 0.92, 0.94, 0.952, 0.964, + 0.968, 0.968, 0.944 ], "y": [ - -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, -0.076, - -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, -0.072, -0.072, -0.08, - -0.076, -0.084, -0.104, -0.088, -0.104, -0.088, -0.116, -0.116, -0.132, - -0.116, -0.108, -0.104, -0.12, -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, - -0.112, -0.1, -0.1, -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, - -0.116, -0.108, -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, - -0.096, -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, - -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, -0.096, - -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, -0.08, -0.1, - -0.096, -0.092, -0.092, -0.112, -0.088 + -0.02, -0.088, -0.116, -0.144, -0.14, -0.016, 0.012, -0.04, -0.1, + -0.076, -0.088, -0.096, -0.104, -0.088, -0.092, -0.096, -0.076, + -0.072, -0.072, -0.08, -0.076, -0.084, -0.104, -0.088, -0.104, + -0.088, -0.116, -0.116, -0.132, -0.116, -0.108, -0.104, -0.12, + -0.116, -0.1, -0.108, -0.112, -0.096, -0.108, -0.112, -0.1, -0.1, + -0.1, -0.104, -0.116, -0.12, -0.096, -0.108, -0.12, -0.116, -0.108, + -0.104, -0.112, -0.112, -0.096, -0.096, -0.104, -0.088, -0.096, + -0.1, -0.108, -0.1, -0.108, -0.096, -0.1, -0.104, -0.108, -0.104, + -0.1, -0.088, -0.092, -0.1, -0.1, -0.096, -0.088, -0.092, -0.096, + -0.096, -0.104, -0.088, -0.08, -0.108, -0.092, -0.096, -0.1, -0.1, + -0.08, -0.1, -0.096, -0.092, -0.092, -0.112, -0.088 ], "z": [ - 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, -0.196, - -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, -0.248, -0.172, -0.14, - -0.136, -0.188, -0.204, -0.14, -0.116, -0.104, -0.18, -0.216, -0.192, -0.168, - -0.132, -0.16, -0.188, -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, - -0.176, -0.152, -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, - -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, -0.168, -0.16, - -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, -0.172, -0.168, -0.168, - -0.18, -0.176, -0.168, -0.16, -0.148, -0.148, -0.164, -0.184, -0.184, -0.176, - -0.144, -0.156, -0.176, -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, - -0.176, -0.14, -0.144 + 0.208, -0.032, -0.252, -0.316, -0.292, -0.224, -0.14, 0.116, 0, + -0.196, -0.188, -0.16, -0.172, -0.096, -0.152, -0.228, -0.268, + -0.248, -0.172, -0.14, -0.136, -0.188, -0.204, -0.14, -0.116, + -0.104, -0.18, -0.216, -0.192, -0.168, -0.132, -0.16, -0.188, + -0.184, -0.14, -0.124, -0.176, -0.176, -0.16, -0.18, -0.176, -0.152, + -0.156, -0.168, -0.176, -0.172, -0.168, -0.152, -0.16, -0.156, + -0.18, -0.168, -0.176, -0.188, -0.172, -0.18, -0.176, -0.188, + -0.168, -0.16, -0.196, -0.192, -0.184, -0.152, -0.148, -0.2, -0.2, + -0.172, -0.168, -0.168, -0.18, -0.176, -0.168, -0.16, -0.148, + -0.148, -0.164, -0.184, -0.184, -0.176, -0.144, -0.156, -0.176, + -0.184, -0.172, -0.172, -0.144, -0.168, -0.188, -0.18, -0.176, + -0.14, -0.144 ] } }, @@ -492,36 +537,40 @@ "ID": 1705437860385, "data": { "x": [ - 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, -0.052, - -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, -0.04, -0.056, -0.052, - -0.052, -0.056, -0.056, -0.044, -0.044, -0.056, -0.052, -0.048, -0.048, - -0.048, -0.048, -0.048, -0.06, -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, - -0.056, -0.056, -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, - -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, -0.056, - -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, -0.052, -0.056, - -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, -0.052, -0.048, -0.056, - -0.056, -0.048, -0.052, -0.056, -0.048, -0.056, -0.048, -0.048, -0.052, - -0.052, -0.048, -0.052, -0.052, -0.044 + 0, 0.004, 0, 0.004, 0.016, 0, 0.004, -0.06, -0.004, -0.04, -0.048, + -0.052, -0.056, -0.048, -0.052, -0.044, -0.052, -0.044, -0.048, + -0.04, -0.056, -0.052, -0.052, -0.056, -0.056, -0.044, -0.044, + -0.056, -0.052, -0.048, -0.048, -0.048, -0.048, -0.048, -0.06, + -0.056, -0.052, -0.056, -0.056, -0.052, -0.056, -0.056, -0.056, + -0.048, -0.048, -0.048, -0.06, -0.052, -0.056, -0.048, -0.052, + -0.048, -0.048, -0.048, -0.044, -0.048, -0.052, -0.052, -0.056, + -0.056, -0.044, -0.048, -0.052, -0.052, -0.048, -0.052, -0.056, + -0.052, -0.056, -0.044, -0.048, -0.052, -0.048, -0.052, -0.052, + -0.052, -0.048, -0.056, -0.056, -0.048, -0.052, -0.056, -0.048, + -0.056, -0.048, -0.048, -0.052, -0.052, -0.048, -0.052, -0.052, + -0.044 ], "y": [ - 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, 0.224, - 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, 0.228, 0.224, 0.224, - 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, 0.228, 0.228, 0.228, 0.232, 0.236, - 0.232, 0.232, 0.228, 0.22, 0.232, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, - 0.224, 0.224, 0.232, 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, - 0.228, 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, 0.228, - 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, 0.228, 0.228, 0.236, - 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, 0.228, 0.228, 0.228, 0.228, 0.224, - 0.232, 0.232, 0.232 + 0.16, 0.172, 0.184, 0.184, 0.168, 0.188, 0.192, 0.212, 0.16, 0.208, + 0.224, 0.224, 0.22, 0.22, 0.224, 0.22, 0.22, 0.216, 0.2, 0.216, + 0.228, 0.224, 0.224, 0.232, 0.228, 0.232, 0.224, 0.224, 0.228, + 0.228, 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, 0.228, 0.22, 0.232, + 0.228, 0.228, 0.232, 0.236, 0.232, 0.232, 0.224, 0.224, 0.232, + 0.224, 0.236, 0.228, 0.232, 0.228, 0.224, 0.228, 0.232, 0.228, + 0.228, 0.232, 0.232, 0.228, 0.224, 0.224, 0.232, 0.236, 0.228, + 0.228, 0.236, 0.232, 0.224, 0.228, 0.224, 0.236, 0.236, 0.232, + 0.228, 0.228, 0.236, 0.232, 0.228, 0.232, 0.232, 0.228, 0.232, + 0.228, 0.228, 0.228, 0.228, 0.224, 0.232, 0.232, 0.232 ], "z": [ - 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, 0.996, 1.012, - 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, 1.012, 1.012, 1.012, 1, - 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, 1.008, 1.008, 0.996, 0.996, 1.008, - 1.012, 1.008, 1.012, 1.008, 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, - 1.008, 1.004, 1.004, 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, - 1.004, 1, 1, 1, 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, - 1.004, 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, + 1, 1.016, 1.032, 1.02, 0.98, 1.004, 0.788, 1.06, 1.028, 1.004, + 0.996, 1.012, 1, 1.012, 1.008, 1.004, 0.996, 1.008, 1.012, 1.012, + 1.012, 1.012, 1.012, 1, 1.012, 1.008, 1.004, 1.012, 1.008, 1.008, + 1.008, 1.008, 0.996, 0.996, 1.008, 1.012, 1.008, 1.012, 1.008, + 1.012, 1.004, 1.008, 1.004, 1.004, 1.004, 1, 1.008, 1.004, 1.004, + 1.012, 1.008, 1.004, 1.004, 1.008, 1.008, 1.008, 1, 1.004, 1, 1, 1, + 1.008, 1.004, 1, 1.004, 1.004, 1.004, 1, 1.008, 1.012, 1.004, 1.004, + 1.008, 1.008, 1, 1.012, 1.004, 0.996, 1.008, 1, 1.008, 1.012, 0.996, 0.996, 1.004, 1.008, 1.012, 1.008, 1.004, 1.008, 1.008, 1.008 ] } @@ -530,39 +579,44 @@ "ID": 1705437854703, "data": { "x": [ - -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, -0.104, - -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, -0.064, -0.068, - -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, -0.072, -0.068, -0.072, - -0.068, -0.056, -0.064, -0.068, -0.068, -0.068, -0.068, -0.06, -0.072, -0.076, - -0.072, -0.068, -0.064, -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, - -0.068, -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, - -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, -0.076, -0.06, - -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, -0.064, -0.064, -0.072, -0.068, - -0.064, -0.064, -0.088, -0.06, -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, - -0.064, -0.064, -0.064, -0.068, -0.072, -0.068, -0.06 + -0.076, -0.076, -0.072, -0.068, -0.076, -0.072, -0.072, -0.056, + -0.104, -0.068, -0.072, -0.064, -0.072, -0.044, -0.072, -0.064, + -0.064, -0.068, -0.076, -0.076, -0.068, -0.068, -0.072, -0.068, + -0.072, -0.068, -0.072, -0.068, -0.056, -0.064, -0.068, -0.068, + -0.068, -0.068, -0.06, -0.072, -0.076, -0.072, -0.068, -0.064, + -0.072, -0.06, -0.072, -0.068, -0.064, -0.06, -0.064, -0.068, + -0.064, -0.072, -0.064, -0.068, -0.076, -0.068, -0.064, -0.064, + -0.064, -0.068, -0.064, -0.072, -0.068, -0.072, -0.06, -0.06, + -0.076, -0.06, -0.064, -0.064, -0.06, -0.068, -0.076, -0.064, + -0.064, -0.064, -0.072, -0.068, -0.064, -0.064, -0.088, -0.06, + -0.06, -0.06, -0.064, -0.056, -0.06, -0.064, -0.064, -0.064, -0.064, + -0.068, -0.072, -0.068, -0.06 ], "y": [ - 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, 0.96, 0.96, - 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, 0.948, 0.952, 0.944, - 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, 0.944, 0.952, 0.952, 0.952, - 0.952, 0.952, 0.948, 0.948, 0.948, 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, - 0.948, 0.952, 0.948, 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, - 0.948, 0.944, 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, - 0.952, 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, - 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, 0.952, - 0.948, 0.948, 0.948, 0.948 + 0.968, 0.96, 0.956, 0.964, 0.964, 0.96, 0.96, 0.976, 0.96, 0.96, + 0.96, 0.96, 0.956, 0.948, 0.952, 0.956, 0.944, 0.944, 0.944, 0.944, + 0.948, 0.952, 0.944, 0.948, 0.948, 0.94, 0.948, 0.944, 0.956, 0.948, + 0.944, 0.952, 0.952, 0.952, 0.952, 0.952, 0.948, 0.948, 0.948, + 0.952, 0.944, 0.944, 0.948, 0.948, 0.944, 0.948, 0.952, 0.948, + 0.952, 0.948, 0.94, 0.944, 0.952, 0.952, 0.936, 0.948, 0.948, 0.944, + 0.948, 0.944, 0.944, 0.944, 0.94, 0.956, 0.944, 0.952, 0.94, 0.952, + 0.944, 0.944, 0.944, 0.956, 0.948, 0.952, 0.944, 0.94, 0.936, 0.944, + 0.936, 0.94, 0.956, 0.952, 0.948, 0.944, 0.952, 0.94, 0.948, 0.948, + 0.952, 0.948, 0.948, 0.948, 0.948 ], "z": [ - -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, -0.396, -0.42, - -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, -0.44, -0.432, -0.428, - -0.428, -0.432, -0.428, -0.428, -0.432, -0.428, -0.424, -0.432, -0.436, - -0.428, -0.428, -0.432, -0.432, -0.428, -0.424, -0.436, -0.428, -0.432, - -0.432, -0.436, -0.424, -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, - -0.436, -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, - -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, -0.42, -0.428, - -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, -0.428, -0.436, -0.432, -0.436, - -0.44, -0.428, -0.432, -0.432, -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, - -0.424, -0.424, -0.432, -0.424, -0.432, -0.436 + -0.396, -0.388, -0.392, -0.388, -0.38, -0.36, -0.404, -0.416, + -0.396, -0.42, -0.388, -0.42, -0.42, -0.564, -0.424, -0.416, -0.424, + -0.44, -0.432, -0.428, -0.428, -0.432, -0.428, -0.428, -0.432, + -0.428, -0.424, -0.432, -0.436, -0.428, -0.428, -0.432, -0.432, + -0.428, -0.424, -0.436, -0.428, -0.432, -0.432, -0.436, -0.424, + -0.424, -0.428, -0.424, -0.436, -0.432, -0.42, -0.428, -0.436, + -0.424, -0.424, -0.424, -0.436, -0.428, -0.428, -0.424, -0.428, + -0.436, -0.424, -0.428, -0.44, -0.424, -0.428, -0.432, -0.432, + -0.42, -0.428, -0.428, -0.428, -0.436, -0.428, -0.428, -0.42, + -0.428, -0.436, -0.432, -0.436, -0.44, -0.428, -0.432, -0.432, + -0.432, -0.432, -0.428, -0.44, -0.444, -0.436, -0.424, -0.424, + -0.432, -0.424, -0.432, -0.436 ] } }, @@ -570,38 +624,41 @@ "ID": 1705437847993, "data": { "x": [ - 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, 0.768, - 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, 0.76, 0.748, - 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, 0.764, 0.76, 0.748, - 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, 0.756, 0.748, 0.752, 0.744, - 0.744, 0.752, 0.756, 0.756, 0.752, 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, - 0.756, 0.752, 0.748, 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, - 0.756, 0.752, 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, - 0.736, 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, + 0.744, 0.76, 0.66, 0.688, 0.784, 0.736, 0.704, 0.712, 0.732, 0.72, + 0.768, 0.772, 0.76, 0.784, 0.76, 0.776, 0.776, 0.776, 0.744, 0.744, + 0.76, 0.748, 0.764, 0.76, 0.764, 0.776, 0.772, 0.752, 0.76, 0.752, + 0.764, 0.76, 0.748, 0.752, 0.744, 0.76, 0.78, 0.772, 0.768, 0.756, + 0.756, 0.748, 0.752, 0.744, 0.744, 0.752, 0.756, 0.756, 0.752, + 0.752, 0.748, 0.752, 0.756, 0.764, 0.756, 0.756, 0.752, 0.748, + 0.752, 0.748, 0.748, 0.744, 0.744, 0.752, 0.76, 0.76, 0.756, 0.752, + 0.756, 0.756, 0.752, 0.744, 0.732, 0.74, 0.744, 0.748, 0.732, 0.736, + 0.732, 0.748, 0.752, 0.748, 0.74, 0.744, 0.752, 0.744, 0.748, 0.732, 0.736, 0.74, 0.752, 0.74 ], "y": [ - 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, 0.248, - 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, 0.268, 0.268, 0.272, - 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, 0.264, 0.264, 0.268, 0.268, - 0.276, 0.276, 0.256, 0.256, 0.264, 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, - 0.276, 0.276, 0.284, 0.268, 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, - 0.276, 0.28, 0.272, 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, - 0.268, 0.272, 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, - 0.3, 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, + 0.292, 0.284, 0.24, 0.264, 0.224, 0.176, 0.232, 0.272, 0.268, 0.264, + 0.248, 0.224, 0.248, 0.248, 0.252, 0.232, 0.248, 0.264, 0.268, + 0.268, 0.268, 0.272, 0.268, 0.268, 0.256, 0.256, 0.26, 0.268, 0.264, + 0.264, 0.264, 0.268, 0.268, 0.276, 0.276, 0.256, 0.256, 0.264, + 0.268, 0.272, 0.268, 0.272, 0.272, 0.3, 0.276, 0.276, 0.284, 0.268, + 0.284, 0.276, 0.276, 0.272, 0.276, 0.264, 0.276, 0.276, 0.28, 0.272, + 0.276, 0.28, 0.28, 0.28, 0.284, 0.276, 0.272, 0.264, 0.268, 0.272, + 0.272, 0.272, 0.272, 0.276, 0.28, 0.276, 0.284, 0.288, 0.284, 0.3, + 0.288, 0.272, 0.28, 0.28, 0.276, 0.272, 0.276, 0.284, 0.284, 0.288, 0.284, 0.276, 0.272, 0.276 ], "z": [ - -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, -0.564, -0.552, - -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, -0.568, -0.572, -0.552, -0.548, - -0.54, -0.544, -0.556, -0.56, -0.56, -0.544, -0.536, -0.54, -0.532, -0.556, - -0.564, -0.572, -0.556, -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, - -0.552, -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, -0.536, - -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, -0.54, -0.568, -0.56, - -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, -0.544, -0.548, -0.548, -0.56, - -0.556, -0.544, -0.552, -0.56, -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, - -0.556, -0.56, -0.568, -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, - -0.564, -0.56 + -0.56, -0.604, -0.56, -0.588, -0.616, -0.616, -0.644, -0.572, + -0.564, -0.552, -0.572, -0.552, -0.54, -0.556, -0.56, -0.568, + -0.568, -0.572, -0.552, -0.548, -0.54, -0.544, -0.556, -0.56, -0.56, + -0.544, -0.536, -0.54, -0.532, -0.556, -0.564, -0.572, -0.556, + -0.552, -0.536, -0.544, -0.544, -0.548, -0.552, -0.56, -0.552, + -0.556, -0.568, -0.56, -0.56, -0.552, -0.552, -0.556, -0.556, + -0.536, -0.528, -0.536, -0.56, -0.564, -0.556, -0.544, -0.536, + -0.54, -0.568, -0.56, -0.552, -0.54, -0.544, -0.56, -0.568, -0.552, + -0.544, -0.548, -0.548, -0.56, -0.556, -0.544, -0.552, -0.56, + -0.556, -0.56, -0.568, -0.564, -0.56, -0.548, -0.556, -0.56, -0.568, + -0.572, -0.564, -0.56, -0.556, -0.552, -0.568, -0.56, -0.564, -0.56 ] } } @@ -621,39 +678,42 @@ "ID": 1705438034019, "data": { "x": [ - -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, -0.536, -0.456, - -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, -0.916, -0.956, -1.032, -1.1, - -1.228, -1.284, -1.312, -1.36, -1.356, -1.296, -1.224, -1.096, -0.844, -0.964, - -0.948, -0.984, -0.896, -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, - -0.392, -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, - -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, -0.36, -0.476, - -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, -0.532, -0.532, -0.608, - -0.652, -0.668, -0.652, -0.764, -0.892, -0.92, -0.864, -0.848, -0.884, -0.872, - -0.848, -0.888, -0.952, -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, - -0.732, -0.84 + -0.148, -0.388, -0.552, -0.64, -0.544, -0.616, -0.528, -0.612, + -0.536, -0.456, -0.532, -0.672, -0.796, -0.836, -0.716, -0.756, + -0.916, -0.956, -1.032, -1.1, -1.228, -1.284, -1.312, -1.36, -1.356, + -1.296, -1.224, -1.096, -0.844, -0.964, -0.948, -0.984, -0.896, + -0.884, -0.884, -0.772, -0.64, -0.508, -0.388, -0.348, -0.392, + -0.392, -0.396, -0.344, -0.3, -0.264, -0.28, -0.252, -0.2, -0.22, + -0.236, -0.244, -0.208, -0.188, -0.22, -0.252, -0.244, -0.288, + -0.36, -0.476, -0.496, -0.472, -0.452, -0.504, -0.552, -0.576, + -0.532, -0.532, -0.608, -0.652, -0.668, -0.652, -0.764, -0.892, + -0.92, -0.864, -0.848, -0.884, -0.872, -0.848, -0.888, -0.952, + -0.992, -1.02, -1.084, -1.068, -0.952, -0.82, -0.748, -0.732, -0.84 ], "y": [ - 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, 0.392, 0.376, - 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, 0.2, 0.152, 0.128, 0.16, - 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, 0.284, 0.372, 0.284, 0.348, 0.412, - 0.408, 0.376, 0.36, 0.396, 0.452, 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, - 0.312, 0.34, 0.356, 0.332, 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, - 0.324, 0.308, 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, - 0.348, 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, - 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, 0.292, - 0.324 + 0.48, 0.444, 0.34, 0.376, 0.392, 0.424, 0.44, 0.42, 0.416, 0.42, + 0.392, 0.376, 0.352, 0.384, 0.348, 0.284, 0.22, 0.204, 0.284, 0.28, + 0.2, 0.152, 0.128, 0.16, 0.144, 0.108, 0.06, 0.072, 0.02, 0.224, + 0.284, 0.372, 0.284, 0.348, 0.412, 0.408, 0.376, 0.36, 0.396, 0.452, + 0.484, 0.424, 0.36, 0.296, 0.296, 0.296, 0.312, 0.34, 0.356, 0.332, + 0.344, 0.344, 0.344, 0.336, 0.336, 0.332, 0.336, 0.324, 0.308, + 0.328, 0.328, 0.316, 0.304, 0.28, 0.284, 0.296, 0.328, 0.348, 0.348, + 0.348, 0.392, 0.428, 0.432, 0.432, 0.452, 0.424, 0.38, 0.356, 0.384, + 0.392, 0.336, 0.272, 0.28, 0.328, 0.348, 0.304, 0.256, 0.224, 0.24, + 0.292, 0.324 ], "z": [ - -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, -0.388, - -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, -0.204, -0.272, -0.308, - -0.308, -0.388, -0.424, -0.512, -0.592, -0.66, -0.696, -0.772, -0.812, -0.968, - -0.948, -1.02, -1.168, -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, - -1.12, -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, - -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, -0.556, -0.532, - -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, -0.392, -0.356, -0.364, - -0.388, -0.376, -0.388, -0.42, -0.408, -0.416, -0.376, -0.372, -0.368, -0.412, - -0.464, -0.492, -0.516, -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, - -0.976, -0.972 + -0.764, -0.612, -0.512, -0.524, -0.512, -0.404, -0.344, -0.372, + -0.388, -0.396, -0.328, -0.252, -0.18, -0.208, -0.228, -0.22, + -0.204, -0.272, -0.308, -0.308, -0.388, -0.424, -0.512, -0.592, + -0.66, -0.696, -0.772, -0.812, -0.968, -0.948, -1.02, -1.168, + -1.064, -1.072, -1.128, -1.148, -1.128, -1.148, -1.164, -1.12, + -1.108, -1.12, -1.156, -1.248, -1.26, -1.196, -1.124, -1.008, -0.96, + -0.924, -0.896, -0.848, -0.796, -0.728, -0.708, -0.68, -0.636, + -0.556, -0.532, -0.5, -0.48, -0.456, -0.412, -0.384, -0.364, -0.384, + -0.392, -0.356, -0.364, -0.388, -0.376, -0.388, -0.42, -0.408, + -0.416, -0.376, -0.372, -0.368, -0.412, -0.464, -0.492, -0.516, + -0.58, -0.584, -0.68, -0.768, -0.848, -0.876, -0.924, -0.976, -0.972 ] } }, @@ -661,39 +721,43 @@ "ID": 1705438029559, "data": { "x": [ - -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, -0.644, -0.536, - -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, -0.94, -0.912, -0.888, -0.816, - -0.836, -0.872, -0.844, -0.848, -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, - -0.664, -0.636, -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, - -0.364, -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, 0.068, - -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, -0.172, -0.264, - -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, -0.612, -0.552, -0.576, -0.676, - -0.804, -0.876, -0.864, -0.856, -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, - -0.856, -0.792, -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, + -0.4, -0.504, -0.556, -0.656, -0.988, -0.568, -0.644, -0.664, + -0.644, -0.536, -0.496, -0.776, -0.804, -0.888, -0.868, -0.904, + -0.94, -0.912, -0.888, -0.816, -0.836, -0.872, -0.844, -0.848, + -0.832, -0.792, -0.756, -0.74, -0.692, -0.684, -0.664, -0.636, + -0.624, -0.592, -0.568, -0.532, -0.516, -0.412, -0.412, -0.364, + -0.292, -0.2, -0.164, -0.1, -0.1, -0.072, -0.048, 0.004, -0.02, + 0.068, -0.004, -0.072, -0.124, -0.188, -0.192, -0.172, -0.152, + -0.172, -0.264, -0.292, -0.312, -0.288, -0.276, -0.348, -0.48, + -0.612, -0.552, -0.576, -0.676, -0.804, -0.876, -0.864, -0.856, + -0.844, -0.836, -0.836, -0.864, -0.92, -0.924, -0.856, -0.792, + -0.744, -0.672, -0.664, -0.644, -0.66, -0.68, -0.724, -0.716, -0.668, -0.588, -0.508 ], "y": [ - 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, 0.132, - 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, 0.232, 0.256, - 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, 0.296, 0.292, 0.304, - 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, 0.368, 0.352, 0.356, 0.328, - 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, - 0.24, 0.248, 0.232, 0.196, 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, - 0.176, 0.152, 0.144, 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, - 0.252, 0.188, 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, - 0.276, 0.248, 0.236 + 0.228, 0.248, 0.252, 0.252, 0.192, 0.132, 0.2, 0.28, 0.364, 0.276, + 0.132, 0.216, 0.24, 0.296, 0.26, 0.228, 0.26, 0.268, 0.304, 0.232, + 0.232, 0.256, 0.24, 0.232, 0.276, 0.268, 0.268, 0.304, 0.292, 0.296, + 0.296, 0.292, 0.304, 0.312, 0.3, 0.304, 0.328, 0.308, 0.32, 0.348, + 0.368, 0.352, 0.356, 0.328, 0.304, 0.308, 0.32, 0.3, 0.324, 0.24, + 0.184, 0.26, 0.276, 0.28, 0.216, 0.224, 0.24, 0.248, 0.232, 0.196, + 0.184, 0.22, 0.232, 0.196, 0.212, 0.188, 0.18, 0.176, 0.152, 0.144, + 0.14, 0.176, 0.184, 0.18, 0.184, 0.18, 0.184, 0.216, 0.252, 0.188, + 0.2, 0.18, 0.208, 0.244, 0.252, 0.248, 0.244, 0.236, 0.244, 0.276, + 0.248, 0.236 ], "z": [ - -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, -1.064, - -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, -0.672, -0.712, - -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, -0.524, -0.484, -0.468, -0.516, - -0.54, -0.596, -0.624, -0.6, -0.596, -0.6, -0.624, -0.632, -0.656, -0.632, - -0.7, -0.744, -0.76, -0.768, -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, - -0.78, -1.112, -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, - -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, -1.064, -0.996, - -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, -0.796, -0.808, -0.812, -0.74, - -0.76, -0.72, -0.744, -0.728, -0.704, -0.692, -0.636, -0.644, -0.656, -0.676, - -0.616 + -1.18, -1.18, -1.12, -1.32, -0.928, -1.048, -1.18, -1.024, -1.224, + -1.064, -1.008, -1.02, -0.888, -0.88, -0.812, -0.78, -0.736, -0.696, + -0.672, -0.712, -0.784, -0.516, -0.64, -0.54, -0.524, -0.536, + -0.524, -0.484, -0.468, -0.516, -0.54, -0.596, -0.624, -0.6, -0.596, + -0.6, -0.624, -0.632, -0.656, -0.632, -0.7, -0.744, -0.76, -0.768, + -0.788, -0.816, -0.904, -0.908, -1.012, -0.996, -0.78, -1.112, + -1.06, -1, -1.06, -1.1, -1.136, -1.184, -1.216, -1.264, -1.24, + -1.228, -1.228, -1.172, -1.16, -1.068, -1.12, -1.112, -1.068, + -1.064, -0.996, -1.008, -0.976, -0.924, -0.88, -0.864, -0.824, + -0.796, -0.808, -0.812, -0.74, -0.76, -0.72, -0.744, -0.728, -0.704, + -0.692, -0.636, -0.644, -0.656, -0.676, -0.616 ] } }, @@ -701,39 +765,43 @@ "ID": 1705438027130, "data": { "x": [ - -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, -0.532, - -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, -1.056, -0.968, -0.9, - -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, -0.8, -0.788, -0.748, -0.728, - -0.636, -0.56, -0.532, -0.504, -0.492, -0.432, -0.424, -0.464, -0.428, -0.408, - -0.336, -0.296, -0.252, -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, - -0.168, -0.136, -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, - -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, -0.628, -0.624, - -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, -0.84, -0.864, -0.888, -0.924, - -0.936, -0.88, -0.848, -0.888, -0.88, -0.832, -0.768, -0.724, -0.68, -0.664, - -0.664, -0.62, -0.572 + -0.064, -0.104, -0.14, -0.208, -0.3, -0.316, -0.428, -0.564, -0.456, + -0.532, -0.676, -0.74, -0.728, -0.692, -0.792, -1.024, -1.076, + -1.056, -0.968, -0.9, -0.912, -0.86, -0.876, -0.872, -0.848, -0.848, + -0.8, -0.788, -0.748, -0.728, -0.636, -0.56, -0.532, -0.504, -0.492, + -0.432, -0.424, -0.464, -0.428, -0.408, -0.336, -0.296, -0.252, + -0.24, -0.248, -0.28, -0.22, -0.204, -0.128, 0, -0.168, -0.136, + -0.064, -0.088, -0.148, -0.228, -0.26, -0.296, -0.352, -0.392, + -0.376, -0.352, -0.364, -0.436, -0.46, -0.496, -0.52, -0.596, + -0.628, -0.624, -0.628, -0.668, -0.792, -0.808, -0.812, -0.824, + -0.84, -0.864, -0.888, -0.924, -0.936, -0.88, -0.848, -0.888, -0.88, + -0.832, -0.768, -0.724, -0.68, -0.664, -0.664, -0.62, -0.572 ], "y": [ - 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, 0.164, - 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, 0.208, 0.212, - 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, 0.324, 0.316, 0.32, - 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, 0.276, 0.248, 0.232, 0.252, - 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, 0.176, 0.156, 0.136, 0.16, 0.18, - 0.208, 0.224, 0.204, 0.18, 0.188, 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, - 0.104, 0.112, 0.084, 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, - 0.1, 0.14, 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, + 0.228, 0.212, 0.224, 0.236, 0.192, 0.204, 0.208, 0.34, 0.208, 0.184, + 0.164, 0.148, 0.144, 0.104, 0.064, 0.132, 0.104, 0.124, 0.14, 0.188, + 0.208, 0.212, 0.24, 0.224, 0.212, 0.24, 0.256, 0.288, 0.296, 0.308, + 0.324, 0.316, 0.32, 0.316, 0.348, 0.348, 0.312, 0.308, 0.3, 0.3, + 0.276, 0.248, 0.232, 0.252, 0.248, 0.152, 0.172, 0.204, 0.18, -0.08, + 0.176, 0.156, 0.136, 0.16, 0.18, 0.208, 0.224, 0.204, 0.18, 0.188, + 0.2, 0.188, 0.184, 0.168, 0.16, 0.14, 0.108, 0.104, 0.112, 0.084, + 0.052, 0.004, 0.056, 0.056, 0.096, 0.084, 0.092, 0.12, 0.1, 0.14, + 0.152, 0.164, 0.156, 0.184, 0.212, 0.224, 0.228, 0.228, 0.236, 0.268, 0.276, 0.284, 0.296 ], "z": [ - -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, -1.176, - -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, -0.964, -0.904, -0.824, - -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, -0.568, -0.56, -0.528, -0.468, - -0.492, -0.488, -0.48, -0.492, -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, - -0.704, -0.732, -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, - -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, -1.208, -1.208, - -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, -1.076, -1.072, -1.028, - -1.028, -0.984, -0.944, -0.968, -0.96, -0.964, -0.964, -0.952, -0.904, -0.856, - -0.808, -0.788, -0.756, -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, - -0.604, -0.592, -0.588 + -0.908, -0.98, -0.956, -1.076, -1.096, -1.176, -1.12, -1.06, -1.124, + -1.176, -1.12, -1.112, -1.064, -1.06, -1.024, -0.984, -1.012, + -0.964, -0.904, -0.824, -0.816, -0.748, -0.7, -0.676, -0.64, -0.628, + -0.568, -0.56, -0.528, -0.468, -0.492, -0.488, -0.48, -0.492, + -0.472, -0.532, -0.544, -0.6, -0.644, -0.656, -0.704, -0.732, + -0.764, -0.776, -0.82, -0.68, -0.912, -0.988, -0.868, -1.148, + -1.056, -1.152, -1.072, -1.12, -1.088, -1.092, -1.092, -1.152, + -1.208, -1.208, -1.18, -1.172, -1.2, -1.192, -1.184, -1.18, -1.104, + -1.076, -1.072, -1.028, -1.028, -0.984, -0.944, -0.968, -0.96, + -0.964, -0.964, -0.952, -0.904, -0.856, -0.808, -0.788, -0.756, + -0.696, -0.708, -0.652, -0.672, -0.64, -0.62, -0.584, -0.604, + -0.592, -0.588 ] } }, @@ -741,39 +809,42 @@ "ID": 1705437979405, "data": { "x": [ - 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, -0.204, - -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, -0.136, -0.28, -0.392, - -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, -0.464, -0.516, -0.616, -0.648, - -0.6, -0.52, -0.48, -0.468, -0.468, -0.46, -0.432, -0.404, -0.392, -0.304, - -0.256, -0.208, -0.204, -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, - -0.792, -0.776, -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, - -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, -0.104, - 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, 0.092, 0.04, 0, - -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, 0.312, 0.188, 0.036, -0.06, - -0.156, -0.248 + 0.24, 0.22, 0.104, -0.036, -0.1, -0.096, -0.152, -0.22, -0.256, + -0.204, -0.256, -0.056, -0.028, -0.076, -0.108, -0.088, -0.068, + -0.136, -0.28, -0.392, -0.468, -0.508, -0.52, -0.516, -0.476, -0.46, + -0.464, -0.516, -0.616, -0.648, -0.6, -0.52, -0.48, -0.468, -0.468, + -0.46, -0.432, -0.404, -0.392, -0.304, -0.256, -0.208, -0.204, + -0.292, -0.388, -0.488, -0.544, -0.656, -0.736, -0.792, -0.776, + -0.716, -0.668, -0.572, -0.504, -0.484, -0.456, -0.404, -0.38, + -0.356, -0.34, -0.328, -0.324, -0.276, -0.2, -0.148, -0.128, -0.136, + -0.104, 0.016, 0.152, 0.268, 0.244, 0.168, 0.128, 0.096, 0.096, + 0.092, 0.04, 0, -0.028, -0.004, 0.128, 0.184, 0.276, 0.348, 0.364, + 0.312, 0.188, 0.036, -0.06, -0.156, -0.248 ], "y": [ - 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, 0.364, - 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, 0.392, 0.364, - 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, 0.312, 0.296, 0.312, - 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, 0.376, 0.316, 0.292, 0.308, - 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, 0.708, 0.728, 0.74, 0.748, 0.716, - 0.68, 0.624, 0.592, 0.564, 0.536, 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, - 0.416, 0.408, 0.42, 0.408, 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, - 0.448, 0.44, 0.464, 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, - 0.348, 0.368, 0.412, 0.484, 0.5 + 0.488, 0.44, 0.364, 0.304, 0.268, 0.272, 0.416, 0.436, 0.408, 0.364, + 0.364, 0.216, 0.224, 0.264, 0.26, 0.248, 0.288, 0.344, 0.388, 0.392, + 0.392, 0.364, 0.344, 0.332, 0.308, 0.304, 0.316, 0.336, 0.34, 0.336, + 0.312, 0.296, 0.312, 0.336, 0.336, 0.34, 0.34, 0.364, 0.372, 0.38, + 0.376, 0.316, 0.292, 0.308, 0.296, 0.324, 0.44, 0.54, 0.584, 0.624, + 0.708, 0.728, 0.74, 0.748, 0.716, 0.68, 0.624, 0.592, 0.564, 0.536, + 0.524, 0.504, 0.48, 0.464, 0.444, 0.428, 0.416, 0.408, 0.42, 0.408, + 0.412, 0.38, 0.388, 0.408, 0.436, 0.448, 0.436, 0.448, 0.44, 0.464, + 0.452, 0.456, 0.312, 0.424, 0.376, 0.388, 0.36, 0.336, 0.348, 0.368, + 0.412, 0.484, 0.5 ], "z": [ - -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, -0.652, - -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, -1.364, -1.312, - -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, -1.004, -0.996, -1.036, - -1.056, -1.032, -0.996, -0.932, -0.868, -0.864, -0.824, -0.808, -0.764, - -0.708, -0.652, -0.624, -0.632, -0.644, -0.628, -0.612, -0.616, -0.692, - -0.788, -0.884, -0.932, -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, - -0.784, -0.74, -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, - -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, -0.92, - -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, -1.092, -1.132, - -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 + -0.996, -0.96, -0.964, -0.896, -0.82, -0.696, -0.552, -0.52, -0.568, + -0.652, -0.652, -1.104, -1.196, -1.4, -1.516, -1.58, -1.54, -1.456, + -1.364, -1.312, -1.248, -1.208, -1.172, -1.112, -1.072, -1.032, + -1.004, -0.996, -1.036, -1.056, -1.032, -0.996, -0.932, -0.868, + -0.864, -0.824, -0.808, -0.764, -0.708, -0.652, -0.624, -0.632, + -0.644, -0.628, -0.612, -0.616, -0.692, -0.788, -0.884, -0.932, + -0.956, -0.936, -0.852, -0.824, -0.812, -0.788, -0.784, -0.74, + -0.732, -0.7, -0.668, -0.64, -0.6, -0.56, -0.552, -0.56, -0.56, + -0.556, -0.528, -0.56, -0.616, -0.7, -0.78, -0.844, -0.88, -0.884, + -0.92, -0.96, -1, -1.004, -0.956, -1.008, -1.024, -1.092, -1.076, + -1.092, -1.132, -1.152, -1.148, -1.132, -1.088, -1.084, -1.06 ] } }, @@ -781,36 +852,42 @@ "ID": 1705437977128, "data": { "x": [ - -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, 0.16, - 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, -0.42, -0.596, - -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, -0.8, -0.868, -0.968, - -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, -0.456, -0.396, -0.436, -0.476, - -0.416, -0.364, -0.296, -0.292, -0.304, -0.332, -0.324, -0.272, -0.208, - -0.156, -0.112, -0.08, -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, - 0.168, 0.176, 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, - 0.156, 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, -0.404, - -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, -0.58, -0.52, -0.428 + -0.084, -0.124, -0.188, -0.112, -0.264, -0.244, -0.092, 0.12, 0.228, + 0.16, 0.316, 0.296, 0.224, -0.084, -0.28, -0.372, -0.372, -0.352, + -0.42, -0.596, -0.792, -0.7, -0.612, -0.696, -0.82, -0.82, -0.824, + -0.8, -0.868, -0.968, -0.94, -0.852, -0.756, -0.684, -0.632, -0.516, + -0.456, -0.396, -0.436, -0.476, -0.416, -0.364, -0.296, -0.292, + -0.304, -0.332, -0.324, -0.272, -0.208, -0.156, -0.112, -0.08, + -0.008, 0.016, 0.04, 0.092, 0.14, 0.16, 0.136, 0.144, 0.168, 0.176, + 0.152, 0.236, 0.304, 0.248, 0.22, 0.12, 0.044, 0.04, 0.128, 0.156, + 0.108, -0.044, -0.188, -0.248, -0.256, -0.408, -0.516, -0.484, + -0.404, -0.396, -0.504, -0.592, -0.636, -0.64, -0.636, -0.596, + -0.58, -0.52, -0.428 ], "y": [ - 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, 0.316, 0.5, - 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, 0.52, 0.44, 0.44, 0.464, - 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, 0.604, 0.584, 0.62, 0.608, 0.58, - 0.56, 0.564, 0.576, 0.592, 0.58, 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, - 0.456, 0.44, 0.4, 0.376, 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, - 0.38, 0.392, 0.436, 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, - 0.244, 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, - 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, 0.428 + 0.3, 0.304, 0.392, 0.324, 0.236, 0.268, 0.268, 0.36, 0.256, 0.332, + 0.316, 0.5, 0.288, 0.336, 0.392, 0.448, 0.452, 0.46, 0.46, 0.5, + 0.52, 0.44, 0.44, 0.464, 0.488, 0.464, 0.456, 0.484, 0.536, 0.584, + 0.604, 0.584, 0.62, 0.608, 0.58, 0.56, 0.564, 0.576, 0.592, 0.58, + 0.532, 0.492, 0.444, 0.444, 0.452, 0.476, 0.456, 0.44, 0.4, 0.376, + 0.352, 0.356, 0.324, 0.316, 0.296, 0.332, 0.348, 0.38, 0.392, 0.436, + 0.452, 0.384, 0.34, 0.3, 0.236, 0.208, 0.22, 0.232, 0.212, 0.244, + 0.28, 0.308, 0.32, 0.312, 0.332, 0.352, 0.34, 0.372, 0.392, 0.392, + 0.352, 0.34, 0.332, 0.36, 0.436, 0.372, 0.396, 0.412, 0.464, 0.416, + 0.428 ], "z": [ - -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, -0.816, - -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, -1.18, -1.196, -1.236, - -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, -1.04, -0.964, -0.888, -0.856, - -0.772, -0.696, -0.62, -0.564, -0.6, -0.592, -0.536, -0.48, -0.432, -0.448, - -0.48, -0.484, -0.516, -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, - -0.62, -0.668, -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, - -0.84, -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, - -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, -1.152, - -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, -1.044, -1, -1, -1.012 + -0.652, -0.644, -0.74, -0.748, -0.732, -0.756, -0.924, -0.8, -0.828, + -0.816, -0.872, -0.832, -1.012, -0.992, -1.04, -1.124, -1.144, + -1.18, -1.196, -1.236, -1.2, -1.244, -1.228, -1.112, -1.104, -1.084, + -1.04, -0.964, -0.888, -0.856, -0.772, -0.696, -0.62, -0.564, -0.6, + -0.592, -0.536, -0.48, -0.432, -0.448, -0.48, -0.484, -0.516, + -0.464, -0.456, -0.452, -0.46, -0.46, -0.504, -0.544, -0.62, -0.668, + -0.76, -0.8, -0.808, -0.844, -0.832, -0.804, -0.788, -0.816, -0.84, + -0.876, -1, -1.048, -1.356, -1.068, -1.184, -1.252, -1.392, -1.36, + -1.32, -1.256, -1.204, -1.208, -1.248, -1.24, -1.22, -1.212, -1.184, + -1.152, -1.108, -1.084, -1.08, -1.092, -1.184, -1.136, -1.084, + -1.044, -1, -1, -1.012 ] } }, @@ -818,39 +895,43 @@ "ID": 1705437922352, "data": { "x": [ - -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, 0.064, 0.064, - -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, -0.688, -0.728, -0.772, - -0.784, -0.768, -0.732, -0.696, -0.648, -0.576, -0.544, -0.52, -0.504, -0.492, - -0.492, -0.484, -0.436, -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, - -0.248, -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, - -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, -0.088, - -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, -0.176, -0.176, - -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, -0.312, -0.376, -0.46, -0.436, - -0.348, -0.26, -0.268, -0.376, -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, - -0.456, -0.436, -0.448, -0.46 + -0.244, -0.216, -0.076, -0.112, -0.196, -0.184, -0.136, -0.044, + 0.064, 0.064, -0.088, -0.244, -0.416, -0.452, -0.48, -0.516, -0.568, + -0.688, -0.728, -0.772, -0.784, -0.768, -0.732, -0.696, -0.648, + -0.576, -0.544, -0.52, -0.504, -0.492, -0.492, -0.484, -0.436, + -0.388, -0.376, -0.352, -0.32, -0.268, -0.252, -0.244, -0.248, + -0.22, -0.208, -0.212, -0.212, -0.18, -0.208, -0.252, -0.32, -0.356, + -0.332, -0.252, -0.228, -0.236, -0.212, -0.148, -0.116, -0.112, + -0.088, -0.056, -0.056, -0.04, 0.032, 0.076, 0.004, -0.148, -0.188, + -0.176, -0.176, -0.264, -0.412, -0.476, -0.42, -0.344, -0.32, + -0.312, -0.376, -0.46, -0.436, -0.348, -0.26, -0.268, -0.376, + -0.484, -0.508, -0.5, -0.512, -0.492, -0.488, -0.456, -0.436, + -0.448, -0.46 ], "y": [ - 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, 0.884, 1.036, - 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, 1.236, 1.188, 1.176, - 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, 1.036, 1.004, 1.004, 1, 0.992, - 0.98, 0.94, 0.92, 0.896, 0.884, 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, - 0.76, 0.728, 0.712, 0.724, 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, - 0.696, 0.732, 0.752, 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, - 0.932, 0.892, 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, - 0.932, 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, 0.928, - 0.9 + 0.664, 0.58, 0.64, 0.796, 0.9, 0.844, 0.764, 0.792, 0.788, 0.78, + 0.884, 1.036, 1.16, 1.228, 1.248, 1.252, 1.264, 1.292, 1.26, 1.264, + 1.236, 1.188, 1.176, 1.136, 1.124, 1.06, 1.004, 0.964, 0.964, 1.008, + 1.036, 1.004, 1.004, 1, 0.992, 0.98, 0.94, 0.92, 0.896, 0.884, + 0.884, 0.852, 0.816, 0.792, 0.792, 0.764, 0.76, 0.728, 0.712, 0.724, + 0.728, 0.728, 0.72, 0.7, 0.708, 0.728, 0.708, 0.696, 0.732, 0.752, + 0.76, 0.752, 0.756, 0.684, 0.668, 0.752, 0.9, 0.96, 0.932, 0.892, + 0.94, 1, 1.028, 1.04, 1.04, 1.044, 1.052, 1.06, 1.02, 0.98, 0.932, + 0.916, 0.928, 0.96, 0.996, 0.98, 1.024, 1.028, 1, 0.968, 0.924, + 0.928, 0.9 ], "z": [ - -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, -0.868, -0.848, - -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, -0.504, -0.444, -0.416, -0.38, - -0.36, -0.316, -0.26, -0.2, -0.152, -0.136, -0.116, -0.116, -0.108, -0.044, - -0.004, 0.036, 0.072, 0.072, 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, - 0.096, 0.092, 0.068, -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, - -0.324, -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, -0.56, - -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, -0.628, -0.656, -0.672, - -0.524, -0.46, -0.468, -0.532, -0.596, -0.604, -0.6, -0.644, -0.66, -0.748, - -0.752, -0.76, -0.676, -0.608, -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, - -0.372, -0.38 + -0.524, -0.62, -0.732, -0.652, -0.576, -0.608, -0.648, -0.796, + -0.868, -0.848, -0.744, -0.648, -0.604, -0.576, -0.568, -0.528, + -0.504, -0.444, -0.416, -0.38, -0.36, -0.316, -0.26, -0.2, -0.152, + -0.136, -0.116, -0.116, -0.108, -0.044, -0.004, 0.036, 0.072, 0.072, + 0.064, 0.072, 0.068, 0.076, 0.08, 0.088, 0.104, 0.096, 0.092, 0.068, + -0.008, -0.084, -0.092, -0.144, -0.184, -0.224, -0.272, -0.324, + -0.332, -0.352, -0.404, -0.436, -0.452, -0.484, -0.492, -0.532, + -0.56, -0.612, -0.688, -0.74, -0.744, -0.688, -0.624, -0.608, + -0.628, -0.656, -0.672, -0.524, -0.46, -0.468, -0.532, -0.596, + -0.604, -0.6, -0.644, -0.66, -0.748, -0.752, -0.76, -0.676, -0.608, + -0.508, -0.392, -0.34, -0.296, -0.304, -0.344, -0.372, -0.38 ] } }, @@ -858,38 +939,41 @@ "ID": 1705437913803, "data": { "x": [ - 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, 0.86, - 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, 0.204, 0.044, - -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, -0.536, -0.488, -0.404, - -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, -0.548, -0.376, -0.444, -0.496, - -0.496, -0.428, -0.344, -0.724, -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, - 0.372, 0.34, 0.296, 0.22, 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, - 0.456, 0.5, 0.516, 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, - 0.344, 0.252, 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, + 0.864, 0.94, 1.108, 1.224, 1.232, 1.22, 1.212, 1.228, 1.012, 0.944, + 0.86, 0.804, 0.788, 0.76, 0.732, 0.68, 0.64, 0.676, 0.496, 0.356, + 0.204, 0.044, -0.02, -0.008, -0.028, -0.072, -0.184, -0.324, -0.488, + -0.536, -0.488, -0.404, -0.4, -0.448, -0.316, -0.34, -0.516, -0.652, + -0.548, -0.376, -0.444, -0.496, -0.496, -0.428, -0.344, -0.724, + -0.196, 0.044, 0.112, 0.248, 0.328, 0.392, 0.372, 0.34, 0.296, 0.22, + 0.192, 0.212, 0.228, 0.32, 0.332, 0.38, 0.416, 0.456, 0.5, 0.516, + 0.508, 0.496, 0.46, 0.448, 0.444, 0.424, 0.34, 0.324, 0.344, 0.252, + 0.228, 0.18, 0.088, 0.016, 0.012, 0.016, 0.004, -0.024, -0.108, -0.16, -0.212, -0.316, -0.412, -0.42, -0.38, -0.296, -0.244 ], "y": [ - 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, -0.192, - -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, -0.044, -0.004, - 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, 0.36, 0.396, 0.424, 0.4, - 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, 0.332, 0.344, 0.244, 0.068, -0.096, - -0.092, -0.06, -0.02, 0.452, -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, - 0.06, 0.072, 0.072, 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, - 0.076, 0.04, 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, - 0.304, 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, + 0.056, 0.036, 0.02, -0.044, -0.104, -0.136, -0.152, 0.064, -0.228, + -0.192, -0.224, -0.2, -0.216, -0.2, -0.188, -0.184, -0.164, -0.08, + -0.044, -0.004, 0.068, 0.176, 0.236, 0.24, 0.232, 0.244, 0.308, + 0.36, 0.396, 0.424, 0.4, 0.368, 0.348, 0.388, 0.36, 0.328, 0.3, + 0.332, 0.344, 0.244, 0.068, -0.096, -0.092, -0.06, -0.02, 0.452, + -0.112, -0.112, -0.032, 0.056, 0.152, 0.096, 0.06, 0.072, 0.072, + 0.18, 0.236, 0.212, 0.136, 0.1, 0.092, 0.108, 0.108, 0.076, 0.04, + 0.028, 0.052, 0.096, 0.124, 0.088, 0.064, 0.056, 0.148, 0.26, 0.304, + 0.272, 0.26, 0.308, 0.34, 0.416, 0.452, 0.404, 0.428, 0.456, 0.52, 0.536, 0.528, 0.552, 0.604, 0.648, 0.696, 0.64, 0.596 ], "z": [ - -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, -0.576, - -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, -0.28, -0.18, -0.248, - -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, -0.524, -0.58, -0.66, -0.78, - -0.808, -0.784, -0.732, -0.76, -0.744, -0.744, -0.8, -0.904, -1.032, -1.076, - -1.14, -1.3, -1.504, -1.62, -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, - -1.08, -1.064, -1.1, -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, - -1.332, -1.292, -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, - -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, -0.736, -0.712, - -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, -0.64, -0.692, -0.724, - -0.752, -0.768, -0.772, -0.792 + -1.236, -1.128, -0.992, -0.916, -0.844, -0.796, -0.724, -0.672, + -0.576, -0.496, -0.552, -0.448, -0.472, -0.452, -0.38, -0.312, + -0.28, -0.18, -0.248, -0.324, -0.428, -0.468, -0.464, -0.432, -0.48, + -0.524, -0.58, -0.66, -0.78, -0.808, -0.784, -0.732, -0.76, -0.744, + -0.744, -0.8, -0.904, -1.032, -1.076, -1.14, -1.3, -1.504, -1.62, + -1.652, -1.604, -1.556, -1.52, -1.432, -1.252, -1.08, -1.064, -1.1, + -1.1, -1.048, -1.044, -1.064, -1.132, -1.184, -1.24, -1.332, -1.292, + -1.268, -1.232, -1.224, -1.18, -1.14, -1.088, -1.016, -0.892, + -0.832, -0.812, -0.836, -0.82, -0.792, -0.748, -0.708, -0.764, + -0.736, -0.712, -0.68, -0.668, -0.68, -0.66, -0.672, -0.656, -0.636, + -0.64, -0.692, -0.724, -0.752, -0.768, -0.772, -0.792 ] } }, @@ -897,39 +981,44 @@ "ID": 1705437908508, "data": { "x": [ - -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, -1.108, - -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, -0.868, -0.908, - -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, -0.484, -0.424, -0.42, -0.436, - -0.408, -0.352, -0.272, -0.216, -0.208, -0.236, -0.188, -0.148, -0.104, - -0.084, -0.08, -0.076, -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, - -0.212, -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, -0.76, - -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, -0.972, -0.992, -1, - -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, -0.796, -0.692, -0.564, -0.404, - -0.388, -0.312, -0.32, -0.324, -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, - -0.508, -0.508, -0.508, -0.496 + -0.432, -0.432, -0.404, -0.48, -0.588, -0.9, -0.752, -0.852, -1.02, + -1.108, -1.044, -0.944, -0.888, -0.876, -0.852, -0.756, -0.828, + -0.868, -0.908, -0.928, -0.84, -0.756, -0.66, -0.576, -0.512, + -0.484, -0.424, -0.42, -0.436, -0.408, -0.352, -0.272, -0.216, + -0.208, -0.236, -0.188, -0.148, -0.104, -0.084, -0.08, -0.076, + -0.064, -0.08, -0.108, -0.132, -0.188, -0.232, -0.212, -0.212, + -0.288, -0.384, -0.392, -0.372, -0.348, -0.388, -0.528, -0.732, + -0.76, -0.684, -0.732, -0.9, -1.032, -1.092, -1.004, -0.98, -1, + -0.972, -0.992, -1, -1.06, -1.084, -1.052, -1.012, -0.996, -0.904, + -0.796, -0.692, -0.564, -0.404, -0.388, -0.312, -0.32, -0.324, + -0.324, -0.364, -0.428, -0.484, -0.508, -0.496, -0.508, -0.508, + -0.508, -0.496 ], "y": [ - 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, 0.712, - 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, 0.96, 0.972, 0.96, - 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, 0.876, 0.832, 0.824, 0.8, 0.74, - 0.716, 0.692, 0.672, 0.628, 0.572, 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, - 0.54, 0.528, 0.54, 0.544, 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, - 0.472, 0.492, 0.52, 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, - 0.872, 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, 1, - 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, 0.776, 0.772, - 0.772, 0.776 + 0.68, 0.644, 0.576, 0.52, 0.496, 0.492, 0.548, 0.68, 0.704, 0.696, + 0.712, 0.772, 0.804, 0.82, 0.812, 0.84, 0.888, 0.948, 0.94, 0.96, + 0.96, 0.972, 0.96, 0.924, 0.916, 0.888, 0.9, 0.904, 0.88, 0.86, + 0.876, 0.832, 0.824, 0.8, 0.74, 0.716, 0.692, 0.672, 0.628, 0.572, + 0.604, 0.608, 0.608, 0.592, 0.572, 0.564, 0.54, 0.528, 0.54, 0.544, + 0.512, 0.512, 0.524, 0.536, 0.524, 0.536, 0.468, 0.472, 0.492, 0.52, + 0.564, 0.596, 0.692, 0.748, 0.764, 0.748, 0.812, 0.876, 0.872, + 0.816, 0.8, 0.86, 0.904, 0.892, 0.892, 0.912, 0.932, 0.936, 0.824, + 1, 0.844, 0.86, 0.832, 0.808, 0.788, 0.776, 0.78, 0.76, 0.756, + 0.776, 0.772, 0.772, 0.776 ], "z": [ - -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, -0.036, - -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, -0.464, -0.508, - -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, -0.664, -0.72, -0.704, -0.744, - -0.784, -0.856, -0.884, -0.94, -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, - -0.744, -0.744, -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, - -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, -0.012, - 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, 0.148, 0.088, - 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, -0.592, -0.684, -0.652, - -0.912, -0.672, -0.604, -0.6, -0.584, -0.564, -0.504, -0.504, -0.496, -0.484, - -0.472, -0.46, -0.456, -0.448 + -0.252, -0.272, -0.324, -0.252, -0.216, -0.104, -0.072, -0.012, + -0.036, -0.128, -0.248, -0.308, -0.364, -0.424, -0.408, -0.572, + -0.464, -0.508, -0.468, -0.504, -0.452, -0.464, -0.496, -0.58, + -0.664, -0.72, -0.704, -0.744, -0.784, -0.856, -0.884, -0.94, + -0.856, -0.84, -0.86, -0.864, -0.832, -0.788, -0.744, -0.744, + -0.716, -0.648, -0.556, -0.516, -0.468, -0.38, -0.312, -0.272, + -0.228, -0.184, -0.16, -0.144, -0.108, -0.048, -0.044, -0.004, 0.02, + -0.012, 0.044, 0.1, 0.144, 0.152, 0.156, 0.128, 0.104, 0.116, 0.172, + 0.148, 0.088, 0.032, -0.032, -0.092, -0.188, -0.34, -0.452, -0.548, + -0.592, -0.684, -0.652, -0.912, -0.672, -0.604, -0.6, -0.584, + -0.564, -0.504, -0.504, -0.496, -0.484, -0.472, -0.46, -0.456, + -0.448 ] } }, @@ -937,38 +1026,42 @@ "ID": 1705437905957, "data": { "x": [ - -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, -0.496, - -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, -0.884, -0.936, - -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, -0.964, -0.872, -0.824, -0.796, - -0.832, -0.868, -0.804, -0.716, -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, - -0.516, -0.5, -0.476, -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, - -0.228, -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, -0.244, - -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, -0.928, -0.972, -1.004, - -0.976, -0.928, -0.92, -0.94, -0.952, -0.904, -0.848, -0.828, -0.856, -0.844, - -0.804, -0.78, -0.764, -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, - -0.64, -0.6, -0.588 + -0.436, -0.42, -0.432, -0.388, -0.36, -0.22, -0.12, -0.156, -0.336, + -0.496, -0.56, -0.6, -0.684, -0.856, -1.036, -1.14, -1.048, -0.908, + -0.884, -0.936, -1.016, -1.056, -1.06, -1.024, -1.028, -1.04, + -0.964, -0.872, -0.824, -0.796, -0.832, -0.868, -0.804, -0.716, + -0.64, -0.608, -0.584, -0.572, -0.544, -0.512, -0.516, -0.5, -0.476, + -0.464, -0.448, -0.444, -0.444, -0.364, -0.28, -0.212, -0.228, + -0.312, -0.392, -0.4, -0.376, -0.332, -0.256, -0.232, -0.244, + -0.244, -0.26, -0.396, -0.552, -0.68, -0.836, -0.888, -0.852, + -0.928, -0.972, -1.004, -0.976, -0.928, -0.92, -0.94, -0.952, + -0.904, -0.848, -0.828, -0.856, -0.844, -0.804, -0.78, -0.764, + -0.764, -0.732, -0.712, -0.712, -0.692, -0.688, -0.692, -0.64, -0.6, + -0.588 ], "y": [ - 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, 0.92, 0.992, - 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, 0.856, 0.848, 0.812, - 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, 0.724, 0.708, 0.7, 0.692, - 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, - 0.492, 0.472, 0.508, 0.524, 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, - 0.808, 0.812, 0.848, 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, - 0.916, 0.86, 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, - 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, 0.716, - 0.724, 0.712 + 0.54, 0.504, 0.48, 0.512, 0.524, 0.6, 0.752, 0.936, 0.916, 0.936, + 0.92, 0.992, 0.992, 1.028, 1.056, 1.072, 1.064, 1.016, 0.976, 0.908, + 0.856, 0.848, 0.812, 0.796, 0.784, 0.792, 0.78, 0.752, 0.744, 0.732, + 0.724, 0.708, 0.7, 0.692, 0.672, 0.672, 0.68, 0.652, 0.644, 0.64, + 0.628, 0.6, 0.56, 0.552, 0.516, 0.496, 0.492, 0.472, 0.508, 0.524, + 0.556, 0.592, 0.632, 0.664, 0.66, 0.712, 0.768, 0.808, 0.812, 0.848, + 0.876, 0.908, 0.932, 0.984, 1.012, 1.02, 1.004, 0.968, 0.916, 0.86, + 0.868, 0.816, 0.784, 0.74, 0.72, 0.692, 0.684, 0.676, 0.7, 0.712, + 0.732, 0.74, 0.764, 0.788, 0.776, 0.756, 0.776, 0.764, 0.752, 0.732, + 0.716, 0.724, 0.712 ], "z": [ - -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, -0.612, - -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, -0.356, -0.328, - -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, -0.204, -0.176, -0.148, - -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, 0.188, 0.216, 0.204, 0.164, 0.088, - 0.032, 0.044, 0.04, 0.068, 0.06, 0.016, -0.048, -0.124, -0.208, -0.296, - -0.404, -0.428, -0.4, -0.38, -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, - -0.8, -0.872, -0.88, -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, - -0.272, -0.28, -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, - -0.376, -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, + -0.288, -0.312, -0.356, -0.36, -0.44, -0.7, -0.964, -0.852, -0.704, + -0.612, -0.592, -0.588, -0.524, -0.468, -0.368, -0.364, -0.364, + -0.356, -0.328, -0.288, -0.248, -0.256, -0.296, -0.292, -0.232, + -0.204, -0.176, -0.148, -0.132, -0.072, -0.004, 0.08, 0.132, 0.164, + 0.188, 0.216, 0.204, 0.164, 0.088, 0.032, 0.044, 0.04, 0.068, 0.06, + 0.016, -0.048, -0.124, -0.208, -0.296, -0.404, -0.428, -0.4, -0.38, + -0.408, -0.512, -0.624, -0.7, -0.712, -0.76, -0.8, -0.872, -0.88, + -0.824, -0.684, -0.6, -0.528, -0.468, -0.384, -0.292, -0.272, -0.28, + -0.3, -0.296, -0.276, -0.3, -0.32, -0.34, -0.372, -0.388, -0.376, + -0.396, -0.412, -0.404, -0.376, -0.38, -0.368, -0.364, -0.396, -0.4, -0.38, -0.356, -0.32, -0.328 ] } @@ -977,39 +1070,43 @@ "ID": 1705437896254, "data": { "x": [ - -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, -0.712, - -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, -0.808, -0.84, -0.944, - -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, -1.076, -1.048, -1.048, -1.112, - -1.144, -1.188, -1.208, -1.228, -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, - -1.02, -0.956, -0.912, -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, - -0.732, -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, - -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, -0.496, -0.48, - -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, -0.404, -0.46, -0.532, - -0.568, -0.484, -0.38, -0.444, -0.572, -0.728, -0.728, -0.616, -0.644, -0.656, - -0.704, -0.688, -0.644, -0.652 + -0.632, -0.608, -0.592, -0.584, -0.644, -0.868, -0.492, -0.528, + -0.712, -0.784, -0.708, -0.588, -0.632, -0.752, -0.876, -0.852, + -0.808, -0.84, -0.944, -0.936, -0.9, -0.936, -1.02, -1.104, -1.084, + -1.076, -1.048, -1.048, -1.112, -1.144, -1.188, -1.208, -1.228, + -1.288, -1.296, -1.264, -1.2, -1.18, -1.132, -1.02, -0.956, -0.912, + -0.872, -0.848, -0.824, -0.876, -0.892, -0.844, -0.808, -0.732, + -0.716, -0.78, -0.852, -0.76, -0.62, -0.588, -0.636, -0.632, -0.564, + -0.52, -0.508, -0.52, -0.484, -0.412, -0.432, -0.428, -0.444, + -0.496, -0.48, -0.452, -0.46, -0.508, -0.516, -0.512, -0.448, -0.4, + -0.404, -0.46, -0.532, -0.568, -0.484, -0.38, -0.444, -0.572, + -0.728, -0.728, -0.616, -0.644, -0.656, -0.704, -0.688, -0.644, + -0.652 ], "y": [ - 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, 0.472, - 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, 0.244, 0.232, - 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, 0.056, 0.076, 0.104, - 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, 0.28, 0.308, 0.324, 0.348, 0.38, - 0.404, 0.408, 0.408, 0.384, 0.372, 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, - 0.38, 0.364, 0.412, 0.32, 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, - 0.372, 0.368, 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, - 0.468, 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, 0.32, - 0.32, 0.316 + 0.364, 0.344, 0.352, 0.38, 0.396, 0.604, 0.4, 0.428, 0.468, 0.48, + 0.472, 0.436, 0.42, 0.408, 0.42, 0.424, 0.38, 0.304, 0.26, 0.244, + 0.244, 0.232, 0.184, 0.176, 0.112, 0.128, 0.14, 0.12, 0.092, 0.072, + 0.056, 0.076, 0.104, 0.124, 0.14, 0.164, 0.2, 0.216, 0.224, 0.248, + 0.28, 0.308, 0.324, 0.348, 0.38, 0.404, 0.408, 0.408, 0.384, 0.372, + 0.356, 0.372, 0.3, 0.284, 0.352, 0.372, 0.38, 0.364, 0.412, 0.32, + 0.36, 0.32, 0.308, 0.276, 0.332, 0.34, 0.324, 0.344, 0.372, 0.368, + 0.368, 0.408, 0.464, 0.476, 0.44, 0.424, 0.468, 0.484, 0.496, 0.468, + 0.46, 0.484, 0.5, 0.504, 0.464, 0.448, 0.432, 0.396, 0.352, 0.34, + 0.32, 0.32, 0.316 ], "z": [ - -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, -0.556, -0.524, - -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, -0.644, -0.72, -0.756, -0.744, - -0.724, -0.7, -0.684, -0.716, -0.716, -0.68, -0.668, -0.656, -0.708, -0.736, - -0.696, -0.712, -0.684, -0.7, -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, - -0.524, -0.496, -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, - -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, -0.392, -0.444, - -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, -0.716, -0.76, -0.76, - -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, -0.728, -0.66, -0.7, -0.756, - -0.86, -0.776, -0.76, -0.812, -0.876, -0.872, -0.908, -0.96, -0.976, -0.996, - -1.032, -0.976, -0.96 + -0.524, -0.552, -0.576, -0.58, -0.58, -0.724, -0.484, -0.496, + -0.556, -0.524, -0.552, -0.58, -0.648, -0.732, -0.784, -0.596, + -0.644, -0.72, -0.756, -0.744, -0.724, -0.7, -0.684, -0.716, -0.716, + -0.68, -0.668, -0.656, -0.708, -0.736, -0.696, -0.712, -0.684, -0.7, + -0.688, -0.644, -0.596, -0.556, -0.572, -0.552, -0.524, -0.496, + -0.472, -0.504, -0.464, -0.368, -0.412, -0.42, -0.42, -0.436, + -0.372, -0.352, -0.352, -0.4, -0.372, -0.336, -0.292, -0.376, + -0.392, -0.444, -0.456, -0.436, -0.504, -0.56, -0.58, -0.62, -0.68, + -0.716, -0.76, -0.76, -0.768, -0.764, -0.792, -0.784, -0.76, -0.74, + -0.728, -0.66, -0.7, -0.756, -0.86, -0.776, -0.76, -0.812, -0.876, + -0.872, -0.908, -0.96, -0.976, -0.996, -1.032, -0.976, -0.96 ] } } diff --git a/src/test-fixtures/test-data-shake-still.json b/src/test-fixtures/test-data-shake-still.json index 23acfa6c3..f6c704f53 100644 --- a/src/test-fixtures/test-data-shake-still.json +++ b/src/test-fixtures/test-data-shake-still.json @@ -7,37 +7,40 @@ "ID": 1718706806260, "data": { "x": [ - 2.04, 2.04, 2.008, 1.516, 1.152, 0.612, 0.056, -0.528, -1.224, -1.716, -1.632, - -1.072, -0.512, 0.408, 1.4, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.68, 1.304, - 0.836, 0.248, -0.412, -1.152, -1.776, -1.868, -1.384, -0.744, 0.232, 1.376, - 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.584, 1.156, 0.656, 0.096, -0.6, -1.352, - -1.792, -1.644, -0.996, -0.316, 0.612, 1.648, 2.04, 2.04, 2.04, 2.04, 2.04, - 2.04, 1.5, 1.096, 0.6, -0.012, -0.656, -1.272, -1.596, -1.408, -0.872, -0.22, - 0.544, 1.488, 2.036, 2.04, 2.04, 2.04, 2.04, 2.04, 1.948, 1.42, 0.928, 0.428, - -0.216, -0.788, -1.272, -1.604, -1.396, -0.856, -0.268, 0.544, 1.488, 2.04, - 2.04, 2.04 + 2.04, 2.04, 2.008, 1.516, 1.152, 0.612, 0.056, -0.528, -1.224, + -1.716, -1.632, -1.072, -0.512, 0.408, 1.4, 2.04, 2.04, 2.04, 2.04, + 2.04, 2.04, 1.68, 1.304, 0.836, 0.248, -0.412, -1.152, -1.776, + -1.868, -1.384, -0.744, 0.232, 1.376, 2.04, 2.04, 2.04, 2.04, 2.04, + 2.04, 1.584, 1.156, 0.656, 0.096, -0.6, -1.352, -1.792, -1.644, + -0.996, -0.316, 0.612, 1.648, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, + 1.5, 1.096, 0.6, -0.012, -0.656, -1.272, -1.596, -1.408, -0.872, + -0.22, 0.544, 1.488, 2.036, 2.04, 2.04, 2.04, 2.04, 2.04, 1.948, + 1.42, 0.928, 0.428, -0.216, -0.788, -1.272, -1.604, -1.396, -0.856, + -0.268, 0.544, 1.488, 2.04, 2.04, 2.04 ], "y": [ - 1.236, 0.976, 0.7, 0.58, 0.496, 0.316, 0.176, 0.004, -0.152, -0.248, -0.312, - -0.372, -0.356, -0.024, 0.372, 0.816, 1.24, 1.416, 1.416, 1.216, 0.932, 0.688, - 0.552, 0.388, 0.208, -0.028, -0.188, -0.292, -0.388, -0.464, -0.504, -0.268, - 0.248, 0.78, 1.28, 1.52, 1.492, 1.26, 0.924, 0.628, 0.48, 0.332, 0.164, - -0.032, -0.172, -0.288, -0.428, -0.416, -0.34, -0.036, 0.5, 0.996, 1.296, - 1.52, 1.42, 1.168, 0.804, 0.6, 0.484, 0.344, 0.164, -0.028, -0.168, -0.264, - -0.304, -0.3, -0.18, 0.084, 0.512, 0.832, 1.08, 1.228, 1.3, 1.268, 1.064, - 0.764, 0.62, 0.452, 0.312, 0.12, -0.088, -0.184, -0.324, -0.404, -0.392, - -0.308, -0.024, 0.408, 0.844, 1.252, 1.536 + 1.236, 0.976, 0.7, 0.58, 0.496, 0.316, 0.176, 0.004, -0.152, -0.248, + -0.312, -0.372, -0.356, -0.024, 0.372, 0.816, 1.24, 1.416, 1.416, + 1.216, 0.932, 0.688, 0.552, 0.388, 0.208, -0.028, -0.188, -0.292, + -0.388, -0.464, -0.504, -0.268, 0.248, 0.78, 1.28, 1.52, 1.492, + 1.26, 0.924, 0.628, 0.48, 0.332, 0.164, -0.032, -0.172, -0.288, + -0.428, -0.416, -0.34, -0.036, 0.5, 0.996, 1.296, 1.52, 1.42, 1.168, + 0.804, 0.6, 0.484, 0.344, 0.164, -0.028, -0.168, -0.264, -0.304, + -0.3, -0.18, 0.084, 0.512, 0.832, 1.08, 1.228, 1.3, 1.268, 1.064, + 0.764, 0.62, 0.452, 0.312, 0.12, -0.088, -0.184, -0.324, -0.404, + -0.392, -0.308, -0.024, 0.408, 0.844, 1.252, 1.536 ], "z": [ - 2.04, 2.024, 1.264, 0.56, 0.044, -0.34, -0.664, -1.004, -1.416, -1.656, - -1.488, -0.916, -0.496, -0.2, 0.276, 0.928, 1.888, 2.04, 2.04, 2.04, 1.448, - 0.712, 0.192, -0.2, -0.576, -0.888, -1.384, -1.7, -1.568, -1.144, -0.636, - -0.208, 0.288, 0.952, 1.824, 2.04, 2.04, 2.04, 1.416, 0.68, 0.112, -0.328, - -0.676, -1, -1.424, -1.568, -1.324, -0.852, -0.404, 0.104, 0.492, 1.204, - 1.948, 2.04, 2.04, 1.98, 1.352, 0.632, 0.124, -0.352, -0.744, -0.988, -1.328, - -1.368, -1.192, -0.812, -0.388, -0.004, 0.368, 0.892, 1.448, 1.944, 2.04, - 2.036, 1.696, 1.1, 0.38, -0.032, -0.4, -0.732, -0.844, -1.196, -1.332, -1.12, - -0.764, -0.428, -0.068, 0.4, 0.708, 1.248, 1.852 + 2.04, 2.024, 1.264, 0.56, 0.044, -0.34, -0.664, -1.004, -1.416, + -1.656, -1.488, -0.916, -0.496, -0.2, 0.276, 0.928, 1.888, 2.04, + 2.04, 2.04, 1.448, 0.712, 0.192, -0.2, -0.576, -0.888, -1.384, -1.7, + -1.568, -1.144, -0.636, -0.208, 0.288, 0.952, 1.824, 2.04, 2.04, + 2.04, 1.416, 0.68, 0.112, -0.328, -0.676, -1, -1.424, -1.568, + -1.324, -0.852, -0.404, 0.104, 0.492, 1.204, 1.948, 2.04, 2.04, + 1.98, 1.352, 0.632, 0.124, -0.352, -0.744, -0.988, -1.328, -1.368, + -1.192, -0.812, -0.388, -0.004, 0.368, 0.892, 1.448, 1.944, 2.04, + 2.036, 1.696, 1.1, 0.38, -0.032, -0.4, -0.732, -0.844, -1.196, + -1.332, -1.12, -0.764, -0.428, -0.068, 0.4, 0.708, 1.248, 1.852 ] } }, @@ -45,37 +48,41 @@ "ID": 1718706797873, "data": { "x": [ - -0.732, -0.276, 0.012, 0.188, 0.368, 0.704, 1.132, 1.32, 1.36, 1.184, 0.884, - 0.628, 0.396, 0.156, -0.036, -0.388, -0.748, -1.092, -0.984, -0.592, -0.212, - 0.056, 0.348, 0.612, 0.892, 1.228, 1.304, 1.272, 1.04, 0.792, 0.628, 0.452, - 0.168, -0.14, -0.5, -0.9, -1.008, -0.736, -0.308, 0.064, 0.284, 0.532, 0.84, - 1.216, 1.42, 1.384, 1.148, 0.928, 0.736, 0.536, 0.328, 0.092, -0.156, -0.504, - -0.872, -1.112, -0.776, -0.324, 0.036, 0.292, 0.472, 0.772, 1.248, 1.452, - 1.488, 1.3, 0.996, 0.736, 0.492, 0.268, 0.032, -0.268, -0.672, -1.088, -1.1, - -0.7, -0.228, 0.116, 0.44, 0.644, 0.928, 1.196, 1.392, 1.328, 1.088, 0.808, - 0.584, 0.368, 0.144, -0.108, -0.48 + -0.732, -0.276, 0.012, 0.188, 0.368, 0.704, 1.132, 1.32, 1.36, + 1.184, 0.884, 0.628, 0.396, 0.156, -0.036, -0.388, -0.748, -1.092, + -0.984, -0.592, -0.212, 0.056, 0.348, 0.612, 0.892, 1.228, 1.304, + 1.272, 1.04, 0.792, 0.628, 0.452, 0.168, -0.14, -0.5, -0.9, -1.008, + -0.736, -0.308, 0.064, 0.284, 0.532, 0.84, 1.216, 1.42, 1.384, + 1.148, 0.928, 0.736, 0.536, 0.328, 0.092, -0.156, -0.504, -0.872, + -1.112, -0.776, -0.324, 0.036, 0.292, 0.472, 0.772, 1.248, 1.452, + 1.488, 1.3, 0.996, 0.736, 0.492, 0.268, 0.032, -0.268, -0.672, + -1.088, -1.1, -0.7, -0.228, 0.116, 0.44, 0.644, 0.928, 1.196, 1.392, + 1.328, 1.088, 0.808, 0.584, 0.368, 0.144, -0.108, -0.48 ], "y": [ - -1.64, -0.936, -0.288, 0.332, 1.16, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, - 1.276, 0.536, -0.084, -0.608, -1.136, -1.7, -1.756, -1.372, -0.9, -0.376, - 0.52, 1.464, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.84, 1.208, 0.46, -0.22, - -0.972, -1.644, -1.968, -1.796, -1.296, -0.66, 0.092, 1.008, 2.04, 2.04, 2.04, - 2.04, 2.04, 2.04, 2.036, 1.344, 0.704, 0.104, -0.516, -1.112, -1.672, -2.036, - -1.672, -1.064, -0.416, 0.46, 1.224, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2, - 1.332, 0.704, 0.156, -0.512, -1.228, -1.816, -1.864, -1.424, -0.768, -0.08, - 0.832, 1.788, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.7, 1.14, 0.524, -0.06, - -0.744 + -1.64, -0.936, -0.288, 0.332, 1.16, 2.04, 2.04, 2.04, 2.04, 2.04, + 2.04, 2.04, 1.276, 0.536, -0.084, -0.608, -1.136, -1.7, -1.756, + -1.372, -0.9, -0.376, 0.52, 1.464, 2.04, 2.04, 2.04, 2.04, 2.04, + 2.04, 1.84, 1.208, 0.46, -0.22, -0.972, -1.644, -1.968, -1.796, + -1.296, -0.66, 0.092, 1.008, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, + 2.036, 1.344, 0.704, 0.104, -0.516, -1.112, -1.672, -2.036, -1.672, + -1.064, -0.416, 0.46, 1.224, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2, + 1.332, 0.704, 0.156, -0.512, -1.228, -1.816, -1.864, -1.424, -0.768, + -0.08, 0.832, 1.788, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.7, 1.14, + 0.524, -0.06, -0.744 ], "z": [ - -0.536, -0.372, -0.476, -0.588, -0.716, -0.54, -0.004, 0.384, 0.452, 0.28, - -0.072, -0.48, -0.68, -0.752, -0.636, -0.74, -0.876, -0.952, -0.732, -0.408, - -0.192, -0.224, -0.34, -0.292, -0.12, 0.292, 0.46, 0.464, 0.156, -0.1, -0.376, - -0.652, -0.844, -0.932, -0.868, -0.864, -0.696, -0.424, -0.152, -0.156, -0.42, - -0.512, -0.488, -0.04, 0.272, 0.384, 0.16, -0.076, -0.376, -0.604, -0.668, - -0.616, -0.508, -0.54, -0.688, -0.748, -0.548, -0.404, -0.392, -0.56, -0.616, - -0.372, 0.196, 0.584, 0.764, 0.624, 0.16, -0.24, -0.54, -0.696, -0.78, -0.756, - -0.792, -0.904, -0.8, -0.5, -0.264, -0.352, -0.348, -0.312, 0.024, 0.396, - 0.696, 0.736, 0.476, 0.136, -0.236, -0.516, -0.62, -0.66, -0.684 + -0.536, -0.372, -0.476, -0.588, -0.716, -0.54, -0.004, 0.384, 0.452, + 0.28, -0.072, -0.48, -0.68, -0.752, -0.636, -0.74, -0.876, -0.952, + -0.732, -0.408, -0.192, -0.224, -0.34, -0.292, -0.12, 0.292, 0.46, + 0.464, 0.156, -0.1, -0.376, -0.652, -0.844, -0.932, -0.868, -0.864, + -0.696, -0.424, -0.152, -0.156, -0.42, -0.512, -0.488, -0.04, 0.272, + 0.384, 0.16, -0.076, -0.376, -0.604, -0.668, -0.616, -0.508, -0.54, + -0.688, -0.748, -0.548, -0.404, -0.392, -0.56, -0.616, -0.372, + 0.196, 0.584, 0.764, 0.624, 0.16, -0.24, -0.54, -0.696, -0.78, + -0.756, -0.792, -0.904, -0.8, -0.5, -0.264, -0.352, -0.348, -0.312, + 0.024, 0.396, 0.696, 0.736, 0.476, 0.136, -0.236, -0.516, -0.62, + -0.66, -0.684 ] } }, @@ -83,36 +90,40 @@ "ID": 1718706789113, "data": { "x": [ - 2.04, 2.04, 2.04, 2.04, 1.856, 1.276, 0.684, 0.096, -0.62, -1.172, -1.54, - -1.592, -1.34, -0.828, -0.212, 0.588, 1.496, 2.04, 2.04, 2.04, 2.04, 2.04, - 2.04, 2.04, 1.504, 0.88, 0.268, -0.368, -0.896, -1.464, -1.768, -1.608, - -1.104, -0.452, 0.248, 1.216, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.66, - 1.088, 0.532, -0.084, -0.672, -1.288, -1.596, -1.6, -1.316, -0.76, 0.004, - 0.784, 1.784, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.872, 1.344, 0.716, 0.168, - -0.436, -1.032, -1.548, -1.752, -1.528, -1.028, -0.1, 1.092, 2.04, 2.04, 2.04, - 2.04, 2.04, 2.04, 2.04, 1.616, 1.088, 0.408, -0.196, -0.88, -1.46, -1.552, - -1.448, -0.96 + 2.04, 2.04, 2.04, 2.04, 1.856, 1.276, 0.684, 0.096, -0.62, -1.172, + -1.54, -1.592, -1.34, -0.828, -0.212, 0.588, 1.496, 2.04, 2.04, + 2.04, 2.04, 2.04, 2.04, 2.04, 1.504, 0.88, 0.268, -0.368, -0.896, + -1.464, -1.768, -1.608, -1.104, -0.452, 0.248, 1.216, 2.04, 2.04, + 2.04, 2.04, 2.04, 2.04, 2.04, 1.66, 1.088, 0.532, -0.084, -0.672, + -1.288, -1.596, -1.6, -1.316, -0.76, 0.004, 0.784, 1.784, 2.04, + 2.04, 2.04, 2.04, 2.04, 2.04, 1.872, 1.344, 0.716, 0.168, -0.436, + -1.032, -1.548, -1.752, -1.528, -1.028, -0.1, 1.092, 2.04, 2.04, + 2.04, 2.04, 2.04, 2.04, 2.04, 1.616, 1.088, 0.408, -0.196, -0.88, + -1.46, -1.552, -1.448, -0.96 ], "y": [ - -0.248, -0.212, -0.224, -0.232, -0.228, -0.196, -0.132, -0.08, 0.036, 0.232, - 0.392, 0.332, 0.136, -0.052, -0.176, -0.232, -0.284, -0.268, -0.196, -0.208, - -0.192, -0.228, -0.276, -0.26, -0.232, -0.16, -0.1, -0.008, 0.184, 0.376, - 0.452, 0.26, -0.016, -0.26, -0.364, -0.356, -0.276, -0.244, -0.172, -0.108, - -0.144, -0.184, -0.232, -0.216, -0.156, -0.092, -0.032, 0.052, 0.232, 0.368, - 0.316, 0.124, -0.124, -0.272, -0.336, -0.368, -0.376, -0.396, -0.344, -0.328, - -0.336, -0.312, -0.264, -0.244, -0.176, -0.124, -0.076, 0.112, 0.404, 0.464, - 0.256, -0.044, -0.3, -0.504, -0.516, -0.52, -0.484, -0.448, -0.42, -0.368, - -0.312, -0.244, -0.184, -0.12, 0.028, 0.228, 0.44, 0.396, 0.216, -0.076 + -0.248, -0.212, -0.224, -0.232, -0.228, -0.196, -0.132, -0.08, + 0.036, 0.232, 0.392, 0.332, 0.136, -0.052, -0.176, -0.232, -0.284, + -0.268, -0.196, -0.208, -0.192, -0.228, -0.276, -0.26, -0.232, + -0.16, -0.1, -0.008, 0.184, 0.376, 0.452, 0.26, -0.016, -0.26, + -0.364, -0.356, -0.276, -0.244, -0.172, -0.108, -0.144, -0.184, + -0.232, -0.216, -0.156, -0.092, -0.032, 0.052, 0.232, 0.368, 0.316, + 0.124, -0.124, -0.272, -0.336, -0.368, -0.376, -0.396, -0.344, + -0.328, -0.336, -0.312, -0.264, -0.244, -0.176, -0.124, -0.076, + 0.112, 0.404, 0.464, 0.256, -0.044, -0.3, -0.504, -0.516, -0.52, + -0.484, -0.448, -0.42, -0.368, -0.312, -0.244, -0.184, -0.12, 0.028, + 0.228, 0.44, 0.396, 0.216, -0.076 ], "z": [ - 1.396, 1.324, 1.044, 0.564, 0.164, -0.164, -0.396, -0.564, -0.652, -0.892, - -1.004, -0.796, -0.512, -0.328, -0.28, -0.176, 0.064, 0.496, 0.88, 1.22, - 1.208, 1.08, 0.8, 0.316, -0.084, -0.404, -0.544, -0.616, -0.844, -0.996, - -0.96, -0.632, -0.28, -0.096, 0.076, 0.12, 0.264, 0.576, 0.952, 1.128, 1.192, - 1.008, 0.608, 0.148, -0.164, -0.42, -0.572, -0.684, -0.852, -0.956, -0.76, - -0.596, -0.336, -0.22, -0.08, 0.104, 0.536, 1, 1.124, 1.12, 1.028, 0.716, - 0.288, -0.02, -0.288, -0.468, -0.68, -0.94, -1.148, -1.116, -0.692, -0.292, - -0.072, -0.052, 0.04, 0.336, 0.8, 1.06, 1.036, 0.748, 0.368, -0.02, -0.308, + 1.396, 1.324, 1.044, 0.564, 0.164, -0.164, -0.396, -0.564, -0.652, + -0.892, -1.004, -0.796, -0.512, -0.328, -0.28, -0.176, 0.064, 0.496, + 0.88, 1.22, 1.208, 1.08, 0.8, 0.316, -0.084, -0.404, -0.544, -0.616, + -0.844, -0.996, -0.96, -0.632, -0.28, -0.096, 0.076, 0.12, 0.264, + 0.576, 0.952, 1.128, 1.192, 1.008, 0.608, 0.148, -0.164, -0.42, + -0.572, -0.684, -0.852, -0.956, -0.76, -0.596, -0.336, -0.22, -0.08, + 0.104, 0.536, 1, 1.124, 1.12, 1.028, 0.716, 0.288, -0.02, -0.288, + -0.468, -0.68, -0.94, -1.148, -1.116, -0.692, -0.292, -0.072, + -0.052, 0.04, 0.336, 0.8, 1.06, 1.036, 0.748, 0.368, -0.02, -0.308, -0.496, -0.672, -0.804, -0.984, -0.864, -0.544, -0.2 ] } @@ -121,37 +132,41 @@ "ID": 1718706779890, "data": { "x": [ - -1.548, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.484, -0.74, - -0.032, 0.68, 1.348, 1.836, 2.04, 1.992, 1.532, 0.892, 0.192, -0.668, -1.624, - -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.516, -0.944, -0.368, - 0.308, 0.94, 1.572, 2.04, 2.04, 1.688, 1.068, 0.444, -0.396, -1.288, -2.04, - -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.748, -1.164, -0.664, -0.16, - 0.368, 0.788, 1.16, 1.5, 1.608, 1.38, 0.828, 0.272, -0.352, -1.188, -2.04, - -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.684, -1.044, -0.56, 0.024, 0.768, - 1.476, 1.768, 1.728, 1.38, 0.768, 0.336, -0.392, -1.576, -2.04, -2.04, -2.04, - -2.04, -2.04, -2.04, -2.04, -1.476, -0.944 + -1.548, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.484, + -0.74, -0.032, 0.68, 1.348, 1.836, 2.04, 1.992, 1.532, 0.892, 0.192, + -0.668, -1.624, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, + -1.516, -0.944, -0.368, 0.308, 0.94, 1.572, 2.04, 2.04, 1.688, + 1.068, 0.444, -0.396, -1.288, -2.04, -2.04, -2.04, -2.04, -2.04, + -2.04, -2.04, -1.748, -1.164, -0.664, -0.16, 0.368, 0.788, 1.16, + 1.5, 1.608, 1.38, 0.828, 0.272, -0.352, -1.188, -2.04, -2.04, -2.04, + -2.04, -2.04, -2.04, -2.04, -1.684, -1.044, -0.56, 0.024, 0.768, + 1.476, 1.768, 1.728, 1.38, 0.768, 0.336, -0.392, -1.576, -2.04, + -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.476, -0.944 ], "y": [ - 0.588, 0.692, 0.696, 0.708, 0.804, 0.704, 0.58, 0.496, 0.36, 0.248, 0.096, - -0.12, -0.384, -0.58, -0.732, -0.7, -0.464, -0.124, 0.144, 0.32, 0.4, 0.536, - 0.652, 0.632, 0.628, 0.58, 0.508, 0.404, 0.3, 0.224, 0.108, -0.088, -0.232, - -0.436, -0.72, -0.72, -0.532, -0.236, 0.008, 0.272, 0.412, 0.396, 0.416, - 0.516, 0.54, 0.484, 0.404, 0.232, 0.104, 0.048, -0.024, -0.108, -0.216, - -0.264, -0.348, -0.472, -0.556, -0.496, -0.308, -0.168, -0.048, 0.056, 0.236, - 0.204, 0.292, 0.388, 0.356, 0.324, 0.176, 0.076, 0.012, 0.008, -0.032, -0.196, - -0.444, -0.572, -0.544, -0.408, -0.156, -0.044, 0.1, 0.352, 0.212, 0.1, 0.028, - 0.036, 0.092, 0.108, 0.012, -0.072, -0.112 + 0.588, 0.692, 0.696, 0.708, 0.804, 0.704, 0.58, 0.496, 0.36, 0.248, + 0.096, -0.12, -0.384, -0.58, -0.732, -0.7, -0.464, -0.124, 0.144, + 0.32, 0.4, 0.536, 0.652, 0.632, 0.628, 0.58, 0.508, 0.404, 0.3, + 0.224, 0.108, -0.088, -0.232, -0.436, -0.72, -0.72, -0.532, -0.236, + 0.008, 0.272, 0.412, 0.396, 0.416, 0.516, 0.54, 0.484, 0.404, 0.232, + 0.104, 0.048, -0.024, -0.108, -0.216, -0.264, -0.348, -0.472, + -0.556, -0.496, -0.308, -0.168, -0.048, 0.056, 0.236, 0.204, 0.292, + 0.388, 0.356, 0.324, 0.176, 0.076, 0.012, 0.008, -0.032, -0.196, + -0.444, -0.572, -0.544, -0.408, -0.156, -0.044, 0.1, 0.352, 0.212, + 0.1, 0.028, 0.036, 0.092, 0.108, 0.012, -0.072, -0.112 ], "z": [ - -0.832, -0.824, -0.684, -0.528, -0.164, 0.232, 0.268, -0.156, -0.62, -0.94, - -0.916, -0.732, -0.428, -0.128, 0.06, 0.26, 0.38, 0.248, -0.052, -0.464, - -0.716, -0.932, -0.736, -0.372, 0.008, 0.252, 0.224, -0.156, -0.484, -0.712, - -0.764, -0.632, -0.392, -0.088, 0.016, 0.104, 0.208, 0.28, 0.14, -0.324, - -0.66, -0.78, -0.572, -0.18, 0.004, 0.04, 0.02, -0.1, -0.344, -0.54, -0.668, - -0.676, -0.508, -0.292, -0.084, 0.052, 0.024, 0.128, 0.148, 0.2, -0.116, - -0.52, -0.62, -0.408, 0.024, 0.328, 0.388, 0.232, -0.116, -0.444, -0.564, - -0.648, -0.664, -0.592, -0.464, -0.2, 0.148, 0.492, 0.664, 0.528, 0.188, - -0.332, -0.632, -0.64, -0.468, -0.252, -0.02, 0.132, 0.028, -0.18, -0.432 + -0.832, -0.824, -0.684, -0.528, -0.164, 0.232, 0.268, -0.156, -0.62, + -0.94, -0.916, -0.732, -0.428, -0.128, 0.06, 0.26, 0.38, 0.248, + -0.052, -0.464, -0.716, -0.932, -0.736, -0.372, 0.008, 0.252, 0.224, + -0.156, -0.484, -0.712, -0.764, -0.632, -0.392, -0.088, 0.016, + 0.104, 0.208, 0.28, 0.14, -0.324, -0.66, -0.78, -0.572, -0.18, + 0.004, 0.04, 0.02, -0.1, -0.344, -0.54, -0.668, -0.676, -0.508, + -0.292, -0.084, 0.052, 0.024, 0.128, 0.148, 0.2, -0.116, -0.52, + -0.62, -0.408, 0.024, 0.328, 0.388, 0.232, -0.116, -0.444, -0.564, + -0.648, -0.664, -0.592, -0.464, -0.2, 0.148, 0.492, 0.664, 0.528, + 0.188, -0.332, -0.632, -0.64, -0.468, -0.252, -0.02, 0.132, 0.028, + -0.18, -0.432 ] } }, @@ -159,38 +174,41 @@ "ID": 1718706773634, "data": { "x": [ - -0.892, -0.768, -0.66, -0.468, -0.236, -0.028, 0.168, 0.56, 1.188, 1.368, - 0.772, 0.044, -0.28, -0.448, -0.552, -0.8, -1.128, -1.216, -1.164, -0.964, - -0.672, -0.404, -0.156, 0.028, 0.276, 0.704, 1.336, 1.444, 0.784, 0.096, - -0.236, -0.408, -0.624, -1.048, -1.364, -1.356, -1.224, -1.016, -0.736, - -0.424, -0.188, 0.032, 0.216, 0.448, 0.804, 1.252, 1.068, 0.544, 0.1, -0.128, - -0.324, -0.712, -1.136, -1.492, -1.496, -1.224, -0.792, -0.436, -0.2, 0.048, - 0.268, 0.66, 1.092, 1.38, 1.036, 0.352, -0.088, -0.32, -0.516, -0.876, -1.232, - -1.28, -1.232, -1.016, -0.732, -0.432, -0.212, 0.036, 0.296, 0.788, 1.2, + -0.892, -0.768, -0.66, -0.468, -0.236, -0.028, 0.168, 0.56, 1.188, + 1.368, 0.772, 0.044, -0.28, -0.448, -0.552, -0.8, -1.128, -1.216, + -1.164, -0.964, -0.672, -0.404, -0.156, 0.028, 0.276, 0.704, 1.336, + 1.444, 0.784, 0.096, -0.236, -0.408, -0.624, -1.048, -1.364, -1.356, + -1.224, -1.016, -0.736, -0.424, -0.188, 0.032, 0.216, 0.448, 0.804, + 1.252, 1.068, 0.544, 0.1, -0.128, -0.324, -0.712, -1.136, -1.492, + -1.496, -1.224, -0.792, -0.436, -0.2, 0.048, 0.268, 0.66, 1.092, + 1.38, 1.036, 0.352, -0.088, -0.32, -0.516, -0.876, -1.232, -1.28, + -1.232, -1.016, -0.732, -0.432, -0.212, 0.036, 0.296, 0.788, 1.2, 1.192, 0.668, 0.068, -0.224, -0.464, -0.712, -0.916, -1.096, -1.132 ], "y": [ - -2.04, -2.04, -2.04, -1.98, -1.152, -0.24, 0.632, 1.668, 2.04, 2.04, 2.04, - 1.408, 0.456, -0.696, -1.936, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, - -1.556, -0.8, -0.112, 0.664, 1.672, 2.04, 2.04, 2.04, 1.104, 0.228, -0.928, - -2.032, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.704, -1.04, -0.416, - 0.284, 1.172, 1.904, 2.04, 2.04, 1.616, 0.724, -0.028, -1.208, -2.04, -2.04, - -2.04, -2.04, -2.04, -2.04, -1.924, -1.132, -0.32, 0.432, 1.292, 2.04, 2.04, - 2.04, 1.516, 0.7, -0.328, -1.54, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, - -1.92, -1.044, -0.168, 0.744, 1.812, 2.04, 2.04, 2.024, 1.124, 0.34, -0.54, + -2.04, -2.04, -2.04, -1.98, -1.152, -0.24, 0.632, 1.668, 2.04, 2.04, + 2.04, 1.408, 0.456, -0.696, -1.936, -2.04, -2.04, -2.04, -2.04, + -2.04, -2.04, -1.556, -0.8, -0.112, 0.664, 1.672, 2.04, 2.04, 2.04, + 1.104, 0.228, -0.928, -2.032, -2.04, -2.04, -2.04, -2.04, -2.04, + -2.04, -1.704, -1.04, -0.416, 0.284, 1.172, 1.904, 2.04, 2.04, + 1.616, 0.724, -0.028, -1.208, -2.04, -2.04, -2.04, -2.04, -2.04, + -2.04, -1.924, -1.132, -0.32, 0.432, 1.292, 2.04, 2.04, 2.04, 1.516, + 0.7, -0.328, -1.54, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.92, + -1.044, -0.168, 0.744, 1.812, 2.04, 2.04, 2.024, 1.124, 0.34, -0.54, -1.588, -2.04, -2.04, -2.04 ], "z": [ - 0.532, 0.264, -0.124, -0.568, -0.896, -0.932, -0.804, -0.712, -0.792, -0.724, - -0.376, -0.108, -0.356, -0.676, -0.76, -0.472, 0.228, 0.66, 0.732, 0.388, - -0.172, -0.628, -0.816, -0.8, -0.712, -0.732, -0.928, -0.856, -0.484, -0.288, - -0.428, -0.728, -0.696, -0.26, 0.312, 0.732, 0.744, 0.42, -0.092, -0.468, - -0.852, -0.928, -0.884, -0.712, -0.64, -0.588, -0.38, -0.18, -0.188, -0.476, - -0.748, -0.56, -0.016, 0.66, 1.004, 0.716, 0.012, -0.556, -0.824, -0.936, - -0.852, -0.768, -0.744, -0.676, -0.432, -0.096, -0.208, -0.584, -0.872, - -0.624, -0.108, 0.268, 0.432, 0.232, -0.232, -0.668, -0.916, -0.996, -0.944, - -0.828, -0.596, -0.304, 0.024, 0.108, -0.204, -0.544, -0.704, -0.704, -0.5, - -0.284 + 0.532, 0.264, -0.124, -0.568, -0.896, -0.932, -0.804, -0.712, + -0.792, -0.724, -0.376, -0.108, -0.356, -0.676, -0.76, -0.472, + 0.228, 0.66, 0.732, 0.388, -0.172, -0.628, -0.816, -0.8, -0.712, + -0.732, -0.928, -0.856, -0.484, -0.288, -0.428, -0.728, -0.696, + -0.26, 0.312, 0.732, 0.744, 0.42, -0.092, -0.468, -0.852, -0.928, + -0.884, -0.712, -0.64, -0.588, -0.38, -0.18, -0.188, -0.476, -0.748, + -0.56, -0.016, 0.66, 1.004, 0.716, 0.012, -0.556, -0.824, -0.936, + -0.852, -0.768, -0.744, -0.676, -0.432, -0.096, -0.208, -0.584, + -0.872, -0.624, -0.108, 0.268, 0.432, 0.232, -0.232, -0.668, -0.916, + -0.996, -0.944, -0.828, -0.596, -0.304, 0.024, 0.108, -0.204, + -0.544, -0.704, -0.704, -0.5, -0.284 ] } }, @@ -198,38 +216,42 @@ "ID": 1718706765639, "data": { "x": [ - -0.228, -0.092, -0.24, -0.212, -0.136, -0.076, -0.08, -0.212, -0.316, -0.444, - -0.696, -0.808, -0.86, -0.704, -0.46, -0.368, -0.268, -0.216, -0.16, -0.108, - -0.136, -0.192, -0.272, -0.24, -0.212, -0.108, -0.108, -0.324, -0.548, -0.764, - -0.916, -0.948, -0.836, -0.648, -0.516, -0.46, -0.38, -0.308, -0.296, -0.252, - -0.172, -0.144, -0.072, 0.008, 0.052, 0.036, -0.096, -0.304, -0.596, -0.748, - -0.96, -0.884, -0.656, -0.42, -0.32, -0.308, -0.296, -0.208, -0.212, -0.196, - -0.156, -0.184, -0.232, -0.276, -0.304, -0.288, -0.412, -0.444, -0.424, - -0.548, -0.488, -0.436, -0.304, -0.252, -0.328, -0.312, -0.352, -0.312, -0.28, - -0.312, -0.268, -0.336, -0.292, -0.244, -0.188, -0.24, -0.32, -0.636, -0.564, - -0.672, -0.52 + -0.228, -0.092, -0.24, -0.212, -0.136, -0.076, -0.08, -0.212, + -0.316, -0.444, -0.696, -0.808, -0.86, -0.704, -0.46, -0.368, + -0.268, -0.216, -0.16, -0.108, -0.136, -0.192, -0.272, -0.24, + -0.212, -0.108, -0.108, -0.324, -0.548, -0.764, -0.916, -0.948, + -0.836, -0.648, -0.516, -0.46, -0.38, -0.308, -0.296, -0.252, + -0.172, -0.144, -0.072, 0.008, 0.052, 0.036, -0.096, -0.304, -0.596, + -0.748, -0.96, -0.884, -0.656, -0.42, -0.32, -0.308, -0.296, -0.208, + -0.212, -0.196, -0.156, -0.184, -0.232, -0.276, -0.304, -0.288, + -0.412, -0.444, -0.424, -0.548, -0.488, -0.436, -0.304, -0.252, + -0.328, -0.312, -0.352, -0.312, -0.28, -0.312, -0.268, -0.336, + -0.292, -0.244, -0.188, -0.24, -0.32, -0.636, -0.564, -0.672, -0.52 ], "y": [ - 0.156, 0.344, 0.556, 0.664, 0.652, 0.508, 0.236, -0.128, -0.548, -1.116, - -1.524, -1.8, -1.712, -1.388, -1.22, -1.036, -0.796, -0.548, -0.284, 0.016, - 0.416, 0.708, 0.864, 0.816, 0.612, 0.304, -0.028, -0.492, -1.044, -1.504, - -1.704, -1.764, -1.592, -1.328, -1.112, -0.9, -0.684, -0.488, -0.284, -0.04, - 0.284, 0.604, 0.804, 0.796, 0.588, 0.28, -0.128, -0.704, -1.212, -1.608, - -1.796, -1.776, -1.536, -1.248, -1.012, -0.748, -0.52, -0.324, -0.072, 0.232, - 0.472, 0.6, 0.672, 0.596, 0.42, 0.132, -0.296, -0.904, -1.492, -1.872, -2.008, - -1.804, -1.52, -1.284, -1.06, -0.864, -0.644, -0.448, -0.148, 0.208, 0.504, - 0.716, 0.788, 0.72, 0.46, 0.124, -0.38, -0.9, -1.348, -1.612, -1.752 + 0.156, 0.344, 0.556, 0.664, 0.652, 0.508, 0.236, -0.128, -0.548, + -1.116, -1.524, -1.8, -1.712, -1.388, -1.22, -1.036, -0.796, -0.548, + -0.284, 0.016, 0.416, 0.708, 0.864, 0.816, 0.612, 0.304, -0.028, + -0.492, -1.044, -1.504, -1.704, -1.764, -1.592, -1.328, -1.112, + -0.9, -0.684, -0.488, -0.284, -0.04, 0.284, 0.604, 0.804, 0.796, + 0.588, 0.28, -0.128, -0.704, -1.212, -1.608, -1.796, -1.776, -1.536, + -1.248, -1.012, -0.748, -0.52, -0.324, -0.072, 0.232, 0.472, 0.6, + 0.672, 0.596, 0.42, 0.132, -0.296, -0.904, -1.492, -1.872, -2.008, + -1.804, -1.52, -1.284, -1.06, -0.864, -0.644, -0.448, -0.148, 0.208, + 0.504, 0.716, 0.788, 0.72, 0.46, 0.124, -0.38, -0.9, -1.348, -1.612, + -1.752 ], "z": [ - 0.804, 1.304, 1.548, 1.548, 1.308, 0.928, 0.388, -0.352, -1.12, -1.984, -2.04, - -2.04, -2.04, -2.04, -2.04, -2.008, -1.324, -0.656, -0.016, 0.58, 1.032, 1.44, - 1.764, 1.648, 1.28, 0.764, 0.08, -0.788, -1.712, -2.04, -2.04, -2.04, -2.04, - -2.04, -2.04, -1.836, -1.172, -0.472, 0.104, 0.592, 1.116, 1.676, 1.86, 1.72, - 1.248, 0.568, -0.144, -1.124, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, - -1.976, -1.42, -0.844, -0.188, 0.432, 0.872, 1.264, 1.5, 1.46, 1.112, 0.66, - 0.164, -0.528, -1.348, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.852, - -1.204, -0.708, -0.148, 0.456, 1.052, 1.464, 1.624, 1.476, 1.176, 0.716, 0.12, - -0.54, -1.46, -2.04, -2.04, -2.04 + 0.804, 1.304, 1.548, 1.548, 1.308, 0.928, 0.388, -0.352, -1.12, + -1.984, -2.04, -2.04, -2.04, -2.04, -2.04, -2.008, -1.324, -0.656, + -0.016, 0.58, 1.032, 1.44, 1.764, 1.648, 1.28, 0.764, 0.08, -0.788, + -1.712, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.836, -1.172, + -0.472, 0.104, 0.592, 1.116, 1.676, 1.86, 1.72, 1.248, 0.568, + -0.144, -1.124, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, -1.976, + -1.42, -0.844, -0.188, 0.432, 0.872, 1.264, 1.5, 1.46, 1.112, 0.66, + 0.164, -0.528, -1.348, -2.04, -2.04, -2.04, -2.04, -2.04, -2.04, + -1.852, -1.204, -0.708, -0.148, 0.456, 1.052, 1.464, 1.624, 1.476, + 1.176, 0.716, 0.12, -0.54, -1.46, -2.04, -2.04, -2.04 ] } }, @@ -237,38 +259,42 @@ "ID": 1718706760274, "data": { "x": [ - -0.2, -0.024, 0.12, 0.308, 0.468, 0.468, 0.292, 0.004, -0.26, -0.404, -0.484, - -0.6, -0.68, -0.712, -0.712, -0.632, -0.524, -0.408, -0.18, 0.004, 0.176, - 0.304, 0.416, 0.404, 0.28, 0.092, -0.14, -0.296, -0.444, -0.596, -0.668, -0.7, - -0.652, -0.592, -0.52, -0.448, -0.256, -0.092, 0.056, 0.212, 0.432, 0.592, - 0.432, 0.084, -0.2, -0.376, -0.504, -0.628, -0.772, -0.752, -0.76, -0.676, - -0.612, -0.492, -0.3, -0.104, 0.084, 0.264, 0.464, 0.524, 0.336, 0.024, - -0.236, -0.372, -0.528, -0.636, -0.74, -0.784, -0.788, -0.672, -0.524, -0.476, - -0.276, -0.156, 0.036, 0.24, 0.392, 0.476, 0.276, 0.016, -0.236, -0.364, - -0.476, -0.624, -0.752, -0.812, -0.84, -0.72, -0.636, -0.524, -0.252 + -0.2, -0.024, 0.12, 0.308, 0.468, 0.468, 0.292, 0.004, -0.26, + -0.404, -0.484, -0.6, -0.68, -0.712, -0.712, -0.632, -0.524, -0.408, + -0.18, 0.004, 0.176, 0.304, 0.416, 0.404, 0.28, 0.092, -0.14, + -0.296, -0.444, -0.596, -0.668, -0.7, -0.652, -0.592, -0.52, -0.448, + -0.256, -0.092, 0.056, 0.212, 0.432, 0.592, 0.432, 0.084, -0.2, + -0.376, -0.504, -0.628, -0.772, -0.752, -0.76, -0.676, -0.612, + -0.492, -0.3, -0.104, 0.084, 0.264, 0.464, 0.524, 0.336, 0.024, + -0.236, -0.372, -0.528, -0.636, -0.74, -0.784, -0.788, -0.672, + -0.524, -0.476, -0.276, -0.156, 0.036, 0.24, 0.392, 0.476, 0.276, + 0.016, -0.236, -0.364, -0.476, -0.624, -0.752, -0.812, -0.84, -0.72, + -0.636, -0.524, -0.252 ], "y": [ - -0.528, -0.732, -0.76, -0.728, -0.64, -0.552, -0.476, -0.412, -0.332, -0.276, - -0.26, -0.136, 0.188, 0.536, 0.688, 0.544, 0.192, -0.172, -0.476, -0.664, - -0.712, -0.6, -0.492, -0.364, -0.32, -0.276, -0.232, -0.192, -0.16, -0.012, - 0.18, 0.444, 0.564, 0.516, 0.276, -0.068, -0.372, -0.516, -0.54, -0.512, - -0.48, -0.508, -0.444, -0.36, -0.284, -0.284, -0.28, -0.176, 0.052, 0.344, - 0.544, 0.444, 0.184, -0.16, -0.388, -0.516, -0.596, -0.552, -0.444, -0.42, - -0.316, -0.228, -0.188, -0.228, -0.172, -0.064, 0.076, 0.316, 0.536, 0.496, - 0.236, -0.092, -0.376, -0.516, -0.564, -0.524, -0.456, -0.488, -0.396, -0.328, - -0.344, -0.396, -0.332, -0.164, 0.08, 0.312, 0.44, 0.296, 0.004, -0.304, - -0.524 + -0.528, -0.732, -0.76, -0.728, -0.64, -0.552, -0.476, -0.412, + -0.332, -0.276, -0.26, -0.136, 0.188, 0.536, 0.688, 0.544, 0.192, + -0.172, -0.476, -0.664, -0.712, -0.6, -0.492, -0.364, -0.32, -0.276, + -0.232, -0.192, -0.16, -0.012, 0.18, 0.444, 0.564, 0.516, 0.276, + -0.068, -0.372, -0.516, -0.54, -0.512, -0.48, -0.508, -0.444, -0.36, + -0.284, -0.284, -0.28, -0.176, 0.052, 0.344, 0.544, 0.444, 0.184, + -0.16, -0.388, -0.516, -0.596, -0.552, -0.444, -0.42, -0.316, + -0.228, -0.188, -0.228, -0.172, -0.064, 0.076, 0.316, 0.536, 0.496, + 0.236, -0.092, -0.376, -0.516, -0.564, -0.524, -0.456, -0.488, + -0.396, -0.328, -0.344, -0.396, -0.332, -0.164, 0.08, 0.312, 0.44, + 0.296, 0.004, -0.304, -0.524 ], "z": [ - 1.668, 0.78, -0.188, -1.188, -1.884, -2.04, -1.928, -1.268, -0.472, 0.396, - 1.224, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.5, 0.64, -0.212, -0.976, - -1.52, -1.804, -1.704, -1.184, -0.496, 0.236, 1.08, 2.008, 2.04, 2.04, 2.04, - 2.04, 2.04, 2.04, 1.644, 0.88, 0.088, -0.884, -1.732, -2.04, -1.964, -1.328, - -0.496, 0.332, 1.204, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.472, 0.708, - -0.176, -0.996, -1.64, -1.944, -1.676, -1.064, -0.396, 0.332, 1.18, 2.04, - 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.556, 0.744, -0.108, -0.96, -1.608, - -1.956, -1.732, -1.116, -0.348, 0.552, 1.556, 2.04, 2.04, 2.04, 2.04, 2.04, - 2.04, 1.9, 1.18 + 1.668, 0.78, -0.188, -1.188, -1.884, -2.04, -1.928, -1.268, -0.472, + 0.396, 1.224, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.5, 0.64, + -0.212, -0.976, -1.52, -1.804, -1.704, -1.184, -0.496, 0.236, 1.08, + 2.008, 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.644, 0.88, 0.088, + -0.884, -1.732, -2.04, -1.964, -1.328, -0.496, 0.332, 1.204, 2.04, + 2.04, 2.04, 2.04, 2.04, 2.04, 2.04, 1.472, 0.708, -0.176, -0.996, + -1.64, -1.944, -1.676, -1.064, -0.396, 0.332, 1.18, 2.04, 2.04, + 2.04, 2.04, 2.04, 2.04, 2.04, 1.556, 0.744, -0.108, -0.96, -1.608, + -1.956, -1.732, -1.116, -0.348, 0.552, 1.556, 2.04, 2.04, 2.04, + 2.04, 2.04, 2.04, 1.9, 1.18 ] } } @@ -288,37 +314,42 @@ "ID": 1718706751151, "data": { "x": [ - 0.444, 0.416, 0.412, 0.416, 0.44, 0.428, 0.44, 0.404, 0.432, 0.432, 0.428, - 0.424, 0.416, 0.412, 0.416, 0.424, 0.436, 0.424, 0.412, 0.42, 0.42, 0.428, - 0.428, 0.416, 0.412, 0.456, 0.424, 0.412, 0.404, 0.412, 0.416, 0.428, 0.436, - 0.412, 0.412, 0.396, 0.42, 0.436, 0.42, 0.416, 0.408, 0.4, 0.388, 0.404, - 0.428, 0.408, 0.404, 0.408, 0.42, 0.44, 0.42, 0.4, 0.396, 0.432, 0.424, 0.396, - 0.4, 0.392, 0.392, 0.424, 0.416, 0.408, 0.412, 0.412, 0.412, 0.408, 0.404, - 0.42, 0.424, 0.404, 0.4, 0.416, 0.412, 0.408, 0.388, 0.396, 0.408, 0.392, - 0.396, 0.404, 0.416, 0.396, 0.408, 0.404, 0.42, 0.408, 0.396, 0.38, 0.392, - 0.4, 0.42 + 0.444, 0.416, 0.412, 0.416, 0.44, 0.428, 0.44, 0.404, 0.432, 0.432, + 0.428, 0.424, 0.416, 0.412, 0.416, 0.424, 0.436, 0.424, 0.412, 0.42, + 0.42, 0.428, 0.428, 0.416, 0.412, 0.456, 0.424, 0.412, 0.404, 0.412, + 0.416, 0.428, 0.436, 0.412, 0.412, 0.396, 0.42, 0.436, 0.42, 0.416, + 0.408, 0.4, 0.388, 0.404, 0.428, 0.408, 0.404, 0.408, 0.42, 0.44, + 0.42, 0.4, 0.396, 0.432, 0.424, 0.396, 0.4, 0.392, 0.392, 0.424, + 0.416, 0.408, 0.412, 0.412, 0.412, 0.408, 0.404, 0.42, 0.424, 0.404, + 0.4, 0.416, 0.412, 0.408, 0.388, 0.396, 0.408, 0.392, 0.396, 0.404, + 0.416, 0.396, 0.408, 0.404, 0.42, 0.408, 0.396, 0.38, 0.392, 0.4, + 0.42 ], "y": [ - -0.828, -0.82, -0.82, -0.808, -0.804, -0.804, -0.804, -0.828, -0.816, -0.808, - -0.816, -0.808, -0.816, -0.82, -0.816, -0.82, -0.816, -0.82, -0.816, -0.816, - -0.808, -0.808, -0.824, -0.816, -0.824, -0.864, -0.808, -0.812, -0.824, - -0.824, -0.824, -0.812, -0.812, -0.82, -0.824, -0.836, -0.84, -0.816, -0.812, - -0.816, -0.812, -0.804, -0.804, -0.804, -0.836, -0.824, -0.824, -0.828, - -0.824, -0.828, -0.816, -0.816, -0.828, -0.824, -0.816, -0.804, -0.8, -0.804, - -0.812, -0.816, -0.82, -0.816, -0.824, -0.824, -0.824, -0.82, -0.824, -0.82, - -0.828, -0.824, -0.828, -0.836, -0.816, -0.836, -0.812, -0.816, -0.82, -0.816, - -0.812, -0.816, -0.816, -0.828, -0.828, -0.824, -0.824, -0.816, -0.816, - -0.812, -0.82, -0.812, -0.824 + -0.828, -0.82, -0.82, -0.808, -0.804, -0.804, -0.804, -0.828, + -0.816, -0.808, -0.816, -0.808, -0.816, -0.82, -0.816, -0.82, + -0.816, -0.82, -0.816, -0.816, -0.808, -0.808, -0.824, -0.816, + -0.824, -0.864, -0.808, -0.812, -0.824, -0.824, -0.824, -0.812, + -0.812, -0.82, -0.824, -0.836, -0.84, -0.816, -0.812, -0.816, + -0.812, -0.804, -0.804, -0.804, -0.836, -0.824, -0.824, -0.828, + -0.824, -0.828, -0.816, -0.816, -0.828, -0.824, -0.816, -0.804, + -0.8, -0.804, -0.812, -0.816, -0.82, -0.816, -0.824, -0.824, -0.824, + -0.82, -0.824, -0.82, -0.828, -0.824, -0.828, -0.836, -0.816, + -0.836, -0.812, -0.816, -0.82, -0.816, -0.812, -0.816, -0.816, + -0.828, -0.828, -0.824, -0.824, -0.816, -0.816, -0.812, -0.82, + -0.812, -0.824 ], "z": [ - 0.384, 0.384, 0.384, 0.38, 0.392, 0.376, 0.376, 0.372, 0.368, 0.384, 0.384, - 0.392, 0.392, 0.384, 0.376, 0.372, 0.388, 0.4, 0.388, 0.376, 0.38, 0.384, - 0.392, 0.384, 0.376, 0.36, 0.408, 0.408, 0.38, 0.368, 0.364, 0.388, 0.4, - 0.392, 0.4, 0.38, 0.376, 0.392, 0.404, 0.4, 0.4, 0.372, 0.372, 0.38, 0.388, - 0.372, 0.376, 0.38, 0.392, 0.404, 0.404, 0.4, 0.376, 0.384, 0.4, 0.38, 0.388, - 0.372, 0.38, 0.4, 0.396, 0.4, 0.392, 0.396, 0.392, 0.376, 0.384, 0.404, 0.408, - 0.392, 0.384, 0.388, 0.388, 0.404, 0.396, 0.408, 0.396, 0.388, 0.388, 0.408, - 0.404, 0.4, 0.392, 0.396, 0.392, 0.408, 0.4, 0.392, 0.396, 0.392, 0.408 + 0.384, 0.384, 0.384, 0.38, 0.392, 0.376, 0.376, 0.372, 0.368, 0.384, + 0.384, 0.392, 0.392, 0.384, 0.376, 0.372, 0.388, 0.4, 0.388, 0.376, + 0.38, 0.384, 0.392, 0.384, 0.376, 0.36, 0.408, 0.408, 0.38, 0.368, + 0.364, 0.388, 0.4, 0.392, 0.4, 0.38, 0.376, 0.392, 0.404, 0.4, 0.4, + 0.372, 0.372, 0.38, 0.388, 0.372, 0.376, 0.38, 0.392, 0.404, 0.404, + 0.4, 0.376, 0.384, 0.4, 0.38, 0.388, 0.372, 0.38, 0.4, 0.396, 0.4, + 0.392, 0.396, 0.392, 0.376, 0.384, 0.404, 0.408, 0.392, 0.384, + 0.388, 0.388, 0.404, 0.396, 0.408, 0.396, 0.388, 0.388, 0.408, + 0.404, 0.4, 0.392, 0.396, 0.392, 0.408, 0.4, 0.392, 0.396, 0.392, + 0.408 ] } }, @@ -326,38 +357,41 @@ "ID": 1718706742225, "data": { "x": [ - -1.024, -1.004, -0.976, -0.98, -1.024, -1.04, -1.02, -0.992, -0.98, -1.008, - -1.032, -1.024, -1.012, -1.004, -1.008, -1.016, -1.04, -1.012, -0.984, -0.988, - -1.008, -1.004, -0.996, -1.012, -0.988, -0.976, -0.996, -1.004, -1.016, - -0.984, -0.972, -0.984, -1.008, -1.032, -1.036, -1, -0.992, -1.032, -1.036, - -1.036, -0.996, -0.984, -0.996, -1.008, -1.032, -1.024, -0.984, -0.984, - -1.008, -1.024, -1.036, -1.012, -0.992, -1.024, -1.008, -0.976, -1.016, - -0.984, -0.984, -1.012, -1.036, -1.016, -1.016, -1, -1.016, -1.036, -1.004, - -0.996, -0.988, -1.004, -1.016, -1.012, -1.008, -0.984, -1, -1.016, -1.032, - -0.992, -0.984, -1.012, -1.024, -1.036, -1.004, -0.98, -0.992, -1.032, -1.016, - -0.996, -0.988, -1.016, -1.036 + -1.024, -1.004, -0.976, -0.98, -1.024, -1.04, -1.02, -0.992, -0.98, + -1.008, -1.032, -1.024, -1.012, -1.004, -1.008, -1.016, -1.04, + -1.012, -0.984, -0.988, -1.008, -1.004, -0.996, -1.012, -0.988, + -0.976, -0.996, -1.004, -1.016, -0.984, -0.972, -0.984, -1.008, + -1.032, -1.036, -1, -0.992, -1.032, -1.036, -1.036, -0.996, -0.984, + -0.996, -1.008, -1.032, -1.024, -0.984, -0.984, -1.008, -1.024, + -1.036, -1.012, -0.992, -1.024, -1.008, -0.976, -1.016, -0.984, + -0.984, -1.012, -1.036, -1.016, -1.016, -1, -1.016, -1.036, -1.004, + -0.996, -0.988, -1.004, -1.016, -1.012, -1.008, -0.984, -1, -1.016, + -1.032, -0.992, -0.984, -1.012, -1.024, -1.036, -1.004, -0.98, + -0.992, -1.032, -1.016, -0.996, -0.988, -1.016, -1.036 ], "y": [ - 0.028, 0.02, 0.008, 0, 0.016, 0.02, 0.032, 0.02, 0.016, 0.016, 0.032, 0.04, - 0.056, 0.056, 0.036, 0.024, 0.012, 0.004, 0.004, 0.004, 0.008, 0.012, 0.016, - 0.02, 0.02, 0.02, 0.02, 0.016, 0.008, 0.008, 0, 0, 0.016, 0.036, 0.032, 0.024, - 0.02, 0.028, 0.028, 0.028, 0.012, 0.02, 0.032, 0.012, 0.008, 0.012, 0.016, - 0.004, 0.012, 0.032, 0.028, 0.032, 0.008, 0.008, 0.012, 0.004, -0.004, 0.012, - 0.008, 0.012, 0.02, 0.028, 0.016, 0.02, 0.004, 0.02, 0.036, 0.048, 0.036, - 0.012, 0.024, 0.032, 0.028, 0.012, 0, 0.004, 0.012, 0.008, 0, 0.012, 0.02, + 0.028, 0.02, 0.008, 0, 0.016, 0.02, 0.032, 0.02, 0.016, 0.016, + 0.032, 0.04, 0.056, 0.056, 0.036, 0.024, 0.012, 0.004, 0.004, 0.004, + 0.008, 0.012, 0.016, 0.02, 0.02, 0.02, 0.02, 0.016, 0.008, 0.008, 0, + 0, 0.016, 0.036, 0.032, 0.024, 0.02, 0.028, 0.028, 0.028, 0.012, + 0.02, 0.032, 0.012, 0.008, 0.012, 0.016, 0.004, 0.012, 0.032, 0.028, + 0.032, 0.008, 0.008, 0.012, 0.004, -0.004, 0.012, 0.008, 0.012, + 0.02, 0.028, 0.016, 0.02, 0.004, 0.02, 0.036, 0.048, 0.036, 0.012, + 0.024, 0.032, 0.028, 0.012, 0, 0.004, 0.012, 0.008, 0, 0.012, 0.02, 0.036, 0.028, 0.016, 0.024, 0.02, 0.012, 0.02, 0.02, 0.032, 0.024 ], "z": [ - -0.22, -0.188, -0.172, -0.172, -0.192, -0.216, -0.204, -0.2, -0.188, -0.176, - -0.172, -0.2, -0.192, -0.164, -0.156, -0.184, -0.236, -0.232, -0.224, -0.208, - -0.2, -0.184, -0.196, -0.208, -0.192, -0.196, -0.196, -0.212, -0.204, -0.196, - -0.192, -0.204, -0.208, -0.196, -0.196, -0.164, -0.14, -0.16, -0.176, -0.204, - -0.196, -0.168, -0.144, -0.156, -0.22, -0.212, -0.184, -0.168, -0.204, -0.204, - -0.2, -0.184, -0.164, -0.196, -0.184, -0.172, -0.192, -0.176, -0.152, -0.16, - -0.204, -0.192, -0.204, -0.18, -0.188, -0.208, -0.204, -0.172, -0.144, -0.16, - -0.172, -0.184, -0.18, -0.18, -0.2, -0.224, -0.22, -0.18, -0.16, -0.176, - -0.188, -0.192, -0.188, -0.168, -0.14, -0.164, -0.188, -0.188, -0.18, -0.188, - -0.184 + -0.22, -0.188, -0.172, -0.172, -0.192, -0.216, -0.204, -0.2, -0.188, + -0.176, -0.172, -0.2, -0.192, -0.164, -0.156, -0.184, -0.236, + -0.232, -0.224, -0.208, -0.2, -0.184, -0.196, -0.208, -0.192, + -0.196, -0.196, -0.212, -0.204, -0.196, -0.192, -0.204, -0.208, + -0.196, -0.196, -0.164, -0.14, -0.16, -0.176, -0.204, -0.196, + -0.168, -0.144, -0.156, -0.22, -0.212, -0.184, -0.168, -0.204, + -0.204, -0.2, -0.184, -0.164, -0.196, -0.184, -0.172, -0.192, + -0.176, -0.152, -0.16, -0.204, -0.192, -0.204, -0.18, -0.188, + -0.208, -0.204, -0.172, -0.144, -0.16, -0.172, -0.184, -0.18, -0.18, + -0.2, -0.224, -0.22, -0.18, -0.16, -0.176, -0.188, -0.192, -0.188, + -0.168, -0.14, -0.164, -0.188, -0.188, -0.18, -0.188, -0.184 ] } }, @@ -365,34 +399,38 @@ "ID": 1718706735565, "data": { "x": [ - -0.06, -0.084, -0.088, -0.096, -0.104, -0.088, -0.076, -0.092, -0.084, -0.08, - -0.076, -0.08, -0.08, -0.084, -0.084, -0.068, -0.068, -0.084, -0.088, -0.08, - -0.08, -0.092, -0.088, -0.092, -0.088, -0.092, -0.096, -0.088, -0.088, -0.092, - -0.076, -0.068, -0.072, -0.076, -0.076, -0.072, -0.068, -0.096, -0.096, -0.08, - -0.084, -0.064, -0.068, -0.084, -0.076, -0.08, -0.064, -0.068, -0.084, -0.08, - -0.06, -0.056, -0.056, -0.06, -0.08, -0.068, -0.076, -0.092, -0.084, -0.076, - -0.068, -0.072, -0.088, -0.1, -0.1, -0.084, -0.08, -0.068, -0.08, -0.084, - -0.072, -0.06, -0.06, -0.068, -0.072, -0.08, -0.092, -0.092, -0.084, -0.084, - -0.076, -0.076, -0.08, -0.08, -0.076, -0.08, -0.096, -0.108, -0.096, -0.084 + -0.06, -0.084, -0.088, -0.096, -0.104, -0.088, -0.076, -0.092, + -0.084, -0.08, -0.076, -0.08, -0.08, -0.084, -0.084, -0.068, -0.068, + -0.084, -0.088, -0.08, -0.08, -0.092, -0.088, -0.092, -0.088, + -0.092, -0.096, -0.088, -0.088, -0.092, -0.076, -0.068, -0.072, + -0.076, -0.076, -0.072, -0.068, -0.096, -0.096, -0.08, -0.084, + -0.064, -0.068, -0.084, -0.076, -0.08, -0.064, -0.068, -0.084, + -0.08, -0.06, -0.056, -0.056, -0.06, -0.08, -0.068, -0.076, -0.092, + -0.084, -0.076, -0.068, -0.072, -0.088, -0.1, -0.1, -0.084, -0.08, + -0.068, -0.08, -0.084, -0.072, -0.06, -0.06, -0.068, -0.072, -0.08, + -0.092, -0.092, -0.084, -0.084, -0.076, -0.076, -0.08, -0.08, + -0.076, -0.08, -0.096, -0.108, -0.096, -0.084 ], "y": [ - 1.012, 1.02, 1.02, 1.004, 1, 1.004, 1, 1.016, 1.02, 1.016, 1.02, 1.032, 1.048, - 1.032, 1.036, 1.008, 1.008, 1.02, 1.024, 1.02, 1.008, 1.016, 1.012, 1.012, - 1.016, 1.008, 1.012, 1.012, 1.016, 1.004, 1.004, 1.008, 1.016, 1.04, 1.02, - 1.004, 1.008, 1.056, 1.028, 1.024, 1.028, 1.012, 1.02, 1.02, 1.016, 1.008, - 0.992, 1.008, 1.02, 1.028, 1.016, 1.012, 1.02, 1.024, 1.024, 1.004, 1.012, - 1.028, 1.016, 1.008, 1.012, 1.016, 1.028, 1.024, 1.024, 0.996, 0.996, 1.02, - 1.02, 1.012, 1.004, 1.02, 1.024, 1.02, 1.012, 1.016, 1.02, 1.016, 1.028, - 1.012, 1.004, 1.012, 1.024, 1.016, 1.008, 1, 1.016, 1.028, 1.024, 1.008 + 1.012, 1.02, 1.02, 1.004, 1, 1.004, 1, 1.016, 1.02, 1.016, 1.02, + 1.032, 1.048, 1.032, 1.036, 1.008, 1.008, 1.02, 1.024, 1.02, 1.008, + 1.016, 1.012, 1.012, 1.016, 1.008, 1.012, 1.012, 1.016, 1.004, + 1.004, 1.008, 1.016, 1.04, 1.02, 1.004, 1.008, 1.056, 1.028, 1.024, + 1.028, 1.012, 1.02, 1.02, 1.016, 1.008, 0.992, 1.008, 1.02, 1.028, + 1.016, 1.012, 1.02, 1.024, 1.024, 1.004, 1.012, 1.028, 1.016, 1.008, + 1.012, 1.016, 1.028, 1.024, 1.024, 0.996, 0.996, 1.02, 1.02, 1.012, + 1.004, 1.02, 1.024, 1.02, 1.012, 1.016, 1.02, 1.016, 1.028, 1.012, + 1.004, 1.012, 1.024, 1.016, 1.008, 1, 1.016, 1.028, 1.024, 1.008 ], "z": [ - 0.076, 0.076, 0.084, 0.084, 0.076, 0.088, 0.1, 0.064, 0.064, 0.064, 0.08, - 0.096, 0.1, 0.088, 0.072, 0.08, 0.088, 0.072, 0.076, 0.084, 0.092, 0.1, 0.076, - 0.072, 0.08, 0.088, 0.08, 0.068, 0.056, 0.068, 0.08, 0.084, 0.092, 0.088, - 0.088, 0.084, 0.084, 0.06, 0.084, 0.092, 0.108, 0.1, 0.084, 0.052, 0.04, - 0.056, 0.056, 0.072, 0.08, 0.096, 0.1, 0.092, 0.096, 0.088, 0.084, 0.1, 0.096, - 0.08, 0.08, 0.072, 0.076, 0.096, 0.108, 0.092, 0.08, 0.076, 0.092, 0.096, - 0.076, 0.056, 0.072, 0.088, 0.084, 0.1, 0.084, 0.088, 0.08, 0.1, 0.096, 0.088, + 0.076, 0.076, 0.084, 0.084, 0.076, 0.088, 0.1, 0.064, 0.064, 0.064, + 0.08, 0.096, 0.1, 0.088, 0.072, 0.08, 0.088, 0.072, 0.076, 0.084, + 0.092, 0.1, 0.076, 0.072, 0.08, 0.088, 0.08, 0.068, 0.056, 0.068, + 0.08, 0.084, 0.092, 0.088, 0.088, 0.084, 0.084, 0.06, 0.084, 0.092, + 0.108, 0.1, 0.084, 0.052, 0.04, 0.056, 0.056, 0.072, 0.08, 0.096, + 0.1, 0.092, 0.096, 0.088, 0.084, 0.1, 0.096, 0.08, 0.08, 0.072, + 0.076, 0.096, 0.108, 0.092, 0.08, 0.076, 0.092, 0.096, 0.076, 0.056, + 0.072, 0.088, 0.084, 0.1, 0.084, 0.088, 0.08, 0.1, 0.096, 0.088, 0.088, 0.096, 0.1, 0.096, 0.08, 0.08, 0.088, 0.096, 0.076, 0.064 ] } @@ -401,36 +439,39 @@ "ID": 1718706729969, "data": { "x": [ - 0.992, 0.996, 0.988, 0.992, 1, 0.996, 0.956, 0.96, 0.972, 1, 0.996, 0.972, - 0.984, 1.004, 1.012, 1.02, 1, 0.984, 0.988, 0.98, 0.984, 0.972, 0.988, 0.968, - 1.028, 1.004, 1.028, 1, 0.98, 0.972, 1.012, 1.008, 0.992, 0.976, 0.992, 0.992, - 1.008, 0.988, 0.972, 0.972, 0.988, 1.008, 1, 0.956, 0.984, 1, 1, 0.996, 0.996, - 0.992, 0.996, 0.992, 0.984, 0.98, 0.988, 0.98, 1.004, 0.996, 0.964, 0.956, - 1.008, 1.016, 0.98, 0.984, 1, 1.004, 1.008, 0.984, 0.988, 0.984, 0.984, 1.016, - 0.984, 0.984, 0.984, 1, 0.992, 0.992, 0.976, 0.972, 1.016, 1, 0.992, 0.968, - 0.976, 1.012, 1.012, 0.988, 0.968, 0.968 + 0.992, 0.996, 0.988, 0.992, 1, 0.996, 0.956, 0.96, 0.972, 1, 0.996, + 0.972, 0.984, 1.004, 1.012, 1.02, 1, 0.984, 0.988, 0.98, 0.984, + 0.972, 0.988, 0.968, 1.028, 1.004, 1.028, 1, 0.98, 0.972, 1.012, + 1.008, 0.992, 0.976, 0.992, 0.992, 1.008, 0.988, 0.972, 0.972, + 0.988, 1.008, 1, 0.956, 0.984, 1, 1, 0.996, 0.996, 0.992, 0.996, + 0.992, 0.984, 0.98, 0.988, 0.98, 1.004, 0.996, 0.964, 0.956, 1.008, + 1.016, 0.98, 0.984, 1, 1.004, 1.008, 0.984, 0.988, 0.984, 0.984, + 1.016, 0.984, 0.984, 0.984, 1, 0.992, 0.992, 0.976, 0.972, 1.016, 1, + 0.992, 0.968, 0.976, 1.012, 1.012, 0.988, 0.968, 0.968 ], "y": [ - 0.012, 0.02, 0.024, 0.024, 0.02, 0.032, 0.04, 0.044, 0.04, 0.004, 0.024, - 0.008, 0.004, -0.004, 0.004, 0, 0, 0, 0.008, 0.02, 0.024, 0.004, 0.004, 0.008, - 0.048, 0.004, 0, -0.008, 0, 0.004, -0.008, 0.004, -0.004, 0.012, 0, 0, 0, - -0.008, -0.004, 0.016, 0.004, -0.004, 0, 0, 0.004, 0.012, 0.008, -0.004, 0, 0, - 0.008, -0.008, -0.008, 0.004, 0.02, 0.008, -0.008, -0.008, -0.012, 0.004, - -0.004, -0.016, -0.012, -0.004, -0.004, -0.012, -0.016, -0.004, 0.004, 0.008, - 0, -0.02, -0.032, -0.016, -0.012, -0.008, -0.004, 0, -0.02, -0.008, -0.008, - -0.008, -0.008, -0.012, 0, -0.016, -0.008, -0.012, -0.008, 0 + 0.012, 0.02, 0.024, 0.024, 0.02, 0.032, 0.04, 0.044, 0.04, 0.004, + 0.024, 0.008, 0.004, -0.004, 0.004, 0, 0, 0, 0.008, 0.02, 0.024, + 0.004, 0.004, 0.008, 0.048, 0.004, 0, -0.008, 0, 0.004, -0.008, + 0.004, -0.004, 0.012, 0, 0, 0, -0.008, -0.004, 0.016, 0.004, -0.004, + 0, 0, 0.004, 0.012, 0.008, -0.004, 0, 0, 0.008, -0.008, -0.008, + 0.004, 0.02, 0.008, -0.008, -0.008, -0.012, 0.004, -0.004, -0.016, + -0.012, -0.004, -0.004, -0.012, -0.016, -0.004, 0.004, 0.008, 0, + -0.02, -0.032, -0.016, -0.012, -0.008, -0.004, 0, -0.02, -0.008, + -0.008, -0.008, -0.008, -0.012, 0, -0.016, -0.008, -0.012, -0.008, 0 ], "z": [ - -0.16, -0.14, -0.152, -0.152, -0.156, -0.16, -0.172, -0.168, -0.16, -0.168, - -0.16, -0.164, -0.152, -0.148, -0.14, -0.14, -0.152, -0.164, -0.168, -0.164, - -0.164, -0.172, -0.16, -0.168, -0.152, -0.128, -0.14, -0.144, -0.148, -0.168, - -0.156, -0.152, -0.14, -0.164, -0.16, -0.152, -0.136, -0.156, -0.164, -0.148, - -0.144, -0.136, -0.132, -0.164, -0.168, -0.152, -0.152, -0.152, -0.144, - -0.148, -0.148, -0.16, -0.168, -0.164, -0.136, -0.144, -0.14, -0.148, -0.156, - -0.176, -0.156, -0.144, -0.156, -0.14, -0.148, -0.164, -0.148, -0.144, -0.136, - -0.14, -0.16, -0.136, -0.16, -0.152, -0.152, -0.148, -0.136, -0.144, -0.16, - -0.168, -0.144, -0.132, -0.144, -0.156, -0.168, -0.152, -0.14, -0.144, -0.16, - -0.156 + -0.16, -0.14, -0.152, -0.152, -0.156, -0.16, -0.172, -0.168, -0.16, + -0.168, -0.16, -0.164, -0.152, -0.148, -0.14, -0.14, -0.152, -0.164, + -0.168, -0.164, -0.164, -0.172, -0.16, -0.168, -0.152, -0.128, + -0.14, -0.144, -0.148, -0.168, -0.156, -0.152, -0.14, -0.164, -0.16, + -0.152, -0.136, -0.156, -0.164, -0.148, -0.144, -0.136, -0.132, + -0.164, -0.168, -0.152, -0.152, -0.152, -0.144, -0.148, -0.148, + -0.16, -0.168, -0.164, -0.136, -0.144, -0.14, -0.148, -0.156, + -0.176, -0.156, -0.144, -0.156, -0.14, -0.148, -0.164, -0.148, + -0.144, -0.136, -0.14, -0.16, -0.136, -0.16, -0.152, -0.152, -0.148, + -0.136, -0.144, -0.16, -0.168, -0.144, -0.132, -0.144, -0.156, + -0.168, -0.152, -0.14, -0.144, -0.16, -0.156 ] } }, @@ -438,39 +479,43 @@ "ID": 1718706724105, "data": { "x": [ - -0.036, -0.056, -0.044, -0.028, -0.028, -0.044, -0.06, -0.036, -0.024, -0.02, - -0.056, -0.06, -0.032, -0.032, -0.032, -0.044, -0.04, -0.032, -0.032, -0.052, - -0.052, -0.036, -0.024, -0.028, -0.048, -0.072, -0.056, -0.056, -0.024, - -0.016, -0.052, -0.072, -0.032, 0.088, -0.036, -0.056, -0.048, -0.036, -0.028, - -0.036, -0.036, -0.04, -0.032, -0.032, -0.04, -0.04, -0.044, -0.052, -0.024, - -0.044, -0.048, -0.056, -0.04, -0.032, -0.032, -0.04, -0.04, -0.052, -0.044, - -0.028, -0.036, -0.048, -0.04, -0.052, -0.052, -0.032, -0.036, -0.052, -0.052, - -0.048, -0.052, -0.036, -0.036, -0.032, -0.044, -0.036, -0.06, -0.052, -0.04, - -0.04, -0.036, -0.028, -0.052, -0.048, -0.04, -0.04, -0.048, -0.076, -0.072, - -0.044 + -0.036, -0.056, -0.044, -0.028, -0.028, -0.044, -0.06, -0.036, + -0.024, -0.02, -0.056, -0.06, -0.032, -0.032, -0.032, -0.044, -0.04, + -0.032, -0.032, -0.052, -0.052, -0.036, -0.024, -0.028, -0.048, + -0.072, -0.056, -0.056, -0.024, -0.016, -0.052, -0.072, -0.032, + 0.088, -0.036, -0.056, -0.048, -0.036, -0.028, -0.036, -0.036, + -0.04, -0.032, -0.032, -0.04, -0.04, -0.044, -0.052, -0.024, -0.044, + -0.048, -0.056, -0.04, -0.032, -0.032, -0.04, -0.04, -0.052, -0.044, + -0.028, -0.036, -0.048, -0.04, -0.052, -0.052, -0.032, -0.036, + -0.052, -0.052, -0.048, -0.052, -0.036, -0.036, -0.032, -0.044, + -0.036, -0.06, -0.052, -0.04, -0.04, -0.036, -0.028, -0.052, -0.048, + -0.04, -0.04, -0.048, -0.076, -0.072, -0.044 ], "y": [ - -0.984, -0.976, -0.98, -0.98, -0.984, -0.98, -0.992, -0.996, -0.992, -0.996, - -0.996, -1, -1.008, -0.992, -0.992, -1, -1, -0.988, -0.98, -0.964, -0.972, - -0.992, -1, -1, -0.996, -0.984, -0.98, -0.992, -1.008, -1.024, -1.016, -0.992, - -0.992, -1.072, -0.972, -0.988, -0.992, -0.988, -0.984, -0.984, -0.992, - -1.004, -0.992, -0.992, -0.996, -0.992, -0.984, -0.984, -0.988, -0.992, - -1.008, -0.996, -1, -1, -0.992, -0.988, -0.996, -0.996, -0.988, -0.984, - -0.992, -0.996, -0.996, -0.984, -0.98, -0.992, -0.988, -0.98, -0.988, -0.996, - -0.996, -0.996, -0.988, -0.992, -0.992, -0.98, -0.98, -0.984, -0.996, -1.004, - -1, -0.996, -0.996, -0.988, -0.984, -0.984, -0.988, -0.984, -0.988, -0.976 + -0.984, -0.976, -0.98, -0.98, -0.984, -0.98, -0.992, -0.996, -0.992, + -0.996, -0.996, -1, -1.008, -0.992, -0.992, -1, -1, -0.988, -0.98, + -0.964, -0.972, -0.992, -1, -1, -0.996, -0.984, -0.98, -0.992, + -1.008, -1.024, -1.016, -0.992, -0.992, -1.072, -0.972, -0.988, + -0.992, -0.988, -0.984, -0.984, -0.992, -1.004, -0.992, -0.992, + -0.996, -0.992, -0.984, -0.984, -0.988, -0.992, -1.008, -0.996, -1, + -1, -0.992, -0.988, -0.996, -0.996, -0.988, -0.984, -0.992, -0.996, + -0.996, -0.984, -0.98, -0.992, -0.988, -0.98, -0.988, -0.996, + -0.996, -0.996, -0.988, -0.992, -0.992, -0.98, -0.98, -0.984, + -0.996, -1.004, -1, -0.996, -0.996, -0.988, -0.984, -0.984, -0.988, + -0.984, -0.988, -0.976 ], "z": [ - -0.228, -0.228, -0.216, -0.192, -0.184, -0.204, -0.216, -0.212, -0.188, - -0.184, -0.2, -0.216, -0.22, -0.204, -0.216, -0.212, -0.228, -0.224, -0.2, - -0.208, -0.208, -0.2, -0.2, -0.2, -0.204, -0.208, -0.204, -0.204, -0.196, - -0.196, -0.2, -0.236, -0.22, -0.22, -0.188, -0.208, -0.216, -0.22, -0.2, - -0.204, -0.224, -0.232, -0.212, -0.2, -0.208, -0.216, -0.208, -0.212, -0.212, - -0.22, -0.212, -0.228, -0.204, -0.216, -0.208, -0.212, -0.216, -0.228, -0.228, - -0.22, -0.2, -0.216, -0.232, -0.212, -0.208, -0.22, -0.208, -0.196, -0.208, - -0.22, -0.22, -0.216, -0.22, -0.212, -0.212, -0.2, -0.212, -0.228, -0.232, - -0.216, -0.212, -0.208, -0.208, -0.216, -0.204, -0.196, -0.204, -0.228, - -0.228, -0.212 + -0.228, -0.228, -0.216, -0.192, -0.184, -0.204, -0.216, -0.212, + -0.188, -0.184, -0.2, -0.216, -0.22, -0.204, -0.216, -0.212, -0.228, + -0.224, -0.2, -0.208, -0.208, -0.2, -0.2, -0.2, -0.204, -0.208, + -0.204, -0.204, -0.196, -0.196, -0.2, -0.236, -0.22, -0.22, -0.188, + -0.208, -0.216, -0.22, -0.2, -0.204, -0.224, -0.232, -0.212, -0.2, + -0.208, -0.216, -0.208, -0.212, -0.212, -0.22, -0.212, -0.228, + -0.204, -0.216, -0.208, -0.212, -0.216, -0.228, -0.228, -0.22, -0.2, + -0.216, -0.232, -0.212, -0.208, -0.22, -0.208, -0.196, -0.208, + -0.22, -0.22, -0.216, -0.22, -0.212, -0.212, -0.2, -0.212, -0.228, + -0.232, -0.216, -0.212, -0.208, -0.208, -0.216, -0.204, -0.196, + -0.204, -0.228, -0.228, -0.212 ] } }, @@ -478,36 +523,40 @@ "ID": 1718706718209, "data": { "x": [ - -0.028, -0.044, -0.068, -0.068, -0.056, -0.028, -0.036, -0.064, -0.064, - -0.036, -0.04, -0.06, -0.06, -0.052, -0.032, -0.04, -0.048, -0.076, -0.064, - -0.036, -0.032, -0.048, -0.044, -0.052, -0.04, -0.032, -0.052, -0.064, -0.052, - -0.04, -0.044, -0.056, -0.052, -0.04, -0.048, -0.064, -0.048, -0.052, -0.04, - -0.056, -0.048, -0.056, -0.048, -0.08, -0.04, 0.04, -0.036, -0.036, -0.044, - -0.044, -0.04, -0.044, -0.048, -0.044, -0.048, -0.036, -0.044, -0.052, -0.044, - -0.044, -0.032, -0.048, -0.06, -0.044, -0.036, -0.048, -0.06, -0.056, -0.052, - -0.04, -0.04, -0.048, -0.056, -0.064, -0.06, -0.044, -0.048, -0.076, -0.052, - -0.044, -0.048, -0.056, -0.064, -0.06, -0.06, -0.06, -0.068, -0.06, -0.048, - -0.044 + -0.028, -0.044, -0.068, -0.068, -0.056, -0.028, -0.036, -0.064, + -0.064, -0.036, -0.04, -0.06, -0.06, -0.052, -0.032, -0.04, -0.048, + -0.076, -0.064, -0.036, -0.032, -0.048, -0.044, -0.052, -0.04, + -0.032, -0.052, -0.064, -0.052, -0.04, -0.044, -0.056, -0.052, + -0.04, -0.048, -0.064, -0.048, -0.052, -0.04, -0.056, -0.048, + -0.056, -0.048, -0.08, -0.04, 0.04, -0.036, -0.036, -0.044, -0.044, + -0.04, -0.044, -0.048, -0.044, -0.048, -0.036, -0.044, -0.052, + -0.044, -0.044, -0.032, -0.048, -0.06, -0.044, -0.036, -0.048, + -0.06, -0.056, -0.052, -0.04, -0.04, -0.048, -0.056, -0.064, -0.06, + -0.044, -0.048, -0.076, -0.052, -0.044, -0.048, -0.056, -0.064, + -0.06, -0.06, -0.06, -0.068, -0.06, -0.048, -0.044 ], "y": [ - -0.024, -0.028, -0.024, -0.024, -0.016, -0.024, -0.028, -0.032, -0.02, -0.028, - -0.032, -0.024, -0.024, -0.016, -0.02, -0.02, -0.024, -0.016, -0.016, -0.012, - -0.032, -0.036, -0.036, -0.032, -0.008, -0.016, -0.024, -0.02, -0.02, -0.028, - -0.02, -0.02, -0.028, -0.02, -0.032, -0.028, -0.032, -0.028, -0.028, -0.02, - -0.016, -0.024, -0.024, -0.028, -0.028, -0.072, -0.044, -0.04, -0.04, -0.024, - -0.024, -0.024, -0.044, -0.024, -0.02, -0.028, -0.04, -0.04, -0.044, -0.028, - -0.028, -0.032, -0.04, -0.04, -0.02, -0.016, -0.024, -0.036, -0.032, -0.036, - -0.036, -0.04, -0.04, -0.032, -0.028, -0.028, -0.04, -0.032, -0.04, -0.036, - -0.036, -0.04, -0.032, -0.028, -0.036, -0.032, -0.032, -0.032, -0.036, -0.048 + -0.024, -0.028, -0.024, -0.024, -0.016, -0.024, -0.028, -0.032, + -0.02, -0.028, -0.032, -0.024, -0.024, -0.016, -0.02, -0.02, -0.024, + -0.016, -0.016, -0.012, -0.032, -0.036, -0.036, -0.032, -0.008, + -0.016, -0.024, -0.02, -0.02, -0.028, -0.02, -0.02, -0.028, -0.02, + -0.032, -0.028, -0.032, -0.028, -0.028, -0.02, -0.016, -0.024, + -0.024, -0.028, -0.028, -0.072, -0.044, -0.04, -0.04, -0.024, + -0.024, -0.024, -0.044, -0.024, -0.02, -0.028, -0.04, -0.04, -0.044, + -0.028, -0.028, -0.032, -0.04, -0.04, -0.02, -0.016, -0.024, -0.036, + -0.032, -0.036, -0.036, -0.04, -0.04, -0.032, -0.028, -0.028, -0.04, + -0.032, -0.04, -0.036, -0.036, -0.04, -0.032, -0.028, -0.036, + -0.032, -0.032, -0.032, -0.036, -0.048 ], "z": [ - 1.016, 1.016, 1.008, 1.012, 1, 1.028, 1.028, 1.02, 1.008, 1.008, 1.024, 1.024, - 1.016, 1.016, 1.028, 1.016, 1.02, 1.02, 0.98, 1.028, 1.04, 1.02, 1.032, 1.012, - 1.02, 1.016, 1.012, 1.016, 1.044, 1.016, 1.02, 0.972, 1.004, 1.036, 1.048, - 1.004, 1.008, 1.004, 1, 0.988, 1.012, 1.032, 1.016, 1.032, 1.008, 0.92, 1.036, - 1.016, 1.032, 1.024, 1.036, 1.012, 1.008, 1.024, 1.012, 1.024, 1.012, 1, 1, - 1.008, 1.056, 1.032, 0.988, 1.008, 1.032, 1.02, 1.024, 1.004, 1.004, 1.012, - 1.044, 1.028, 1.016, 1.036, 1.02, 1.024, 1.028, 1.008, 1.02, 1.004, 1.008, + 1.016, 1.016, 1.008, 1.012, 1, 1.028, 1.028, 1.02, 1.008, 1.008, + 1.024, 1.024, 1.016, 1.016, 1.028, 1.016, 1.02, 1.02, 0.98, 1.028, + 1.04, 1.02, 1.032, 1.012, 1.02, 1.016, 1.012, 1.016, 1.044, 1.016, + 1.02, 0.972, 1.004, 1.036, 1.048, 1.004, 1.008, 1.004, 1, 0.988, + 1.012, 1.032, 1.016, 1.032, 1.008, 0.92, 1.036, 1.016, 1.032, 1.024, + 1.036, 1.012, 1.008, 1.024, 1.012, 1.024, 1.012, 1, 1, 1.008, 1.056, + 1.032, 0.988, 1.008, 1.032, 1.02, 1.024, 1.004, 1.004, 1.012, 1.044, + 1.028, 1.016, 1.036, 1.02, 1.024, 1.028, 1.008, 1.02, 1.004, 1.008, 1.012, 1.004, 1.012, 1.032, 1.036, 1.036, 1.02, 1.02, 1.02 ] } @@ -516,38 +565,42 @@ "ID": 1718706711804, "data": { "x": [ - 0.004, -0.004, 0, 0, 0, 0.024, 0.012, 0, 0.004, -0.008, 0.004, 0.024, 0.012, - 0.004, 0, 0.004, 0.008, -0.008, 0.004, -0.012, 0.016, 0.02, 0.012, 0.004, 0, - 0.008, 0.024, 0.02, 0.012, 0, 0.004, 0.004, 0.004, 0.008, 0.008, 0.004, 0.004, - 0, 0, 0.016, 0.016, 0.012, 0.008, -0.004, -0.004, 0.016, 0.004, 0.008, -0.008, - 0, 0.012, 0.012, 0, 0, 0.004, 0.008, 0.004, 0.008, 0.008, 0.028, 0.012, - -0.004, -0.02, -0.004, 0, -0.004, -0.008, -0.004, 0.004, 0, 0.004, -0.004, - 0.004, 0.008, 0.004, -0.004, 0, -0.008, -0.004, 0.004, -0.004, -0.004, -0.004, - 0.004, -0.004, 0.004, 0.004, 0, -0.004, -0.004, 0.008 + 0.004, -0.004, 0, 0, 0, 0.024, 0.012, 0, 0.004, -0.008, 0.004, + 0.024, 0.012, 0.004, 0, 0.004, 0.008, -0.008, 0.004, -0.012, 0.016, + 0.02, 0.012, 0.004, 0, 0.008, 0.024, 0.02, 0.012, 0, 0.004, 0.004, + 0.004, 0.008, 0.008, 0.004, 0.004, 0, 0, 0.016, 0.016, 0.012, 0.008, + -0.004, -0.004, 0.016, 0.004, 0.008, -0.008, 0, 0.012, 0.012, 0, 0, + 0.004, 0.008, 0.004, 0.008, 0.008, 0.028, 0.012, -0.004, -0.02, + -0.004, 0, -0.004, -0.008, -0.004, 0.004, 0, 0.004, -0.004, 0.004, + 0.008, 0.004, -0.004, 0, -0.008, -0.004, 0.004, -0.004, -0.004, + -0.004, 0.004, -0.004, 0.004, 0.004, 0, -0.004, -0.004, 0.008 ], "y": [ - -0.04, -0.048, -0.04, -0.056, -0.036, -0.048, -0.048, -0.048, -0.048, -0.056, - -0.044, -0.048, -0.048, -0.044, -0.048, -0.048, -0.044, -0.028, -0.032, - -0.032, -0.04, -0.068, -0.036, -0.044, -0.044, -0.044, -0.052, -0.04, -0.044, - -0.036, -0.036, -0.044, -0.028, -0.04, -0.032, -0.04, -0.052, -0.04, -0.044, - -0.044, -0.044, -0.04, -0.04, -0.04, -0.048, -0.052, -0.048, -0.044, -0.036, - -0.036, -0.04, -0.04, -0.04, -0.044, -0.044, -0.048, -0.044, -0.036, -0.036, - -0.044, -0.044, -0.044, -0.056, -0.06, -0.052, -0.044, -0.04, -0.048, -0.06, - -0.052, -0.056, -0.036, -0.048, -0.044, -0.052, -0.044, -0.044, -0.036, - -0.048, -0.048, -0.04, -0.044, -0.04, -0.052, -0.048, -0.052, -0.044, -0.044, - -0.052, -0.048, -0.048 + -0.04, -0.048, -0.04, -0.056, -0.036, -0.048, -0.048, -0.048, + -0.048, -0.056, -0.044, -0.048, -0.048, -0.044, -0.048, -0.048, + -0.044, -0.028, -0.032, -0.032, -0.04, -0.068, -0.036, -0.044, + -0.044, -0.044, -0.052, -0.04, -0.044, -0.036, -0.036, -0.044, + -0.028, -0.04, -0.032, -0.04, -0.052, -0.04, -0.044, -0.044, -0.044, + -0.04, -0.04, -0.04, -0.048, -0.052, -0.048, -0.044, -0.036, -0.036, + -0.04, -0.04, -0.04, -0.044, -0.044, -0.048, -0.044, -0.036, -0.036, + -0.044, -0.044, -0.044, -0.056, -0.06, -0.052, -0.044, -0.04, + -0.048, -0.06, -0.052, -0.056, -0.036, -0.048, -0.044, -0.052, + -0.044, -0.044, -0.036, -0.048, -0.048, -0.04, -0.044, -0.04, + -0.052, -0.048, -0.052, -0.044, -0.044, -0.052, -0.048, -0.048 ], "z": [ - -1.06, -1.06, -1.068, -1.068, -1.052, -1.028, -1.032, -1.06, -1.096, -1.092, - -1.068, -1.064, -1.072, -1.072, -1.084, -1.068, -1.056, -1.064, -1.056, - -1.056, -1.048, -1.048, -1.064, -1.072, -1.08, -1.076, -1.052, -1.056, -1.06, - -1.056, -1.084, -1.064, -1.06, -1.064, -1.072, -1.072, -1.068, -1.08, -1.076, - -1.056, -1.056, -1.064, -1.06, -1.076, -1.08, -1.052, -1.072, -1.064, -1.06, - -1.052, -1.064, -1.064, -1.072, -1.076, -1.064, -1.064, -1.068, -1.064, - -1.056, -1.052, -1.044, -1.068, -1.092, -1.072, -1.064, -1.064, -1.064, -1.06, - -1.068, -1.068, -1.076, -1.064, -1.068, -1.06, -1.068, -1.064, -1.064, -1.056, - -1.064, -1.064, -1.068, -1.068, -1.068, -1.064, -1.056, -1.064, -1.056, - -1.068, -1.084, -1.064, -1.068 + -1.06, -1.06, -1.068, -1.068, -1.052, -1.028, -1.032, -1.06, -1.096, + -1.092, -1.068, -1.064, -1.072, -1.072, -1.084, -1.068, -1.056, + -1.064, -1.056, -1.056, -1.048, -1.048, -1.064, -1.072, -1.08, + -1.076, -1.052, -1.056, -1.06, -1.056, -1.084, -1.064, -1.06, + -1.064, -1.072, -1.072, -1.068, -1.08, -1.076, -1.056, -1.056, + -1.064, -1.06, -1.076, -1.08, -1.052, -1.072, -1.064, -1.06, -1.052, + -1.064, -1.064, -1.072, -1.076, -1.064, -1.064, -1.068, -1.064, + -1.056, -1.052, -1.044, -1.068, -1.092, -1.072, -1.064, -1.064, + -1.064, -1.06, -1.068, -1.068, -1.076, -1.064, -1.068, -1.06, + -1.068, -1.064, -1.064, -1.056, -1.064, -1.064, -1.068, -1.068, + -1.068, -1.064, -1.056, -1.064, -1.056, -1.068, -1.084, -1.064, + -1.068 ] } } From 0799776268c86c49d3d67071f197e8a3155fc995 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 14:15:38 +0100 Subject: [PATCH 037/172] Delete dummy gesture data and temporarily use test fixture data instead --- src/components/RecordingDialog.tsx | 4 +- src/dummy-gesture-data.ts | 897 ----------------------------- 2 files changed, 2 insertions(+), 899 deletions(-) delete mode 100644 src/dummy-gesture-data.ts diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index 101d178e8..8ec1a5c39 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -14,7 +14,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { motion } from "framer-motion"; import { FormattedMessage, useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; -import { dummyGestureData } from "../dummy-gesture-data"; +import gestureData from "../test-fixtures/gesture-data.json"; const recordingDuration = 1800; @@ -102,7 +102,7 @@ const RecordingDialog = ({ // TODO: Record samples // Stubbing of recording of gesture actions.addGestureRecordings(gestureId, [ - dummyGestureData[0].recordings[0], + (gestureData as GestureData[])[0].recordings[0], ]); handleOnClose(); } diff --git a/src/dummy-gesture-data.ts b/src/dummy-gesture-data.ts deleted file mode 100644 index 7ed17d5a7..000000000 --- a/src/dummy-gesture-data.ts +++ /dev/null @@ -1,897 +0,0 @@ -export const dummyGestureData = [ - { - ID: 1717765478850, - name: "still", - matrix: [ - false, - false, - false, - false, - false, - true, - true, - false, - true, - true, - false, - false, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - false, - false, - false, - ], - recordings: [ - { - ID: 1717765566215, - data: { - x: [ - 0.02, 0, 0.004, 0, 0, 0, 0.008, 0.012, 0.004, 0.008, 0.004, 0, - 0.004, 0.008, 0, -0.004, 0.008, 0.02, 0.004, -0.004, 0.004, 0.008, - 0.02, 0.008, 0.008, 0.008, 0.012, 0.016, 0.012, 0.012, 0.008, 0.012, - -0.004, 0, 0.016, 0.02, 0.02, 0.004, -0.012, 0, 0.024, 0.016, 0.012, - 0.004, 0, 0.004, 0.012, 0.012, 0.008, 0.024, 0.02, 0.012, 0.004, - 0.004, 0, 0.016, 0.016, 0.012, 0.008, 0.012, 0.004, -0.004, 0.008, - 0.012, 0.004, 0.004, -0.004, 0.012, 0.016, 0.004, 0.008, 0.016, - 0.032, 0.012, -0.012, -0.008, 0.004, 0.028, 0.02, 0.008, -0.004, 0, - 0.024, 0.024, 0.008, 0.008, -0.004, 0.004, 0.016, 0.02, - ], - y: [ - -0.168, -0.16, -0.144, -0.116, -0.128, -0.136, -0.148, -0.176, - -0.184, -0.18, -0.152, -0.136, -0.156, -0.168, -0.168, -0.16, - -0.148, -0.144, -0.12, -0.14, -0.172, -0.172, -0.168, -0.132, - -0.124, -0.152, -0.16, -0.136, -0.124, -0.132, -0.176, -0.172, - -0.14, -0.124, -0.136, -0.172, -0.172, -0.14, -0.104, -0.136, - -0.168, -0.164, -0.152, -0.144, -0.136, -0.14, -0.116, -0.128, - -0.16, -0.164, -0.164, -0.128, -0.124, -0.14, -0.16, -0.156, -0.148, - -0.152, -0.168, -0.156, -0.128, -0.136, -0.144, -0.16, -0.148, - -0.124, -0.128, -0.148, -0.136, -0.14, -0.152, -0.16, -0.164, - -0.136, -0.116, -0.132, -0.16, -0.156, -0.14, -0.112, -0.112, - -0.136, -0.156, -0.16, -0.128, -0.116, -0.112, -0.124, -0.14, - -0.148, - ], - z: [ - -1.048, -1.052, -1.036, -1.036, -1.056, -1.072, -1.056, -1.048, - -1.06, -1.048, -1.048, -1.044, -1.04, -1.048, -1.056, -1.048, - -1.028, -1.028, -1.052, -1.076, -1.068, -1.04, -1.052, -1.068, - -1.048, -1.06, -1.056, -1.084, -1.088, -1.068, -1.04, -1.028, - -1.048, -1.068, -1.044, -1.008, -1.016, -1.048, -1.072, -1.056, - -1.044, -1.068, -1.064, -1.056, -1.044, -1.056, -1.068, -1.084, - -1.092, -1.076, -1.064, -1.048, -1.044, -1.044, -1.056, -1.064, - -1.056, -1.068, -1.028, -1.004, -1.032, -1.04, -1.048, -1.064, - -1.06, -1.06, -1.064, -1.052, -1.06, -1.072, -1.072, -1.048, -1.028, - -1.056, -1.084, -1.084, -1.06, -1.028, -1.028, -1.056, -1.08, - -1.068, -1.044, -1.028, -1.04, -1.048, -1.056, -1.064, -1.044, - -1.036, - ], - }, - }, - { - ID: 1717765560761, - data: { - x: [ - 0.976, 0.968, 0.96, 0.968, 0.956, 0.96, 0.956, 0.968, 0.972, 0.972, - 0.964, 0.956, 0.96, 0.964, 0.964, 0.968, 0.988, 0.956, 0.952, 0.952, - 0.964, 0.972, 0.972, 0.96, 0.968, 0.96, 0.964, 0.968, 0.968, 0.968, - 0.964, 0.956, 0.956, 0.968, 0.968, 0.964, 0.964, 0.964, 0.968, - 0.956, 0.964, 0.984, 0.976, 0.964, 0.968, 0.952, 0.956, 0.956, - 0.972, 0.968, 0.964, 0.964, 0.96, 0.968, 0.964, 0.964, 0.96, 0.968, - 0.968, 0.972, 0.968, 0.96, 0.968, 0.976, 0.956, 0.96, 0.964, 0.964, - 0.968, 0.968, 0.968, 0.96, 0.96, 0.964, 0.972, 0.956, 0.96, 0.956, - 0.96, 0.972, 0.968, 0.964, 0.96, 0.956, 0.964, 0.98, 0.98, 0.96, - 0.964, 0.96, 0.956, - ], - y: [ - -0.224, -0.204, -0.2, -0.208, -0.212, -0.204, -0.184, -0.192, -0.22, - -0.228, -0.2, -0.204, -0.216, -0.22, -0.192, -0.196, -0.208, -0.236, - -0.244, -0.22, -0.208, -0.192, -0.224, -0.224, -0.22, -0.228, -0.22, - -0.204, -0.216, -0.204, -0.2, -0.204, -0.224, -0.22, -0.224, -0.22, - -0.2, -0.204, -0.228, -0.22, -0.212, -0.228, -0.192, -0.196, -0.216, - -0.212, -0.212, -0.22, -0.228, -0.22, -0.216, -0.22, -0.236, -0.236, - -0.208, -0.216, -0.216, -0.216, -0.228, -0.228, -0.208, -0.2, - -0.208, -0.212, -0.216, -0.22, -0.204, -0.216, -0.228, -0.212, - -0.204, -0.2, -0.204, -0.196, -0.208, -0.236, -0.236, -0.216, - -0.208, -0.208, -0.22, -0.24, -0.24, -0.216, -0.212, -0.224, -0.22, - -0.216, -0.212, -0.236, -0.216, - ], - z: [ - -0.188, -0.184, -0.172, -0.176, -0.184, -0.188, -0.184, -0.18, - -0.164, -0.156, -0.168, -0.176, -0.184, -0.164, -0.18, -0.176, - -0.184, -0.176, -0.168, -0.176, -0.192, -0.156, -0.16, -0.18, - -0.168, -0.172, -0.176, -0.172, -0.172, -0.188, -0.188, -0.196, - -0.184, -0.172, -0.172, -0.172, -0.172, -0.18, -0.172, -0.172, - -0.176, -0.18, -0.156, -0.156, -0.18, -0.184, -0.196, -0.176, -0.18, - -0.18, -0.188, -0.188, -0.172, -0.172, -0.188, -0.18, -0.176, - -0.176, -0.164, -0.172, -0.164, -0.176, -0.18, -0.164, -0.164, - -0.176, -0.188, -0.204, -0.192, -0.176, -0.188, -0.192, -0.196, - -0.18, -0.172, -0.184, -0.172, -0.176, -0.176, -0.176, -0.156, - -0.16, -0.168, -0.18, -0.164, -0.164, -0.152, -0.156, -0.168, - -0.192, -0.208, - ], - }, - }, - { - ID: 1717765554773, - data: { - x: [ - -0.944, -0.96, -0.96, -0.972, -0.968, -0.976, -0.976, -0.968, - -0.972, -0.968, -0.96, -0.948, -0.956, -0.956, -0.964, -0.96, - -0.976, -0.964, -0.964, -0.964, -0.964, -0.972, -0.968, -0.96, - -0.96, -0.976, -0.976, -0.976, -0.972, -0.972, -0.968, -0.968, - -0.964, -0.964, -0.968, -0.968, -0.964, -0.968, -0.964, -0.968, - -0.96, -0.956, -0.952, -0.952, -0.956, -0.96, -0.964, -0.968, -0.96, - -0.968, -0.968, -0.972, -0.968, -0.964, -0.964, -0.96, -0.96, - -0.984, -0.964, -0.968, -0.952, -0.952, -0.964, -0.968, -0.968, - -0.956, -0.956, -0.96, -0.956, -0.948, -0.968, -0.968, -0.968, - -0.968, -0.964, -0.972, -0.964, -0.964, -0.956, -0.964, -0.956, - -0.96, -0.96, -0.972, -0.96, -0.96, -0.968, -0.972, -0.964, -0.964, - ], - y: [ - 0.344, 0.336, 0.336, 0.336, 0.328, 0.328, 0.324, 0.336, 0.336, - 0.336, 0.324, 0.332, 0.332, 0.34, 0.344, 0.324, 0.324, 0.328, 0.332, - 0.336, 0.336, 0.336, 0.332, 0.34, 0.336, 0.336, 0.324, 0.32, 0.32, - 0.324, 0.32, 0.324, 0.336, 0.324, 0.336, 0.336, 0.344, 0.328, 0.344, - 0.336, 0.348, 0.332, 0.328, 0.332, 0.348, 0.344, 0.332, 0.332, - 0.344, 0.336, 0.332, 0.324, 0.32, 0.328, 0.332, 0.348, 0.336, 0.328, - 0.328, 0.336, 0.34, 0.344, 0.328, 0.332, 0.332, 0.34, 0.344, 0.344, - 0.352, 0.34, 0.332, 0.324, 0.328, 0.324, 0.34, 0.34, 0.348, 0.356, - 0.344, 0.344, 0.34, 0.336, 0.336, 0.336, 0.34, 0.336, 0.34, 0.34, - 0.34, 0.34, - ], - z: [ - -0.052, -0.056, -0.06, -0.06, -0.064, -0.08, -0.06, -0.052, -0.036, - -0.044, -0.048, -0.056, -0.056, -0.06, -0.052, -0.056, -0.064, - -0.052, -0.06, -0.064, -0.056, -0.056, -0.048, -0.048, -0.048, - -0.052, -0.056, -0.064, -0.048, -0.056, -0.04, -0.056, -0.044, - -0.044, -0.048, -0.052, -0.068, -0.068, -0.06, -0.06, -0.048, - -0.052, -0.06, -0.036, -0.052, -0.06, -0.068, -0.064, -0.052, - -0.048, -0.052, -0.064, -0.064, -0.048, -0.044, -0.036, -0.044, - -0.072, -0.052, -0.052, -0.04, -0.052, -0.072, -0.064, -0.076, - -0.048, -0.044, -0.044, -0.056, -0.052, -0.064, -0.056, -0.052, - -0.052, -0.048, -0.04, -0.052, -0.052, -0.056, -0.056, -0.056, - -0.06, -0.064, -0.056, -0.044, -0.048, -0.056, -0.064, -0.06, -0.06, - ], - }, - }, - { - ID: 1717765549053, - data: { - x: [ - -0.276, -0.276, -0.264, -0.276, -0.276, -0.276, -0.276, -0.284, - -0.276, -0.272, -0.272, -0.268, -0.284, -0.276, -0.28, -0.276, - -0.272, -0.276, -0.28, -0.28, -0.272, -0.272, -0.268, -0.272, - -0.288, -0.276, -0.284, -0.28, -0.276, -0.272, -0.272, -0.272, - -0.272, -0.288, -0.292, -0.276, -0.268, -0.268, -0.276, -0.28, - -0.272, -0.272, -0.276, -0.268, -0.268, -0.26, -0.26, -0.272, - -0.276, -0.272, -0.284, -0.276, -0.272, -0.272, -0.276, -0.268, - -0.268, -0.268, -0.26, -0.256, -0.26, -0.272, -0.276, -0.268, - -0.268, -0.28, -0.292, -0.28, -0.268, -0.276, -0.268, -0.268, - -0.256, -0.272, -0.272, -0.28, -0.276, -0.268, -0.268, -0.28, -0.28, - -0.28, -0.272, -0.268, -0.272, -0.272, -0.272, -0.276, -0.272, - -0.276, -0.276, - ], - y: [ - -0.052, -0.056, -0.064, -0.076, -0.076, -0.068, -0.064, -0.06, - -0.076, -0.076, -0.064, -0.06, -0.06, -0.064, -0.068, -0.068, -0.06, - -0.068, -0.072, -0.06, -0.06, -0.056, -0.068, -0.064, -0.072, - -0.068, -0.068, -0.072, -0.076, -0.072, -0.068, -0.064, -0.076, - -0.076, -0.08, -0.092, -0.068, -0.064, -0.068, -0.064, -0.076, - -0.072, -0.072, -0.072, -0.072, -0.064, -0.076, -0.076, -0.076, - -0.088, -0.092, -0.092, -0.092, -0.092, -0.084, -0.072, -0.084, - -0.092, -0.072, -0.072, -0.064, -0.064, -0.08, -0.068, -0.084, - -0.08, -0.088, -0.084, -0.084, -0.076, -0.088, -0.08, -0.088, - -0.092, -0.076, -0.076, -0.076, -0.072, -0.088, -0.084, -0.08, - -0.076, -0.08, -0.072, -0.076, -0.076, -0.076, -0.072, -0.08, -0.08, - -0.076, - ], - z: [ - 0.98, 0.992, 0.996, 0.992, 0.996, 0.988, 0.98, 0.98, 0.98, 0.984, - 0.992, 0.98, 0.956, 0.976, 0.972, 0.968, 0.972, 0.976, 0.976, 0.968, - 0.972, 0.972, 0.976, 0.98, 0.984, 0.968, 0.968, 0.984, 0.992, 0.992, - 0.988, 0.984, 0.98, 0.96, 0.96, 0.976, 0.976, 0.98, 0.968, 0.968, - 0.976, 0.976, 0.968, 0.984, 0.988, 0.988, 0.976, 0.964, 0.964, - 0.964, 0.984, 0.98, 0.992, 1, 0.996, 1, 1.004, 0.992, 1.008, 1, - 0.968, 0.948, 0.968, 0.98, 0.98, 0.948, 0.948, 0.968, 0.984, 0.988, - 0.992, 1.012, 1, 0.996, 0.98, 0.98, 0.984, 0.988, 0.976, 0.972, - 0.956, 0.964, 0.976, 0.968, 0.976, 0.984, 0.976, 0.976, 0.98, 0.988, - 0.968, - ], - }, - }, - { - ID: 1717765543427, - data: { - x: [ - 0.212, 0.212, 0.208, 0.2, 0.204, 0.204, 0.216, 0.22, 0.208, 0.2, - 0.208, 0.216, 0.212, 0.204, 0.2, 0.212, 0.212, 0.212, 0.204, 0.208, - 0.216, 0.204, 0.204, 0.212, 0.208, 0.208, 0.2, 0.208, 0.2, 0.204, - 0.22, 0.216, 0.208, 0.196, 0.196, 0.208, 0.216, 0.216, 0.204, 0.2, - 0.2, 0.208, 0.216, 0.216, 0.216, 0.212, 0.208, 0.216, 0.224, 0.22, - 0.22, 0.22, 0.228, 0.224, 0.224, 0.212, 0.224, 0.204, 0.212, 0.212, - 0.224, 0.224, 0.212, 0.208, 0.224, 0.216, 0.212, 0.216, 0.216, - 0.216, 0.216, 0.232, 0.232, 0.216, 0.22, 0.228, 0.228, 0.228, 0.224, - 0.22, 0.212, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.228, 0.22, 0.212, - 0.216, - ], - y: [ - 0.508, 0.504, 0.516, 0.52, 0.512, 0.492, 0.512, 0.516, 0.512, 0.508, - 0.492, 0.504, 0.516, 0.516, 0.512, 0.504, 0.508, 0.52, 0.508, 0.504, - 0.504, 0.508, 0.508, 0.5, 0.504, 0.512, 0.52, 0.512, 0.508, 0.5, - 0.512, 0.508, 0.512, 0.516, 0.512, 0.512, 0.508, 0.504, 0.508, - 0.508, 0.516, 0.504, 0.512, 0.52, 0.52, 0.516, 0.508, 0.516, 0.512, - 0.508, 0.508, 0.508, 0.512, 0.512, 0.508, 0.508, 0.516, 0.508, - 0.504, 0.516, 0.516, 0.516, 0.516, 0.512, 0.504, 0.512, 0.516, - 0.508, 0.508, 0.528, 0.524, 0.524, 0.508, 0.504, 0.516, 0.52, 0.516, - 0.52, 0.516, 0.512, 0.504, 0.5, 0.516, 0.524, 0.524, 0.516, 0.508, - 0.508, 0.512, 0.516, 0.504, - ], - z: [ - -0.892, -0.884, -0.896, -0.908, -0.904, -0.908, -0.904, -0.9, - -0.908, -0.924, -0.912, -0.908, -0.9, -0.904, -0.908, -0.9, -0.896, - -0.908, -0.912, -0.9, -0.884, -0.888, -0.904, -0.892, -0.892, - -0.908, -0.912, -0.892, -0.912, -0.924, -0.896, -0.888, -0.884, - -0.904, -0.92, -0.908, -0.896, -0.884, -0.88, -0.884, -0.912, -0.9, - -0.9, -0.904, -0.908, -0.916, -0.908, -0.892, -0.884, -0.892, - -0.888, -0.904, -0.896, -0.892, -0.896, -0.912, -0.908, -0.924, - -0.896, -0.884, -0.884, -0.876, -0.888, -0.904, -0.892, -0.9, -0.9, - -0.892, -0.9, -0.904, -0.904, -0.888, -0.892, -0.9, -0.9, -0.892, - -0.88, -0.872, -0.892, -0.904, -0.896, -0.888, -0.88, -0.892, - -0.892, -0.892, -0.892, -0.896, -0.892, -0.896, -0.904, - ], - }, - }, - { - ID: 1717765538099, - data: { - x: [ - -0.328, -0.336, -0.324, -0.32, -0.332, -0.336, -0.332, -0.332, - -0.336, -0.336, -0.328, -0.328, -0.32, -0.316, -0.336, -0.328, - -0.324, -0.324, -0.328, -0.328, -0.328, -0.332, -0.324, -0.324, - -0.324, -0.32, -0.32, -0.32, -0.32, -0.328, -0.332, -0.324, -0.316, - -0.32, -0.316, -0.316, -0.324, -0.32, -0.328, -0.32, -0.32, -0.316, - -0.316, -0.324, -0.32, -0.324, -0.328, -0.32, -0.324, -0.32, -0.316, - -0.312, -0.32, -0.312, -0.328, -0.336, -0.324, -0.32, -0.32, -0.3, - -0.316, -0.324, -0.32, -0.316, -0.304, -0.312, -0.32, -0.328, - -0.312, -0.316, -0.316, -0.316, -0.32, -0.324, -0.316, -0.316, - -0.32, -0.324, -0.324, -0.324, -0.312, -0.312, -0.32, -0.32, -0.328, - -0.324, -0.316, -0.316, -0.316, -0.316, - ], - y: [ - -0.868, -0.872, -0.86, -0.844, -0.848, -0.84, -0.836, -0.84, -0.84, - -0.848, -0.844, -0.852, -0.852, -0.848, -0.844, -0.848, -0.852, - -0.864, -0.86, -0.868, -0.86, -0.864, -0.86, -0.86, -0.852, -0.856, - -0.852, -0.852, -0.864, -0.864, -0.872, -0.864, -0.86, -0.864, - -0.86, -0.86, -0.872, -0.868, -0.868, -0.868, -0.86, -0.856, -0.864, - -0.868, -0.864, -0.864, -0.864, -0.872, -0.864, -0.872, -0.864, - -0.848, -0.86, -0.864, -0.864, -0.864, -0.864, -0.864, -0.852, - -0.852, -0.856, -0.868, -0.876, -0.856, -0.856, -0.852, -0.852, - -0.868, -0.868, -0.868, -0.864, -0.86, -0.86, -0.856, -0.86, -0.852, - -0.848, -0.852, -0.856, -0.872, -0.868, -0.876, -0.868, -0.864, - -0.876, -0.868, -0.868, -0.86, -0.852, -0.852, - ], - z: [ - -0.48, -0.464, -0.452, -0.436, -0.456, -0.448, -0.448, -0.46, -0.48, - -0.484, -0.48, -0.476, -0.46, -0.456, -0.46, -0.444, -0.452, -0.452, - -0.444, -0.472, -0.476, -0.468, -0.464, -0.468, -0.464, -0.452, - -0.452, -0.448, -0.452, -0.468, -0.468, -0.456, -0.46, -0.448, - -0.448, -0.448, -0.464, -0.448, -0.46, -0.456, -0.452, -0.448, - -0.456, -0.448, -0.436, -0.452, -0.46, -0.456, -0.468, -0.448, - -0.468, -0.456, -0.452, -0.456, -0.46, -0.48, -0.464, -0.448, - -0.468, -0.444, -0.448, -0.452, -0.452, -0.468, -0.452, -0.452, - -0.452, -0.456, -0.464, -0.452, -0.444, -0.476, -0.472, -0.472, - -0.468, -0.452, -0.444, -0.46, -0.464, -0.46, -0.448, -0.444, - -0.444, -0.432, -0.456, -0.452, -0.44, -0.444, -0.448, -0.468, - ], - }, - }, - ], - output: {}, - confidence: { - currentConfidence: 0.9898542761802673, - requiredConfidence: 0.8, - isConfident: true, - }, - }, - { - ID: 1717765568963, - name: "wave", - matrix: [ - false, - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - true, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - false, - ], - recordings: [ - { - ID: 1717765679450, - data: { - x: [ - -0.512, -0.476, -0.412, -0.332, -0.248, -0.176, -0.132, -0.164, - -0.216, -0.248, -0.244, -0.276, -0.292, -0.292, -0.252, -0.22, - -0.176, -0.068, 0.032, 0.012, -0.036, -0.1, -0.168, -0.26, -0.44, - -0.584, -0.62, -0.6, -0.608, -0.612, -0.604, -0.612, -0.584, -0.528, - -0.448, -0.368, -0.272, -0.208, -0.18, -0.144, -0.156, -0.144, - -0.156, -0.2, -0.236, -0.288, -0.312, -0.304, -0.3, -0.312, -0.356, - -0.364, -0.38, -0.444, -0.5, -0.612, -0.684, -0.756, -0.772, -0.72, - -0.644, -0.536, -0.44, -0.376, -0.324, -0.308, -0.308, -0.28, -0.26, - -0.292, -0.316, -0.344, -0.34, -0.344, -0.316, -0.272, -0.244, - -0.232, -0.268, -0.344, -0.436, -0.52, -0.576, -0.644, -0.716, - -0.78, -0.844, -0.784, -0.732, -0.712, - ], - y: [ - 2.04, 2.04, 2.04, 1.784, 1.516, 1.224, 0.956, 0.768, 0.672, 0.54, - 0.396, 0.3, 0.164, 0.068, -0.004, -0.04, -0.116, -0.384, -0.752, - -0.976, -1.156, -1.18, -1.284, -1.432, -1.508, -1.624, -1.872, - -2.04, -2.04, -2.04, -2.04, -1.94, -1.692, -1.512, -1.328, -1.192, - -1.04, -0.896, -0.724, -0.5, -0.328, -0.244, -0.12, 0.016, 0.148, - 0.216, 0.264, 0.312, 0.412, 0.552, 0.772, 0.896, 1.032, 1.312, 1.6, - 1.932, 2.04, 2.04, 2.04, 2.04, 1.996, 1.74, 1.456, 1.2, 0.956, - 0.744, 0.564, 0.376, 0.136, 0, -0.132, -0.24, -0.276, -0.332, - -0.388, -0.452, -0.54, -0.688, -0.796, -0.864, -0.92, -0.992, - -1.128, -1.272, -1.488, -1.724, -1.944, -2.04, -2.04, -2.04, - ], - z: [ - -0.472, -0.364, -0.268, -0.228, -0.24, -0.276, -0.324, -0.292, - -0.212, -0.168, -0.208, -0.232, -0.212, -0.2, -0.224, -0.344, -0.42, - -0.332, -0.092, 0.188, 0.372, 0.356, 0.304, 0.244, 0.212, 0.08, - -0.152, -0.4, -0.292, -0.064, 0.092, 0.108, 0.084, 0.008, -0.004, - 0.024, 0.096, 0.14, 0.172, 0.16, 0.132, 0.08, 0.088, 0.024, -0.052, - -0.04, -0.076, -0.108, -0.108, -0.18, -0.264, -0.284, -0.328, - -0.488, -0.644, -0.756, -0.768, -0.752, -0.656, -0.54, -0.464, - -0.436, -0.42, -0.376, -0.308, -0.24, -0.208, -0.18, -0.176, -0.192, - -0.212, -0.176, -0.088, 0.004, 0.124, 0.192, 0.208, 0.248, 0.308, - 0.384, 0.408, 0.34, 0.2, 0.06, -0.068, -0.164, -0.212, -0.172, - -0.12, -0.036, - ], - }, - }, - { - ID: 1717765674496, - data: { - x: [ - -0.544, -0.452, -0.372, -0.312, -0.26, -0.244, -0.248, -0.28, - -0.332, -0.384, -0.4, -0.42, -0.44, -0.416, -0.456, -0.48, -0.5, - -0.508, -0.532, -0.536, -0.584, -0.632, -0.716, -0.76, -0.724, - -0.64, -0.532, -0.46, -0.416, -0.364, -0.288, -0.284, -0.312, - -0.332, -0.316, -0.328, -0.364, -0.36, -0.36, -0.32, -0.304, -0.256, - -0.26, -0.32, -0.404, -0.472, -0.62, -0.792, -0.912, -0.916, -0.908, - -0.864, -0.78, -0.712, -0.62, -0.52, -0.452, -0.412, -0.384, -0.38, - -0.368, -0.444, -0.432, -0.484, -0.484, -0.5, -0.5, -0.492, -0.488, - -0.496, -0.508, -0.504, -0.484, -0.5, -0.472, -0.512, -0.572, - -0.656, -0.708, -0.704, -0.66, -0.572, -0.52, -0.456, -0.392, - -0.364, -0.368, -0.4, -0.38, -0.368, -0.364, - ], - y: [ - -1.156, -1.012, -0.78, -0.536, -0.404, -0.268, -0.12, 0.084, 0.276, - 0.332, 0.3, 0.288, 0.376, 0.536, 0.712, 0.84, 0.98, 1.068, 1.204, - 1.312, 1.476, 1.744, 2.04, 2.04, 2.04, 1.9, 1.656, 1.456, 1.272, - 1.016, 0.688, 0.464, 0.368, 0.296, 0.144, -0.024, -0.096, -0.104, - -0.14, -0.208, -0.32, -0.444, -0.568, -0.652, -0.752, -0.976, -1.2, - -1.456, -1.884, -2.04, -2.04, -2.04, -1.828, -1.48, -1.2, -0.924, - -0.78, -0.652, -0.516, -0.344, -0.14, 0.032, 0.132, 0.22, 0.192, - 0.204, 0.24, 0.3, 0.424, 0.544, 0.62, 0.628, 0.72, 0.908, 1.124, - 1.42, 1.712, 2.02, 2.04, 2.04, 2.04, 1.844, 1.604, 1.372, 1.076, - 0.784, 0.584, 0.516, 0.424, 0.192, -0.028, - ], - z: [ - 0.336, 0.296, 0.16, 0.016, -0.024, 0.04, 0.056, -0.028, -0.124, - -0.164, -0.088, -0.072, -0.18, -0.288, -0.396, -0.396, -0.376, - -0.404, -0.48, -0.632, -0.768, -0.804, -0.876, -0.84, -0.708, - -0.644, -0.652, -0.676, -0.596, -0.516, -0.416, -0.308, -0.272, - -0.248, -0.16, -0.028, 0.012, 0.004, 0.048, 0.152, 0.316, 0.4, - 0.444, 0.484, 0.504, 0.548, 0.616, 0.6, 0.476, 0.328, 0.32, 0.336, - 0.332, 0.348, 0.348, 0.264, 0.184, 0.14, 0.104, 0.056, 0.12, 0.132, - 0.112, 0.084, 0.036, 0.076, 0.108, 0.032, -0.096, -0.236, -0.248, - -0.16, -0.192, -0.32, -0.468, -0.552, -0.564, -0.596, -0.66, -0.628, - -0.516, -0.424, -0.456, -0.432, -0.404, -0.332, -0.312, -0.28, - -0.256, -0.192, -0.092, - ], - }, - }, - { - ID: 1717765669623, - data: { - x: [ - -0.372, -0.376, -0.392, -0.352, -0.352, -0.404, -0.444, -0.468, - -0.464, -0.456, -0.444, -0.444, -0.472, -0.528, -0.608, -0.704, - -0.808, -0.912, -0.992, -0.996, -0.988, -0.948, -0.916, -0.856, - -0.844, -0.772, -0.676, -0.572, -0.52, -0.508, -0.46, -0.42, -0.396, - -0.384, -0.376, -0.388, -0.384, -0.38, -0.396, -0.396, -0.428, - -0.472, -0.492, -0.504, -0.544, -0.6, -0.66, -0.72, -0.724, -0.68, - -0.6, -0.528, -0.492, -0.444, -0.388, -0.392, -0.428, -0.436, - -0.432, -0.464, -0.504, -0.516, -0.532, -0.512, -0.472, -0.508, - -0.496, -0.46, -0.44, -0.492, -0.544, -0.644, -0.768, -0.916, - -1.048, -1.18, -1.192, -1.176, -1.092, -1.008, -0.916, -0.804, - -0.704, -0.6, -0.48, -0.348, -0.288, -0.272, -0.296, -0.324, - ], - y: [ - 0.544, 0.428, 0.268, 0.036, -0.084, -0.124, -0.116, -0.1, -0.132, - -0.224, -0.328, -0.44, -0.512, -0.656, -0.832, -1.024, -1.272, - -1.496, -1.776, -2.024, -2.028, -1.844, -1.56, -1.268, -0.94, - -0.752, -0.656, -0.592, -0.408, -0.276, -0.216, -0.108, 0.004, - 0.152, 0.304, 0.368, 0.344, 0.352, 0.428, 0.52, 0.636, 0.732, 0.848, - 1.056, 1.428, 1.812, 2.04, 2.04, 2.04, 2.04, 1.756, 1.472, 1.244, - 0.968, 0.668, 0.5, 0.424, 0.332, 0.188, 0.048, -0.012, 0, 0, -0.064, - -0.156, -0.22, -0.28, -0.368, -0.46, -0.576, -0.812, -1.036, -1.108, - -1.188, -1.304, -1.464, -1.628, -1.76, -1.728, -1.556, -1.34, - -1.132, -0.86, -0.68, -0.576, -0.536, -0.38, -0.088, 0.176, 0.304, - ], - z: [ - -0.308, -0.228, -0.124, -0.168, -0.16, -0.072, 0.008, 0.032, 0.056, - 0.076, 0.176, 0.32, 0.38, 0.388, 0.416, 0.42, 0.36, 0.316, 0.284, - 0.28, 0.308, 0.252, 0.26, 0.152, 0.056, 0.04, 0.064, 0.12, 0.088, - 0.016, -0.02, -0.012, -0.024, -0.044, -0.032, -0.004, 0.024, -0.04, - -0.108, -0.22, -0.296, -0.38, -0.444, -0.548, -0.712, -0.864, -0.9, - -0.856, -0.792, -0.72, -0.704, -0.652, -0.636, -0.56, -0.484, - -0.392, -0.332, -0.316, -0.252, -0.108, 0.02, 0.068, 0.064, 0.088, - 0.104, 0.18, 0.248, 0.252, 0.228, 0.34, 0.444, 0.492, 0.416, 0.304, - 0.248, 0.272, 0.264, 0.3, 0.256, 0.224, 0.248, 0.224, 0.176, 0.156, - 0.204, 0.176, 0.136, 0.032, -0.056, -0.096, - ], - }, - }, - { - ID: 1717765664759, - data: { - x: [ - -0.524, -0.416, -0.384, -0.352, -0.352, -0.344, -0.356, -0.396, - -0.428, -0.452, -0.468, -0.468, -0.484, -0.5, -0.528, -0.548, - -0.556, -0.576, -0.604, -0.624, -0.616, -0.628, -0.656, -0.656, - -0.576, -0.508, -0.428, -0.412, -0.392, -0.364, -0.348, -0.328, - -0.332, -0.328, -0.364, -0.416, -0.456, -0.48, -0.476, -0.476, - -0.516, -0.6, -0.684, -0.712, -0.804, -0.88, -0.916, -0.924, -0.916, - -0.888, -0.836, -0.776, -0.76, -0.732, -0.692, -0.604, -0.536, - -0.504, -0.484, -0.488, -0.48, -0.5, -0.488, -0.492, -0.508, -0.508, - -0.46, -0.444, -0.456, -0.452, -0.42, -0.44, -0.456, -0.476, -0.472, - -0.476, -0.452, -0.412, -0.372, -0.348, -0.364, -0.384, -0.4, - -0.376, -0.412, -0.456, -0.504, -0.504, -0.508, -0.484, -0.492, - ], - y: [ - -0.824, -0.72, -0.52, -0.292, -0.124, 0.008, 0.084, 0.164, 0.256, - 0.336, 0.416, 0.432, 0.484, 0.608, 0.78, 0.924, 0.988, 1.1, 1.284, - 1.428, 1.608, 1.96, 2.04, 2.04, 1.98, 1.584, 1.284, 1.148, 1.004, - 0.808, 0.576, 0.416, 0.22, -0.012, -0.132, -0.152, -0.176, -0.252, - -0.38, -0.54, -0.7, -0.892, -1.028, -1.252, -1.324, -1.476, -1.64, - -1.824, -1.812, -1.612, -1.4, -1.16, -0.836, -0.552, -0.408, -0.372, - -0.332, -0.16, 0.048, 0.264, 0.452, 0.56, 0.576, 0.588, 0.584, - 0.584, 0.596, 0.7, 0.86, 1.048, 1.248, 1.56, 1.936, 2.04, 2.04, - 2.04, 1.868, 1.66, 1.4, 1.124, 0.884, 0.748, 0.54, 0.3, 0.18, 0.072, - 0.02, -0.016, -0.076, -0.172, -0.308, - ], - z: [ - 0.124, 0.112, 0.176, 0.156, 0.072, -0.024, -0.068, -0.092, -0.136, - -0.22, -0.312, -0.34, -0.376, -0.392, -0.468, -0.544, -0.636, - -0.644, -0.616, -0.604, -0.584, -0.632, -0.728, -0.648, -0.436, - -0.296, -0.268, -0.292, -0.34, -0.324, -0.268, -0.224, -0.212, - -0.248, -0.236, -0.216, -0.164, -0.1, -0.02, 0.048, 0.144, 0.284, - 0.408, 0.424, 0.424, 0.408, 0.38, 0.32, 0.304, 0.272, 0.228, 0.184, - 0.036, -0.132, -0.172, -0.088, 0.036, 0.116, 0.128, 0.056, -0.024, - -0.052, -0.056, -0.004, 0.02, -0.056, -0.176, -0.268, -0.32, -0.332, - -0.38, -0.472, -0.564, -0.64, -0.556, -0.396, -0.324, -0.368, - -0.432, -0.392, -0.34, -0.304, -0.264, -0.272, -0.256, -0.256, - -0.244, -0.216, -0.064, 0.084, 0.176, - ], - }, - }, - { - ID: 1717765659660, - data: { - x: [ - -0.428, -0.42, -0.424, -0.408, -0.452, -0.48, -0.476, -0.452, -0.44, - -0.444, -0.452, -0.456, -0.504, -0.524, -0.508, -0.52, -0.524, - -0.592, -0.696, -0.816, -0.892, -0.968, -1.06, -1.08, -1.032, - -0.968, -0.888, -0.824, -0.74, -0.608, -0.468, -0.36, -0.312, - -0.328, -0.356, -0.36, -0.412, -0.464, -0.508, -0.54, -0.552, - -0.548, -0.552, -0.548, -0.536, -0.536, -0.544, -0.552, -0.56, - -0.588, -0.668, -0.72, -0.744, -0.7, -0.628, -0.556, -0.472, -0.46, - -0.48, -0.48, -0.484, -0.488, -0.54, -0.572, -0.592, -0.596, -0.608, - -0.596, -0.572, -0.544, -0.548, -0.552, -0.556, -0.588, -0.612, - -0.712, -0.804, -0.888, -0.936, -0.96, -0.98, -0.96, -0.9, -0.844, - -0.768, -0.68, -0.624, -0.556, -0.488, -0.436, -0.404, - ], - y: [ - 0.68, 0.556, 0.452, 0.24, 0.092, 0.068, 0.068, 0.008, -0.16, -0.316, - -0.412, -0.376, -0.376, -0.424, -0.576, -0.704, -0.812, -0.908, - -1.028, -1.176, -1.336, -1.524, -1.616, -1.624, -1.628, -1.548, - -1.38, -1.132, -0.96, -0.864, -0.86, -0.776, -0.54, -0.22, -0.008, - 0.116, 0.212, 0.292, 0.4, 0.472, 0.5, 0.52, 0.588, 0.66, 0.7, 0.744, - 0.86, 1.06, 1.32, 1.6, 1.904, 2.04, 2.04, 1.996, 1.704, 1.312, - 0.972, 0.704, 0.564, 0.444, 0.3, 0.164, 0.048, 0.012, 0.004, 0.016, - -0.032, -0.096, -0.152, -0.152, -0.172, -0.324, -0.492, -0.572, - -0.684, -0.792, -1.024, -1.28, -1.544, -1.684, -1.756, -1.68, - -1.524, -1.372, -1.212, -0.988, -0.736, -0.604, -0.564, -0.496, - -0.352, - ], - z: [ - -0.532, -0.5, -0.42, -0.232, -0.072, -0.004, -0.08, -0.132, 0, 0.22, - 0.3, 0.24, 0.168, 0.216, 0.32, 0.424, 0.428, 0.472, 0.468, 0.452, - 0.376, 0.304, 0.308, 0.356, 0.464, 0.564, 0.52, 0.356, 0.264, 0.288, - 0.372, 0.408, 0.284, 0.112, -0.048, -0.132, -0.108, -0.14, -0.196, - -0.228, -0.208, -0.256, -0.304, -0.36, -0.352, -0.332, -0.332, - -0.42, -0.584, -0.788, -0.86, -0.816, -0.732, -0.72, -0.736, -0.696, - -0.56, -0.448, -0.376, -0.352, -0.292, -0.168, -0.02, 0.048, 0.068, - 0.036, 0.036, 0.104, 0.188, 0.196, 0.176, 0.204, 0.32, 0.4, 0.344, - 0.372, 0.396, 0.4, 0.368, 0.396, 0.448, 0.484, 0.472, 0.5, 0.48, - 0.352, 0.212, 0.164, 0.148, 0.116, 0.032, - ], - }, - }, - { - ID: 1717765654503, - data: { - x: [ - -0.312, -0.352, -0.4, -0.468, -0.464, -0.416, -0.396, -0.308, -0.22, - -0.084, -0.016, -0.012, -0.064, -0.104, -0.152, -0.252, -0.352, - -0.404, -0.484, -0.508, -0.52, -0.516, -0.5, -0.468, -0.444, -0.432, - -0.4, -0.404, -0.396, -0.284, -0.304, -0.448, -0.684, -0.848, - -1.028, -1.184, -1.264, -1.24, -1.092, -0.86, -0.636, -0.444, - -0.288, -0.212, -0.16, -0.188, -0.22, -0.224, -0.22, -0.308, -0.38, - -0.416, -0.424, -0.44, -0.46, -0.504, -0.524, -0.5, -0.512, -0.504, - -0.44, -0.408, -0.424, -0.448, -0.424, -0.452, -0.484, -0.52, - -0.588, -0.584, -0.52, -0.412, -0.356, -0.316, -0.244, -0.224, - -0.248, -0.276, -0.324, -0.364, -0.42, -0.468, -0.476, -0.496, - -0.484, -0.484, -0.448, -0.46, -0.428, -0.38, -0.38, - ], - y: [ - 1.256, 1.584, 2.032, 2.04, 2.04, 2.04, 2.04, 1.928, 1.624, 1.16, - 0.716, 0.4, 0.264, 0.112, -0.108, -0.216, -0.172, -0.16, -0.22, - -0.348, -0.368, -0.292, -0.256, -0.352, -0.488, -0.52, -0.52, -0.44, - -0.36, -0.644, -1.112, -1.312, -1.524, -1.908, -2.04, -2.04, -1.916, - -1.68, -1.548, -1.552, -1.54, -1.428, -1.248, -0.972, -0.748, - -0.456, -0.296, -0.236, -0.172, 0.052, 0.204, 0.212, 0.188, 0.244, - 0.428, 0.612, 0.64, 0.552, 0.548, 0.6, 0.592, 0.652, 0.82, 0.956, - 1.04, 1.264, 1.588, 1.996, 2.04, 2.04, 2.04, 1.784, 1.536, 1.24, - 0.872, 0.552, 0.42, 0.28, 0.128, 0.032, 0.068, 0.08, -0.008, -0.112, - -0.112, -0.116, -0.18, -0.272, -0.356, -0.424, -0.448, - ], - z: [ - -0.932, -1.012, -1.192, -1.34, -1.34, -1.144, -0.956, -0.936, - -0.932, -0.876, -0.748, -0.588, -0.472, -0.364, -0.192, 0.028, - 0.136, 0.116, 0.12, 0.22, 0.292, 0.236, 0.22, 0.212, 0.272, 0.292, - 0.244, 0.26, 0.24, 0.224, 0.408, 0.472, 0.6, 0.56, 0.296, 0.084, - 0.02, 0.12, 0.324, 0.548, 0.664, 0.612, 0.56, 0.468, 0.364, 0.188, - 0.076, 0.096, 0.104, -0.056, -0.164, -0.132, -0.068, -0.14, -0.256, - -0.336, -0.328, -0.3, -0.304, -0.364, -0.448, -0.468, -0.528, - -0.632, -0.708, -0.82, -1.02, -1.208, -1.212, -1.052, -0.904, - -0.872, -0.844, -0.764, -0.656, -0.52, -0.448, -0.408, -0.276, - -0.14, -0.076, -0.084, -0.024, 0.108, 0.116, 0.084, 0.128, 0.22, - 0.248, 0.236, 0.28, - ], - }, - }, - ], - output: {}, - confidence: { - currentConfidence: 0.0034859327133744955, - requiredConfidence: 0.8, - isConfident: false, - }, - }, - { - ID: 1717765681522, - name: "clap", - matrix: [ - true, - true, - true, - true, - true, - true, - false, - false, - false, - true, - true, - false, - false, - false, - true, - true, - false, - false, - false, - true, - true, - true, - true, - true, - true, - ], - recordings: [ - { - ID: 1717765716738, - data: { - x: [ - -1.372, -1.5, -1.608, -1.636, -1.556, -1.428, -1.252, -1.04, -0.844, - -0.684, -0.544, -0.468, -0.436, -0.396, -0.408, -0.372, -0.304, - -0.244, -0.236, -0.188, -0.092, -0.052, -0.072, -0.02, -0.008, - -0.06, -0.212, -0.34, -0.432, -0.516, -0.576, -0.736, -0.796, -0.72, - -0.52, -0.38, -0.228, -0.052, 0.032, 0.056, 0.048, 0.008, 0, 0.004, - -0.028, -0.104, -0.18, -0.208, -0.276, -0.32, -0.372, -0.448, -0.52, - -0.532, -0.496, -0.492, -0.564, -0.576, -0.62, -0.832, -1.156, - -1.42, -1.528, -1.62, -1.628, -1.536, -1.372, -1.172, -1.02, -0.892, - -0.74, -0.604, -0.524, -0.456, -0.372, -0.292, -0.232, -0.196, - -0.188, -0.188, -0.168, -0.136, -0.068, -0.06, -0.072, -0.112, - -0.22, -0.388, -0.552, -0.696, -0.768, - ], - y: [ - 0.176, 0.26, 0.276, 0.36, 0.42, 0.436, 0.408, 0.3, 0.208, 0.064, - -0.076, -0.116, -0.112, -0.1, -0.084, -0.104, -0.148, -0.14, -0.08, - -0.084, -0.116, -0.144, -0.176, -0.304, -0.332, -0.328, -0.348, - -0.4, -0.504, -0.532, -0.58, -0.632, -0.756, -0.892, -1.1, -1.208, - -1.176, -1.124, -0.904, -0.688, -0.52, -0.388, -0.308, -0.256, - -0.212, -0.148, -0.116, -0.104, -0.056, 0.024, 0.152, 0.208, 0.236, - 0.296, 0.412, 0.476, 0.348, 0.132, -0.016, 0.112, 0.516, 0.652, - 0.588, 0.544, 0.58, 0.624, 0.604, 0.488, 0.316, 0.108, -0.036, - -0.072, -0.068, -0.052, -0.04, -0.028, -0.024, -0.052, -0.08, - -0.092, -0.064, -0.04, -0.044, -0.204, -0.46, -0.548, -0.412, - -0.324, -0.252, -0.148, -0.092, - ], - z: [ - -1.32, -1.404, -1.244, -1.316, -1.408, -1.328, -1.032, -0.708, - -0.448, -0.28, -0.16, -0.02, 0.184, 0.296, 0.368, 0.44, 0.536, - 0.676, 0.804, 0.92, 0.98, 1.012, 1.064, 1.184, 1.256, 1.16, 1.084, - 1.088, 1.148, 1.26, 1.332, 1.544, 1.78, 1.944, 2.036, 2.04, 2.008, - 1.832, 1.756, 1.688, 1.476, 1.244, 1.016, 0.828, 0.744, 0.672, - 0.536, 0.304, 0.124, 0.06, 0.136, 0.212, 0.048, -0.248, -0.252, - 0.14, 0.348, 0.056, -0.468, -0.596, -0.764, -1.264, -1.604, -1.8, - -1.656, -1.38, -1.076, -0.8, -0.556, -0.412, -0.332, -0.192, 0.032, - 0.228, 0.356, 0.452, 0.532, 0.688, 0.772, 0.844, 0.904, 0.976, - 1.024, 1.048, 1.096, 1.16, 1.064, 0.988, 0.96, 0.94, 1, - ], - }, - }, - { - ID: 1717765710734, - data: { - x: [ - -0.632, -0.512, -0.452, -0.436, -0.448, -0.428, -0.42, -0.4, -0.4, - -0.404, -0.352, -0.308, -0.336, -0.276, -0.232, -0.22, -0.272, - -0.304, -0.328, -0.4, -0.488, -0.524, -0.528, -0.556, -0.548, - -0.588, -0.668, -0.524, -0.356, -0.272, -0.256, -0.244, -0.24, - -0.256, -0.268, -0.312, -0.344, -0.368, -0.364, -0.404, -0.388, - -0.392, -0.396, -0.416, -0.436, -0.452, -0.412, -0.448, -0.512, - -0.528, -0.556, -0.516, -0.552, -0.708, -0.796, -0.98, -1.256, - -1.496, -1.6, -1.62, -1.52, -1.392, -1.256, -1.108, -0.972, -0.88, - -0.772, -0.696, -0.66, -0.632, -0.552, -0.492, -0.464, -0.372, - -0.34, -0.324, -0.288, -0.244, -0.212, -0.228, -0.24, -0.26, -0.268, - -0.288, -0.248, -0.256, -0.34, -0.452, -0.596, -0.724, -0.844, - ], - y: [ - -0.184, -0.304, -0.284, -0.18, -0.104, -0.04, -0.016, -0.024, -0.04, - -0.04, -0.028, -0.044, -0.06, -0.108, -0.196, -0.268, -0.272, -0.24, - -0.216, -0.156, -0.12, -0.256, -0.408, -0.5, -0.608, -0.692, -0.784, - -1.02, -1.352, -1.288, -1.076, -0.868, -0.736, -0.612, -0.504, - -0.412, -0.316, -0.26, -0.252, -0.288, -0.26, -0.188, -0.08, 0.004, - 0.004, 0.056, 0.108, 0.144, 0.112, 0.044, 0.144, 0.328, 0.328, - 0.048, 0.024, 0.3, 0.404, 0.444, 0.512, 0.608, 0.564, 0.48, 0.344, - 0.196, 0.04, -0.116, -0.208, -0.224, -0.192, -0.112, -0.044, -0.024, - -0.04, -0.032, -0.024, -0.064, -0.1, -0.136, -0.132, -0.12, -0.14, - -0.136, -0.072, -0.06, -0.232, -0.452, -0.528, -0.464, -0.444, - -0.496, -0.584, - ], - z: [ - -0.184, -0.004, 0.152, 0.308, 0.416, 0.468, 0.532, 0.608, 0.68, - 0.74, 0.812, 0.9, 0.972, 0.98, 0.92, 0.884, 0.964, 1, 0.956, 0.872, - 0.936, 1.072, 1.26, 1.436, 1.548, 1.736, 1.896, 1.996, 2.04, 2.04, - 1.9, 1.736, 1.58, 1.356, 1.124, 0.88, 0.712, 0.644, 0.612, 0.552, - 0.528, 0.452, 0.408, 0.412, 0.312, 0.144, 0.044, 0.12, 0.156, - -0.056, -0.232, -0.008, 0.276, 0.048, -0.6, -0.904, -0.872, -1.052, - -1.328, -1.444, -1.408, -1.172, -0.868, -0.652, -0.516, -0.408, - -0.336, -0.204, -0.012, 0.06, 0.152, 0.3, 0.416, 0.492, 0.64, 0.828, - 0.94, 0.956, 0.916, 0.908, 0.92, 0.892, 0.86, 0.844, 0.896, 1.1, - 1.32, 1.388, 1.364, 1.456, 1.564, - ], - }, - }, - { - ID: 1717765704869, - data: { - x: [ - -0.788, -0.86, -0.8, -0.616, -0.448, -0.364, -0.264, -0.224, -0.22, - -0.184, -0.2, -0.184, -0.22, -0.244, -0.244, -0.272, -0.316, -0.352, - -0.352, -0.344, -0.364, -0.456, -0.5, -0.564, -0.588, -0.704, - -0.652, -0.924, -1.256, -1.564, -1.776, -1.748, -1.536, -1.272, - -0.988, -0.724, -0.532, -0.456, -0.412, -0.456, -0.516, -0.564, - -0.616, -0.62, -0.592, -0.552, -0.512, -0.4, -0.28, -0.324, -0.252, - -0.156, -0.148, -0.288, -0.396, -0.472, -0.552, -0.664, -0.748, - -0.876, -1, -0.988, -0.788, -0.528, -0.364, -0.26, -0.216, -0.248, - -0.26, -0.24, -0.272, -0.28, -0.268, -0.284, -0.248, -0.248, -0.284, - -0.292, -0.304, -0.292, -0.304, -0.36, -0.444, -0.436, -0.456, - -0.584, -0.68, -0.884, -1.224, -1.484, -1.636, - ], - y: [ - -0.832, -1.016, -1.228, -1.456, -1.496, -1.288, -1.068, -0.932, - -0.84, -0.756, -0.608, -0.452, -0.34, -0.308, -0.308, -0.232, - -0.136, -0.128, -0.084, 0.028, 0.164, 0.232, 0.26, 0.316, 0.444, - 0.376, 0.108, 0.072, 0.244, 0.388, 0.788, 1.096, 0.968, 0.74, 0.432, - 0.092, -0.232, -0.464, -0.468, -0.432, -0.364, -0.216, -0.084, - -0.02, 0, -0.06, -0.088, -0.14, -0.148, -0.176, -0.144, -0.164, - -0.164, -0.1, -0.056, -0.152, -0.332, -0.512, -0.696, -0.884, - -0.984, -1.096, -1.28, -1.524, -1.468, -1.188, -0.928, -0.752, -0.6, - -0.532, -0.452, -0.396, -0.348, -0.328, -0.324, -0.3, -0.264, - -0.208, -0.132, 0.008, 0.104, 0.096, 0.052, 0.136, 0.308, 0.24, - 0.112, 0.24, 0.272, 0.384, 0.46, - ], - z: [ - 1.684, 1.848, 1.968, 2.004, 2.028, 2, 1.828, 1.616, 1.34, 1.072, - 0.912, 0.8, 0.652, 0.536, 0.356, 0.292, 0.328, 0.276, 0.08, -0.032, - -0.004, -0.052, -0.12, -0.2, -0.148, 0.088, -0.3, -1.004, -1.376, - -1.764, -2.032, -2.024, -1.676, -1.204, -0.848, -0.552, -0.18, 0.06, - 0.32, 0.436, 0.424, 0.34, 0.316, 0.388, 0.456, 0.536, 0.472, 0.44, - 0.592, 0.788, 0.904, 0.96, 0.956, 0.988, 1.028, 1.112, 1.124, 1.068, - 1.116, 1.372, 1.548, 1.588, 1.776, 2.036, 2.04, 2.024, 1.788, 1.572, - 1.356, 1.104, 0.916, 0.792, 0.68, 0.604, 0.608, 0.584, 0.52, 0.36, - 0.2, 0.208, 0.248, 0.168, -0.02, -0.212, -0.072, 0, -0.42, -0.808, - -1.284, -1.716, -1.616, - ], - }, - }, - { - ID: 1717765700042, - data: { - x: [ - -0.296, -0.248, -0.244, -0.216, -0.16, -0.192, -0.16, -0.204, - -0.284, -0.34, -0.384, -0.424, -0.468, -0.444, -0.42, -0.396, - -0.372, -0.508, -0.608, -0.68, -0.764, -0.908, -1.092, -1.248, - -1.396, -1.384, -1.16, -0.856, -0.64, -0.488, -0.444, -0.492, - -0.536, -0.588, -0.632, -0.672, -0.684, -0.692, -0.712, -0.664, - -0.532, -0.42, -0.528, -0.484, -0.384, -0.284, -0.352, -0.416, - -0.364, -0.368, -0.552, -0.76, -0.888, -1.008, -1.004, -0.836, - -0.632, -0.46, -0.348, -0.292, -0.272, -0.276, -0.236, -0.216, - -0.284, -0.352, -0.368, -0.404, -0.392, -0.416, -0.4, -0.416, - -0.388, -0.424, -0.416, -0.436, -0.46, -0.528, -0.556, -0.652, -0.8, - -0.984, -1.168, -1.352, -1.428, -1.3, -1.1, -0.868, -0.676, -0.556, - -0.524, - ], - y: [ - -0.592, -0.528, -0.484, -0.412, -0.388, -0.392, -0.388, -0.34, - -0.276, -0.212, -0.172, -0.088, -0.012, 0.152, 0.152, 0.072, 0.06, - 0.284, 0.424, 0.312, 0.316, 0.416, 0.544, 0.716, 1.024, 1.14, 0.88, - 0.512, 0.196, -0.104, -0.264, -0.216, -0.144, -0.08, -0.06, -0.056, - -0.016, 0.008, 0.064, 0.092, 0.012, -0.112, -0.156, -0.144, -0.14, - -0.24, -0.272, -0.164, -0.12, -0.188, -0.304, -0.504, -0.716, -0.84, - -0.98, -1.128, -1.216, -1.108, -0.996, -0.78, -0.644, -0.552, - -0.524, -0.516, -0.416, -0.336, -0.304, -0.296, -0.336, -0.324, - -0.196, -0.048, -0.06, -0.136, -0.092, 0.164, 0.288, 0.248, 0.164, - 0.276, 0.356, 0.32, 0.424, 0.704, 0.996, 0.972, 0.8, 0.532, 0.22, - -0.052, -0.188, - ], - z: [ - 2.036, 1.744, 1.356, 1.124, 0.98, 0.796, 0.54, 0.36, 0.268, 0.22, - 0.108, 0.008, -0.064, 0.076, 0.28, 0.156, -0.192, -0.34, -0.176, - -0.268, -0.6, -0.964, -1.376, -1.624, -1.836, -1.816, -1.508, -1.1, - -0.644, -0.328, -0.028, 0.156, 0.228, 0.256, 0.308, 0.344, 0.36, - 0.412, 0.44, 0.472, 0.532, 0.46, 0.48, 0.532, 0.644, 0.668, 0.704, - 0.88, 1.164, 1.3, 1.3, 1.34, 1.484, 1.52, 1.692, 2.012, 2.04, 2.04, - 2.028, 1.932, 1.752, 1.528, 1.22, 0.88, 0.7, 0.58, 0.584, 0.544, - 0.412, 0.3, 0.328, 0.472, 0.544, 0.244, -0.044, -0.096, 0.096, - 0.056, -0.2, -0.496, -0.684, -1.124, -1.54, -1.912, -1.92, -1.556, - -1.152, -0.8, -0.472, -0.12, 0.112, - ], - }, - }, - { - ID: 1717765694490, - data: { - x: [ - -0.448, -0.452, -0.496, -0.504, -0.464, -0.444, -0.452, -0.452, - -0.416, -0.412, -0.388, -0.412, -0.356, -0.368, -0.424, -0.428, - -0.476, -0.548, -0.664, -0.764, -0.864, -0.868, -0.788, -0.66, - -0.552, -0.456, -0.376, -0.288, -0.264, -0.272, -0.288, -0.272, - -0.224, -0.2, -0.192, -0.224, -0.316, -0.408, -0.488, -0.516, - -0.588, -0.584, -0.62, -0.648, -0.648, -0.64, -0.668, -0.664, -0.82, - -1.1, -1.364, -1.556, -1.536, -1.396, -1.252, -1.068, -0.916, -0.8, - -0.752, -0.692, -0.692, -0.704, -0.672, -0.628, -0.628, -0.644, - -0.696, -0.668, -0.556, -0.532, -0.52, -0.5, -0.464, -0.444, -0.42, - -0.504, -0.52, -0.488, -0.444, -0.532, -0.592, -0.64, -0.716, - -0.748, -0.704, -0.584, -0.536, -0.492, -0.452, -0.436, -0.388, - ], - y: [ - 0.14, 0.204, 0.256, 0.248, 0.216, 0.12, 0.016, -0.024, -0.048, - -0.064, -0.144, -0.224, -0.256, -0.28, -0.3, -0.408, -0.528, -0.66, - -0.716, -0.692, -0.704, -0.888, -1.108, -1.224, -1.06, -0.9, -0.768, - -0.692, -0.592, -0.456, -0.372, -0.276, -0.252, -0.24, -0.256, - -0.22, -0.116, -0.024, 0.004, 0.052, 0.14, 0.252, 0.164, 0.036, - 0.064, 0.328, 0.404, 0.092, 0.008, 0.216, 0.516, 0.608, 0.528, - 0.368, 0.232, 0.084, -0.04, -0.16, -0.228, -0.256, -0.188, -0.12, - -0.096, -0.044, 0.008, 0.08, 0.156, 0.172, 0.068, -0.036, -0.104, - -0.144, -0.132, -0.176, -0.212, -0.216, -0.188, -0.196, -0.308, - -0.408, -0.472, -0.476, -0.56, -0.672, -0.828, -1.048, -1.16, - -1.072, -0.968, -0.844, -0.736, - ], - z: [ - 0.372, 0.404, 0.452, 0.468, 0.52, 0.564, 0.588, 0.636, 0.596, 0.632, - 0.664, 0.74, 0.836, 0.876, 0.924, 1.024, 1.272, 1.668, 1.884, 1.884, - 1.924, 1.92, 1.912, 1.948, 2.02, 1.896, 1.76, 1.436, 1.172, 1.016, - 0.808, 0.684, 0.58, 0.524, 0.408, 0.28, 0.192, 0.124, 0.044, 0.016, - 0.02, 0.172, 0.184, 0.016, -0.228, -0.052, 0.172, -0.212, -0.704, - -1.116, -1.392, -1.52, -1.616, -1.604, -1.312, -0.98, -0.596, - -0.352, -0.208, -0.096, 0.004, 0.1, 0.1, 0.124, 0.196, 0.256, 0.264, - 0.276, 0.368, 0.436, 0.548, 0.612, 0.624, 0.648, 0.668, 0.648, - 0.712, 0.76, 0.86, 1.012, 1.116, 1.264, 1.424, 1.548, 1.764, 2.036, - 2.04, 2.04, 2.04, 1.928, 1.78, - ], - }, - }, - { - ID: 1717765689141, - data: { - x: [ - -0.444, -0.4, -0.368, -0.368, -0.304, -0.26, -0.28, -0.312, -0.368, - -0.336, -0.316, -0.328, -0.408, -0.436, -0.42, -0.412, -0.456, - -0.456, -0.44, -0.412, -0.304, -0.26, -0.244, -0.192, -0.192, - -0.184, -0.164, -0.12, -0.144, -0.152, -0.14, -0.128, -0.108, - -0.184, -0.248, -0.284, -0.364, -0.468, -0.516, -0.5, -0.516, -0.52, - -0.532, -0.568, -0.596, -0.608, -0.672, -0.764, -0.896, -0.988, - -1.096, -1.268, -1.352, -1.372, -1.304, -1.168, -0.992, -0.832, - -0.736, -0.652, -0.604, -0.62, -0.592, -0.576, -0.536, -0.516, - -0.496, -0.464, -0.432, -0.444, -0.432, -0.42, -0.416, -0.384, - -0.348, -0.308, -0.328, -0.384, -0.364, -0.34, -0.38, -0.464, - -0.532, -0.592, -0.616, -0.556, -0.5, -0.416, -0.396, -0.344, - ], - y: [ - 0, -0.04, -0.02, 0, -0.024, -0.08, -0.164, -0.236, -0.248, -0.284, - -0.34, -0.388, -0.412, -0.44, -0.484, -0.588, -0.668, -0.764, - -0.892, -1.044, -1.144, -1.1, -1, -0.916, -0.828, -0.76, -0.696, - -0.648, -0.572, -0.504, -0.476, -0.444, -0.488, -0.484, -0.436, - -0.272, -0.132, -0.068, 0.024, 0.084, 0.1, 0.016, 0.056, 0.268, - 0.268, 0.112, -0.004, 0.112, 0.18, 0.208, 0.256, 0.396, 0.516, 0.6, - 0.64, 0.504, 0.344, 0.184, 0.064, -0.056, -0.024, 0.06, 0.084, - 0.092, 0.048, -0.004, -0.016, -0.032, -0.052, -0.072, -0.128, - -0.176, -0.18, -0.196, -0.224, -0.292, -0.34, -0.344, -0.396, - -0.528, -0.644, -0.6, -0.476, -0.456, -0.54, -0.724, -0.864, -1, - -1.1, -1.136, - ], - z: [ - 0.256, 0.38, 0.532, 0.632, 0.744, 0.816, 0.856, 0.864, 0.868, 0.868, - 0.82, 0.784, 0.86, 0.98, 1.108, 1.188, 1.348, 1.556, 1.82, 2.032, - 2.04, 2.04, 2.04, 2.04, 1.956, 1.688, 1.42, 1.208, 1.044, 0.94, - 0.848, 0.752, 0.588, 0.468, 0.288, 0.132, 0.028, -0.072, -0.004, - 0.16, 0.24, 0.148, -0.032, 0.056, 0.22, 0.072, -0.14, -0.228, -0.34, - -0.74, -1.116, -1.476, -1.684, -1.716, -1.608, -1.408, -1.204, - -0.956, -0.688, -0.484, -0.304, -0.128, 0.004, 0.124, 0.224, 0.32, - 0.468, 0.564, 0.668, 0.732, 0.728, 0.688, 0.704, 0.728, 0.712, - 0.712, 0.74, 0.8, 0.916, 0.988, 0.952, 0.968, 1.036, 1.084, 1.248, - 1.392, 1.628, 1.948, 2.04, 2.04, - ], - }, - }, - ], - // output: {}, - // confidence: { - // currentConfidence: 0.0066597783006727695, - // requiredConfidence: 0.8, - // isConfident: false, - // }, - }, -]; From db0900f17f77e2476e0fe4f957d2ee168e944588 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 14:54:42 +0100 Subject: [PATCH 038/172] Training status hook --- src/components/TrainingStatusView.tsx | 19 +++++++--------- src/gestures-hooks.tsx | 31 ++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx index f651e6139..15c911394 100644 --- a/src/components/TrainingStatusView.tsx +++ b/src/components/TrainingStatusView.tsx @@ -8,27 +8,24 @@ import { } from "@chakra-ui/react"; import { ReactNode, useCallback, useState } from "react"; import { FormattedMessage } from "react-intl"; -import { useGestureActions, useGestureData } from "../gestures-hooks"; +import { + TrainingStatus, + useGestureActions, + useGestureData, + useTrainingStatus, +} from "../gestures-hooks"; import { useNavigate } from "react-router"; import { createStepPageUrl } from "../urls"; import TrainingButton from "./TrainingButton"; import { trainModel } from "../ml"; import TrainingErrorDialog from "./TrainingErrorDialog"; -enum TrainingStatus { - NotStarted, - InProgress, - Complete, -} - const TrainingStatusView = () => { const navigate = useNavigate(); const [gestureData] = useGestureData(); const actions = useGestureActions(); const isSufficientData = actions.isSufficientForTraining(); - const [trainStatus, setTrainStatus] = useState( - TrainingStatus.NotStarted - ); + const [trainStatus, setTrainStatus] = useTrainingStatus(); const [trainProgress, setTrainProgress] = useState(0); const trainErrorDialog = useDisclosure(); @@ -54,7 +51,7 @@ const TrainingStatusView = () => { setTrainStatus(TrainingStatus.NotStarted); }, }); - }, [gestureData.data]); + }, [gestureData.data, setTrainStatus]); if (!isSufficientData) { return ( diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index ef18f42c8..4ce83fabf 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,4 +1,10 @@ -import { ReactNode, createContext, useContext, useMemo } from "react"; +import { + ReactNode, + createContext, + useCallback, + useContext, + useMemo, +} from "react"; import { useStorage } from "./hooks/use-storage"; export interface XYZData { x: number[]; @@ -17,7 +23,14 @@ export interface GestureData { recordings: RecordingData[]; } +export enum TrainingStatus { + NotStarted, + InProgress, + Complete, +} + interface GestureContextState { + trainingStatus: TrainingStatus; data: GestureData[]; } @@ -73,6 +86,7 @@ const generateNewGesture = (): GestureData => ({ }); const initialGestureContextState: GestureContextState = { + trainingStatus: TrainingStatus.NotStarted, data: [generateNewGesture()], }; @@ -90,6 +104,21 @@ export const GesturesProvider = ({ children }: { children: ReactNode }) => { ); }; +export const useTrainingStatus = (): [ + TrainingStatus, + (status: TrainingStatus) => void +] => { + const [gestures, setGestures] = useGestureData(); + const trainingStatus = gestures.trainingStatus; + const setTrainingStatus = useCallback( + (status: TrainingStatus) => { + setGestures({ ...gestures, trainingStatus: status }); + }, + [gestures, setGestures] + ); + return [trainingStatus, setTrainingStatus]; +}; + export const useGestureActions = () => { const [gestures, setGestures] = useGestureData(); const actions = useMemo( From 2149932e69a5a1a1f02589ef71e0fe83c5bd11f2 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 15:19:54 +0100 Subject: [PATCH 039/172] Retrain state in training page --- src/components/TrainingStatusView.tsx | 6 ++++++ src/gestures-hooks.tsx | 22 ++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx index 15c911394..b249a955d 100644 --- a/src/components/TrainingStatusView.tsx +++ b/src/components/TrainingStatusView.tsx @@ -103,6 +103,12 @@ const TrainingStatusView = () => { ); + case TrainingStatus.Retrain: + return ( + + + + ); } }; diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 4ce83fabf..9b59cd503 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -27,6 +27,7 @@ export enum TrainingStatus { NotStarted, InProgress, Complete, + Retrain, } interface GestureContextState { @@ -145,7 +146,6 @@ export class GestureActions { addNewGesture = () => { this.setGestures([...this.state.data, generateNewGesture()]); }; - addGestureRecordings = (id: GestureData["ID"], recs: RecordingData[]) => { const newGestures = this.state.data.map((g) => { return id !== g.ID ? g : { ...g, recordings: [...recs, ...g.recordings] }; @@ -153,13 +153,19 @@ export class GestureActions { this.setGestures(newGestures); }; - setGestures = (gestures: GestureData[]) => { - if (gestures.length === 0) { + setGestures = (gestures: GestureData[], isRetrainNeeded: boolean = true) => { + this.setState({ + ...this.state, // Always have at least one gesture - this.setState({ ...this.state, ...initialGestureContextState }); - return; - } - this.setState({ ...this.state, data: gestures }); + ...(gestures.length === 0 + ? initialGestureContextState + : { data: gestures }), + // Update training status to retrain if needed + trainingStatus: + isRetrainNeeded && this.state.trainingStatus === TrainingStatus.Complete + ? TrainingStatus.Retrain + : this.state.trainingStatus, + }); }; deleteGesture = (id: GestureData["ID"]) => { @@ -170,7 +176,7 @@ export class GestureActions { const newGestures = this.state.data.map((g) => { return id !== g.ID ? g : { ...g, name }; }); - this.setGestures(newGestures); + this.setGestures(newGestures, false); }; deleteGestureRecording = ( From b2ec0f4beb5e754c277bb35525a0235ff01fff04 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 16:48:19 +0100 Subject: [PATCH 040/172] Training hook for passing training status state A separate context and provider was made for passing the training status hook because, unlike the gesture hook, the state does not need to be stored in the local storage. Additonal fix in using useNavigate to navigate between tabs in TabView so that the state persists between navigation. --- src/App.tsx | 17 +++-- src/components/TabView.tsx | 5 +- src/components/TrainingButton.tsx | 9 ++- src/components/TrainingStatusView.tsx | 52 ++++++------- src/gestures-hooks.tsx | 105 +++++++++++--------------- src/training-hook.tsx | 38 ++++++++++ 6 files changed, 123 insertions(+), 103 deletions(-) create mode 100644 src/training-hook.tsx diff --git a/src/App.tsx b/src/App.tsx index ce1c5ec25..76db56d12 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import { stepsConfig } from "./steps-config"; import { LoggingProvider } from "./logging/logging-hooks"; import { ConnectionFlowProvider } from "./connections"; import { GesturesProvider } from "./gestures-hooks"; +import { TrainingStatusProvider } from "./training-hook"; export interface ProviderLayoutProps { children: ReactNode; @@ -36,13 +37,15 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - - - {children} - - - + + + + + {children} + + + + diff --git a/src/components/TabView.tsx b/src/components/TabView.tsx index d557aec97..c3b01748d 100644 --- a/src/components/TabView.tsx +++ b/src/components/TabView.tsx @@ -2,12 +2,14 @@ import { Tab, TabIndicator, TabList, Tabs, VStack } from "@chakra-ui/react"; import { useIntl } from "react-intl"; import { StepId, stepsConfig } from "../steps-config"; import { createStepPageUrl } from "../urls"; +import { useNavigate } from "react-router"; interface TabViewProps { activeStep: StepId; } const TabView = ({ activeStep }: TabViewProps) => { const intl = useIntl(); + const navigate = useNavigate(); const activeIndex = stepsConfig.findIndex((s) => s.id === activeStep); return ( @@ -20,14 +22,13 @@ const TabView = ({ activeStep }: TabViewProps) => { {stepsConfig.map((step, idx) => ( navigate(createStepPageUrl(step.id))} key={step.id} fontSize="lg" fontWeight="bold" px={12} opacity={0.55} _selected={{ opacity: 1 }} - href={createStepPageUrl(step.id)} > {`${idx + 1}. ${intl.formatMessage({ id: `${step.id}-title` })}`} diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx index 6c5f312c4..77dafa471 100644 --- a/src/components/TrainingButton.tsx +++ b/src/components/TrainingButton.tsx @@ -1,15 +1,18 @@ import { Button, ButtonProps } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { useGestureActions } from "../gestures-hooks"; +import { TrainingStatus, useTrainingStatus } from "../training-hook"; const TrainingButton = (props: ButtonProps) => { - const actions = useGestureActions(); + const [trainingStatus] = useTrainingStatus(); // TODO: disable when isTraining return ( - - ); - } + }, [data, setTrainingStatus]); - switch (trainStatus) { + switch (trainingStatus) { + case TrainingStatus.InsufficientData: + return ( + + + + ); case TrainingStatus.NotStarted: return ( <> diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 9b59cd503..e6f8bbd92 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,11 +1,6 @@ -import { - ReactNode, - createContext, - useCallback, - useContext, - useMemo, -} from "react"; +import { ReactNode, createContext, useContext, useMemo } from "react"; import { useStorage } from "./hooks/use-storage"; +import { TrainingStatus, useTrainingStatus } from "./training-hook"; export interface XYZData { x: number[]; y: number[]; @@ -23,15 +18,7 @@ export interface GestureData { recordings: RecordingData[]; } -export enum TrainingStatus { - NotStarted, - InProgress, - Complete, - Retrain, -} - interface GestureContextState { - trainingStatus: TrainingStatus; data: GestureData[]; } @@ -87,7 +74,6 @@ const generateNewGesture = (): GestureData => ({ }); const initialGestureContextState: GestureContextState = { - trainingStatus: TrainingStatus.NotStarted, data: [generateNewGesture()], }; @@ -105,75 +91,72 @@ export const GesturesProvider = ({ children }: { children: ReactNode }) => { ); }; -export const useTrainingStatus = (): [ - TrainingStatus, - (status: TrainingStatus) => void -] => { - const [gestures, setGestures] = useGestureData(); - const trainingStatus = gestures.trainingStatus; - const setTrainingStatus = useCallback( - (status: TrainingStatus) => { - setGestures({ ...gestures, trainingStatus: status }); - }, - [gestures, setGestures] - ); - return [trainingStatus, setTrainingStatus]; -}; - export const useGestureActions = () => { const [gestures, setGestures] = useGestureData(); + const [trainingStatus, setTrainingStatus] = useTrainingStatus(); const actions = useMemo( - () => new GestureActions(gestures, setGestures), - [gestures, setGestures] + () => + new GestureActions( + gestures, + setGestures, + trainingStatus, + setTrainingStatus + ), + [gestures, setGestures, setTrainingStatus, trainingStatus] ); return actions; }; export class GestureActions { constructor( - private state: GestureContextState, - private setState: (gestureData: GestureContextState) => void + private gestureState: GestureContextState, + private setGestureState: (gestureData: GestureContextState) => void, + private trainingStatus: TrainingStatus, + private setTrainingStatus: (status: TrainingStatus) => void ) {} - isSufficientForTraining = (): boolean => { - const gestures = this.state.data; - if (gestures.length < 2) { - return false; - } - return !gestures.some((g) => g.recordings.length < 3); + private hasSufficientDataForTraining = (): boolean => { + return ( + this.gestureState.data.length > 2 && + this.gestureState.data.every((g) => g.recordings.length >= 3) + ); + }; + + setGestures = (gs: GestureData[], isRetrainNeeded: boolean = true) => { + this.setGestureState({ + ...this.gestureState, + // Always have at least one gesture + data: gs.length === 0 ? initialGestureContextState.data : gs, + }); + + // Update training status to retrain if needed + const hasTrainedBefore = this.trainingStatus === TrainingStatus.Complete; + this.setTrainingStatus( + this.hasSufficientDataForTraining() + ? isRetrainNeeded && hasTrainedBefore + ? TrainingStatus.Retrain + : this.trainingStatus + : TrainingStatus.InsufficientData + ); }; addNewGesture = () => { - this.setGestures([...this.state.data, generateNewGesture()]); + this.setGestures([...this.gestureState.data, generateNewGesture()]); }; + addGestureRecordings = (id: GestureData["ID"], recs: RecordingData[]) => { - const newGestures = this.state.data.map((g) => { + const newGestures = this.gestureState.data.map((g) => { return id !== g.ID ? g : { ...g, recordings: [...recs, ...g.recordings] }; }); this.setGestures(newGestures); }; - setGestures = (gestures: GestureData[], isRetrainNeeded: boolean = true) => { - this.setState({ - ...this.state, - // Always have at least one gesture - ...(gestures.length === 0 - ? initialGestureContextState - : { data: gestures }), - // Update training status to retrain if needed - trainingStatus: - isRetrainNeeded && this.state.trainingStatus === TrainingStatus.Complete - ? TrainingStatus.Retrain - : this.state.trainingStatus, - }); - }; - deleteGesture = (id: GestureData["ID"]) => { - this.setGestures(this.state.data.filter((g) => g.ID !== id)); + this.setGestures(this.gestureState.data.filter((g) => g.ID !== id)); }; setGestureName = (id: GestureData["ID"], name: string) => { - const newGestures = this.state.data.map((g) => { + const newGestures = this.gestureState.data.map((g) => { return id !== g.ID ? g : { ...g, name }; }); this.setGestures(newGestures, false); @@ -183,7 +166,7 @@ export class GestureActions { gestureId: GestureData["ID"], recordingIdx: number ) => { - const newGestures = this.state.data.map((g) => { + const newGestures = this.gestureState.data.map((g) => { if (gestureId !== g.ID) { return g; } diff --git a/src/training-hook.tsx b/src/training-hook.tsx new file mode 100644 index 000000000..a8fb45fb9 --- /dev/null +++ b/src/training-hook.tsx @@ -0,0 +1,38 @@ +import { ReactNode, createContext, useContext, useState } from "react"; + +export enum TrainingStatus { + InsufficientData, + NotStarted, + InProgress, + Complete, + Retrain, +} + +type TrainingContextValue = [TrainingStatus, (status: TrainingStatus) => void]; + +const TrainingStatusContext = createContext( + undefined +); + +export const TrainingStatusProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const trainingStatusContextValue = useState( + TrainingStatus.NotStarted + ); + return ( + + {children} + + ); +}; + +export const useTrainingStatus = (): TrainingContextValue => { + const trainingStatusContextValue = useContext(TrainingStatusContext); + if (!trainingStatusContextValue) { + throw new Error("Missing provider"); + } + return trainingStatusContextValue; +}; From e85c7e3765d4b8b2c96969ee7825d70ac6dda620 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 17:03:07 +0100 Subject: [PATCH 041/172] Rename file training-hook.tsx to training-status-hook.tsx --- src/App.tsx | 2 +- src/components/TrainingButton.tsx | 2 +- src/components/TrainingStatusView.tsx | 2 +- src/gestures-hooks.tsx | 2 +- src/{training-hook.tsx => training-status-hook.tsx} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/{training-hook.tsx => training-status-hook.tsx} (100%) diff --git a/src/App.tsx b/src/App.tsx index 76db56d12..e92477c8a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,7 +19,7 @@ import { stepsConfig } from "./steps-config"; import { LoggingProvider } from "./logging/logging-hooks"; import { ConnectionFlowProvider } from "./connections"; import { GesturesProvider } from "./gestures-hooks"; -import { TrainingStatusProvider } from "./training-hook"; +import { TrainingStatusProvider } from "./training-status-hook"; export interface ProviderLayoutProps { children: ReactNode; diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx index 77dafa471..2763af2dc 100644 --- a/src/components/TrainingButton.tsx +++ b/src/components/TrainingButton.tsx @@ -1,6 +1,6 @@ import { Button, ButtonProps } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { TrainingStatus, useTrainingStatus } from "../training-hook"; +import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; const TrainingButton = (props: ButtonProps) => { const [trainingStatus] = useTrainingStatus(); diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx index 7c522504b..2d2e99bc8 100644 --- a/src/components/TrainingStatusView.tsx +++ b/src/components/TrainingStatusView.tsx @@ -14,7 +14,7 @@ import { trainModel } from "../ml"; import { createStepPageUrl } from "../urls"; import TrainingButton from "./TrainingButton"; import TrainingErrorDialog from "./TrainingErrorDialog"; -import { TrainingStatus, useTrainingStatus } from "../training-hook"; +import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; const TrainingStatusView = () => { const navigate = useNavigate(); diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index e6f8bbd92..4f15289b6 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,6 +1,6 @@ import { ReactNode, createContext, useContext, useMemo } from "react"; import { useStorage } from "./hooks/use-storage"; -import { TrainingStatus, useTrainingStatus } from "./training-hook"; +import { TrainingStatus, useTrainingStatus } from "./training-status-hook"; export interface XYZData { x: number[]; y: number[]; diff --git a/src/training-hook.tsx b/src/training-status-hook.tsx similarity index 100% rename from src/training-hook.tsx rename to src/training-status-hook.tsx From dbdfff80e5a9eedea6a7ed867bb692223524120d Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 11 Jul 2024 17:51:33 +0100 Subject: [PATCH 042/172] Test model page train model first states --- src/components/TrainModelFirstView.tsx | 85 ++++++++++++++++++++++++++ src/components/TrainingStatusView.tsx | 20 +++--- src/pages/TestDataPage.tsx | 5 -- src/pages/TestModelPage.tsx | 22 +++++++ src/pages/TrainModelPage.tsx | 4 +- src/steps-config.ts | 8 +-- 6 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 src/components/TrainModelFirstView.tsx delete mode 100644 src/pages/TestDataPage.tsx create mode 100644 src/pages/TestModelPage.tsx diff --git a/src/components/TrainModelFirstView.tsx b/src/components/TrainModelFirstView.tsx new file mode 100644 index 000000000..1b03dda2a --- /dev/null +++ b/src/components/TrainModelFirstView.tsx @@ -0,0 +1,85 @@ +import { Button, Heading, Image, Text, VStack } from "@chakra-ui/react"; +import { useCallback } from "react"; +import { FormattedMessage } from "react-intl"; +import { useNavigate } from "react-router"; +import testModelImage from "../images/test_model_black.svg"; +import { StepId } from "../steps-config"; +import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; +import { createStepPageUrl } from "../urls"; +import TrainingButton from "./TrainingButton"; + +interface TrainModelFirstViewConfig { + textIds: string[]; + navigateToStep: StepId; +} + +const getConfig = (status: TrainingStatus): TrainModelFirstViewConfig => { + switch (status) { + case TrainingStatus.InsufficientData: + return { + textIds: [ + "content.model.notEnoughDataInfoBody1", + "content.model.notEnoughDataInfoBody2", + ], + navigateToStep: "add-data", + }; + case TrainingStatus.Retrain: + return { + textIds: ["content.model.retrainModelBody"], + navigateToStep: "train-model", + }; + default: + return { + textIds: ["content.model.trainModelBody"], + navigateToStep: "train-model", + }; + } +}; + +const TrainModelFirstView = () => { + const navigate = useNavigate(); + const [trainingStatus] = useTrainingStatus(); + + const navigateToDataPage = useCallback(() => { + navigate(createStepPageUrl("add-data")); + }, [navigate]); + + const navigateToTrainModelPage = useCallback(() => { + navigate(createStepPageUrl("train-model")); + }, [navigate]); + + const config = getConfig(trainingStatus); + return ( + + + + + + + + {config.textIds.map((textId, idx) => ( + + + + ))} + + + {config.navigateToStep === "add-data" ? ( + + ) : ( + + )} + + ); +}; + +export default TrainModelFirstView; diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx index 2d2e99bc8..d19c339ed 100644 --- a/src/components/TrainingStatusView.tsx +++ b/src/components/TrainingStatusView.tsx @@ -4,6 +4,7 @@ import { Heading, Progress, Text, + VStack, useDisclosure, } from "@chakra-ui/react"; import { ReactNode, useCallback, useState } from "react"; @@ -50,10 +51,10 @@ const TrainingStatusView = () => { switch (trainingStatus) { case TrainingStatus.InsufficientData: return ( - + + + + @@ -106,27 +107,20 @@ const TrainingStatusView = () => { interface TrainingStatusSectionProps { statusId: string; - descriptionId?: string; children: ReactNode; } const TrainingStatusSection = ({ statusId, - descriptionId, children, }: TrainingStatusSectionProps) => { return ( - <> + - {descriptionId && ( - - - - )} {children} - + ); }; diff --git a/src/pages/TestDataPage.tsx b/src/pages/TestDataPage.tsx deleted file mode 100644 index d12ebe47d..000000000 --- a/src/pages/TestDataPage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const TestDataPage = () => { - return <>Test data page!; -}; - -export default TestDataPage; diff --git a/src/pages/TestModelPage.tsx b/src/pages/TestModelPage.tsx new file mode 100644 index 000000000..6ea333e3b --- /dev/null +++ b/src/pages/TestModelPage.tsx @@ -0,0 +1,22 @@ +import DefaultPageLayout from "../components/DefaultPageLayout"; +import TabView from "../components/TabView"; +import TrainModelFirstView from "../components/TrainModelFirstView"; +import { testModelConfig } from "../steps-config"; +import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; + +const TestModelPage = () => { + const [trainingStatus] = useTrainingStatus(); + + return ( + + + {trainingStatus === TrainingStatus.Complete ? ( + <> + ) : ( + + )} + + ); +}; + +export default TestModelPage; diff --git a/src/pages/TrainModelPage.tsx b/src/pages/TrainModelPage.tsx index f29f30fa8..7c0752c70 100644 --- a/src/pages/TrainModelPage.tsx +++ b/src/pages/TrainModelPage.tsx @@ -20,8 +20,8 @@ const TrainModelPage = () => { h="249px" alt="" /> - - + + diff --git a/src/steps-config.ts b/src/steps-config.ts index 0a2733c4f..d863b1a43 100644 --- a/src/steps-config.ts +++ b/src/steps-config.ts @@ -2,7 +2,7 @@ import addDataImage from "./images/add_data.svg"; import testModelImage from "./images/test_model_blue.svg"; import trainModelImage from "./images/train_model_blue.svg"; import AddDataPage from "./pages/AddDataPage"; -import TestDataPage from "./pages/TestDataPage"; +import TestModelPage from "./pages/TestModelPage"; import TrainModelPage from "./pages/TrainModelPage"; export type StepId = "add-data" | "train-model" | "test-model"; @@ -25,14 +25,14 @@ export const trainModelConfig: StepConfig = { pageElement: TrainModelPage, }; -export const testDataConfig: StepConfig = { +export const testModelConfig: StepConfig = { id: "test-model", imgSrc: testModelImage, - pageElement: TestDataPage, + pageElement: TestModelPage, }; export const stepsConfig: StepConfig[] = [ addDataConfig, trainModelConfig, - testDataConfig, + testModelConfig, ]; From 8d9366fa17d5736dc2834c9c76d7c31b987cdec0 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 12 Jul 2024 11:41:20 +0100 Subject: [PATCH 043/172] Fix update training status in gesture hook The training state now includes hasTrainedBefore so that the logic for whether going to retrain state is in the useTrainingStatus hook. --- src/components/TrainingStatusView.tsx | 4 +- src/gestures-hooks.tsx | 53 +++++++++++++++++---------- src/training-status-hook.tsx | 52 ++++++++++++++++++++++---- 3 files changed, 79 insertions(+), 30 deletions(-) diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx index d19c339ed..2934f27f5 100644 --- a/src/components/TrainingStatusView.tsx +++ b/src/components/TrainingStatusView.tsx @@ -43,7 +43,7 @@ const TrainingStatusView = () => { setTrainingStatus(TrainingStatus.Complete); }, onError: () => { - setTrainingStatus(TrainingStatus.NotStarted); + setTrainingStatus(TrainingStatus.NotTrained); }, }); }, [data, setTrainingStatus]); @@ -60,7 +60,7 @@ const TrainingStatusView = () => { ); - case TrainingStatus.NotStarted: + case TrainingStatus.NotTrained: return ( <> void ) {} - private hasSufficientDataForTraining = (): boolean => { - return ( - this.gestureState.data.length > 2 && - this.gestureState.data.every((g) => g.recordings.length >= 3) - ); - }; - - setGestures = (gs: GestureData[], isRetrainNeeded: boolean = true) => { - this.setGestureState({ - ...this.gestureState, + setGestures = (gestures: GestureData[], isRetrainNeeded: boolean = true) => { + const data = // Always have at least one gesture - data: gs.length === 0 ? initialGestureContextState.data : gs, - }); + gestures.length === 0 ? initialGestureContextState.data : gestures; + this.setGestureState({ ...this.gestureState, data }); - // Update training status to retrain if needed - const hasTrainedBefore = this.trainingStatus === TrainingStatus.Complete; - this.setTrainingStatus( - this.hasSufficientDataForTraining() - ? isRetrainNeeded && hasTrainedBefore - ? TrainingStatus.Retrain - : this.trainingStatus - : TrainingStatus.InsufficientData + const newTrainingStatus = updateTrainingStatus( + data, + this.trainingStatus, + isRetrainNeeded ); + this.setTrainingStatus(newTrainingStatus); }; addNewGesture = () => { @@ -180,3 +169,27 @@ export class GestureActions { this.setGestures([]); }; } + +const hasSufficientDataForTraining = (gestures: GestureData[]): boolean => { + return ( + gestures.length >= 2 && gestures.every((g) => g.recordings.length >= 3) + ); +}; + +const updateTrainingStatus = ( + data: GestureData[], + currTrainingStatus: TrainingStatus, + isTrainingNeeded: boolean +) => { + if (!hasSufficientDataForTraining(data)) { + return TrainingStatus.InsufficientData; + } + if ( + isTrainingNeeded || + currTrainingStatus === TrainingStatus.InsufficientData + ) { + // Logic for updating status to retrain is in the training status hook + return TrainingStatus.NotTrained; + } + return currTrainingStatus; +}; diff --git a/src/training-status-hook.tsx b/src/training-status-hook.tsx index a8fb45fb9..6cadc29b6 100644 --- a/src/training-status-hook.tsx +++ b/src/training-status-hook.tsx @@ -1,27 +1,42 @@ -import { ReactNode, createContext, useContext, useState } from "react"; +import { + ReactNode, + createContext, + useCallback, + useContext, + useState, +} from "react"; export enum TrainingStatus { InsufficientData, - NotStarted, + NotTrained, InProgress, Complete, Retrain, } -type TrainingContextValue = [TrainingStatus, (status: TrainingStatus) => void]; +interface TrainingState { + status: TrainingStatus; + hasTrainedBefore: boolean; +} + +type TrainingContextValue = [TrainingState, (state: TrainingState) => void]; const TrainingStatusContext = createContext( undefined ); +const initialTrainingState = { + status: TrainingStatus.NotTrained, + hasTrainedBefore: false, +}; + export const TrainingStatusProvider = ({ children, }: { children: ReactNode; }) => { - const trainingStatusContextValue = useState( - TrainingStatus.NotStarted - ); + const trainingStatusContextValue = + useState(initialTrainingState); return ( {children} @@ -29,10 +44,31 @@ export const TrainingStatusProvider = ({ ); }; -export const useTrainingStatus = (): TrainingContextValue => { +export const useTrainingStatus = (): [ + TrainingStatus, + (status: TrainingStatus) => void +] => { const trainingStatusContextValue = useContext(TrainingStatusContext); if (!trainingStatusContextValue) { throw new Error("Missing provider"); } - return trainingStatusContextValue; + const [state, setState] = trainingStatusContextValue; + + const setTrainingStatus = useCallback( + (trainingStatus: TrainingStatus) => { + const hasTrainedBefore = + trainingStatus === TrainingStatus.Complete || state.hasTrainedBefore; + + // Update to retrain instead of not trained if has trained before + const status = + hasTrainedBefore && trainingStatus === TrainingStatus.NotTrained + ? TrainingStatus.Retrain + : trainingStatus; + + setState({ ...state, status, hasTrainedBefore }); + }, + [setState, state] + ); + + return [state.status, setTrainingStatus]; }; From 73fdf0b9b9202f6c2b50597d815a5b399f42203d Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 12 Jul 2024 17:48:37 +0100 Subject: [PATCH 044/172] WIP Test model page - UI for grid view and creating shared components between add data grid view and test model grid view - Separating gesture data state into stored part and not stored part in preparation of holding confidence data - Adding predict function in ml.ts - Add a ml actions hook --- ...aGridGestureRow.tsx => AddDataGridRow.tsx} | 25 +-- src/components/AddDataGridView.tsx | 53 +++---- src/components/AddDataGridWalkThrough.tsx | 11 +- src/components/CertaintyThresholdGridItem.tsx | 79 ++++++++++ ...GridItem.tsx => DataRecordingGridItem.tsx} | 28 ++-- ...meGridItem.tsx => GestureNameGridItem.tsx} | 61 +++++--- src/components/HeadingGrid.tsx | 42 +++++ src/components/PercentageMeter.tsx | 71 +++++++++ src/components/TestModelGridView.tsx | 60 ++++++++ src/components/TrainingStatusView.tsx | 34 ++--- src/deployment/default/colors.ts | 2 + src/deployment/default/components/button.ts | 2 +- src/gestures-hooks.tsx | 52 ++++++- src/ml-actions.tsx | 144 ++++++++++++++++++ src/ml.ts | 36 ++++- src/pages/TestModelPage.tsx | 3 +- src/training-status-hook.tsx | 1 + 17 files changed, 577 insertions(+), 127 deletions(-) rename src/components/{AddDataGridGestureRow.tsx => AddDataGridRow.tsx} (66%) create mode 100644 src/components/CertaintyThresholdGridItem.tsx rename src/components/{AddDataGestureRecordingGridItem.tsx => DataRecordingGridItem.tsx} (82%) rename src/components/{AddDataGestureNameGridItem.tsx => GestureNameGridItem.tsx} (61%) create mode 100644 src/components/HeadingGrid.tsx create mode 100644 src/components/PercentageMeter.tsx create mode 100644 src/components/TestModelGridView.tsx create mode 100644 src/ml-actions.tsx diff --git a/src/components/AddDataGridGestureRow.tsx b/src/components/AddDataGridRow.tsx similarity index 66% rename from src/components/AddDataGridGestureRow.tsx rename to src/components/AddDataGridRow.tsx index 6a6ee535d..10d48ae4b 100644 --- a/src/components/AddDataGridGestureRow.tsx +++ b/src/components/AddDataGridRow.tsx @@ -1,25 +1,25 @@ import { useCallback } from "react"; import { useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; -import AddDataGestureRecordingGridItem from "./AddDataGestureRecordingGridItem"; -import AddDataGestureNameGridItem from "./AddDataGestureNameGridItem"; +import DataRecordingGridItem from "./DataRecordingGridItem"; +import GestureNameGridItem from "./GestureNameGridItem"; import { GridItem } from "@chakra-ui/react"; -interface AddDataGridGestureRowProps { +interface AddDataGridRowProps { gesture: GestureData; selected: boolean; onSelectRow: () => void; } -const AddDataGridGestureRow = ({ +const AddDataGridRow = ({ gesture, selected, onSelectRow, -}: AddDataGridGestureRowProps) => { +}: AddDataGridRowProps) => { const intl = useIntl(); const actions = useGestureActions(); - const handleDeleteGesture = useCallback(() => { + const handleDeleteDataItem = useCallback(() => { const confirmationText = intl.formatMessage( { id: "alert.deleteGestureConfirm" }, { action: gesture.name } @@ -32,16 +32,17 @@ const AddDataGridGestureRow = ({ return ( <> - {gesture.name.length > 0 || gesture.recordings.length > 0 ? ( - @@ -53,4 +54,4 @@ const AddDataGridGestureRow = ({ ); }; -export default AddDataGridGestureRow; +export default AddDataGridRow; diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index ae0b7e185..da04978f0 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -1,10 +1,9 @@ -import { Grid, GridItem, GridProps, HStack, Text } from "@chakra-ui/react"; +import { Grid, GridProps } from "@chakra-ui/react"; import { useMemo, useState } from "react"; -import { FormattedMessage } from "react-intl"; import { useGestureData } from "../gestures-hooks"; -import AddDataGridGestureRow from "./AddDataGridGestureRow"; +import AddDataGridRow from "./AddDataGridRow"; import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; -import InfoToolTip, { InfoToolTipProps } from "./InfoToolTip"; +import HeadingGrid from "./HeadingGrid"; const gridCommonProps: Partial = { gridTemplateColumns: "200px 1fr", @@ -26,26 +25,21 @@ const AddDataGridView = () => { return ( <> - - - - + {...gridCommonProps} + headings={[ + { + titleId: "content.data.classification", + descriptionId: "content.data.classHelpBody", + }, + { + titleId: "content.data.data", + descriptionId: "content.data.dataDescription", + }, + ]} + /> { ) : ( gestures.data.map((g, idx) => ( - { ); }; -const GridColumnHeadingItem = (props: InfoToolTipProps) => { - return ( - - - - - - - - - ); -}; - export default AddDataGridView; diff --git a/src/components/AddDataGridWalkThrough.tsx b/src/components/AddDataGridWalkThrough.tsx index ae14cbd05..d14e6f1c6 100644 --- a/src/components/AddDataGridWalkThrough.tsx +++ b/src/components/AddDataGridWalkThrough.tsx @@ -2,8 +2,8 @@ import { GridItem, VStack, Image, Text, HStack } from "@chakra-ui/react"; import { GestureData } from "../gestures-hooks"; import greetingEmojiWithArrowImage from "../images/greeting-emoji-with-arrow.svg"; import upCurveArrowImage from "../images/curve-arrow-up.svg"; -import AddDataGestureNameGridItem from "./AddDataGestureNameGridItem"; -import AddDataGestureRecordingGridItem from "./AddDataGestureRecordingGridItem"; +import GestureNameGridItem from "./GestureNameGridItem"; +import DataRecordingGridItem from "./DataRecordingGridItem"; import { FormattedMessage } from "react-intl"; interface AddDataGridWalkThrough { @@ -13,10 +13,11 @@ interface AddDataGridWalkThrough { const AddDataGridWalkThrough = ({ gesture }: AddDataGridWalkThrough) => { return ( <> - {gesture.name.length === 0 ? ( @@ -29,7 +30,7 @@ const AddDataGridWalkThrough = ({ gesture }: AddDataGridWalkThrough) => { ) : ( <> - + {/* Empty grid item to fill first column of grid */} diff --git a/src/components/CertaintyThresholdGridItem.tsx b/src/components/CertaintyThresholdGridItem.tsx new file mode 100644 index 000000000..dee590c58 --- /dev/null +++ b/src/components/CertaintyThresholdGridItem.tsx @@ -0,0 +1,79 @@ +import { + Card, + CardBody, + GridItem, + Slider, + SliderFilledTrack, + SliderThumb, + SliderTrack, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage, useIntl } from "react-intl"; +import PercentageMeter from "./PercentageMeter"; + +interface CertaintyThresholdGridItemProps { + requiredConfidence?: number; + currentConfidence?: number; + onThresholdChange: (val: number) => void; + isTriggered: boolean; +} + +const CertaintyThresholdGridItem = ({ + requiredConfidence = 0, + currentConfidence = 0, + onThresholdChange, + isTriggered, +}: CertaintyThresholdGridItemProps) => { + const intl = useIntl(); + const barWidth = 240; + return ( + + + + + + + + + + + + + + + + + + + ); +}; + +export default CertaintyThresholdGridItem; diff --git a/src/components/AddDataGestureRecordingGridItem.tsx b/src/components/DataRecordingGridItem.tsx similarity index 82% rename from src/components/AddDataGestureRecordingGridItem.tsx rename to src/components/DataRecordingGridItem.tsx index 615e7b82f..74935661d 100644 --- a/src/components/AddDataGestureRecordingGridItem.tsx +++ b/src/components/DataRecordingGridItem.tsx @@ -15,17 +15,17 @@ import RecordingGraph from "./RecordingGraph"; import RecordingDialog from "./RecordingDialog"; import { useCallback, useRef } from "react"; -interface AddDataGestureRecordingGridItemProps { - gesture: GestureData; +interface DataRecordingGridItemProps { + data: GestureData; selected: boolean; onSelectRow?: () => void; } -const AddDataGestureRecordingGridItem = ({ - gesture, +const DataRecordingGridItem = ({ + data, selected, onSelectRow, -}: AddDataGestureRecordingGridItemProps) => { +}: DataRecordingGridItemProps) => { const intl = useIntl(); const { isOpen, onClose, onOpen: onStartRecording } = useDisclosure(); const closeRecordingDialogFocusRef = useRef(null); @@ -33,20 +33,20 @@ const AddDataGestureRecordingGridItem = ({ // TODO: Replace with checking if micro:bit is connected const isConnected = true; - const handleDeleteGestureRecording = useCallback( + const handleDeleteRecording = useCallback( (idx: number) => { - actions.deleteGestureRecording(gesture.ID, idx); + actions.deleteGestureRecording(data.ID, idx); }, - [actions, gesture.ID] + [actions, data.ID] ); return ( <> @@ -72,7 +72,7 @@ const AddDataGestureRecordingGridItem = ({ _hover={{ backgroundColor: "transparent" }} aria-label={intl.formatMessage( { id: "content.data.recordAction" }, - { action: gesture.name } + { action: data.name } )} isDisabled={!isConnected} icon={ @@ -85,7 +85,7 @@ const AddDataGestureRecordingGridItem = ({ } /> - {gesture.recordings.map((recording, idx) => ( + {data.recordings.map((recording, idx) => ( { - handleDeleteGestureRecording(idx); + handleDeleteRecording(idx); }} /> @@ -109,4 +109,4 @@ const AddDataGestureRecordingGridItem = ({ ); }; -export default AddDataGestureRecordingGridItem; +export default DataRecordingGridItem; diff --git a/src/components/AddDataGestureNameGridItem.tsx b/src/components/GestureNameGridItem.tsx similarity index 61% rename from src/components/AddDataGestureNameGridItem.tsx rename to src/components/GestureNameGridItem.tsx index ab7bb27e9..1b9cb482c 100644 --- a/src/components/AddDataGestureNameGridItem.tsx +++ b/src/components/GestureNameGridItem.tsx @@ -11,23 +11,25 @@ import { useCallback } from "react"; import { useIntl } from "react-intl"; import { useGestureActions } from "../gestures-hooks"; -interface AddDataGestureNameGridItemProps { +interface GestureNameGridItemProps { name: string; onCloseClick?: () => void; onSelectRow?: () => void; - gestureId: number; - selected: boolean; + id: number; + selected?: boolean; + readOnly: boolean; } const gestureNameMaxLength = 18; -const AddDataGestureNameGridItem = ({ +const GestureNameGridItem = ({ name, onCloseClick, onSelectRow, - gestureId, - selected, -}: AddDataGestureNameGridItemProps) => { + id, + selected = false, + readOnly = false, +}: GestureNameGridItemProps) => { const intl = useIntl(); const toast = useToast(); const toastId = "name-too-long-toast"; @@ -51,9 +53,9 @@ const AddDataGestureNameGridItem = ({ }); return; } - actions.setGestureName(gestureId, name); + actions.setGestureName(id, name); }, - [actions, gestureId, intl, toast] + [actions, id, intl, toast] ); return ( @@ -66,29 +68,38 @@ const AddDataGestureNameGridItem = ({ borderWidth={selected ? 1 : 0} onClick={onSelectRow} > - - {onCloseClick && ( - - )} - - + {!readOnly && ( + + {onCloseClick && ( + + )} + + )} + @@ -96,4 +107,4 @@ const AddDataGestureNameGridItem = ({ ); }; -export default AddDataGestureNameGridItem; +export default GestureNameGridItem; diff --git a/src/components/HeadingGrid.tsx b/src/components/HeadingGrid.tsx new file mode 100644 index 000000000..1d96d5371 --- /dev/null +++ b/src/components/HeadingGrid.tsx @@ -0,0 +1,42 @@ +import { Grid, GridItem, GridProps, HStack, Text } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import InfoToolTip, { InfoToolTipProps } from "./InfoToolTip"; + +type GridColumnHeadingItemProps = InfoToolTipProps; + +interface HeadingGridProps extends Omit { + headings: GridColumnHeadingItemProps[]; +} + +const HeadingGrid = ({ headings, ...props }: HeadingGridProps) => { + return ( + + {headings.map((props, idx) => ( + + ))} + + ); +}; + +const GridColumnHeadingItem = (props: GridColumnHeadingItemProps) => { + return ( + + + + + + + + + ); +}; + +export default HeadingGrid; diff --git a/src/components/PercentageMeter.tsx b/src/components/PercentageMeter.tsx new file mode 100644 index 000000000..9b2d61016 --- /dev/null +++ b/src/components/PercentageMeter.tsx @@ -0,0 +1,71 @@ +import { HStack, StackProps, Text } from "@chakra-ui/react"; + +interface PercentageMeterProps extends StackProps { + percentage: number; + colorScheme?: string; + meterBarWidthPx?: number; +} + +const PercentageMeter = ({ + percentage, + colorScheme = "gray.600", + meterBarWidthPx, + ...props +}: PercentageMeterProps) => { + const height = 3; + const numTicks = 9; + return ( + + + + + { + // Adding 2 transparent ticks for each end + // so that it can be justified using space-between + Array(numTicks + 2) + .fill(0) + .map((_, i) => ( + + )) + } + + + + {`${Math.round(percentage)}%`} + + + ); +}; + +export default PercentageMeter; diff --git a/src/components/TestModelGridView.tsx b/src/components/TestModelGridView.tsx new file mode 100644 index 000000000..6b3311946 --- /dev/null +++ b/src/components/TestModelGridView.tsx @@ -0,0 +1,60 @@ +import { Grid, GridProps } from "@chakra-ui/react"; +import React, { useMemo } from "react"; +import { useGestureData } from "../gestures-hooks"; +import { useMlActions } from "../ml-actions"; +import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; +import GestureNameGridItem from "./GestureNameGridItem"; +import HeadingGrid from "./HeadingGrid"; + +const gridCommonProps: Partial = { + gridTemplateColumns: "200px 1fr", + gap: 3, + px: 10, + py: 2, + w: "100%", +}; + +const TestModelGridView = () => { + const [gestures] = useGestureData(); + const actions = useMlActions(); + const predicted = useMemo(() => actions.getPredicted(), [actions]); + + return ( + <> + + + {gestures.data.map(({ ID, name, confidence }, idx) => ( + + + {}} + {...confidence} + isTriggered={predicted?.ID === ID} + /> + + ))} + + + ); +}; + +export default TestModelGridView; diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx index 2934f27f5..e37ff7a87 100644 --- a/src/components/TrainingStatusView.tsx +++ b/src/components/TrainingStatusView.tsx @@ -7,20 +7,19 @@ import { VStack, useDisclosure, } from "@chakra-ui/react"; -import { ReactNode, useCallback, useState } from "react"; +import { ReactNode, useCallback, useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; -import { useGestureData } from "../gestures-hooks"; -import { trainModel } from "../ml"; +import { useMlActions } from "../ml-actions"; +import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; import { createStepPageUrl } from "../urls"; import TrainingButton from "./TrainingButton"; import TrainingErrorDialog from "./TrainingErrorDialog"; -import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; const TrainingStatusView = () => { const navigate = useNavigate(); - const [{ data }] = useGestureData(); - const [trainingStatus, setTrainingStatus] = useTrainingStatus(); + const [trainingStatus] = useTrainingStatus(); + const actions = useMlActions(); const [trainProgress, setTrainProgress] = useState(0); const trainErrorDialog = useDisclosure(); @@ -33,20 +32,14 @@ const TrainingStatusView = () => { }, [navigate]); const handleTrain = useCallback(async () => { - setTrainingStatus(TrainingStatus.InProgress); - await trainModel({ - data, - onTraining: (progress) => { - setTrainProgress(progress); - }, - onTrainEnd: () => { - setTrainingStatus(TrainingStatus.Complete); - }, - onError: () => { - setTrainingStatus(TrainingStatus.NotTrained); - }, - }); - }, [data, setTrainingStatus]); + await actions.trainModel((progress) => setTrainProgress(progress)); + }, [actions]); + + useEffect(() => { + if (trainingStatus === TrainingStatus.Error) { + trainErrorDialog.onOpen(); + } + }, [trainErrorDialog, trainingStatus]); switch (trainingStatus) { case TrainingStatus.InsufficientData: @@ -60,6 +53,7 @@ const TrainingStatusView = () => { ); + case TrainingStatus.Error: case TrainingStatus.NotTrained: return ( <> diff --git a/src/deployment/default/colors.ts b/src/deployment/default/colors.ts index 6f5c15410..b48121341 100644 --- a/src/deployment/default/colors.ts +++ b/src/deployment/default/colors.ts @@ -6,6 +6,8 @@ const gray = { ...theme.colors.gray, // Brand grey 500: "#e5e5e5", + // windi css text color + 600: "#6b7280", }; const brand = { diff --git a/src/deployment/default/components/button.ts b/src/deployment/default/components/button.ts index ccdc688a0..d359d3461 100644 --- a/src/deployment/default/components/button.ts +++ b/src/deployment/default/components/button.ts @@ -40,7 +40,7 @@ const Button: StyleConfig = { borderColor: "brand.600", }, _active: { - bg: "brand.50", + bg: "brand.500", borderColor: "brand.700", }, }), diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 1bebf8d99..5f16b8233 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,4 +1,4 @@ -import { ReactNode, createContext, useContext, useMemo } from "react"; +import { ReactNode, createContext, useContext, useMemo, useState } from "react"; import { useStorage } from "./hooks/use-storage"; import { TrainingStatus, useTrainingStatus } from "./training-status-hook"; export interface XYZData { @@ -12,13 +12,32 @@ interface RecordingData { data: XYZData; } -export interface GestureData { +interface StoredGestureData { name: string; ID: number; recordings: RecordingData[]; } +export interface GestureData extends StoredGestureData { + // Confidence is added after a successful training and predicting of data + confidence?: { + currentConfidence?: number; // from 0-1 + requiredConfidence: number; // from 0-1 + }; +} + +// Used for getPredicted in MlActions hook +export interface ConfidentGestureData extends StoredGestureData { + confidence: { + currentConfidence: number; // from 0-1 + requiredConfidence: number; // from 0-1 + }; +} -interface GestureContextState { +interface StoredGestureContextState { + data: StoredGestureData[]; +} + +export interface GestureContextState { data: GestureData[]; } @@ -27,7 +46,7 @@ type GestureContextValue = [ (gestureData: GestureContextState) => void ]; -const isValidGestureData = (v: unknown): v is GestureContextState => { +const isValidStoredGestureData = (v: unknown): v is GestureContextState => { if (typeof v !== "object") { return false; } @@ -78,14 +97,31 @@ const initialGestureContextState: GestureContextState = { }; export const GesturesProvider = ({ children }: { children: ReactNode }) => { - const gestures = useStorage( + // Only name, ID, and recordings of gesture data are stored in local storage + // so two separate states (one stored and the other not) are used to store + // gesture data and are kept in sync by this provider. + const [storedState, setStoredState] = useStorage( "local", "gestures", initialGestureContextState, - isValidGestureData + isValidStoredGestureData ); + const [state, setState] = useState({ + data: storedState.data as GestureData[], + }); + const setStates = (newState: GestureContextState) => { + setStoredState({ + ...newState, + data: newState.data.map(({ name, recordings, ID }) => ({ + name, + recordings, + ID, + })), + }); + setState(newState); + }; return ( - + {children} ); @@ -107,7 +143,7 @@ export const useGestureActions = () => { return actions; }; -export class GestureActions { +class GestureActions { constructor( private gestureState: GestureContextState, private setGestureState: (gestureData: GestureContextState) => void, diff --git a/src/ml-actions.tsx b/src/ml-actions.tsx new file mode 100644 index 000000000..5e1265659 --- /dev/null +++ b/src/ml-actions.tsx @@ -0,0 +1,144 @@ +import { LayersModel } from "@tensorflow/tfjs"; +import { useMemo } from "react"; +import { + ConfidentGestureData, + GestureContextState, + GestureData, + useGestureData, +} from "./gestures-hooks"; +import { Logging } from "./logging/logging"; +import { useLogging } from "./logging/logging-hooks"; +import { + Confidences, + TrainModelInput, + mlSettings, + predict, + trainModel, +} from "./ml"; +import gestureData from "./test-fixtures/gesture-data.json"; +import { TrainingStatus, useTrainingStatus } from "./training-status-hook"; + +const defaultRequiredConfidence = 0.8; + +export const useMlActions = () => { + const [gestures, setGestures] = useGestureData(); + const [trainingStatus, setTrainingStatus] = useTrainingStatus(); + const logger = useLogging(); + + const actions = useMemo( + () => + new MlActions( + logger, + gestures, + setGestures, + trainingStatus, + setTrainingStatus + ), + [gestures, logger, setGestures, setTrainingStatus, trainingStatus] + ); + return actions; +}; + +class MlActions { + private predictionInterval: NodeJS.Timeout | undefined; + private model: LayersModel | undefined; + constructor( + private logger: Logging, + private gestureState: GestureContextState, + private setGestureState: (gestureData: GestureContextState) => void, + private trainingStatus: TrainingStatus, + private setTrainingStatus: (status: TrainingStatus) => void + ) {} + + trainModel = async (onTraining: TrainModelInput["onTraining"]) => { + this.setTrainingStatus(TrainingStatus.InProgress); + const { data } = this.gestureState; + const model = await trainModel({ + data, + onTraining, + onTrainEnd: () => this.setTrainingStatus(TrainingStatus.Complete), + onError: () => this.setTrainingStatus(TrainingStatus.Error), + }); + + if (!model) { + // Errors in training a model are handled in train model function + return; + } + + this.model = model; + this.logger.event({ + type: "Data", + message: "Train model", + detail: { + numActions: data.length, + numRecordings: data.reduce((acc, d) => d.recordings.length + acc, 0), + }, + }); + this.predictionInterval = setInterval( + this.testModel, + 1000 / mlSettings.updatesPrSecond + ); + }; + + private testModel = async () => { + if (this.checkIfInterrupted() && this.predictionInterval) { + clearInterval(this.predictionInterval); + } + + // TODO: Hook for input connection + const inputIsConnected = true; + if (inputIsConnected) { + const confidences = await predict({ + model: this.model as LayersModel, + // TODO: Get data from accelerometer instead of using dummy data + data: gestureData[1].recordings[0].data, + classificationIds: this.gestureState.data.map((d) => d.ID), + }); + if (!confidences) { + // Error is handled in predict function + return; + } + this.updateGestureDataConfidences(confidences); + } + }; + + private checkIfInterrupted = () => + // TODO: Add state check for whether user is recording data + this.trainingStatus !== TrainingStatus.Complete; + + private updateGestureDataConfidences = (confidences: Confidences) => { + const updatedGestureData = this.gestureState.data.map( + (gesture): GestureData => { + return { + ...gesture, + confidence: { + requiredConfidence: + gesture.confidence?.requiredConfidence ?? + defaultRequiredConfidence, + currentConfidence: confidences[gesture.ID] ?? undefined, + }, + }; + } + ); + this.setGestureState({ data: updatedGestureData }); + }; + + getPredicted = () => { + const confidentGestures = this.gestureState.data.filter((g) => { + return ( + g.confidence !== undefined && + g.confidence.currentConfidence !== undefined && + g.confidence.currentConfidence > g.confidence.requiredConfidence + ); + }) as ConfidentGestureData[]; + if (confidentGestures.length === 0) { + return undefined; + } + return confidentGestures.reduce((prevPredictedGesture, gesture) => { + return prevPredictedGesture.confidence.currentConfidence < + gesture.confidence.currentConfidence + ? gesture + : prevPredictedGesture; + }); + }; +} diff --git a/src/ml.ts b/src/ml.ts index ce83f1acd..e6ab6de5f 100644 --- a/src/ml.ts +++ b/src/ml.ts @@ -9,11 +9,10 @@ export enum Axes { Z = "z", } -const mlSettings = { +export const mlSettings = { duration: 1800, // Duration of recording numSamples: 80, // number of samples in one recording (when recording samples) minSamples: 80, // minimum number of samples for reliable detection (when detecting gestures) - automaticClassification: true, // If true, automatically classify gestures updatesPrSecond: 4, // Times algorithm predicts data pr second numEpochs: 80, // Number of epochs for ML learningRate: 0.5, @@ -30,7 +29,7 @@ const mlSettings = { ]), }; -interface TrainModelInput { +export interface TrainModelInput { data: GestureData[]; onTrainEnd?: () => void; onTraining?: (progress: number) => void; @@ -90,7 +89,7 @@ export const prepareFeaturesAndLabels = ( return { features, labels }; }; -function createModel(gestureData: GestureData[]): tf.LayersModel { +const createModel = (gestureData: GestureData[]): tf.LayersModel => { const numberOfClasses: number = gestureData.length; const inputShape = [ mlSettings.includedFilters.size * mlSettings.includedAxes.length, @@ -113,7 +112,7 @@ function createModel(gestureData: GestureData[]): tf.LayersModel { }); return model; -} +}; // Exported for testing // applyFilters reduces array of x, y and z inputs to a single number array with values. @@ -123,3 +122,30 @@ export const applyFilters = ({ x, y, z }: XYZData): number[] => { return [...acc, filterStrategy(x), filterStrategy(y), filterStrategy(z)]; }, [] as number[]); }; + +interface PredictInput { + model: tf.LayersModel; + data: XYZData; + classificationIds: number[]; +} + +export type Confidences = Record; + +// For predicting +export const predict = async ({ + model, + data, + classificationIds, +}: PredictInput): Promise => { + const input = applyFilters(data); + const prediction = model.predict(tf.tensor([input])) as tf.Tensor; + try { + const confidences = (await prediction.data()) as Float32Array; + return classificationIds.reduce( + (acc, id, idx) => ({ ...acc, [id]: confidences[idx] }), + {} + ); + } catch (e) { + console.error("Prediction error:", e); + } +}; diff --git a/src/pages/TestModelPage.tsx b/src/pages/TestModelPage.tsx index 6ea333e3b..2c62b51b8 100644 --- a/src/pages/TestModelPage.tsx +++ b/src/pages/TestModelPage.tsx @@ -1,5 +1,6 @@ import DefaultPageLayout from "../components/DefaultPageLayout"; import TabView from "../components/TabView"; +import TestModelGridView from "../components/TestModelGridView"; import TrainModelFirstView from "../components/TrainModelFirstView"; import { testModelConfig } from "../steps-config"; import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; @@ -11,7 +12,7 @@ const TestModelPage = () => { {trainingStatus === TrainingStatus.Complete ? ( - <> + ) : ( )} diff --git a/src/training-status-hook.tsx b/src/training-status-hook.tsx index 6cadc29b6..16c91c8f1 100644 --- a/src/training-status-hook.tsx +++ b/src/training-status-hook.tsx @@ -12,6 +12,7 @@ export enum TrainingStatus { InProgress, Complete, Retrain, + Error, } interface TrainingState { From d6f079456b0599c07d0e274dea77e4e5a0bfa867 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 15 Jul 2024 12:20:26 +0100 Subject: [PATCH 045/172] Test model page estimated action card --- src/components/CertaintyThresholdGridItem.tsx | 19 +++-- src/components/HeadingGrid.tsx | 2 +- src/components/PercentageDisplay.tsx | 24 ++++++ src/components/PercentageMeter.tsx | 83 ++++++++----------- src/components/TestModelGridView.tsx | 62 +++++++++++++- 5 files changed, 132 insertions(+), 58 deletions(-) create mode 100644 src/components/PercentageDisplay.tsx diff --git a/src/components/CertaintyThresholdGridItem.tsx b/src/components/CertaintyThresholdGridItem.tsx index dee590c58..f8b7a8996 100644 --- a/src/components/CertaintyThresholdGridItem.tsx +++ b/src/components/CertaintyThresholdGridItem.tsx @@ -2,6 +2,7 @@ import { Card, CardBody, GridItem, + HStack, Slider, SliderFilledTrack, SliderThumb, @@ -11,6 +12,7 @@ import { } from "@chakra-ui/react"; import { FormattedMessage, useIntl } from "react-intl"; import PercentageMeter from "./PercentageMeter"; +import PercentageDisplay from "./PercentageDisplay"; interface CertaintyThresholdGridItemProps { requiredConfidence?: number; @@ -46,12 +48,17 @@ const CertaintyThresholdGridItem = ({ gap={1} justifyContent="center" > - + + + + diff --git a/src/components/HeadingGrid.tsx b/src/components/HeadingGrid.tsx index 1d96d5371..e0bd6b0d6 100644 --- a/src/components/HeadingGrid.tsx +++ b/src/components/HeadingGrid.tsx @@ -11,13 +11,13 @@ interface HeadingGridProps extends Omit { const HeadingGrid = ({ headings, ...props }: HeadingGridProps) => { return ( {headings.map((props, idx) => ( diff --git a/src/components/PercentageDisplay.tsx b/src/components/PercentageDisplay.tsx new file mode 100644 index 000000000..67e5c1a67 --- /dev/null +++ b/src/components/PercentageDisplay.tsx @@ -0,0 +1,24 @@ +import { StackProps, Text } from "@chakra-ui/react"; + +interface PercentageDisplayProps extends StackProps { + value: number; + colorScheme?: string; +} + +const PercentageDisplay = ({ + value, + colorScheme = "gray.600", +}: PercentageDisplayProps) => { + return ( + {`${Math.round(value * 100)}%`} + ); +}; + +export default PercentageDisplay; diff --git a/src/components/PercentageMeter.tsx b/src/components/PercentageMeter.tsx index 9b2d61016..d50158228 100644 --- a/src/components/PercentageMeter.tsx +++ b/src/components/PercentageMeter.tsx @@ -1,68 +1,55 @@ -import { HStack, StackProps, Text } from "@chakra-ui/react"; +import { HStack, StackProps } from "@chakra-ui/react"; interface PercentageMeterProps extends StackProps { - percentage: number; + value: number; colorScheme?: string; meterBarWidthPx?: number; } const PercentageMeter = ({ - percentage, + value, colorScheme = "gray.600", meterBarWidthPx, - ...props }: PercentageMeterProps) => { const height = 3; const numTicks = 9; return ( - + + - - - { - // Adding 2 transparent ticks for each end - // so that it can be justified using space-between - Array(numTicks + 2) - .fill(0) - .map((_, i) => ( - - )) - } - - - - {`${Math.round(percentage)}%`} + { + // Adding 2 transparent ticks for each end + // so that it can be justified using space-between + Array(numTicks + 2) + .fill(0) + .map((_, i) => ( + + )) + } ); diff --git a/src/components/TestModelGridView.tsx b/src/components/TestModelGridView.tsx index 6b3311946..a7add74ab 100644 --- a/src/components/TestModelGridView.tsx +++ b/src/components/TestModelGridView.tsx @@ -1,10 +1,21 @@ -import { Grid, GridProps } from "@chakra-ui/react"; +import { + Card, + CardBody, + Grid, + GridProps, + HStack, + Text, + VisuallyHidden, +} from "@chakra-ui/react"; import React, { useMemo } from "react"; import { useGestureData } from "../gestures-hooks"; import { useMlActions } from "../ml-actions"; import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; import GestureNameGridItem from "./GestureNameGridItem"; import HeadingGrid from "./HeadingGrid"; +import { FormattedMessage, useIntl } from "react-intl"; +import InfoToolTip from "./InfoToolTip"; +import PercentageDisplay from "./PercentageDisplay"; const gridCommonProps: Partial = { gridTemplateColumns: "200px 1fr", @@ -15,14 +26,59 @@ const gridCommonProps: Partial = { }; const TestModelGridView = () => { + const intl = useIntl(); const [gestures] = useGestureData(); const actions = useMlActions(); - const predicted = useMemo(() => actions.getPredicted(), [actions]); + const predicted = useMemo(() => { + const predictedGesture = actions.getPredicted(); + return ( + predictedGesture || { + name: intl.formatMessage({ + id: "content.model.output.estimatedGesture.none", + }), + } + ); + }, [actions, intl]); return ( <> + + + + + + + + + + + {predicted.name} + + + {"confidence" in predicted && ( + + )} + + + + + { {}} {...confidence} - isTriggered={predicted?.ID === ID} + isTriggered={"ID" in predicted && predicted.ID === ID} /> ))} From cc153b068bff380457419a25ccce3348b802d1ea Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 15 Jul 2024 12:49:41 +0100 Subject: [PATCH 046/172] Add Live Graph Panel --- src/pages/TestModelPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/TestModelPage.tsx b/src/pages/TestModelPage.tsx index 2c62b51b8..a102cc7a3 100644 --- a/src/pages/TestModelPage.tsx +++ b/src/pages/TestModelPage.tsx @@ -1,4 +1,5 @@ import DefaultPageLayout from "../components/DefaultPageLayout"; +import LiveGraphPanel from "../components/LiveGraphPanel"; import TabView from "../components/TabView"; import TestModelGridView from "../components/TestModelGridView"; import TrainModelFirstView from "../components/TrainModelFirstView"; @@ -12,7 +13,10 @@ const TestModelPage = () => { {trainingStatus === TrainingStatus.Complete ? ( - + <> + + + ) : ( )} From ad1124c30227562e7b5ef204caaf234f262e8773 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 15 Jul 2024 13:04:36 +0100 Subject: [PATCH 047/172] Handle gesture threshold change --- src/components/CertaintyThresholdGridItem.tsx | 8 +++++++- src/components/TestModelGridView.tsx | 4 +++- src/ml-actions.tsx | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/components/CertaintyThresholdGridItem.tsx b/src/components/CertaintyThresholdGridItem.tsx index f8b7a8996..ac04ce34b 100644 --- a/src/components/CertaintyThresholdGridItem.tsx +++ b/src/components/CertaintyThresholdGridItem.tsx @@ -13,6 +13,7 @@ import { import { FormattedMessage, useIntl } from "react-intl"; import PercentageMeter from "./PercentageMeter"; import PercentageDisplay from "./PercentageDisplay"; +import { useCallback } from "react"; interface CertaintyThresholdGridItemProps { requiredConfidence?: number; @@ -29,6 +30,11 @@ const CertaintyThresholdGridItem = ({ }: CertaintyThresholdGridItemProps) => { const intl = useIntl(); const barWidth = 240; + + const handleThresholdChange = useCallback( + (val: number) => onThresholdChange(val * 0.01), + [onThresholdChange] + ); return ( { {}} + onThresholdChange={(val) => + actions.updateGestureRequiredConfidence(ID, val) + } {...confidence} isTriggered={"ID" in predicted && predicted.ID === ID} /> diff --git a/src/ml-actions.tsx b/src/ml-actions.tsx index 5e1265659..933403653 100644 --- a/src/ml-actions.tsx +++ b/src/ml-actions.tsx @@ -141,4 +141,24 @@ class MlActions { : prevPredictedGesture; }); }; + + updateGestureRequiredConfidence = ( + gestureId: GestureData["ID"], + requiredConfidence: number + ) => { + this.setGestureState({ + ...this.setGestureState, + data: this.gestureState.data.map((gesture) => { + return gesture.ID === gestureId + ? { + ...gesture, + confidence: { + ...(gesture.confidence || {}), + requiredConfidence, + }, + } + : gesture; + }), + }); + }; } From b5f5d8503f2734b85d5658f98831558ac4fd81d7 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 15 Jul 2024 13:07:27 +0100 Subject: [PATCH 048/172] Truncate long gesture names --- src/components/GestureNameGridItem.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/GestureNameGridItem.tsx b/src/components/GestureNameGridItem.tsx index 1b9cb482c..16fd61933 100644 --- a/src/components/GestureNameGridItem.tsx +++ b/src/components/GestureNameGridItem.tsx @@ -89,6 +89,7 @@ const GestureNameGridItem = ({ alignContent="center" > Date: Mon, 15 Jul 2024 14:03:04 +0100 Subject: [PATCH 049/172] Generalise trainingStatus -> status as a way to keep track of which stage of the process the user is at --- src/App.tsx | 6 +-- src/components/RecordingDialog.tsx | 8 ++- src/components/TrainModelFirstView.tsx | 12 ++--- src/components/TrainingButton.tsx | 7 ++- src/components/TrainingStatusView.tsx | 29 +++++----- src/gestures-hooks.tsx | 43 ++++++--------- src/ml-actions.tsx | 41 +++++--------- src/ml.ts | 2 +- src/pages/TestModelPage.tsx | 6 +-- src/status-hook.tsx | 75 ++++++++++++++++++++++++++ src/training-status-hook.tsx | 75 -------------------------- 11 files changed, 140 insertions(+), 164 deletions(-) create mode 100644 src/status-hook.tsx delete mode 100644 src/training-status-hook.tsx diff --git a/src/App.tsx b/src/App.tsx index e92477c8a..b9a4cb955 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,7 +19,7 @@ import { stepsConfig } from "./steps-config"; import { LoggingProvider } from "./logging/logging-hooks"; import { ConnectionFlowProvider } from "./connections"; import { GesturesProvider } from "./gestures-hooks"; -import { TrainingStatusProvider } from "./training-status-hook"; +import { StatusProvider } from "./status-hook"; export interface ProviderLayoutProps { children: ReactNode; @@ -37,7 +37,7 @@ const Providers = ({ children }: ProviderLayoutProps) => { - + @@ -45,7 +45,7 @@ const Providers = ({ children }: ProviderLayoutProps) => { - + diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index 8ec1a5c39..1847649d0 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -15,6 +15,7 @@ import { motion } from "framer-motion"; import { FormattedMessage, useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; import gestureData from "../test-fixtures/gesture-data.json"; +import { Stage, useStatus } from "../status-hook"; const recordingDuration = 1800; @@ -47,6 +48,7 @@ const RecordingDialog = ({ }: RecordingDialogProps) => { const intl = useIntl(); const actions = useGestureActions(); + const [, setStatus] = useStatus(); const [recordingStatus, setRecordingStatus] = useState( RecordingStatus.None ); @@ -68,9 +70,10 @@ const RecordingDialog = ({ const handleOnClose = useCallback(() => { setRecordingStatus(RecordingStatus.None); + setStatus({ stage: Stage.NotTrained }); setIsCountdownIdx(0); onClose(); - }, [onClose]); + }, [onClose, setStatus]); useEffect(() => { if (isOpen) { @@ -90,10 +93,11 @@ const RecordingDialog = ({ return; } else { setRecordingStatus(RecordingStatus.Recording); + setStatus({ stage: Stage.RecordingData }); } }, config.duration); } - }, [countdownConfigs, isOpen, recordingStatus, countdownIdx]); + }, [countdownConfigs, isOpen, recordingStatus, countdownIdx, setStatus]); useEffect(() => { if (recordingStatus === RecordingStatus.Recording) { diff --git a/src/components/TrainModelFirstView.tsx b/src/components/TrainModelFirstView.tsx index 1b03dda2a..da8ecd74a 100644 --- a/src/components/TrainModelFirstView.tsx +++ b/src/components/TrainModelFirstView.tsx @@ -4,7 +4,7 @@ import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; import testModelImage from "../images/test_model_black.svg"; import { StepId } from "../steps-config"; -import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; +import { Stage, useStatus } from "../status-hook"; import { createStepPageUrl } from "../urls"; import TrainingButton from "./TrainingButton"; @@ -13,9 +13,9 @@ interface TrainModelFirstViewConfig { navigateToStep: StepId; } -const getConfig = (status: TrainingStatus): TrainModelFirstViewConfig => { +const getConfig = (status: Stage): TrainModelFirstViewConfig => { switch (status) { - case TrainingStatus.InsufficientData: + case Stage.InsufficientData: return { textIds: [ "content.model.notEnoughDataInfoBody1", @@ -23,7 +23,7 @@ const getConfig = (status: TrainingStatus): TrainModelFirstViewConfig => { ], navigateToStep: "add-data", }; - case TrainingStatus.Retrain: + case Stage.RetrainingNeeded: return { textIds: ["content.model.retrainModelBody"], navigateToStep: "train-model", @@ -38,7 +38,7 @@ const getConfig = (status: TrainingStatus): TrainModelFirstViewConfig => { const TrainModelFirstView = () => { const navigate = useNavigate(); - const [trainingStatus] = useTrainingStatus(); + const [{ stage }] = useStatus(); const navigateToDataPage = useCallback(() => { navigate(createStepPageUrl("add-data")); @@ -48,7 +48,7 @@ const TrainModelFirstView = () => { navigate(createStepPageUrl("train-model")); }, [navigate]); - const config = getConfig(trainingStatus); + const config = getConfig(stage); return ( diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx index 2763af2dc..a5b8ad3cf 100644 --- a/src/components/TrainingButton.tsx +++ b/src/components/TrainingButton.tsx @@ -1,17 +1,16 @@ import { Button, ButtonProps } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; +import { Stage, useStatus } from "../status-hook"; const TrainingButton = (props: ButtonProps) => { - const [trainingStatus] = useTrainingStatus(); + const [{ stage }] = useStatus(); // TODO: disable when isTraining return ( ); - case TrainingStatus.Error: - case TrainingStatus.NotTrained: + case Stage.TrainingError: + case Stage.NotTrained: return ( <> { ); - case TrainingStatus.InProgress: + case Stage.TrainingInProgress: return ( ); - case TrainingStatus.Complete: + case Stage.TrainingComplete: return ( @@ -90,7 +89,7 @@ const TrainingStatusView = () => { ); - case TrainingStatus.Retrain: + case Stage.RetrainingNeeded: return ( diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 5f16b8233..7fb0d9a77 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,6 +1,6 @@ import { ReactNode, createContext, useContext, useMemo, useState } from "react"; import { useStorage } from "./hooks/use-storage"; -import { TrainingStatus, useTrainingStatus } from "./training-status-hook"; +import { Stage, Status, useStatus } from "./status-hook"; export interface XYZData { x: number[]; y: number[]; @@ -129,16 +129,10 @@ export const GesturesProvider = ({ children }: { children: ReactNode }) => { export const useGestureActions = () => { const [gestures, setGestures] = useGestureData(); - const [trainingStatus, setTrainingStatus] = useTrainingStatus(); + const [status, setStatus] = useStatus(); const actions = useMemo( - () => - new GestureActions( - gestures, - setGestures, - trainingStatus, - setTrainingStatus - ), - [gestures, setGestures, setTrainingStatus, trainingStatus] + () => new GestureActions(gestures, setGestures, status, setStatus), + [gestures, setGestures, setStatus, status] ); return actions; }; @@ -147,8 +141,8 @@ class GestureActions { constructor( private gestureState: GestureContextState, private setGestureState: (gestureData: GestureContextState) => void, - private trainingStatus: TrainingStatus, - private setTrainingStatus: (status: TrainingStatus) => void + private status: Status, + private setStatus: (status: Status) => void ) {} setGestures = (gestures: GestureData[], isRetrainNeeded: boolean = true) => { @@ -157,12 +151,8 @@ class GestureActions { gestures.length === 0 ? initialGestureContextState.data : gestures; this.setGestureState({ ...this.gestureState, data }); - const newTrainingStatus = updateTrainingStatus( - data, - this.trainingStatus, - isRetrainNeeded - ); - this.setTrainingStatus(newTrainingStatus); + const newTrainingStatus = updateStatus(data, this.status, isRetrainNeeded); + this.setStatus(newTrainingStatus); }; addNewGesture = () => { @@ -212,20 +202,17 @@ const hasSufficientDataForTraining = (gestures: GestureData[]): boolean => { ); }; -const updateTrainingStatus = ( +const updateStatus = ( data: GestureData[], - currTrainingStatus: TrainingStatus, + currStatus: Status, isTrainingNeeded: boolean -) => { +): Status => { if (!hasSufficientDataForTraining(data)) { - return TrainingStatus.InsufficientData; + return { stage: Stage.InsufficientData }; } - if ( - isTrainingNeeded || - currTrainingStatus === TrainingStatus.InsufficientData - ) { + if (isTrainingNeeded || currStatus.stage === Stage.InsufficientData) { // Logic for updating status to retrain is in the training status hook - return TrainingStatus.NotTrained; + return { stage: Stage.NotTrained }; } - return currTrainingStatus; + return currStatus; }; diff --git a/src/ml-actions.tsx b/src/ml-actions.tsx index 933403653..f10bcb9fd 100644 --- a/src/ml-actions.tsx +++ b/src/ml-actions.tsx @@ -8,33 +8,20 @@ import { } from "./gestures-hooks"; import { Logging } from "./logging/logging"; import { useLogging } from "./logging/logging-hooks"; -import { - Confidences, - TrainModelInput, - mlSettings, - predict, - trainModel, -} from "./ml"; +import { Confidences, mlSettings, predict, trainModel } from "./ml"; +import { Stage, Status, useStatus } from "./status-hook"; import gestureData from "./test-fixtures/gesture-data.json"; -import { TrainingStatus, useTrainingStatus } from "./training-status-hook"; const defaultRequiredConfidence = 0.8; export const useMlActions = () => { const [gestures, setGestures] = useGestureData(); - const [trainingStatus, setTrainingStatus] = useTrainingStatus(); + const [status, setStatus] = useStatus(); const logger = useLogging(); const actions = useMemo( - () => - new MlActions( - logger, - gestures, - setGestures, - trainingStatus, - setTrainingStatus - ), - [gestures, logger, setGestures, setTrainingStatus, trainingStatus] + () => new MlActions(logger, gestures, setGestures, status, setStatus), + [gestures, logger, setGestures, setStatus, status] ); return actions; }; @@ -46,18 +33,19 @@ class MlActions { private logger: Logging, private gestureState: GestureContextState, private setGestureState: (gestureData: GestureContextState) => void, - private trainingStatus: TrainingStatus, - private setTrainingStatus: (status: TrainingStatus) => void + private status: Status, + private setStatus: (status: Status) => void ) {} - trainModel = async (onTraining: TrainModelInput["onTraining"]) => { - this.setTrainingStatus(TrainingStatus.InProgress); + trainModel = async () => { + this.setStatus({ stage: Stage.TrainingInProgress, progressValue: 0 }); const { data } = this.gestureState; const model = await trainModel({ data, - onTraining, - onTrainEnd: () => this.setTrainingStatus(TrainingStatus.Complete), - onError: () => this.setTrainingStatus(TrainingStatus.Error), + onTraining: (progressValue) => + this.setStatus({ stage: Stage.TrainingInProgress, progressValue }), + onTrainEnd: () => this.setStatus({ stage: Stage.TrainingComplete }), + onError: () => this.setStatus({ stage: Stage.TrainingError }), }); if (!model) { @@ -103,8 +91,7 @@ class MlActions { }; private checkIfInterrupted = () => - // TODO: Add state check for whether user is recording data - this.trainingStatus !== TrainingStatus.Complete; + this.status.stage !== Stage.TrainingComplete; private updateGestureDataConfidences = (confidences: Confidences) => { const updatedGestureData = this.gestureState.data.map( diff --git a/src/ml.ts b/src/ml.ts index e6ab6de5f..82891022f 100644 --- a/src/ml.ts +++ b/src/ml.ts @@ -29,7 +29,7 @@ export const mlSettings = { ]), }; -export interface TrainModelInput { +interface TrainModelInput { data: GestureData[]; onTrainEnd?: () => void; onTraining?: (progress: number) => void; diff --git a/src/pages/TestModelPage.tsx b/src/pages/TestModelPage.tsx index a102cc7a3..a98938eac 100644 --- a/src/pages/TestModelPage.tsx +++ b/src/pages/TestModelPage.tsx @@ -4,15 +4,15 @@ import TabView from "../components/TabView"; import TestModelGridView from "../components/TestModelGridView"; import TrainModelFirstView from "../components/TrainModelFirstView"; import { testModelConfig } from "../steps-config"; -import { TrainingStatus, useTrainingStatus } from "../training-status-hook"; +import { Stage, useStatus } from "../status-hook"; const TestModelPage = () => { - const [trainingStatus] = useTrainingStatus(); + const [{ stage }] = useStatus(); return ( - {trainingStatus === TrainingStatus.Complete ? ( + {stage === Stage.TrainingComplete ? ( <> diff --git a/src/status-hook.tsx b/src/status-hook.tsx new file mode 100644 index 000000000..b7e07018f --- /dev/null +++ b/src/status-hook.tsx @@ -0,0 +1,75 @@ +import { + ReactNode, + createContext, + useCallback, + useContext, + useState, +} from "react"; + +export enum Stage { + RecordingData, + InsufficientData, + NotTrained, + TrainingInProgress, + TrainingComplete, + TrainingError, + RetrainingNeeded, +} + +export type Status = + | { + stage: Stage.TrainingInProgress; + progressValue: number; + } + | { + stage: Exclude; + }; + +interface StatusState { + status: Status; + hasTrainedBefore: boolean; +} + +type StatusContextValue = [StatusState, (status: StatusState) => void]; + +const StatusContext = createContext(undefined); + +const initialStatusState: StatusState = { + status: { stage: Stage.NotTrained }, + hasTrainedBefore: false, +}; + +export const StatusProvider = ({ children }: { children: ReactNode }) => { + const statusContextValue = useState(initialStatusState); + return ( + + {children} + + ); +}; + +export const useStatus = (): [Status, (status: Status) => void] => { + const statusContextValue = useContext(StatusContext); + if (!statusContextValue) { + throw new Error("Missing provider"); + } + const [state, setState] = statusContextValue; + + const setStatus = useCallback( + (s: Status) => { + const hasTrainedBefore = + s.stage === Stage.TrainingComplete || state.hasTrainedBefore; + + // Update to retrain instead of not trained if has trained before + const status = + hasTrainedBefore && s.stage === Stage.NotTrained + ? ({ stage: Stage.RetrainingNeeded } as const) + : s; + + setState({ ...state, status, hasTrainedBefore }); + }, + [setState, state] + ); + + return [state.status, setStatus]; +}; diff --git a/src/training-status-hook.tsx b/src/training-status-hook.tsx deleted file mode 100644 index 16c91c8f1..000000000 --- a/src/training-status-hook.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { - ReactNode, - createContext, - useCallback, - useContext, - useState, -} from "react"; - -export enum TrainingStatus { - InsufficientData, - NotTrained, - InProgress, - Complete, - Retrain, - Error, -} - -interface TrainingState { - status: TrainingStatus; - hasTrainedBefore: boolean; -} - -type TrainingContextValue = [TrainingState, (state: TrainingState) => void]; - -const TrainingStatusContext = createContext( - undefined -); - -const initialTrainingState = { - status: TrainingStatus.NotTrained, - hasTrainedBefore: false, -}; - -export const TrainingStatusProvider = ({ - children, -}: { - children: ReactNode; -}) => { - const trainingStatusContextValue = - useState(initialTrainingState); - return ( - - {children} - - ); -}; - -export const useTrainingStatus = (): [ - TrainingStatus, - (status: TrainingStatus) => void -] => { - const trainingStatusContextValue = useContext(TrainingStatusContext); - if (!trainingStatusContextValue) { - throw new Error("Missing provider"); - } - const [state, setState] = trainingStatusContextValue; - - const setTrainingStatus = useCallback( - (trainingStatus: TrainingStatus) => { - const hasTrainedBefore = - trainingStatus === TrainingStatus.Complete || state.hasTrainedBefore; - - // Update to retrain instead of not trained if has trained before - const status = - hasTrainedBefore && trainingStatus === TrainingStatus.NotTrained - ? TrainingStatus.Retrain - : trainingStatus; - - setState({ ...state, status, hasTrainedBefore }); - }, - [setState, state] - ); - - return [state.status, setTrainingStatus]; -}; From 9ac326e05a58666bde305ca9150093e9320aa5d0 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 15 Jul 2024 14:08:54 +0100 Subject: [PATCH 050/172] Remove already implemented TODOs --- src/App.tsx | 1 - src/components/TrainingButton.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b9a4cb955..f4992f55d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,6 @@ export interface ProviderLayoutProps { children: ReactNode; } -// TODO: Use for logging provider const logging = deployment.logging; const Providers = ({ children }: ProviderLayoutProps) => { diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx index a5b8ad3cf..f09231059 100644 --- a/src/components/TrainingButton.tsx +++ b/src/components/TrainingButton.tsx @@ -5,7 +5,6 @@ import { Stage, useStatus } from "../status-hook"; const TrainingButton = (props: ButtonProps) => { const [{ stage }] = useStatus(); - // TODO: disable when isTraining return ( )} diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 7fb0d9a77..19f5d160e 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -145,6 +145,14 @@ class GestureActions { private setStatus: (status: Status) => void ) {} + hasGestures = () => { + return ( + this.gestureState.data.length > 0 && + (this.gestureState.data[0].name.length > 0 || + this.gestureState.data[0].recordings.length > 0) + ); + }; + setGestures = (gestures: GestureData[], isRetrainNeeded: boolean = true) => { const data = // Always have at least one gesture From 73de097b67c7db39b18050653902e42b5636cccc Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 15 Jul 2024 16:11:25 +0100 Subject: [PATCH 053/172] Change add data / train model btn variants depending on whether there is sufficient data to train Tweaks - make primary disabled button not hoverable --- src/deployment/default/components/button.ts | 1 - src/gestures-hooks.tsx | 37 +++++++++++---------- src/pages/AddDataPage.tsx | 13 ++++++-- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/deployment/default/components/button.ts b/src/deployment/default/components/button.ts index d359d3461..945e9b1b0 100644 --- a/src/deployment/default/components/button.ts +++ b/src/deployment/default/components/button.ts @@ -61,7 +61,6 @@ const Button: StyleConfig = { bg: "brand.600", _disabled: { bg: "brand.500", - opacity: 0.6, }, }, _active: { diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 19f5d160e..3d854fc6b 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,4 +1,11 @@ -import { ReactNode, createContext, useContext, useMemo, useState } from "react"; +import { + ReactNode, + createContext, + useContext, + useEffect, + useMemo, + useState, +} from "react"; import { useStorage } from "./hooks/use-storage"; import { Stage, Status, useStatus } from "./status-hook"; export interface XYZData { @@ -134,6 +141,13 @@ export const useGestureActions = () => { () => new GestureActions(gestures, setGestures, status, setStatus), [gestures, setGestures, setStatus, status] ); + + useEffect(() => { + if (!hasSufficientDataForTraining(gestures.data)) { + setStatus({ stage: Stage.InsufficientData }); + } + }, [gestures.data, setStatus]); + return actions; }; @@ -159,7 +173,11 @@ class GestureActions { gestures.length === 0 ? initialGestureContextState.data : gestures; this.setGestureState({ ...this.gestureState, data }); - const newTrainingStatus = updateStatus(data, this.status, isRetrainNeeded); + // Logic for updating status to retrain is in the training status hook + const newTrainingStatus = + isRetrainNeeded || this.status.stage === Stage.InsufficientData + ? ({ stage: Stage.NotTrained } as const) + : this.status; this.setStatus(newTrainingStatus); }; @@ -209,18 +227,3 @@ const hasSufficientDataForTraining = (gestures: GestureData[]): boolean => { gestures.length >= 2 && gestures.every((g) => g.recordings.length >= 3) ); }; - -const updateStatus = ( - data: GestureData[], - currStatus: Status, - isTrainingNeeded: boolean -): Status => { - if (!hasSufficientDataForTraining(data)) { - return { stage: Stage.InsufficientData }; - } - if (isTrainingNeeded || currStatus.stage === Stage.InsufficientData) { - // Logic for updating status to retrain is in the training status hook - return { stage: Stage.NotTrained }; - } - return currStatus; -}; diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index a7a59c35c..8d5815ad2 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -28,10 +28,16 @@ import { useCallback, useMemo } from "react"; import { createStepPageUrl } from "../urls"; import { useNavigate } from "react-router"; import TrainingButton from "../components/TrainingButton"; +import { Stage, useStatus } from "../status-hook"; const AddDataPage = () => { const intl = useIntl(); const navigate = useNavigate(); + const [{ stage }] = useStatus(); + const hasInsufficientData = useMemo( + () => stage === Stage.InsufficientData, + [stage] + ); const [gestures] = useGestureData(); const actions = useGestureActions(); const isInputConnected = true; @@ -76,7 +82,7 @@ const AddDataPage = () => { alignItems="center" > - + Date: Mon, 15 Jul 2024 16:27:20 +0100 Subject: [PATCH 054/172] Minor tweaks --- src/components/AddDataGridView.tsx | 22 ++++++++++--------- src/components/CertaintyThresholdGridItem.tsx | 10 ++++++--- src/components/PercentageMeter.tsx | 4 ++-- src/components/TestModelGridView.tsx | 22 ++++++++++--------- src/gestures-hooks.tsx | 2 +- src/ml-actions.tsx | 5 +---- 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index da04978f0..15b7c0815 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -13,6 +13,17 @@ const gridCommonProps: Partial = { w: "100%", }; +const headings = [ + { + titleId: "content.data.classification", + descriptionId: "content.data.classHelpBody", + }, + { + titleId: "content.data.data", + descriptionId: "content.data.dataDescription", + }, +]; + const AddDataGridView = () => { const [gestures] = useGestureData(); const [selected, setSelected] = useState(0); @@ -29,16 +40,7 @@ const AddDataGridView = () => { position="sticky" top={0} {...gridCommonProps} - headings={[ - { - titleId: "content.data.classification", - descriptionId: "content.data.classHelpBody", - }, - { - titleId: "content.data.data", - descriptionId: "content.data.dataDescription", - }, - ]} + headings={headings} /> { const intl = useIntl(); const barWidth = 240; + const colorScheme = useMemo( + () => (isTriggered ? "green.500" : undefined), + [isTriggered] + ); const handleThresholdChange = useCallback( (val: number) => onThresholdChange(val * 0.01), @@ -58,11 +62,11 @@ const CertaintyThresholdGridItem = ({ diff --git a/src/components/PercentageMeter.tsx b/src/components/PercentageMeter.tsx index d50158228..4147f78e3 100644 --- a/src/components/PercentageMeter.tsx +++ b/src/components/PercentageMeter.tsx @@ -36,8 +36,8 @@ const PercentageMeter = ({ justifyContent="space-between" > { - // Adding 2 transparent ticks for each end - // so that it can be justified using space-between + // Adding 2 ticks with no background color for each end of the meter + // so that the ticks can be justified using space-between Array(numTicks + 2) .fill(0) .map((_, i) => ( diff --git a/src/components/TestModelGridView.tsx b/src/components/TestModelGridView.tsx index c32ecf676..eaf4f9a17 100644 --- a/src/components/TestModelGridView.tsx +++ b/src/components/TestModelGridView.tsx @@ -25,6 +25,17 @@ const gridCommonProps: Partial = { w: "100%", }; +const headings = [ + { + titleId: "content.model.output.action.descriptionTitle", + descriptionId: "content.model.output.action.descriptionBody", + }, + { + titleId: "content.model.output.certainty.descriptionTitle", + descriptionId: "content.model.output.certainty.descriptionBody", + }, +]; + const TestModelGridView = () => { const intl = useIntl(); const [gestures] = useGestureData(); @@ -79,16 +90,7 @@ const TestModelGridView = () => { { const data = - // Always have at least one gesture + // Always have at least one gesture for walk through gestures.length === 0 ? initialGestureContextState.data : gestures; this.setGestureState({ ...this.gestureState, data }); diff --git a/src/ml-actions.tsx b/src/ml-actions.tsx index f10bcb9fd..5794863de 100644 --- a/src/ml-actions.tsx +++ b/src/ml-actions.tsx @@ -139,10 +139,7 @@ class MlActions { return gesture.ID === gestureId ? { ...gesture, - confidence: { - ...(gesture.confidence || {}), - requiredConfidence, - }, + confidence: { ...(gesture.confidence || {}), requiredConfidence }, } : gesture; }), From 52900fce7c42aa821ffca4e5f2a07337d71bfa3a Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 15 Jul 2024 16:29:24 +0100 Subject: [PATCH 055/172] Run prettier --- src/components/ResourcePageLayout/styles.module.css | 2 +- src/messages/ui.en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ResourcePageLayout/styles.module.css b/src/components/ResourcePageLayout/styles.module.css index a30e03534..ca7e25dde 100644 --- a/src/components/ResourcePageLayout/styles.module.css +++ b/src/components/ResourcePageLayout/styles.module.css @@ -24,4 +24,4 @@ .content img { margin-top: 1em; -} \ No newline at end of file +} diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index d47464478..a551a2121 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -1703,4 +1703,4 @@ "value": "Train model" } ] -} \ No newline at end of file +} From fc022a9ceaed7712342dbbef10a2474b3e6582c8 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 15 Jul 2024 17:19:27 +0100 Subject: [PATCH 056/172] Add test for isValidStoredGestureData --- src/gestures-hooks.tsx | 38 ++++++++++++++++++++++++++++++++------ src/gestures.test.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/gestures.test.ts diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 77d68aa8e..aff5c82b9 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -53,7 +53,12 @@ type GestureContextValue = [ (gestureData: GestureContextState) => void ]; -const isValidStoredGestureData = (v: unknown): v is GestureContextState => { +const isArray = (v: unknown) => typeof v === "object" && Array.isArray(v); + +// Exported for testing +export const isValidStoredGestureData = ( + v: unknown +): v is GestureContextState => { if (typeof v !== "object") { return false; } @@ -62,7 +67,7 @@ const isValidStoredGestureData = (v: unknown): v is GestureContextState => { return false; } const data = valueObject.data; - if (typeof data !== "object" && !Array.isArray(data)) { + if (!isArray(data)) { return false; } const array = data as unknown[]; @@ -70,13 +75,34 @@ const isValidStoredGestureData = (v: unknown): v is GestureContextState => { if (typeof item !== "object" || item === null) { return false; } - if (!("name" in item) || !("ID" in item) || !("recordings" in item)) { + if ( + !("name" in item) || + !("ID" in item) || + !("recordings" in item) || + !isArray(item.recordings) + ) { return false; } - if (typeof item.recordings !== "object") { - return false; + const recordings = item.recordings as unknown[]; + for (const rec of recordings) { + if (typeof rec !== "object" || rec === null) { + return false; + } + if (!("data" in rec) || !("ID" in rec) || isArray(rec.data)) { + return false; + } + const xyzData = rec.data as object; + if ( + !("x" in xyzData) || + !("y" in xyzData) || + !("z" in xyzData) || + !isArray(xyzData.x) || + !isArray(xyzData.y) || + !isArray(xyzData.z) + ) { + return false; + } } - // TODO: Validate recordings } return true; }; diff --git a/src/gestures.test.ts b/src/gestures.test.ts new file mode 100644 index 000000000..1d0587997 --- /dev/null +++ b/src/gestures.test.ts @@ -0,0 +1,42 @@ +import { isValidStoredGestureData } from "./gestures-hooks"; + +describe("isValidStoredGestureData", () => { + it("checks data", () => { + expect(isValidStoredGestureData({})).toEqual(false); + expect(isValidStoredGestureData({ data: [] })).toEqual(true); + expect(isValidStoredGestureData({ data: 123 })).toEqual(false); + expect(isValidStoredGestureData({ data: {} })).toEqual(false); + }); + it("checks data properties", () => { + expect(isValidStoredGestureData({ data: [{ invalid: 3 }] })).toEqual(false); + expect(isValidStoredGestureData({ data: [{ name: 3 }] })).toEqual(false); + expect( + isValidStoredGestureData({ + data: [{ ID: 0, name: "some name", recordings: [] }], + }) + ).toEqual(true); + }); + it("checks data recordings", () => { + const generateData = (recordings: unknown) => ({ + data: [{ ID: 0, name: "some name", recordings }], + }); + expect(isValidStoredGestureData(generateData({}))).toEqual(false); + expect(isValidStoredGestureData(generateData([]))).toEqual(true); + expect( + isValidStoredGestureData(generateData([{ ID: 0, data: [] }])) + ).toEqual(false); + expect( + isValidStoredGestureData(generateData([{ ID: 0, data: {} }])) + ).toEqual(false); + expect( + isValidStoredGestureData( + generateData([{ ID: 0, data: { x: 0, y: 0, z: 0 } }]) + ) + ).toEqual(false); + expect( + isValidStoredGestureData( + generateData([{ ID: 0, data: { x: [], y: [], z: [] } }]) + ) + ).toEqual(true); + }); +}); From e40cb23828cab35082aef9ba76212565729a7b5e Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 16 Jul 2024 09:18:54 +0100 Subject: [PATCH 057/172] Fixing resource path, live graph panel buttons so that they are click-able, and tweaks to training status view --- src/components/LiveGraphPanel.tsx | 13 ++++++++++++- src/components/TrainingStatusView.tsx | 2 +- src/urls.ts | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index c0a1ced39..9393fbd4a 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -3,10 +3,20 @@ import { MdBolt } from "react-icons/md"; import { FormattedMessage } from "react-intl"; import InfoToolTip from "./InfoToolTip"; import LiveGraph from "./LiveGraph"; +import { useCallback } from "react"; +import { useConnectionFlow } from "../connections"; +import { ConnEvent } from "../connection-flow"; const LiveGraphPanel = () => { + const { dispatch } = useConnectionFlow(); + // TODO: replace with hook const isConnected = false; + + const handleConnect = useCallback(() => { + // TODO: Handle incompatibility dialog and reconnection + dispatch(ConnEvent.Start); + }, [dispatch]); return ( { right={0} px={7} py={4} + zIndex={1} > @@ -25,7 +36,7 @@ const LiveGraphPanel = () => { ) : ( - )} diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx index b9202ddee..afb484c8c 100644 --- a/src/components/TrainingStatusView.tsx +++ b/src/components/TrainingStatusView.tsx @@ -44,7 +44,7 @@ const TrainingStatusView = () => { case Stage.InsufficientData: return ( - + + + + + + + ); +}; + +export default WebUsbBluetoothUnsupportedDialog; diff --git a/src/connection-flow.ts b/src/connection-flow.ts index 82da8cbe8..986d69ef3 100644 --- a/src/connection-flow.ts +++ b/src/connection-flow.ts @@ -1,6 +1,7 @@ import { Reducer } from "react"; export enum ConnStage { + // Happy flow stages None, Start, ConnectCable, @@ -23,6 +24,7 @@ export enum ConnStage { TryAgainBluetoothConnect, BadFirmware, MicrobitUnsupported, + WebUsbBluetoothUnsupported, } export enum ConnType { @@ -34,7 +36,8 @@ export enum ConnType { export type ConnState = { stage: ConnStage; type: ConnType; - isUsbSupported: boolean; + isWebUsbSupported: boolean; + isWebBluetoothSupported: boolean; }; export enum ConnEvent { @@ -79,7 +82,13 @@ export const connectionDialogReducer: Reducer = ( ) => { switch (event) { case ConnEvent.Start: - return { ...state, stage: ConnStage.Start }; + return { + ...state, + stage: + !state.isWebBluetoothSupported && !state.isWebUsbSupported + ? ConnStage.WebUsbBluetoothUnsupported + : ConnStage.Start, + }; case ConnEvent.Close: return { ...state, stage: ConnStage.None }; case ConnEvent.SkipFlashing: @@ -140,7 +149,8 @@ const getStageAndTypeOrder = (state: ConnState): StageAndType[] => { { stage: ConnStage.Start, type: Bluetooth }, { stage: ConnStage.ConnectCable, type: Bluetooth }, // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. - !state.isUsbSupported || state.stage === ConnStage.ManualFlashingTutorial + !state.isWebUsbSupported || + state.stage === ConnStage.ManualFlashingTutorial ? { stage: ConnStage.ManualFlashingTutorial, type: Bluetooth } : { stage: ConnStage.WebUsbFlashingTutorial, type: Bluetooth }, { stage: ConnStage.ConnectBattery, type: Bluetooth }, diff --git a/src/connections.tsx b/src/connections.tsx index 766873e4f..f4351b2e3 100644 --- a/src/connections.tsx +++ b/src/connections.tsx @@ -22,12 +22,15 @@ interface ConnectionFlowProviderProps { export const ConnectionFlowProvider = ({ children, }: ConnectionFlowProviderProps) => { - const isBluetoothSupported = true; + // TODO: Check bt and usb compatibility + const isWebBluetoothSupported = true; + const isWebUsbSupported = true; + const [state, dispatch] = useReducer(connectionDialogReducer, { - // TODO: Check bt and usb compatibility - type: isBluetoothSupported ? ConnType.Bluetooth : ConnType.RadioRemote, + type: isWebBluetoothSupported ? ConnType.Bluetooth : ConnType.RadioRemote, stage: ConnStage.None, - isUsbSupported: true, + isWebUsbSupported, + isWebBluetoothSupported, }); return ( From 0b831fc475f03965535ba464d0e4abfa2d76cc66 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 16 Jul 2024 11:25:58 +0100 Subject: [PATCH 059/172] Add StartOverWarningDialog and start and resume actions logic --- src/components/StartOverWarningDialog.tsx | 84 +++++++++++++++++++++++ src/components/StartResumeActions.tsx | 59 +++++++++++----- src/pages/AddDataPage.tsx | 30 ++------ 3 files changed, 132 insertions(+), 41 deletions(-) create mode 100644 src/components/StartOverWarningDialog.tsx diff --git a/src/components/StartOverWarningDialog.tsx b/src/components/StartOverWarningDialog.tsx new file mode 100644 index 000000000..3a94d04fd --- /dev/null +++ b/src/components/StartOverWarningDialog.tsx @@ -0,0 +1,84 @@ +import { + Button, + Heading, + Link, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; +import { ReactNode, useCallback } from "react"; +import { FormattedMessage } from "react-intl"; +import { useGestureActions } from "../gestures-hooks"; + +interface StartOverWardningDialogProps { + isOpen: boolean; + onClose: () => void; + onStart: () => void; +} + +const StartOverWarningDialog = ({ + isOpen, + onClose, + onStart, +}: StartOverWardningDialogProps) => { + const actions = useGestureActions(); + const handleDatasetDownload = useCallback(() => { + actions.downloadDataset(); + }, [actions]); + return ( + + + + + + + + + + + + + + + ( + + {chunks} + + ), + }} + /> + + + + + + + + + + + ); +}; + +export default StartOverWarningDialog; diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index 5cc9d54dd..24fb8994a 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -1,15 +1,17 @@ -import { Button, HStack, StackProps } from "@chakra-ui/react"; +import { Button, HStack, StackProps, useDisclosure } from "@chakra-ui/react"; import { useCallback, useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; -import { createStepPageUrl } from "../urls"; -import { useConnectionFlow } from "../connections"; import { ConnEvent } from "../connection-flow"; +import { useConnectionFlow } from "../connections"; import { useGestureActions } from "../gestures-hooks"; +import { createStepPageUrl } from "../urls"; +import StartOverWarningDialog from "./StartOverWarningDialog"; const StartResumeActions = ({ ...props }: Partial) => { const actions = useGestureActions(); const hasExistingSession = useMemo(() => actions.hasGestures(), [actions]); + const startOverWarningDialogDisclosure = useDisclosure(); const navigate = useNavigate(); const { dispatch } = useConnectionFlow(); // TODO: check input connected @@ -19,28 +21,49 @@ const StartResumeActions = ({ ...props }: Partial) => { navigate(createStepPageUrl("add-data")); }, [navigate]); - const handleNewSession = useCallback(() => { + const handleStartNewSession = useCallback(() => { + actions.deleteAllGestures(); if (isInputConnected) { handleNavigateToAddData(); } else { dispatch(ConnEvent.Start); } - }, [dispatch, handleNavigateToAddData, isInputConnected]); + }, [actions, dispatch, handleNavigateToAddData, isInputConnected]); + + const onClickStartNewSession = useCallback(() => { + if (hasExistingSession) { + startOverWarningDialogDisclosure.onOpen(); + } else { + handleStartNewSession(); + } + }, [ + handleStartNewSession, + hasExistingSession, + startOverWarningDialogDisclosure, + ]); + return ( - - {hasExistingSession && ( - + )} + - )} - - + + ); }; diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index 8d5815ad2..b8add6eec 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -9,26 +9,22 @@ import { MenuList, VStack, } from "@chakra-ui/react"; +import { useCallback, useMemo } from "react"; import { MdMoreVert } from "react-icons/md"; import { RiAddLine, RiDeleteBin2Line, RiDownload2Line } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; +import { useNavigate } from "react-router"; import AddDataGridView from "../components/AddDataGridView"; import ConnectFirstView from "../components/ConnectFirstView"; import DefaultPageLayout from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; import TabView from "../components/TabView"; +import TrainingButton from "../components/TrainingButton"; import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; +import { useGestureActions, useGestureData } from "../gestures-hooks"; import { addDataConfig } from "../pages-config"; -import { - GestureData, - useGestureActions, - useGestureData, -} from "../gestures-hooks"; -import { useCallback, useMemo } from "react"; -import { createStepPageUrl } from "../urls"; -import { useNavigate } from "react-router"; -import TrainingButton from "../components/TrainingButton"; import { Stage, useStatus } from "../status-hook"; +import { createStepPageUrl } from "../urls"; const AddDataPage = () => { const intl = useIntl(); @@ -55,8 +51,8 @@ const AddDataPage = () => { }, [actions]); const handleDatasetDownload = useCallback(() => { - downloadDataset(gestures.data); - }, [gestures.data]); + actions.downloadDataset(); + }, [actions]); const navigateToTrainModelPage = useCallback(() => { navigate(createStepPageUrl("train-model")); @@ -131,16 +127,4 @@ const AddDataPage = () => { ); }; -const downloadDataset = (gestures: GestureData[]) => { - const a = document.createElement("a"); - a.setAttribute( - "href", - "data:application/json;charset=utf-8," + - encodeURIComponent(JSON.stringify(gestures, null, 2)) - ); - a.setAttribute("download", "dataset"); - a.style.display = "none"; - a.click(); -}; - export default AddDataPage; From be321137e8d940a7d1eebad4cdf87160df6cd180 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 16 Jul 2024 11:26:32 +0100 Subject: [PATCH 060/172] Use Portal to control live graph panel buttons clickability --- src/components/LiveGraphPanel.tsx | 66 +++++++++++++++++-------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 9393fbd4a..4069f096b 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -1,14 +1,15 @@ -import { Button, HStack, Text } from "@chakra-ui/react"; +import { Button, HStack, Portal, Text } from "@chakra-ui/react"; import { MdBolt } from "react-icons/md"; import { FormattedMessage } from "react-intl"; import InfoToolTip from "./InfoToolTip"; import LiveGraph from "./LiveGraph"; -import { useCallback } from "react"; +import { useCallback, useRef } from "react"; import { useConnectionFlow } from "../connections"; import { ConnEvent } from "../connection-flow"; const LiveGraphPanel = () => { const { dispatch } = useConnectionFlow(); + const parentPortalRef = useRef(null); // TODO: replace with hook const isConnected = false; @@ -18,34 +19,41 @@ const LiveGraphPanel = () => { dispatch(ConnEvent.Start); }, [dispatch]); return ( - - - - - {isConnected ? ( - - ) : ( - - )} + + + + + + {isConnected ? ( + + ) : ( + + )} + + - - + From baed0974053235ae8d5f526fac2dc89cd79c49a2 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 16 Jul 2024 11:46:11 +0100 Subject: [PATCH 061/172] Recover downloadDataset action and remove re-rendering issue to reimplement functionality later --- src/gestures-hooks.tsx | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index aff5c82b9..3661d0dab 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,11 +1,4 @@ -import { - ReactNode, - createContext, - useContext, - useEffect, - useMemo, - useState, -} from "react"; +import { ReactNode, createContext, useContext, useMemo, useState } from "react"; import { useStorage } from "./hooks/use-storage"; import { Stage, Status, useStatus } from "./status-hook"; export interface XYZData { @@ -168,12 +161,6 @@ export const useGestureActions = () => { [gestures, setGestures, setStatus, status] ); - useEffect(() => { - if (!hasSufficientDataForTraining(gestures.data)) { - setStatus({ stage: Stage.InsufficientData }); - } - }, [gestures.data, setStatus]); - return actions; }; @@ -185,7 +172,7 @@ class GestureActions { private setStatus: (status: Status) => void ) {} - hasGestures = () => { + hasGestures = (): boolean => { return ( this.gestureState.data.length > 0 && (this.gestureState.data[0].name.length > 0 || @@ -199,11 +186,14 @@ class GestureActions { gestures.length === 0 ? initialGestureContextState.data : gestures; this.setGestureState({ ...this.gestureState, data }); - // Logic for updating status to retrain is in the training status hook - const newTrainingStatus = - isRetrainNeeded || this.status.stage === Stage.InsufficientData - ? ({ stage: Stage.NotTrained } as const) - : this.status; + // Update training status + const newTrainingStatus = !hasSufficientDataForTraining(data) + ? { stage: Stage.InsufficientData as const } + : isRetrainNeeded || this.status.stage === Stage.InsufficientData + ? // Updating status to retrain status is in status hook + { stage: Stage.NotTrained as const } + : this.status; + this.setStatus(newTrainingStatus); }; @@ -246,6 +236,18 @@ class GestureActions { deleteAllGestures = () => { this.setGestures([]); }; + + downloadDataset = () => { + const a = document.createElement("a"); + a.setAttribute( + "href", + "data:application/json;charset=utf-8," + + encodeURIComponent(JSON.stringify(this.gestureState.data, null, 2)) + ); + a.setAttribute("download", "dataset"); + a.style.display = "none"; + a.click(); + }; } const hasSufficientDataForTraining = (gestures: GestureData[]): boolean => { From 003a245a9b36590fb286a9ae05ac503cf922efe1 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 16 Jul 2024 12:00:11 +0100 Subject: [PATCH 062/172] Establish initial status based on gesture data to check if there is sufficient training data to begin with --- src/App.tsx | 8 ++++---- src/gestures-hooks.tsx | 4 +++- src/status-hook.tsx | 16 ++++++++++------ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 57b181013..96b466edb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,15 +40,15 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - + + {children} - - + + diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 3661d0dab..1865bfb17 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -250,7 +250,9 @@ class GestureActions { }; } -const hasSufficientDataForTraining = (gestures: GestureData[]): boolean => { +export const hasSufficientDataForTraining = ( + gestures: GestureData[] +): boolean => { return ( gestures.length >= 2 && gestures.every((g) => g.recordings.length >= 3) ); diff --git a/src/status-hook.tsx b/src/status-hook.tsx index b7e07018f..0b71b4342 100644 --- a/src/status-hook.tsx +++ b/src/status-hook.tsx @@ -5,6 +5,7 @@ import { useContext, useState, } from "react"; +import { hasSufficientDataForTraining, useGestureData } from "./gestures-hooks"; export enum Stage { RecordingData, @@ -34,13 +35,16 @@ type StatusContextValue = [StatusState, (status: StatusState) => void]; const StatusContext = createContext(undefined); -const initialStatusState: StatusState = { - status: { stage: Stage.NotTrained }, - hasTrainedBefore: false, -}; - export const StatusProvider = ({ children }: { children: ReactNode }) => { - const statusContextValue = useState(initialStatusState); + const [gestureState] = useGestureData(); + const statusContextValue = useState({ + status: { + stage: hasSufficientDataForTraining(gestureState.data) + ? Stage.NotTrained + : Stage.InsufficientData, + }, + hasTrainedBefore: false, + }); return ( {children} From b71dc14d7d0dbe72f064c001dffc0dffdeb1c641 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 16 Jul 2024 12:23:50 +0100 Subject: [PATCH 063/172] Check hasSufficientDataForTraining in add data page instead of using status state The status stages include recordingData stage, which would make the add data and train button flicker between variants when user is recording an action. --- src/pages/AddDataPage.tsx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index b8add6eec..ee8e32ab2 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -21,23 +21,26 @@ import LiveGraphPanel from "../components/LiveGraphPanel"; import TabView from "../components/TabView"; import TrainingButton from "../components/TrainingButton"; import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; -import { useGestureActions, useGestureData } from "../gestures-hooks"; +import { + hasSufficientDataForTraining, + useGestureActions, + useGestureData, +} from "../gestures-hooks"; import { addDataConfig } from "../pages-config"; -import { Stage, useStatus } from "../status-hook"; import { createStepPageUrl } from "../urls"; const AddDataPage = () => { const intl = useIntl(); const navigate = useNavigate(); - const [{ stage }] = useStatus(); - const hasInsufficientData = useMemo( - () => stage === Stage.InsufficientData, - [stage] - ); const [gestures] = useGestureData(); const actions = useGestureActions(); const isInputConnected = true; + const hasSufficientData = useMemo( + () => hasSufficientDataForTraining(gestures.data), + [gestures.data] + ); + const noStoredData = useMemo(() => { const gestureData = gestures.data; return ( @@ -78,7 +81,7 @@ const AddDataPage = () => { alignItems="center" > diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index 24fb8994a..3767b38fe 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -2,33 +2,39 @@ import { Button, HStack, StackProps, useDisclosure } from "@chakra-ui/react"; import { useCallback, useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; -import { ConnEvent } from "../connection-flow"; -import { useConnectionFlow } from "../connections"; +import { useConnectionFlow, useConnections } from "../connection-hooks"; import { useGestureActions } from "../gestures-hooks"; import { createStepPageUrl } from "../urls"; import StartOverWarningDialog from "./StartOverWarningDialog"; const StartResumeActions = ({ ...props }: Partial) => { - const actions = useGestureActions(); - const hasExistingSession = useMemo(() => actions.hasGestures(), [actions]); + const gestureActions = useGestureActions(); + const hasExistingSession = useMemo( + () => gestureActions.hasGestures(), + [gestureActions] + ); const startOverWarningDialogDisclosure = useDisclosure(); const navigate = useNavigate(); - const { dispatch } = useConnectionFlow(); - // TODO: check input connected - const isInputConnected = true; + const { actions: connectionActions } = useConnectionFlow(); + const { isInputConnected } = useConnections(); const handleNavigateToAddData = useCallback(() => { navigate(createStepPageUrl("add-data")); }, [navigate]); const handleStartNewSession = useCallback(() => { - actions.deleteAllGestures(); + gestureActions.deleteAllGestures(); if (isInputConnected) { handleNavigateToAddData(); } else { - dispatch(ConnEvent.Start); + connectionActions.start(); } - }, [actions, dispatch, handleNavigateToAddData, isInputConnected]); + }, [ + gestureActions, + connectionActions, + handleNavigateToAddData, + isInputConnected, + ]); const onClickStartNewSession = useCallback(() => { if (hasExistingSession) { diff --git a/src/components/TryAgainDialog.tsx b/src/components/TryAgainDialog.tsx index 7f7d55775..629872eea 100644 --- a/src/components/TryAgainDialog.tsx +++ b/src/components/TryAgainDialog.tsx @@ -13,7 +13,7 @@ import { VStack, } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { ConnStage } from "../connections"; +import { ConnStage } from "../connection-flow"; const OneLineContent = ({ textId }: { textId: string }) => { return ( diff --git a/src/connect-actions.ts b/src/connect-actions.ts new file mode 100644 index 000000000..0a501db51 --- /dev/null +++ b/src/connect-actions.ts @@ -0,0 +1,147 @@ +import { ConnStatus, ConnType, Connections, ProgramType } from "./connections"; +import { getHexFileUrl } from "./device/get-hex-file"; +import MicrobitWebUSBConnection from "./device/microbit-usb"; +import { Logging } from "./logging/logging"; + +export enum ConnectAndFlashResult { + Success, + Failed, + ErrorMicrobitUnsupported, + ErrorBadFirmware, + ErrorNoDeviceSelected, + ErrorUnableToClaimInterface, +} + +export enum BluetoothConnectResult { + Success, + Failed, +} + +export enum RadioConnectResult { + Success, + Failed, +} + +const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); + +export class ConnectActions { + public device: MicrobitWebUSBConnection | undefined; + constructor(private logging: Logging, private connections: Connections) {} + + requestUSBConnectionAndFlash = async ( + hexType: ConnType, + progressCallback: (progress: number) => void + ): Promise => { + try { + this.device = new MicrobitWebUSBConnection(this.logging); + await this.device.connect(); + const result = await this.flashMicrobit(hexType, progressCallback); + + // Save remote micro:bit device id is stored for passing it to bridge micro:bit + const deviceId = this.device.getDeviceId(); + if ( + !!deviceId && + result === ConnectAndFlashResult.Success && + hexType === ConnType.RadioRemote + ) { + this.connections.setConnection(ProgramType.Input, { + status: ConnStatus.Disconnected, + type: "radio", + remoteDeviceId: deviceId, + }); + } + + return result; + } catch (e) { + this.logging.error( + `USB request device failed/cancelled: ${JSON.stringify(e)}` + ); + return this.handleConnectAndFlashError(e); + } + }; + + flashMicrobit = async ( + hexType: ConnType, + progressCallback: (progress: number) => void + ): Promise => { + if (!this.device) { + return ConnectAndFlashResult.Failed; + } + const deviceVersion = this.device.getBoardVersion(); + const hexUrl = deviceVersion + ? getHexFileUrl(deviceVersion, hexType) + : deviceVersion; + + if (!hexUrl) { + return ConnectAndFlashResult.ErrorMicrobitUnsupported; + } + try { + await this.device.flashHex(hexUrl, progressCallback); + return ConnectAndFlashResult.Success; + } catch (e) { + this.logging.error(`Flashing failed: ${JSON.stringify(e)}`); + return ConnectAndFlashResult.Failed; + } + }; + + private handleConnectAndFlashError = ( + err: unknown + ): ConnectAndFlashResult => { + // We might get Error objects as Promise rejection arguments + if ( + typeof err === "object" && + err !== null && + !("message" in err) && + "promise" in err && + "reason" in err + ) { + err = err.reason; + } + if ( + typeof err !== "object" || + err === null || + !(typeof err === "object" && "message" in err) + ) { + return ConnectAndFlashResult.Failed; + } + + const errMessage = err.message as string; + + // This is somewhat fragile but worth it for scenario specific errors. + // These messages changed to be prefixed in 2023 so we've relaxed the checks. + if (/No valid interfaces found/.test(errMessage)) { + // This comes from DAPjs's WebUSB open. + return ConnectAndFlashResult.ErrorBadFirmware; + } else if (/No device selected/.test(errMessage)) { + return ConnectAndFlashResult.ErrorNoDeviceSelected; + } else if (/Unable to claim interface/.test(errMessage)) { + return ConnectAndFlashResult.ErrorUnableToClaimInterface; + } else { + return ConnectAndFlashResult.Failed; + } + }; + + // TODO: Replace with real connecting logic + connectMicrobitsSerial = async (): Promise => { + await delay(5000); + this.connections.setConnection(ProgramType.Input, { + status: ConnStatus.Connected, + type: "radio", + }); + return RadioConnectResult.Success; + }; + + // TODO: Replace with real connecting logic + connectBluetooth = async (): Promise => { + await delay(5000); + const isSuccess = true; + if (isSuccess) { + this.connections.setConnection(ProgramType.Input, { + status: ConnStatus.Connected, + type: "bluetooth", + }); + return BluetoothConnectResult.Success; + } + return BluetoothConnectResult.Failed; + }; +} diff --git a/src/connection-flow.ts b/src/connection-flow.ts index ca329c52c..540d49bc5 100644 --- a/src/connection-flow.ts +++ b/src/connection-flow.ts @@ -1,11 +1,43 @@ import { - Conn, - ConnStage, - ConnState, - ConnStatus, - ConnType, - ProgramType, -} from "./connections"; + BluetoothConnectResult, + ConnectActions, + ConnectAndFlashResult, +} from "./connect-actions"; +import { ConnType } from "./connections"; + +export interface ConnectionFlowState { + stage: ConnStage; + type: ConnType; + isWebUsbSupported: boolean; + isWebBluetoothSupported: boolean; +} + +export enum ConnStage { + // Happy flow stages + None, + Start, + ConnectCable, + WebUsbFlashingTutorial, + ManualFlashingTutorial, + ConnectBattery, + EnterBluetoothPattern, + ConnectBluetoothTutorial, + + // Stages that are not user-controlled + WebUsbChooseMicrobit, + ConnectingBluetooth, + ConnectingMicrobits, + FlashingInProgress, + + // Failure stages + TryAgainReplugMicrobit, + TryAgainCloseTabs, + TryAgainSelectMicrobit, + TryAgainBluetoothConnect, + BadFirmware, + MicrobitUnsupported, + WebUsbBluetoothUnsupported, +} export enum ConnEvent { // User triggered events @@ -41,43 +73,77 @@ export enum ConnEvent { ConnectingMicrobits, } -type StageAndType = Pick; +type StageAndType = Pick; -export class ConnectionActions { +export class ConnectionFlowActions { constructor( - private connState: ConnState, - private setConnState: (state: ConnState) => void + private actions: ConnectActions, + private connState: ConnectionFlowState, + private setConnState: (state: ConnectionFlowState) => void ) {} - dispatchConnectFlowEvent = (event: ConnEvent) => { - this.setConnState(dispatchConnFlowEvent(this.connState, event)); + start = () => { + this.dispatchEvent(ConnEvent.Start); }; - private setConn = (programType: ProgramType, conn: Conn) => { - const { connections } = this.connState; - // Replace existing conn or add as new conn depending on whether a conn - // with the same programType already exists - const connExists = !!connections.find((c) => c.program === programType); - const newConnState = { - ...this.connState, - connections: connExists - ? connections.map((c) => (c.program === programType ? conn : c)) - : [...connections, conn], - }; - this.setConnState(newConnState); + dispatchEvent = (event: ConnEvent) => { + this.setConnState(getUpdatedConnState(this.connState, event)); }; - setBluetoothConn = ({ status }: { status?: ConnStatus }) => { - const newConn = { - program: this.connState.program, - status: status ?? ConnStatus.Disconnected, - type: ConnType.Bluetooth, - } as Conn; - this.setConn(this.connState.program, newConn); + connectAndflashMicrobit = async ( + progressCallback: (progress: number) => void + ) => { + this.dispatchEvent(ConnEvent.WebUsbChooseMicrobit); + const result = await this.actions.requestUSBConnectionAndFlash( + this.connState.type, + progressCallback + ); + if ( + this.connState.type === ConnType.Bluetooth && + result !== ConnectAndFlashResult.Success + ) { + return this.dispatchEvent(ConnEvent.InstructManualFlashing); + } + switch (result) { + case ConnectAndFlashResult.ErrorMicrobitUnsupported: + return this.dispatchEvent(ConnEvent.MicrobitUnsupported); + case ConnectAndFlashResult.ErrorBadFirmware: + return this.dispatchEvent(ConnEvent.BadFirmware); + case ConnectAndFlashResult.ErrorNoDeviceSelected: + return this.dispatchEvent(ConnEvent.TryAgainSelectMicrobit); + case ConnectAndFlashResult.ErrorUnableToClaimInterface: + return this.dispatchEvent(ConnEvent.TryAgainCloseTabs); + case ConnectAndFlashResult.Failed: + return this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); + case ConnectAndFlashResult.Success: + this.dispatchEvent(ConnEvent.FlashingComplete); + break; + } + // TODO: not sure if connecting microbits should be triggered here + if (this.connState.type === ConnType.RadioBridge) { + await this.actions.connectMicrobitsSerial(); + } + }; + + connectBluetooth = async (onSuccess: () => void) => { + this.dispatchEvent(ConnEvent.ConnectingBluetooth); + const result = await this.actions.connectBluetooth(); + if (result === BluetoothConnectResult.Success) { + onSuccess(); + } else { + this.dispatchEvent(ConnEvent.TryAgainBluetoothConnect); + } + }; + + getDeviceId = () => { + return this.actions.device?.getDeviceId(); }; } -export const dispatchConnFlowEvent = (state: ConnState, event: ConnEvent) => { +export const getUpdatedConnState = ( + state: ConnectionFlowState, + event: ConnEvent +) => { switch (event) { case ConnEvent.Start: return { @@ -124,8 +190,8 @@ export const dispatchConnFlowEvent = (state: ConnState, event: ConnEvent) => { ...state, stage: state.type === ConnType.RadioRemote - ? ConnStage.ConnectBattery - : ConnStage.ConnectingMicrobits, + ? ConnStage.ConnectingMicrobits + : ConnStage.ConnectBattery, }; case ConnEvent.TryAgain: return { @@ -140,7 +206,7 @@ export const dispatchConnFlowEvent = (state: ConnState, event: ConnEvent) => { } }; -const getStageAndTypeOrder = (state: ConnState): StageAndType[] => { +const getStageAndTypeOrder = (state: ConnectionFlowState): StageAndType[] => { const { RadioRemote, RadioBridge, Bluetooth } = ConnType; if (state.type === ConnType.Bluetooth) { return [ @@ -179,7 +245,10 @@ const getStageAndTypeIdx = ( throw new Error("Should be able to match stage and type again order"); }; -const getNextStageAndType = (state: ConnState, step: number): StageAndType => { +const getNextStageAndType = ( + state: ConnectionFlowState, + step: number +): StageAndType => { const order = getStageAndTypeOrder(state); const curr = { stage: state.stage, type: state.type }; const currIdx = getStageAndTypeIdx(curr, order); diff --git a/src/connection-hooks.tsx b/src/connection-hooks.tsx new file mode 100644 index 000000000..8fdb55d7e --- /dev/null +++ b/src/connection-hooks.tsx @@ -0,0 +1,110 @@ +import { ReactNode, createContext, useContext, useMemo, useState } from "react"; +import { + ConnStage, + ConnectionFlowActions, + ConnectionFlowState, +} from "./connection-flow"; +import { useLogging } from "./logging/logging-hooks"; +import { + ConnType, + Connections, + ConnectionsState, + ProgramType, +} from "./connections"; +import { ConnectActions } from "./connect-actions"; + +type ConnectionContextValue = { + // Used for storing the state of connections + connectionsState: ConnectionsState; + setConnectionsState: (state: ConnectionsState) => void; + + // Used for connection flow + connectionFlowState: ConnectionFlowState; + setConnectionFlowState: (state: ConnectionFlowState) => void; +}; + +export const ConnectionContext = createContext( + null +); + +interface ConnectionProviderProps { + children: ReactNode; +} + +export const ConnectionProvider = ({ children }: ConnectionProviderProps) => { + // TODO: Check bt and usb compatibility + const isWebBluetoothSupported = true; + const isWebUsbSupported = true; + + const [connectionsState, setConnectionsState] = useState( + {} + ); + + const [connectionFlowState, setConnectionFlowState] = + useState({ + type: isWebBluetoothSupported ? ConnType.Bluetooth : ConnType.RadioRemote, + stage: ConnStage.None, + isWebUsbSupported, + isWebBluetoothSupported, + }); + return ( + + {children} + + ); +}; + +export const useConnections = (): { + connections: Connections; + isInputConnected: boolean; +} => { + const connContextValue = useContext(ConnectionContext); + if (!connContextValue) { + throw new Error("Missing provider"); + } + const { connectionsState, setConnectionsState } = connContextValue; + const conn = useMemo( + () => new Connections(connectionsState, setConnectionsState), + [connectionsState, setConnectionsState] + ); + const isInputConnected = useMemo( + () => conn.isConnected(ProgramType.Input), + [conn] + ); + return { connections: conn, isInputConnected }; +}; + +export const useConnectionFlow = (): { + state: ConnectionFlowState; + actions: ConnectionFlowActions; +} => { + const connContextValue = useContext(ConnectionContext); + if (!connContextValue) { + throw new Error("Missing provider"); + } + const { connectionFlowState, setConnectionFlowState } = connContextValue; + const logging = useLogging(); + const { connections } = useConnections(); + + const actions = useMemo(() => { + const connectActions = new ConnectActions(logging, connections); + + return new ConnectionFlowActions( + connectActions, + connectionFlowState, + setConnectionFlowState + ); + }, [logging, connections, connectionFlowState, setConnectionFlowState]); + + return { + state: connectionFlowState, + actions, + }; +}; diff --git a/src/connections.ts b/src/connections.ts new file mode 100644 index 000000000..a2df33d3d --- /dev/null +++ b/src/connections.ts @@ -0,0 +1,61 @@ +export enum ConnType { + Bluetooth, + RadioBridge, + RadioRemote, +} + +export enum ConnStatus { + Disconnected, + Connected, +} + +export interface BluetoothConnection { + status: ConnStatus; + type: "bluetooth"; +} +export interface RadioConnection { + status: ConnStatus; + type: "radio"; + // Remote micro:bit device id is stored for passing it to bridge micro:bit + remoteDeviceId: string; +} + +export type Connection = BluetoothConnection | RadioConnection; + +export enum ProgramType { + Input, +} + +export interface ConnectionsState { + [ProgramType.Input]?: Connection; +} + +export class Connections { + constructor( + private connections: ConnectionsState, + private setConnections: (state: ConnectionsState) => void + ) {} + + get = () => this.connections; + + setConnection = ( + programType: ProgramType, + conn: { + status?: ConnStatus; + type: "bluetooth" | "radio"; + remoteDeviceId?: RadioConnection["remoteDeviceId"]; + } + ) => { + this.setConnections({ + [programType]: { + status: ConnStatus.Disconnected, + ...this.connections[programType], + ...conn, + } as Connection, + }); + }; + + isConnected = (programType: ProgramType): boolean => { + return this.connections[programType]?.status === ConnStatus.Connected; + }; +} diff --git a/src/connections.tsx b/src/connections.tsx deleted file mode 100644 index af6b549f9..000000000 --- a/src/connections.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { ReactNode, createContext, useContext, useMemo, useState } from "react"; -import { ConnEvent, ConnectionActions } from "./connection-flow"; - -export enum ConnStage { - // Happy flow stages - None, - Start, - ConnectCable, - WebUsbFlashingTutorial, - ManualFlashingTutorial, - ConnectBattery, - EnterBluetoothPattern, - ConnectBluetoothTutorial, - - // Stages that are not user-controlled - WebUsbChooseMicrobit, - ConnectingBluetooth, - ConnectingMicrobits, - FlashingInProgress, - - // Failure stages - TryAgainReplugMicrobit, - TryAgainCloseTabs, - TryAgainSelectMicrobit, - TryAgainBluetoothConnect, - BadFirmware, - MicrobitUnsupported, - WebUsbBluetoothUnsupported, -} - -export enum ConnType { - Bluetooth, - RadioBridge, - RadioRemote, -} - -// For now, we only have one program type, but here we can expand to other -// programs e.g. CollectDataInField -export enum ProgramType { - CollectData, -} - -export enum ConnStatus { - Disconnected, - Connected, -} - -export interface Conn { - status: ConnStatus; - program: ProgramType; - type: ConnType; -} - -export type ConnState = { - stage: ConnStage; - type: ConnType; - program: ProgramType; - isWebUsbSupported: boolean; - isWebBluetoothSupported: boolean; - connections: Conn[]; -}; - -type ConnContextValue = { - state: ConnState; - setState: (state: ConnState) => void; -}; - -export const ConnectionContext = createContext(null); - -interface ConnectionProviderProps { - children: ReactNode; -} - -export const ConnectionProvider = ({ children }: ConnectionProviderProps) => { - // TODO: Check bt and usb compatibility - const isWebBluetoothSupported = true; - const isWebUsbSupported = true; - - const [state, setState] = useState({ - type: isWebBluetoothSupported ? ConnType.Bluetooth : ConnType.RadioRemote, - program: ProgramType.CollectData, - stage: ConnStage.None, - isWebUsbSupported, - isWebBluetoothSupported, - connections: [], - }); - return ( - - {children} - - ); -}; - -export const useConnectionFlow = (): { - state: ConnState; - dispatch: (event: ConnEvent) => void; - actions: ConnectionActions; -} => { - const connContextValue = useContext(ConnectionContext); - if (!connContextValue) { - throw new Error("Missing provider"); - } - const { state, setState } = connContextValue; - const actions = useMemo( - () => new ConnectionActions(state, setState), - [setState, state] - ); - return { - state: connContextValue.state, - dispatch: actions.dispatchConnectFlowEvent, - actions, - }; -}; diff --git a/src/ml-actions.tsx b/src/ml-actions.tsx index 5794863de..792f33394 100644 --- a/src/ml-actions.tsx +++ b/src/ml-actions.tsx @@ -11,6 +11,7 @@ import { useLogging } from "./logging/logging-hooks"; import { Confidences, mlSettings, predict, trainModel } from "./ml"; import { Stage, Status, useStatus } from "./status-hook"; import gestureData from "./test-fixtures/gesture-data.json"; +import { useConnections } from "./connection-hooks"; const defaultRequiredConfidence = 0.8; @@ -18,10 +19,19 @@ export const useMlActions = () => { const [gestures, setGestures] = useGestureData(); const [status, setStatus] = useStatus(); const logger = useLogging(); + const { isInputConnected } = useConnections(); const actions = useMemo( - () => new MlActions(logger, gestures, setGestures, status, setStatus), - [gestures, logger, setGestures, setStatus, status] + () => + new MlActions( + logger, + isInputConnected, + gestures, + setGestures, + status, + setStatus + ), + [gestures, isInputConnected, logger, setGestures, setStatus, status] ); return actions; }; @@ -31,6 +41,7 @@ class MlActions { private model: LayersModel | undefined; constructor( private logger: Logging, + private keepTesting: boolean, private gestureState: GestureContextState, private setGestureState: (gestureData: GestureContextState) => void, private status: Status, @@ -73,9 +84,7 @@ class MlActions { clearInterval(this.predictionInterval); } - // TODO: Hook for input connection - const inputIsConnected = true; - if (inputIsConnected) { + if (this.keepTesting) { const confidences = await predict({ model: this.model as LayersModel, // TODO: Get data from accelerometer instead of using dummy data From 210c67ecd0268c0a2130477a6d8a7576bf81577a Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 18 Jul 2024 10:11:56 +0100 Subject: [PATCH 070/172] Retrieve remote device id for connect microbit serial to create a better placeholder for real connecting logic --- src/connect-actions.ts | 10 +++++++++- src/connections.ts | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 0a501db51..2480cb925 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -123,8 +123,16 @@ export class ConnectActions { // TODO: Replace with real connecting logic connectMicrobitsSerial = async (): Promise => { + const programType = ProgramType.Input; + + // TODO: Use deviceId to assign to connect microbits + const deviceId = this.connections.getRemoteDeviceId(programType); + if (!deviceId) { + throw new Error("Radio bridge device id not set"); + } + await delay(5000); - this.connections.setConnection(ProgramType.Input, { + this.connections.setConnection(programType, { status: ConnStatus.Connected, type: "radio", }); diff --git a/src/connections.ts b/src/connections.ts index a2df33d3d..dc2d7913c 100644 --- a/src/connections.ts +++ b/src/connections.ts @@ -55,6 +55,15 @@ export class Connections { }); }; + getRemoteDeviceId = ( + programType: ProgramType + ): RadioConnection["remoteDeviceId"] | undefined => { + const inputConnection = this.connections[programType]; + return inputConnection?.type === "radio" + ? inputConnection.remoteDeviceId + : undefined; + }; + isConnected = (programType: ProgramType): boolean => { return this.connections[programType]?.status === ConnStatus.Connected; }; From 9898d3267783e7227835114d7f83f15afd348407 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 18 Jul 2024 10:25:55 +0100 Subject: [PATCH 071/172] Add disconnect microbit stubbing and validate setting of connections state --- src/components/LiveGraphPanel.tsx | 6 +++++- src/connect-actions.ts | 7 +++++++ src/connection-flow.ts | 2 ++ src/connections.ts | 27 +++++++++++++++++---------- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index cc390c878..ad50baa6d 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -11,6 +11,10 @@ const LiveGraphPanel = () => { const { isInputConnected } = useConnections(); const parentPortalRef = useRef(null); + const handleDisconnect = useCallback(() => { + actions.disconnect(); + }, [actions]); + const handleConnect = useCallback(() => { // TODO: Handle incompatibility dialog and reconnection actions.start(); @@ -36,7 +40,7 @@ const LiveGraphPanel = () => { {isInputConnected ? ( - ) : ( diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 2480cb925..6c4c5a76d 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -152,4 +152,11 @@ export class ConnectActions { } return BluetoothConnectResult.Failed; }; + + // TODO: Replace with real disconnect logic + disconnect = () => { + this.connections.setConnection(ProgramType.Input, { + status: ConnStatus.Disconnected, + }); + }; } diff --git a/src/connection-flow.ts b/src/connection-flow.ts index 540d49bc5..9fb999be2 100644 --- a/src/connection-flow.ts +++ b/src/connection-flow.ts @@ -135,6 +135,8 @@ export class ConnectionFlowActions { } }; + disconnect = () => this.actions.disconnect(); + getDeviceId = () => { return this.actions.device?.getDeviceId(); }; diff --git a/src/connections.ts b/src/connections.ts index dc2d7913c..0990eac93 100644 --- a/src/connections.ts +++ b/src/connections.ts @@ -36,23 +36,30 @@ export class Connections { private setConnections: (state: ConnectionsState) => void ) {} - get = () => this.connections; - setConnection = ( programType: ProgramType, conn: { status?: ConnStatus; - type: "bluetooth" | "radio"; + type?: "bluetooth" | "radio"; remoteDeviceId?: RadioConnection["remoteDeviceId"]; } ) => { - this.setConnections({ - [programType]: { - status: ConnStatus.Disconnected, - ...this.connections[programType], - ...conn, - } as Connection, - }); + const newConnection = { + status: ConnStatus.Disconnected, + ...this.connections[programType], + ...conn, + }; + if ( + !("status" in newConnection) || + !("type" in newConnection) || + !("program" in newConnection) || + !(newConnection.type === "radio" && !!newConnection.remoteDeviceId) + ) { + throw new Error( + `Invalid new connection state: ${JSON.stringify(newConnection)}` + ); + } + this.setConnections({ [programType]: newConnection as Connection }); }; getRemoteDeviceId = ( From 7d782dbc9383d2a8056db6f4cc0165b44058cffe Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 18 Jul 2024 11:49:50 +0100 Subject: [PATCH 072/172] Refactor connections hooks --- src/App.tsx | 21 +- src/components/ConnectCableDialog.tsx | 12 +- src/components/ConnectFirstView.tsx | 4 +- src/components/ConnectionFlowDialogs.tsx | 79 +++--- src/components/DataRecordingGridItem.tsx | 2 +- src/components/LiveGraph.tsx | 2 +- src/components/LiveGraphPanel.tsx | 5 +- src/components/ManualFlashingDialog.tsx | 4 +- src/components/RecordingDialog.tsx | 8 +- src/components/StartResumeActions.tsx | 9 +- src/components/TestModelGridView.tsx | 2 +- src/components/TrainModelFirstView.tsx | 10 +- src/components/TrainingButton.tsx | 7 +- src/components/TrainingStatusView.tsx | 20 +- src/components/TryAgainDialog.tsx | 18 +- src/components/WhatYouWillNeedDialog.tsx | 10 +- src/connect-actions.ts | 18 +- src/connection-flow.ts | 263 ------------------- src/connection-hooks.tsx | 110 -------- src/connection-stage-actions.ts | 194 ++++++++++++++ src/connection-stage-hooks.tsx | 139 ++++++++++ src/connections-hooks.tsx | 70 +++++ src/connections.ts | 58 ++-- src/device/get-hex-file.ts | 10 +- src/gestures-hooks.tsx | 14 +- src/{ml-actions.tsx => ml-actions.ts} | 45 +--- src/ml-hooks.tsx | 27 ++ src/{status-hook.tsx => ml-status-hooks.tsx} | 42 +-- src/pages/TestModelPage.tsx | 6 +- 29 files changed, 616 insertions(+), 593 deletions(-) delete mode 100644 src/connection-flow.ts delete mode 100644 src/connection-hooks.tsx create mode 100644 src/connection-stage-actions.ts create mode 100644 src/connection-stage-hooks.tsx create mode 100644 src/connections-hooks.tsx rename src/{ml-actions.tsx => ml-actions.ts} (75%) create mode 100644 src/ml-hooks.tsx rename src/{status-hook.tsx => ml-status-hooks.tsx} (50%) diff --git a/src/App.tsx b/src/App.tsx index 96cab5fe4..6412822e3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,8 +22,9 @@ import { deployment, useDeployment } from "./deployment"; import { resourcesConfig, stepsConfig } from "./pages-config"; import { LoggingProvider } from "./logging/logging-hooks"; import { GesturesProvider } from "./gestures-hooks"; -import { StatusProvider } from "./status-hook"; -import { ConnectionProvider } from "./connection-hooks"; +import { MlStatusProvider } from "./ml-status-hooks"; +import { ConnectionsProvider } from "./connections-hooks"; +import { ConnectionStageProvider } from "./connection-stage-hooks"; export interface ProviderLayoutProps { children: ReactNode; @@ -41,13 +42,15 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - - - {children} - - - + + + + + {children} + + + + diff --git a/src/components/ConnectCableDialog.tsx b/src/components/ConnectCableDialog.tsx index 1fda21a86..5e17e1d84 100644 --- a/src/components/ConnectCableDialog.tsx +++ b/src/components/ConnectCableDialog.tsx @@ -4,7 +4,7 @@ import connectCableImage from "../images/connect-cable.gif"; import ConnectContainerDialog, { ConnectContainerDialogProps, } from "./ConnectContainerDialog"; -import { ConnType } from "../connections"; +import { ConnectionFlowType } from "../connection-stage-hooks"; enum LinkType { Switch, @@ -18,19 +18,19 @@ interface Config { onLink: LinkType; } -const configs: Record = { - [ConnType.Bluetooth]: { +const configs: Record = { + [ConnectionFlowType.Bluetooth]: { headingId: "connectMB.connectCable.heading", subtitleId: "connectMB.connectCable.subtitle", linkTextId: "connectMB.connectCable.skip", onLink: LinkType.Skip, }, - [ConnType.RadioRemote]: { + [ConnectionFlowType.RadioRemote]: { headingId: "connectMB.connectCableMB1.heading", subtitleId: "connectMB.connectCableMB1.subtitle", onLink: LinkType.None, }, - [ConnType.RadioBridge]: { + [ConnectionFlowType.RadioBridge]: { headingId: "connectMB.connectCableMB2.heading", subtitleId: "connectMB.connectCableMB2.subtitle", linkTextId: "connectMB.radioStart.switchBluetooth", @@ -40,7 +40,7 @@ const configs: Record = { export interface ConnectCableDialogProps extends Omit { - type: ConnType; + type: ConnectionFlowType; onSkip: () => void; onSwitch: () => void; } diff --git a/src/components/ConnectFirstView.tsx b/src/components/ConnectFirstView.tsx index fd94f3a81..8f9f5f786 100644 --- a/src/components/ConnectFirstView.tsx +++ b/src/components/ConnectFirstView.tsx @@ -1,12 +1,12 @@ import { Button, Text, VStack } from "@chakra-ui/react"; import { useCallback } from "react"; import { FormattedMessage } from "react-intl"; -import { useConnectionFlow } from "../connection-hooks"; +import { useConnectionStage } from "../connection-stage-hooks"; const ConnectFirstView = () => { const connecting = false; const isReconnect = false; - const { actions } = useConnectionFlow(); + const { actions } = useConnectionStage(); const handleConnect = useCallback(() => { actions.start(); diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index f58e5af68..c33ff0765 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -1,9 +1,12 @@ import { useDisclosure } from "@chakra-ui/react"; import { useCallback, useEffect, useState } from "react"; import { microbitNameToBluetoothPattern } from "../bt-pattern-utils"; -import { ConnEvent, ConnStage } from "../connection-flow"; -import { useConnectionFlow } from "../connection-hooks"; -import { ConnType } from "../connections"; +import { + ConnEvent, + ConnectionFlowStep, + ConnectionFlowType, + useConnectionStage, +} from "../connection-stage-hooks"; import BrokenFirmwareDialog from "./BrokenFirmwareDialog"; import ConnectBatteryDialog from "./ConnectBatteryDialog"; import ConnectCableDialog from "./ConnectCableDialog"; @@ -21,7 +24,7 @@ import WebUsbBluetoothUnsupportedDialog from "./WebUsbBluetoothUnsupportedDialog import WhatYouWillNeedDialog from "./WhatYouWillNeedDialog"; const ConnectionDialogs = () => { - const { state, actions } = useConnectionFlow(); + const { stage, actions } = useConnectionStage(); const dispatch = actions.dispatchEvent; const [flashProgress, setFlashProgress] = useState(0); const { isOpen, onClose: onCloseDialog, onOpen } = useDisclosure(); @@ -31,28 +34,28 @@ const ConnectionDialogs = () => { useEffect(() => { if ( - state.stage === ConnStage.Start || - state.stage === ConnStage.WebUsbBluetoothUnsupported + stage.step === ConnectionFlowStep.Start || + stage.step === ConnectionFlowStep.WebUsbBluetoothUnsupported ) { onOpen(); } - }, [onOpen, state]); + }, [onOpen, stage]); const progressCallback = useCallback( (progress: number) => { - if (state.stage !== ConnStage.FlashingInProgress) { + if (stage.step !== ConnectionFlowStep.FlashingInProgress) { dispatch(ConnEvent.FlashingInProgress); } setFlashProgress(progress * 100); }, - [dispatch, state.stage] + [dispatch, stage.step] ); async function connectAndFlash(): Promise { await actions.connectAndflashMicrobit(progressCallback); const deviceId = actions.getDeviceId(); - if (!!deviceId && state.type === ConnType.Bluetooth) { + if (!!deviceId && stage.type === ConnectionFlowType.Bluetooth) { // Infer pattern from device id saves the user from entering the pattern setBluetoothPattern(microbitNameToBluetoothPattern("")); } @@ -88,14 +91,14 @@ const ConnectionDialogs = () => { }, [dispatch, onCloseDialog]); const dialogCommonProps = { isOpen, onClose }; - switch (state.stage) { - case ConnStage.Start: { + switch (stage.step) { + case ConnectionFlowStep.Start: { return ( { /> ); } - case ConnStage.ConnectCable: { + case ConnectionFlowStep.ConnectCable: { const commonProps = { onBackClick, onNextClick, ...dialogCommonProps }; return ( ); } - case ConnStage.WebUsbFlashingTutorial: { + case ConnectionFlowStep.WebUsbFlashingTutorial: { return ( { /> ); } - case ConnStage.ManualFlashingTutorial: { + case ConnectionFlowStep.ManualFlashingTutorial: { return ( { /> ); } - case ConnStage.ConnectBattery: { + case ConnectionFlowStep.ConnectBattery: { return ( { /> ); } - case ConnStage.EnterBluetoothPattern: { + case ConnectionFlowStep.EnterBluetoothPattern: { return ( { /> ); } - case ConnStage.ConnectBluetoothTutorial: { + case ConnectionFlowStep.ConnectBluetoothTutorial: { return ( { /> ); } - case ConnStage.WebUsbChooseMicrobit: { + case ConnectionFlowStep.WebUsbChooseMicrobit: { // Browser dialog is shown, no custom dialog shown at the same time return <>; } - case ConnStage.FlashingInProgress: { + case ConnectionFlowStep.FlashingInProgress: { const headingIdVariations = { - [ConnType.Bluetooth]: "connectMB.usbDownloading.header", - [ConnType.RadioRemote]: "connectMB.usbDownloadingMB1.header", - [ConnType.RadioBridge]: "connectMB.usbDownloadingMB2.header", + [ConnectionFlowType.Bluetooth]: "connectMB.usbDownloading.header", + [ConnectionFlowType.RadioRemote]: "connectMB.usbDownloadingMB1.header", + [ConnectionFlowType.RadioBridge]: "connectMB.usbDownloadingMB2.header", }; return ( ); } - case ConnStage.ConnectingBluetooth: { + case ConnectionFlowStep.ConnectingBluetooth: { return ( { /> ); } - case ConnStage.ConnectingMicrobits: { + case ConnectionFlowStep.ConnectingMicrobits: { return ( ); } - case ConnStage.TryAgainBluetoothConnect: - case ConnStage.TryAgainReplugMicrobit: - case ConnStage.TryAgainSelectMicrobit: - case ConnStage.TryAgainCloseTabs: { + case ConnectionFlowStep.TryAgainBluetoothConnect: + case ConnectionFlowStep.TryAgainReplugMicrobit: + case ConnectionFlowStep.TryAgainSelectMicrobit: + case ConnectionFlowStep.TryAgainCloseTabs: { return ( ); } - case ConnStage.BadFirmware: { + case ConnectionFlowStep.BadFirmware: { return ( { /> ); } - case ConnStage.MicrobitUnsupported: { + case ConnectionFlowStep.MicrobitUnsupported: { return ( ); } - case ConnStage.WebUsbBluetoothUnsupported: { + case ConnectionFlowStep.WebUsbBluetoothUnsupported: { console.log("here"); return ( diff --git a/src/components/DataRecordingGridItem.tsx b/src/components/DataRecordingGridItem.tsx index bfe92816c..0e71610f4 100644 --- a/src/components/DataRecordingGridItem.tsx +++ b/src/components/DataRecordingGridItem.tsx @@ -14,7 +14,7 @@ import RecordIcon from "../images/record-icon.svg?react"; import RecordingGraph from "./RecordingGraph"; import RecordingDialog from "./RecordingDialog"; import { useCallback, useRef } from "react"; -import { useConnections } from "../connection-hooks"; +import { useConnections } from "../connections-hooks"; interface DataRecordingGridItemProps { data: GestureData; diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index a5c5064a6..8d69b3c5f 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -2,7 +2,7 @@ import { HStack } from "@chakra-ui/react"; import { useSize } from "@chakra-ui/react-use-size"; import { useEffect, useRef, useState } from "react"; import { SmoothieChart, TimeSeries } from "smoothie"; -import { useConnections } from "../connection-hooks"; +import { useConnections } from "../connections-hooks"; const LiveGraph = () => { const { isInputConnected } = useConnections(); diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index ad50baa6d..4903e65b3 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -2,12 +2,13 @@ import { Button, HStack, Portal, Text } from "@chakra-ui/react"; import { useCallback, useRef } from "react"; import { MdBolt } from "react-icons/md"; import { FormattedMessage } from "react-intl"; -import { useConnectionFlow, useConnections } from "../connection-hooks"; +import { useConnections } from "../connections-hooks"; import InfoToolTip from "./InfoToolTip"; import LiveGraph from "./LiveGraph"; +import { useConnectionStage } from "../connection-stage-hooks"; const LiveGraphPanel = () => { - const { actions } = useConnectionFlow(); + const { actions } = useConnectionStage(); const { isInputConnected } = useConnections(); const parentPortalRef = useRef(null); diff --git a/src/components/ManualFlashingDialog.tsx b/src/components/ManualFlashingDialog.tsx index 2d2c4568b..912edd2d8 100644 --- a/src/components/ManualFlashingDialog.tsx +++ b/src/components/ManualFlashingDialog.tsx @@ -9,7 +9,7 @@ import ConnectContainerDialog, { ConnectContainerDialogProps, } from "./ConnectContainerDialog"; import { getHexFileUrl } from "../device/get-hex-file"; -import { ConnType } from "../connections"; +import { ConnectionFlowType } from "../connection-stage-hooks"; interface ImageProps { src: string; @@ -51,7 +51,7 @@ const ManualFlashingDialog = ({ ...props }: ManualFlashingDialogProps) => { const handleDownload = useCallback(() => { download( - getHexFileUrl("universal", ConnType.Bluetooth)!, + getHexFileUrl("universal", ConnectionFlowType.Bluetooth)!, "machine-learning-tool-program.hex" ); }, []); diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index 1847649d0..aebbb131b 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -15,7 +15,7 @@ import { motion } from "framer-motion"; import { FormattedMessage, useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; import gestureData from "../test-fixtures/gesture-data.json"; -import { Stage, useStatus } from "../status-hook"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; const recordingDuration = 1800; @@ -48,7 +48,7 @@ const RecordingDialog = ({ }: RecordingDialogProps) => { const intl = useIntl(); const actions = useGestureActions(); - const [, setStatus] = useStatus(); + const [, setStatus] = useMlStatus(); const [recordingStatus, setRecordingStatus] = useState( RecordingStatus.None ); @@ -70,7 +70,7 @@ const RecordingDialog = ({ const handleOnClose = useCallback(() => { setRecordingStatus(RecordingStatus.None); - setStatus({ stage: Stage.NotTrained }); + setStatus({ stage: MlStage.NotTrained }); setIsCountdownIdx(0); onClose(); }, [onClose, setStatus]); @@ -93,7 +93,7 @@ const RecordingDialog = ({ return; } else { setRecordingStatus(RecordingStatus.Recording); - setStatus({ stage: Stage.RecordingData }); + setStatus({ stage: MlStage.RecordingData }); } }, config.duration); } diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index 3767b38fe..d0adf3776 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -2,10 +2,11 @@ import { Button, HStack, StackProps, useDisclosure } from "@chakra-ui/react"; import { useCallback, useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; -import { useConnectionFlow, useConnections } from "../connection-hooks"; +import { useConnections } from "../connections-hooks"; import { useGestureActions } from "../gestures-hooks"; import { createStepPageUrl } from "../urls"; import StartOverWarningDialog from "./StartOverWarningDialog"; +import { useConnectionStage } from "../connection-stage-hooks"; const StartResumeActions = ({ ...props }: Partial) => { const gestureActions = useGestureActions(); @@ -15,7 +16,7 @@ const StartResumeActions = ({ ...props }: Partial) => { ); const startOverWarningDialogDisclosure = useDisclosure(); const navigate = useNavigate(); - const { actions: connectionActions } = useConnectionFlow(); + const { actions: connectionStageActions } = useConnectionStage(); const { isInputConnected } = useConnections(); const handleNavigateToAddData = useCallback(() => { @@ -27,11 +28,11 @@ const StartResumeActions = ({ ...props }: Partial) => { if (isInputConnected) { handleNavigateToAddData(); } else { - connectionActions.start(); + connectionStageActions.start(); } }, [ gestureActions, - connectionActions, + connectionStageActions, handleNavigateToAddData, isInputConnected, ]); diff --git a/src/components/TestModelGridView.tsx b/src/components/TestModelGridView.tsx index eaf4f9a17..bc3f365bf 100644 --- a/src/components/TestModelGridView.tsx +++ b/src/components/TestModelGridView.tsx @@ -9,7 +9,7 @@ import { } from "@chakra-ui/react"; import React, { useMemo } from "react"; import { useGestureData } from "../gestures-hooks"; -import { useMlActions } from "../ml-actions"; +import { useMlActions } from "../ml-hooks"; import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; import GestureNameGridItem from "./GestureNameGridItem"; import HeadingGrid from "./HeadingGrid"; diff --git a/src/components/TrainModelFirstView.tsx b/src/components/TrainModelFirstView.tsx index d2ac1fb91..70b036d81 100644 --- a/src/components/TrainModelFirstView.tsx +++ b/src/components/TrainModelFirstView.tsx @@ -4,7 +4,7 @@ import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; import testModelImage from "../images/test_model_black.svg"; import { StepId } from "../pages-config"; -import { Stage, useStatus } from "../status-hook"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; import { createStepPageUrl } from "../urls"; import TrainingButton from "./TrainingButton"; @@ -13,9 +13,9 @@ interface TrainModelFirstViewConfig { navigateToStep: StepId; } -const getConfig = (status: Stage): TrainModelFirstViewConfig => { +const getConfig = (status: MlStage): TrainModelFirstViewConfig => { switch (status) { - case Stage.InsufficientData: + case MlStage.InsufficientData: return { textIds: [ "content.model.notEnoughDataInfoBody1", @@ -23,7 +23,7 @@ const getConfig = (status: Stage): TrainModelFirstViewConfig => { ], navigateToStep: "add-data", }; - case Stage.RetrainingNeeded: + case MlStage.RetrainingNeeded: return { textIds: ["content.model.retrainModelBody"], navigateToStep: "train-model", @@ -38,7 +38,7 @@ const getConfig = (status: Stage): TrainModelFirstViewConfig => { const TrainModelFirstView = () => { const navigate = useNavigate(); - const [{ stage }] = useStatus(); + const [{ stage }] = useMlStatus(); const navigateToDataPage = useCallback(() => { navigate(createStepPageUrl("add-data")); diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx index f09231059..049d12dbe 100644 --- a/src/components/TrainingButton.tsx +++ b/src/components/TrainingButton.tsx @@ -1,15 +1,16 @@ import { Button, ButtonProps } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { Stage, useStatus } from "../status-hook"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; const TrainingButton = (props: ButtonProps) => { - const [{ stage }] = useStatus(); + const [{ stage }] = useMlStatus(); return ( ); - case Stage.TrainingError: - case Stage.NotTrained: + case MlStage.TrainingError: + case MlStage.NotTrained: return ( <> { ); - case Stage.TrainingInProgress: + case MlStage.TrainingInProgress: return ( { /> ); - case Stage.TrainingComplete: + case MlStage.TrainingComplete: return ( @@ -89,7 +89,7 @@ const TrainingStatusView = () => { ); - case Stage.RetrainingNeeded: + case MlStage.RetrainingNeeded: return ( diff --git a/src/components/TryAgainDialog.tsx b/src/components/TryAgainDialog.tsx index 629872eea..65ece4089 100644 --- a/src/components/TryAgainDialog.tsx +++ b/src/components/TryAgainDialog.tsx @@ -13,7 +13,7 @@ import { VStack, } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { ConnStage } from "../connection-flow"; +import { ConnectionFlowStep } from "../connection-stage-hooks"; const OneLineContent = ({ textId }: { textId: string }) => { return ( @@ -60,19 +60,19 @@ const CloseTabsContent = () => { }; const configs = { - [ConnStage.TryAgainReplugMicrobit]: { + [ConnectionFlowStep.TryAgainReplugMicrobit]: { headingId: "connectMB.usbTryAgain.heading", children: , }, - [ConnStage.TryAgainCloseTabs]: { + [ConnectionFlowStep.TryAgainCloseTabs]: { headingId: "connectMB.usbTryAgain.heading", children: , }, - [ConnStage.TryAgainSelectMicrobit]: { + [ConnectionFlowStep.TryAgainSelectMicrobit]: { headingId: "connectMB.usbTryAgain.heading", children: , }, - [ConnStage.TryAgainBluetoothConnect]: { + [ConnectionFlowStep.TryAgainBluetoothConnect]: { headingId: "connectMB.bluetooth.heading", children: ( @@ -85,10 +85,10 @@ interface TryAgainWebUsbDialogProps { onClose: () => void; onTryAgain: () => void; type: - | ConnStage.TryAgainReplugMicrobit - | ConnStage.TryAgainCloseTabs - | ConnStage.TryAgainSelectMicrobit - | ConnStage.TryAgainBluetoothConnect; + | ConnectionFlowStep.TryAgainReplugMicrobit + | ConnectionFlowStep.TryAgainCloseTabs + | ConnectionFlowStep.TryAgainSelectMicrobit + | ConnectionFlowStep.TryAgainBluetoothConnect; } const TryAgainWebUsbDialog = ({ diff --git a/src/components/WhatYouWillNeedDialog.tsx b/src/components/WhatYouWillNeedDialog.tsx index 728c363c8..9c23f9ba9 100644 --- a/src/components/WhatYouWillNeedDialog.tsx +++ b/src/components/WhatYouWillNeedDialog.tsx @@ -9,7 +9,7 @@ import twoMicrobitsImage from "../images/stylised-two-microbits-black.svg"; import usbCableImage from "../images/stylised-usb-cable.svg"; import computerImage from "../images/stylised_computer.svg"; import computerBluetoothImage from "../images/stylised_computer_w_bluetooth.svg"; -import { ConnType } from "../connections"; +import { ConnectionFlowType } from "../connection-stage-hooks"; const whatYouWillNeedRadioConfig = { headingId: "connectMB.radioStart.heading", @@ -69,7 +69,7 @@ export interface WhatYouWillNeedDialogProps "children" | "onBack" | "headingId" > { reconnect: boolean; - type: ConnType; + type: ConnectionFlowType; } const WhatYouWillNeedDialog = ({ @@ -78,9 +78,9 @@ const WhatYouWillNeedDialog = ({ ...props }: WhatYouWillNeedDialogProps) => { const configs = { - [ConnType.Bluetooth]: whatYouWillNeedBluetoothConfig, - [ConnType.RadioRemote]: whatYouWillNeedRadioConfig, - [ConnType.RadioBridge]: whatYouWillNeedRadioConfig, + [ConnectionFlowType.Bluetooth]: whatYouWillNeedBluetoothConfig, + [ConnectionFlowType.RadioRemote]: whatYouWillNeedRadioConfig, + [ConnectionFlowType.RadioBridge]: whatYouWillNeedRadioConfig, }; const { items, headingId, reconnectHeadingId, linkTextId } = configs[type]; return ( diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 6c4c5a76d..e1bd390d8 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -1,4 +1,6 @@ -import { ConnStatus, ConnType, Connections, ProgramType } from "./connections"; +import { ConnectionFlowType } from "./connection-stage-hooks"; +import { Connections } from "./connections"; +import { ConnectionStatus, ProgramType } from "./connections-hooks"; import { getHexFileUrl } from "./device/get-hex-file"; import MicrobitWebUSBConnection from "./device/microbit-usb"; import { Logging } from "./logging/logging"; @@ -29,7 +31,7 @@ export class ConnectActions { constructor(private logging: Logging, private connections: Connections) {} requestUSBConnectionAndFlash = async ( - hexType: ConnType, + hexType: ConnectionFlowType, progressCallback: (progress: number) => void ): Promise => { try { @@ -42,10 +44,10 @@ export class ConnectActions { if ( !!deviceId && result === ConnectAndFlashResult.Success && - hexType === ConnType.RadioRemote + hexType === ConnectionFlowType.RadioRemote ) { this.connections.setConnection(ProgramType.Input, { - status: ConnStatus.Disconnected, + status: ConnectionStatus.Disconnected, type: "radio", remoteDeviceId: deviceId, }); @@ -61,7 +63,7 @@ export class ConnectActions { }; flashMicrobit = async ( - hexType: ConnType, + hexType: ConnectionFlowType, progressCallback: (progress: number) => void ): Promise => { if (!this.device) { @@ -133,7 +135,7 @@ export class ConnectActions { await delay(5000); this.connections.setConnection(programType, { - status: ConnStatus.Connected, + status: ConnectionStatus.Connected, type: "radio", }); return RadioConnectResult.Success; @@ -145,7 +147,7 @@ export class ConnectActions { const isSuccess = true; if (isSuccess) { this.connections.setConnection(ProgramType.Input, { - status: ConnStatus.Connected, + status: ConnectionStatus.Connected, type: "bluetooth", }); return BluetoothConnectResult.Success; @@ -156,7 +158,7 @@ export class ConnectActions { // TODO: Replace with real disconnect logic disconnect = () => { this.connections.setConnection(ProgramType.Input, { - status: ConnStatus.Disconnected, + status: ConnectionStatus.Disconnected, }); }; } diff --git a/src/connection-flow.ts b/src/connection-flow.ts deleted file mode 100644 index 9fb999be2..000000000 --- a/src/connection-flow.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { - BluetoothConnectResult, - ConnectActions, - ConnectAndFlashResult, -} from "./connect-actions"; -import { ConnType } from "./connections"; - -export interface ConnectionFlowState { - stage: ConnStage; - type: ConnType; - isWebUsbSupported: boolean; - isWebBluetoothSupported: boolean; -} - -export enum ConnStage { - // Happy flow stages - None, - Start, - ConnectCable, - WebUsbFlashingTutorial, - ManualFlashingTutorial, - ConnectBattery, - EnterBluetoothPattern, - ConnectBluetoothTutorial, - - // Stages that are not user-controlled - WebUsbChooseMicrobit, - ConnectingBluetooth, - ConnectingMicrobits, - FlashingInProgress, - - // Failure stages - TryAgainReplugMicrobit, - TryAgainCloseTabs, - TryAgainSelectMicrobit, - TryAgainBluetoothConnect, - BadFirmware, - MicrobitUnsupported, - WebUsbBluetoothUnsupported, -} - -export enum ConnEvent { - // User triggered events - Start, - Switch, - Next, - Back, - SkipFlashing, - TryAgain, - GoToBluetoothStart, - Close, - - // Web USB Flashing events - WebUsbChooseMicrobit, - FlashingInProgress, - FlashingComplete, - - // Web USB Flashing failure events - TryAgainReplugMicrobit, - TryAgainCloseTabs, - TryAgainSelectMicrobit, - InstructManualFlashing, - BadFirmware, - MicrobitUnsupported, - - // Bluetooth connection event - ConnectingBluetooth, - - // Bluetooth connection failure event - TryAgainBluetoothConnect, - - // Connecting microbits for radio connection - ConnectingMicrobits, -} - -type StageAndType = Pick; - -export class ConnectionFlowActions { - constructor( - private actions: ConnectActions, - private connState: ConnectionFlowState, - private setConnState: (state: ConnectionFlowState) => void - ) {} - - start = () => { - this.dispatchEvent(ConnEvent.Start); - }; - - dispatchEvent = (event: ConnEvent) => { - this.setConnState(getUpdatedConnState(this.connState, event)); - }; - - connectAndflashMicrobit = async ( - progressCallback: (progress: number) => void - ) => { - this.dispatchEvent(ConnEvent.WebUsbChooseMicrobit); - const result = await this.actions.requestUSBConnectionAndFlash( - this.connState.type, - progressCallback - ); - if ( - this.connState.type === ConnType.Bluetooth && - result !== ConnectAndFlashResult.Success - ) { - return this.dispatchEvent(ConnEvent.InstructManualFlashing); - } - switch (result) { - case ConnectAndFlashResult.ErrorMicrobitUnsupported: - return this.dispatchEvent(ConnEvent.MicrobitUnsupported); - case ConnectAndFlashResult.ErrorBadFirmware: - return this.dispatchEvent(ConnEvent.BadFirmware); - case ConnectAndFlashResult.ErrorNoDeviceSelected: - return this.dispatchEvent(ConnEvent.TryAgainSelectMicrobit); - case ConnectAndFlashResult.ErrorUnableToClaimInterface: - return this.dispatchEvent(ConnEvent.TryAgainCloseTabs); - case ConnectAndFlashResult.Failed: - return this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); - case ConnectAndFlashResult.Success: - this.dispatchEvent(ConnEvent.FlashingComplete); - break; - } - // TODO: not sure if connecting microbits should be triggered here - if (this.connState.type === ConnType.RadioBridge) { - await this.actions.connectMicrobitsSerial(); - } - }; - - connectBluetooth = async (onSuccess: () => void) => { - this.dispatchEvent(ConnEvent.ConnectingBluetooth); - const result = await this.actions.connectBluetooth(); - if (result === BluetoothConnectResult.Success) { - onSuccess(); - } else { - this.dispatchEvent(ConnEvent.TryAgainBluetoothConnect); - } - }; - - disconnect = () => this.actions.disconnect(); - - getDeviceId = () => { - return this.actions.device?.getDeviceId(); - }; -} - -export const getUpdatedConnState = ( - state: ConnectionFlowState, - event: ConnEvent -) => { - switch (event) { - case ConnEvent.Start: - return { - ...state, - stage: - !state.isWebBluetoothSupported && !state.isWebUsbSupported - ? ConnStage.WebUsbBluetoothUnsupported - : ConnStage.Start, - }; - case ConnEvent.Close: - return { ...state, stage: ConnStage.None }; - case ConnEvent.SkipFlashing: - return { ...state, stage: ConnStage.ConnectBattery }; - case ConnEvent.FlashingInProgress: - return { ...state, stage: ConnStage.FlashingInProgress }; - case ConnEvent.InstructManualFlashing: - return { ...state, stage: ConnStage.ManualFlashingTutorial }; - case ConnEvent.WebUsbChooseMicrobit: - return { ...state, stage: ConnStage.WebUsbChooseMicrobit }; - case ConnEvent.ConnectingBluetooth: - return { ...state, stage: ConnStage.ConnectingBluetooth }; - case ConnEvent.ConnectingMicrobits: - return { ...state, stage: ConnStage.ConnectingMicrobits }; - case ConnEvent.Next: - return { ...state, ...getNextStageAndType(state, 1) }; - case ConnEvent.Back: - return { ...state, ...getNextStageAndType(state, -1) }; - case ConnEvent.Switch: - return { - ...state, - type: - state.type === ConnType.Bluetooth - ? ConnType.RadioRemote - : ConnType.Bluetooth, - }; - case ConnEvent.GoToBluetoothStart: - return { - ...state, - stage: ConnStage.Start, - type: ConnType.Bluetooth, - }; - case ConnEvent.FlashingComplete: - return { - ...state, - stage: - state.type === ConnType.RadioRemote - ? ConnStage.ConnectingMicrobits - : ConnStage.ConnectBattery, - }; - case ConnEvent.TryAgain: - return { - ...state, - stage: - state.stage === ConnStage.TryAgainBluetoothConnect - ? ConnStage.ConnectBluetoothTutorial - : ConnStage.ConnectCable, - }; - default: - return state; - } -}; - -const getStageAndTypeOrder = (state: ConnectionFlowState): StageAndType[] => { - const { RadioRemote, RadioBridge, Bluetooth } = ConnType; - if (state.type === ConnType.Bluetooth) { - return [ - { stage: ConnStage.Start, type: Bluetooth }, - { stage: ConnStage.ConnectCable, type: Bluetooth }, - // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. - !state.isWebUsbSupported || - state.stage === ConnStage.ManualFlashingTutorial - ? { stage: ConnStage.ManualFlashingTutorial, type: Bluetooth } - : { stage: ConnStage.WebUsbFlashingTutorial, type: Bluetooth }, - { stage: ConnStage.ConnectBattery, type: Bluetooth }, - { stage: ConnStage.EnterBluetoothPattern, type: Bluetooth }, - { stage: ConnStage.ConnectBluetoothTutorial, type: Bluetooth }, - ]; - } - return [ - { stage: ConnStage.Start, type: RadioRemote }, - { stage: ConnStage.ConnectCable, type: RadioRemote }, - { stage: ConnStage.WebUsbFlashingTutorial, type: RadioRemote }, - { stage: ConnStage.ConnectBattery, type: RadioRemote }, - { stage: ConnStage.ConnectCable, type: RadioBridge }, - { stage: ConnStage.WebUsbFlashingTutorial, type: RadioBridge }, - ]; -}; - -const getStageAndTypeIdx = ( - { stage, type }: StageAndType, - order: StageAndType[] -) => { - for (let idx = 0; idx < order.length; idx++) { - const step = order[idx]; - if (step.stage === stage && step.type === type) { - return idx; - } - } - throw new Error("Should be able to match stage and type again order"); -}; - -const getNextStageAndType = ( - state: ConnectionFlowState, - step: number -): StageAndType => { - const order = getStageAndTypeOrder(state); - const curr = { stage: state.stage, type: state.type }; - const currIdx = getStageAndTypeIdx(curr, order); - const newIdx = currIdx + step; - // If impossible step stage, stick to current step - if (newIdx === order.length || newIdx < 0) { - return curr; - } - return order[newIdx]; -}; diff --git a/src/connection-hooks.tsx b/src/connection-hooks.tsx deleted file mode 100644 index 8fdb55d7e..000000000 --- a/src/connection-hooks.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { ReactNode, createContext, useContext, useMemo, useState } from "react"; -import { - ConnStage, - ConnectionFlowActions, - ConnectionFlowState, -} from "./connection-flow"; -import { useLogging } from "./logging/logging-hooks"; -import { - ConnType, - Connections, - ConnectionsState, - ProgramType, -} from "./connections"; -import { ConnectActions } from "./connect-actions"; - -type ConnectionContextValue = { - // Used for storing the state of connections - connectionsState: ConnectionsState; - setConnectionsState: (state: ConnectionsState) => void; - - // Used for connection flow - connectionFlowState: ConnectionFlowState; - setConnectionFlowState: (state: ConnectionFlowState) => void; -}; - -export const ConnectionContext = createContext( - null -); - -interface ConnectionProviderProps { - children: ReactNode; -} - -export const ConnectionProvider = ({ children }: ConnectionProviderProps) => { - // TODO: Check bt and usb compatibility - const isWebBluetoothSupported = true; - const isWebUsbSupported = true; - - const [connectionsState, setConnectionsState] = useState( - {} - ); - - const [connectionFlowState, setConnectionFlowState] = - useState({ - type: isWebBluetoothSupported ? ConnType.Bluetooth : ConnType.RadioRemote, - stage: ConnStage.None, - isWebUsbSupported, - isWebBluetoothSupported, - }); - return ( - - {children} - - ); -}; - -export const useConnections = (): { - connections: Connections; - isInputConnected: boolean; -} => { - const connContextValue = useContext(ConnectionContext); - if (!connContextValue) { - throw new Error("Missing provider"); - } - const { connectionsState, setConnectionsState } = connContextValue; - const conn = useMemo( - () => new Connections(connectionsState, setConnectionsState), - [connectionsState, setConnectionsState] - ); - const isInputConnected = useMemo( - () => conn.isConnected(ProgramType.Input), - [conn] - ); - return { connections: conn, isInputConnected }; -}; - -export const useConnectionFlow = (): { - state: ConnectionFlowState; - actions: ConnectionFlowActions; -} => { - const connContextValue = useContext(ConnectionContext); - if (!connContextValue) { - throw new Error("Missing provider"); - } - const { connectionFlowState, setConnectionFlowState } = connContextValue; - const logging = useLogging(); - const { connections } = useConnections(); - - const actions = useMemo(() => { - const connectActions = new ConnectActions(logging, connections); - - return new ConnectionFlowActions( - connectActions, - connectionFlowState, - setConnectionFlowState - ); - }, [logging, connections, connectionFlowState, setConnectionFlowState]); - - return { - state: connectionFlowState, - actions, - }; -}; diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts new file mode 100644 index 000000000..4f1b8b960 --- /dev/null +++ b/src/connection-stage-actions.ts @@ -0,0 +1,194 @@ +import { + BluetoothConnectResult, + ConnectActions, + ConnectAndFlashResult, +} from "./connect-actions"; +import { + ConnEvent, + ConnectionFlowStep, + ConnectionFlowType, + ConnectionStage, +} from "./connection-stage-hooks"; + +type Stage = Pick; + +export class ConnectionStageActions { + constructor( + private actions: ConnectActions, + private stage: ConnectionStage, + private setStage: (stage: ConnectionStage) => void + ) {} + + start = () => { + this.dispatchEvent(ConnEvent.Start); + }; + + dispatchEvent = (event: ConnEvent) => { + this.setStage(getUpdatedConnState(this.stage, event)); + }; + + connectAndflashMicrobit = async ( + progressCallback: (progress: number) => void + ) => { + this.dispatchEvent(ConnEvent.WebUsbChooseMicrobit); + const result = await this.actions.requestUSBConnectionAndFlash( + this.stage.type, + progressCallback + ); + if ( + this.stage.type === ConnectionFlowType.Bluetooth && + result !== ConnectAndFlashResult.Success + ) { + return this.dispatchEvent(ConnEvent.InstructManualFlashing); + } + switch (result) { + case ConnectAndFlashResult.ErrorMicrobitUnsupported: + return this.dispatchEvent(ConnEvent.MicrobitUnsupported); + case ConnectAndFlashResult.ErrorBadFirmware: + return this.dispatchEvent(ConnEvent.BadFirmware); + case ConnectAndFlashResult.ErrorNoDeviceSelected: + return this.dispatchEvent(ConnEvent.TryAgainSelectMicrobit); + case ConnectAndFlashResult.ErrorUnableToClaimInterface: + return this.dispatchEvent(ConnEvent.TryAgainCloseTabs); + case ConnectAndFlashResult.Failed: + return this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); + case ConnectAndFlashResult.Success: + this.dispatchEvent(ConnEvent.FlashingComplete); + break; + } + // TODO: not sure if connecting microbits should be triggered here + if (this.stage.type === ConnectionFlowType.RadioBridge) { + await this.actions.connectMicrobitsSerial(); + } + }; + + connectBluetooth = async (onSuccess: () => void) => { + this.dispatchEvent(ConnEvent.ConnectingBluetooth); + const result = await this.actions.connectBluetooth(); + if (result === BluetoothConnectResult.Success) { + onSuccess(); + } else { + this.dispatchEvent(ConnEvent.TryAgainBluetoothConnect); + } + }; + + disconnect = () => this.actions.disconnect(); + + getDeviceId = () => { + return this.actions.device?.getDeviceId(); + }; +} + +export const getUpdatedConnState = ( + state: ConnectionStage, + event: ConnEvent +): ConnectionStage => { + switch (event) { + case ConnEvent.Start: + return { + ...state, + step: + !state.isWebBluetoothSupported && !state.isWebUsbSupported + ? ConnectionFlowStep.WebUsbBluetoothUnsupported + : ConnectionFlowStep.Start, + }; + case ConnEvent.Close: + return { ...state, step: ConnectionFlowStep.None }; + case ConnEvent.SkipFlashing: + return { ...state, step: ConnectionFlowStep.ConnectBattery }; + case ConnEvent.FlashingInProgress: + return { ...state, step: ConnectionFlowStep.FlashingInProgress }; + case ConnEvent.InstructManualFlashing: + return { ...state, step: ConnectionFlowStep.ManualFlashingTutorial }; + case ConnEvent.WebUsbChooseMicrobit: + return { ...state, step: ConnectionFlowStep.WebUsbChooseMicrobit }; + case ConnEvent.ConnectingBluetooth: + return { ...state, step: ConnectionFlowStep.ConnectingBluetooth }; + case ConnEvent.ConnectingMicrobits: + return { ...state, step: ConnectionFlowStep.ConnectingMicrobits }; + case ConnEvent.Next: + return { ...state, ...getNextStage(state, 1) }; + case ConnEvent.Back: + return { ...state, ...getNextStage(state, -1) }; + case ConnEvent.Switch: + return { + ...state, + type: + state.type === ConnectionFlowType.Bluetooth + ? ConnectionFlowType.RadioRemote + : ConnectionFlowType.Bluetooth, + }; + case ConnEvent.GoToBluetoothStart: + return { + ...state, + step: ConnectionFlowStep.Start, + type: ConnectionFlowType.Bluetooth, + }; + case ConnEvent.FlashingComplete: + return { + ...state, + step: + state.type === ConnectionFlowType.RadioRemote + ? ConnectionFlowStep.ConnectingMicrobits + : ConnectionFlowStep.ConnectBattery, + }; + case ConnEvent.TryAgain: + return { + ...state, + step: + state.step === ConnectionFlowStep.TryAgainBluetoothConnect + ? ConnectionFlowStep.ConnectBluetoothTutorial + : ConnectionFlowStep.ConnectCable, + }; + default: + return state; + } +}; + +const getStagesOrder = (state: ConnectionStage): Stage[] => { + const { RadioRemote, RadioBridge, Bluetooth } = ConnectionFlowType; + if (state.type === ConnectionFlowType.Bluetooth) { + return [ + { step: ConnectionFlowStep.Start, type: Bluetooth }, + { step: ConnectionFlowStep.ConnectCable, type: Bluetooth }, + // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. + !state.isWebUsbSupported || + state.step === ConnectionFlowStep.ManualFlashingTutorial + ? { step: ConnectionFlowStep.ManualFlashingTutorial, type: Bluetooth } + : { step: ConnectionFlowStep.WebUsbFlashingTutorial, type: Bluetooth }, + { step: ConnectionFlowStep.ConnectBattery, type: Bluetooth }, + { step: ConnectionFlowStep.EnterBluetoothPattern, type: Bluetooth }, + { step: ConnectionFlowStep.ConnectBluetoothTutorial, type: Bluetooth }, + ]; + } + return [ + { step: ConnectionFlowStep.Start, type: RadioRemote }, + { step: ConnectionFlowStep.ConnectCable, type: RadioRemote }, + { step: ConnectionFlowStep.WebUsbFlashingTutorial, type: RadioRemote }, + { step: ConnectionFlowStep.ConnectBattery, type: RadioRemote }, + { step: ConnectionFlowStep.ConnectCable, type: RadioBridge }, + { step: ConnectionFlowStep.WebUsbFlashingTutorial, type: RadioBridge }, + ]; +}; + +const getStageAndTypeIdx = ({ step, type }: Stage, order: Stage[]) => { + for (let idx = 0; idx < order.length; idx++) { + const currStage = order[idx]; + if (currStage.step === step && currStage.type === type) { + return idx; + } + } + throw new Error("Should be able to match stage and type again order"); +}; + +const getNextStage = (stage: ConnectionStage, step: number): Stage => { + const order = getStagesOrder(stage); + const curr = { step: stage.step, type: stage.type }; + const currIdx = getStageAndTypeIdx(curr, order); + const newIdx = currIdx + step; + // If impossible step stage, stick to current step + if (newIdx === order.length || newIdx < 0) { + return curr; + } + return order[newIdx]; +}; diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx new file mode 100644 index 000000000..65a8ffdd5 --- /dev/null +++ b/src/connection-stage-hooks.tsx @@ -0,0 +1,139 @@ +import { ReactNode, createContext, useContext, useMemo, useState } from "react"; +import { ConnectionStageActions } from "./connection-stage-actions"; +import { useLogging } from "./logging/logging-hooks"; +import { useConnections } from "./connections-hooks"; +import { ConnectActions } from "./connect-actions"; + +export enum ConnectionFlowType { + Bluetooth, + RadioBridge, + RadioRemote, +} + +export interface ConnectionStage { + step: ConnectionFlowStep; + type: ConnectionFlowType; + + // TODO: Separate into different hook + isWebUsbSupported: boolean; + isWebBluetoothSupported: boolean; +} + +export enum ConnectionFlowStep { + // Happy flow stages + None, + Start, + ConnectCable, + WebUsbFlashingTutorial, + ManualFlashingTutorial, + ConnectBattery, + EnterBluetoothPattern, + ConnectBluetoothTutorial, + + // Stages that are not user-controlled + WebUsbChooseMicrobit, + ConnectingBluetooth, + ConnectingMicrobits, + FlashingInProgress, + + // Failure stages + TryAgainReplugMicrobit, + TryAgainCloseTabs, + TryAgainSelectMicrobit, + TryAgainBluetoothConnect, + BadFirmware, + MicrobitUnsupported, + WebUsbBluetoothUnsupported, +} + +export enum ConnEvent { + // User triggered events + Start, + Switch, + Next, + Back, + SkipFlashing, + TryAgain, + GoToBluetoothStart, + Close, + + // Web USB Flashing events + WebUsbChooseMicrobit, + FlashingInProgress, + FlashingComplete, + + // Web USB Flashing failure events + TryAgainReplugMicrobit, + TryAgainCloseTabs, + TryAgainSelectMicrobit, + InstructManualFlashing, + BadFirmware, + MicrobitUnsupported, + + // Bluetooth connection event + ConnectingBluetooth, + + // Bluetooth connection failure event + TryAgainBluetoothConnect, + + // Connecting microbits for radio connection + ConnectingMicrobits, +} + +type ConnectionStageContextValue = [ + ConnectionStage, + (state: ConnectionStage) => void +]; + +export const ConnectionStageContext = + createContext(null); + +interface ConnectionStageProviderProps { + children: ReactNode; +} + +export const ConnectionStageProvider = ({ + children, +}: ConnectionStageProviderProps) => { + // TODO: Check bt and usb compatibility + const isWebBluetoothSupported = true; + const isWebUsbSupported = true; + + const connectionStageContextValue = useState({ + type: isWebBluetoothSupported + ? ConnectionFlowType.Bluetooth + : ConnectionFlowType.RadioRemote, + step: ConnectionFlowStep.None, + isWebUsbSupported, + isWebBluetoothSupported, + }); + return ( + + {children} + + ); +}; + +export const useConnectionStage = (): { + stage: ConnectionStage; + actions: ConnectionStageActions; +} => { + const connectionStageContextValue = useContext(ConnectionStageContext); + if (!connectionStageContextValue) { + throw new Error("Missing provider"); + } + const [stage, setStage] = connectionStageContextValue; + const logging = useLogging(); + const { connections } = useConnections(); + + const actions = useMemo(() => { + const connectActions = new ConnectActions(logging, connections); + + return new ConnectionStageActions(connectActions, stage, setStage); + }, [logging, connections, stage, setStage]); + + return { + stage, + actions, + }; +}; diff --git a/src/connections-hooks.tsx b/src/connections-hooks.tsx new file mode 100644 index 000000000..c3f5541f6 --- /dev/null +++ b/src/connections-hooks.tsx @@ -0,0 +1,70 @@ +import { ReactNode, createContext, useContext, useMemo, useState } from "react"; +import { Connections } from "./connections"; + +export enum ConnectionStatus { + Disconnected, + Connected, +} + +export interface BluetoothConnection { + status: ConnectionStatus; + type: "bluetooth"; +} +export interface RadioConnection { + status: ConnectionStatus; + type: "radio"; + // Remote micro:bit device id is stored for passing it to bridge micro:bit + remoteDeviceId: string; +} + +export type Connection = BluetoothConnection | RadioConnection; + +export enum ProgramType { + Input, +} + +export interface ConnectionsState { + [ProgramType.Input]?: Connection; +} + +type ConnectionsContextValue = [ + ConnectionsState, + setConnectionsState: (state: ConnectionsState) => void +]; + +export const ConnectionsContext = createContext( + null +); + +interface ConnectionsProviderProps { + children: ReactNode; +} + +export const ConnectionsProvider = ({ children }: ConnectionsProviderProps) => { + const connectionsContextValue = useState({}); + return ( + + {children} + + ); +}; + +export const useConnections = (): { + connections: Connections; + isInputConnected: boolean; +} => { + const connectionsContextValue = useContext(ConnectionsContext); + if (!connectionsContextValue) { + throw new Error("Missing provider"); + } + const [state, setState] = connectionsContextValue; + const conn = useMemo( + () => new Connections(state, setState), + [state, setState] + ); + const isInputConnected = useMemo( + () => conn.isConnected(ProgramType.Input), + [conn] + ); + return { connections: conn, isInputConnected }; +}; diff --git a/src/connections.ts b/src/connections.ts index 0990eac93..c6e81f835 100644 --- a/src/connections.ts +++ b/src/connections.ts @@ -1,34 +1,10 @@ -export enum ConnType { - Bluetooth, - RadioBridge, - RadioRemote, -} - -export enum ConnStatus { - Disconnected, - Connected, -} - -export interface BluetoothConnection { - status: ConnStatus; - type: "bluetooth"; -} -export interface RadioConnection { - status: ConnStatus; - type: "radio"; - // Remote micro:bit device id is stored for passing it to bridge micro:bit - remoteDeviceId: string; -} - -export type Connection = BluetoothConnection | RadioConnection; - -export enum ProgramType { - Input, -} - -export interface ConnectionsState { - [ProgramType.Input]?: Connection; -} +import { + Connection, + ConnectionStatus, + ConnectionsState, + ProgramType, + RadioConnection, +} from "./connections-hooks"; export class Connections { constructor( @@ -37,29 +13,31 @@ export class Connections { ) {} setConnection = ( - programType: ProgramType, + program: ProgramType, conn: { - status?: ConnStatus; + status?: ConnectionStatus; type?: "bluetooth" | "radio"; remoteDeviceId?: RadioConnection["remoteDeviceId"]; } ) => { const newConnection = { - status: ConnStatus.Disconnected, - ...this.connections[programType], + status: ConnectionStatus.Disconnected, + ...this.connections[program], ...conn, - }; + } as Connection; if ( !("status" in newConnection) || !("type" in newConnection) || - !("program" in newConnection) || - !(newConnection.type === "radio" && !!newConnection.remoteDeviceId) + !( + newConnection.type === "bluetooth" || + (newConnection.type === "radio" && !!newConnection.remoteDeviceId) + ) ) { throw new Error( `Invalid new connection state: ${JSON.stringify(newConnection)}` ); } - this.setConnections({ [programType]: newConnection as Connection }); + this.setConnections({ [program]: newConnection }); }; getRemoteDeviceId = ( @@ -72,6 +50,6 @@ export class Connections { }; isConnected = (programType: ProgramType): boolean => { - return this.connections[programType]?.status === ConnStatus.Connected; + return this.connections[programType]?.status === ConnectionStatus.Connected; }; } diff --git a/src/device/get-hex-file.ts b/src/device/get-hex-file.ts index e468e572e..81ea91527 100644 --- a/src/device/get-hex-file.ts +++ b/src/device/get-hex-file.ts @@ -1,11 +1,11 @@ -import { ConnType } from "../connections"; +import { ConnectionFlowType } from "../connection-stage-hooks"; import { MicrobitVersion } from "./device"; export const getHexFileUrl = ( version: MicrobitVersion | "universal", - type: ConnType | "radio-remote-dev" | "radio-local" + type: ConnectionFlowType | "radio-remote-dev" | "radio-local" ): string | undefined => { - if (type === ConnType.Bluetooth) { + if (type === ConnectionFlowType.Bluetooth) { return { 1: "firmware/ml-microbit-cpp-version-combined.hex", 2: "firmware/MICROBIT.hex", @@ -17,8 +17,8 @@ export const getHexFileUrl = ( } return { "radio-remote-dev": "firmware/radio-remote-v0.2.1-dev.hex", - [ConnType.RadioRemote]: "firmware/radio-remote-v0.2.1.hex", - [ConnType.RadioBridge]: "firmware/radio-bridge-v0.2.1.hex", + [ConnectionFlowType.RadioRemote]: "firmware/radio-remote-v0.2.1.hex", + [ConnectionFlowType.RadioBridge]: "firmware/radio-bridge-v0.2.1.hex", "radio-local": "firmware/local-sensors-v0.2.1.hex", }[type]; }; diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 716dffabf..470dd2800 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,6 +1,6 @@ import { ReactNode, createContext, useContext, useMemo, useState } from "react"; import { useStorage } from "./hooks/use-storage"; -import { Stage, Status, useStatus } from "./status-hook"; +import { MlStage, MlStatus, useMlStatus } from "./ml-status-hooks"; import { isArray } from "./utils"; export interface XYZData { x: number[]; @@ -154,7 +154,7 @@ export const GesturesProvider = ({ children }: { children: ReactNode }) => { export const useGestureActions = () => { const [gestures, setGestures] = useGestureData(); - const [status, setStatus] = useStatus(); + const [status, setStatus] = useMlStatus(); const actions = useMemo( () => new GestureActions(gestures, setGestures, status, setStatus), [gestures, setGestures, setStatus, status] @@ -167,8 +167,8 @@ class GestureActions { constructor( private gestureState: GestureContextState, private setGestureState: (gestureData: GestureContextState) => void, - private status: Status, - private setStatus: (status: Status) => void + private status: MlStatus, + private setStatus: (status: MlStatus) => void ) {} hasGestures = (): boolean => { @@ -187,10 +187,10 @@ class GestureActions { // Update training status const newTrainingStatus = !hasSufficientDataForTraining(data) - ? { stage: Stage.InsufficientData as const } - : isRetrainNeeded || this.status.stage === Stage.InsufficientData + ? { stage: MlStage.InsufficientData as const } + : isRetrainNeeded || this.status.stage === MlStage.InsufficientData ? // Updating status to retrain status is in status hook - { stage: Stage.NotTrained as const } + { stage: MlStage.NotTrained as const } : this.status; this.setStatus(newTrainingStatus); diff --git a/src/ml-actions.tsx b/src/ml-actions.ts similarity index 75% rename from src/ml-actions.tsx rename to src/ml-actions.ts index 792f33394..65fe3c815 100644 --- a/src/ml-actions.tsx +++ b/src/ml-actions.ts @@ -1,42 +1,17 @@ import { LayersModel } from "@tensorflow/tfjs"; -import { useMemo } from "react"; import { ConfidentGestureData, GestureContextState, GestureData, - useGestureData, } from "./gestures-hooks"; +import gestureData from "./test-fixtures/gesture-data.json"; import { Logging } from "./logging/logging"; -import { useLogging } from "./logging/logging-hooks"; import { Confidences, mlSettings, predict, trainModel } from "./ml"; -import { Stage, Status, useStatus } from "./status-hook"; -import gestureData from "./test-fixtures/gesture-data.json"; -import { useConnections } from "./connection-hooks"; +import { MlStage, MlStatus } from "./ml-status-hooks"; const defaultRequiredConfidence = 0.8; -export const useMlActions = () => { - const [gestures, setGestures] = useGestureData(); - const [status, setStatus] = useStatus(); - const logger = useLogging(); - const { isInputConnected } = useConnections(); - - const actions = useMemo( - () => - new MlActions( - logger, - isInputConnected, - gestures, - setGestures, - status, - setStatus - ), - [gestures, isInputConnected, logger, setGestures, setStatus, status] - ); - return actions; -}; - -class MlActions { +export class MlActions { private predictionInterval: NodeJS.Timeout | undefined; private model: LayersModel | undefined; constructor( @@ -44,19 +19,19 @@ class MlActions { private keepTesting: boolean, private gestureState: GestureContextState, private setGestureState: (gestureData: GestureContextState) => void, - private status: Status, - private setStatus: (status: Status) => void + private status: MlStatus, + private setStatus: (status: MlStatus) => void ) {} trainModel = async () => { - this.setStatus({ stage: Stage.TrainingInProgress, progressValue: 0 }); + this.setStatus({ stage: MlStage.TrainingInProgress, progressValue: 0 }); const { data } = this.gestureState; const model = await trainModel({ data, onTraining: (progressValue) => - this.setStatus({ stage: Stage.TrainingInProgress, progressValue }), - onTrainEnd: () => this.setStatus({ stage: Stage.TrainingComplete }), - onError: () => this.setStatus({ stage: Stage.TrainingError }), + this.setStatus({ stage: MlStage.TrainingInProgress, progressValue }), + onTrainEnd: () => this.setStatus({ stage: MlStage.TrainingComplete }), + onError: () => this.setStatus({ stage: MlStage.TrainingError }), }); if (!model) { @@ -100,7 +75,7 @@ class MlActions { }; private checkIfInterrupted = () => - this.status.stage !== Stage.TrainingComplete; + this.status.stage !== MlStage.TrainingComplete; private updateGestureDataConfidences = (confidences: Confidences) => { const updatedGestureData = this.gestureState.data.map( diff --git a/src/ml-hooks.tsx b/src/ml-hooks.tsx new file mode 100644 index 000000000..5d4c73c0b --- /dev/null +++ b/src/ml-hooks.tsx @@ -0,0 +1,27 @@ +import { useMemo } from "react"; +import { useConnections } from "./connections-hooks"; +import { useGestureData } from "./gestures-hooks"; +import { useLogging } from "./logging/logging-hooks"; +import { useMlStatus } from "./ml-status-hooks"; +import { MlActions } from "./ml-actions"; + +export const useMlActions = () => { + const [gestures, setGestures] = useGestureData(); + const [status, setStatus] = useMlStatus(); + const logger = useLogging(); + const { isInputConnected } = useConnections(); + + const actions = useMemo( + () => + new MlActions( + logger, + isInputConnected, + gestures, + setGestures, + status, + setStatus + ), + [gestures, isInputConnected, logger, setGestures, setStatus, status] + ); + return actions; +}; diff --git a/src/status-hook.tsx b/src/ml-status-hooks.tsx similarity index 50% rename from src/status-hook.tsx rename to src/ml-status-hooks.tsx index 0b71b4342..a4a498af3 100644 --- a/src/status-hook.tsx +++ b/src/ml-status-hooks.tsx @@ -7,7 +7,7 @@ import { } from "react"; import { hasSufficientDataForTraining, useGestureData } from "./gestures-hooks"; -export enum Stage { +export enum MlStage { RecordingData, InsufficientData, NotTrained, @@ -17,57 +17,59 @@ export enum Stage { RetrainingNeeded, } -export type Status = +export type MlStatus = | { - stage: Stage.TrainingInProgress; + stage: MlStage.TrainingInProgress; progressValue: number; } | { - stage: Exclude; + stage: Exclude; }; -interface StatusState { - status: Status; +interface MlStatusState { + status: MlStatus; hasTrainedBefore: boolean; } -type StatusContextValue = [StatusState, (status: StatusState) => void]; +type MlStatusContextValue = [MlStatusState, (status: MlStatusState) => void]; -const StatusContext = createContext(undefined); +const MlStatusContext = createContext( + undefined +); -export const StatusProvider = ({ children }: { children: ReactNode }) => { +export const MlStatusProvider = ({ children }: { children: ReactNode }) => { const [gestureState] = useGestureData(); - const statusContextValue = useState({ + const statusContextValue = useState({ status: { stage: hasSufficientDataForTraining(gestureState.data) - ? Stage.NotTrained - : Stage.InsufficientData, + ? MlStage.NotTrained + : MlStage.InsufficientData, }, hasTrainedBefore: false, }); return ( - + {children} - + ); }; -export const useStatus = (): [Status, (status: Status) => void] => { - const statusContextValue = useContext(StatusContext); +export const useMlStatus = (): [MlStatus, (status: MlStatus) => void] => { + const statusContextValue = useContext(MlStatusContext); if (!statusContextValue) { throw new Error("Missing provider"); } const [state, setState] = statusContextValue; const setStatus = useCallback( - (s: Status) => { + (s: MlStatus) => { const hasTrainedBefore = - s.stage === Stage.TrainingComplete || state.hasTrainedBefore; + s.stage === MlStage.TrainingComplete || state.hasTrainedBefore; // Update to retrain instead of not trained if has trained before const status = - hasTrainedBefore && s.stage === Stage.NotTrained - ? ({ stage: Stage.RetrainingNeeded } as const) + hasTrainedBefore && s.stage === MlStage.NotTrained + ? ({ stage: MlStage.RetrainingNeeded } as const) : s; setState({ ...state, status, hasTrainedBefore }); diff --git a/src/pages/TestModelPage.tsx b/src/pages/TestModelPage.tsx index d66433790..c25a563ce 100644 --- a/src/pages/TestModelPage.tsx +++ b/src/pages/TestModelPage.tsx @@ -4,15 +4,15 @@ import TabView from "../components/TabView"; import TestModelGridView from "../components/TestModelGridView"; import TrainModelFirstView from "../components/TrainModelFirstView"; import { testModelConfig } from "../pages-config"; -import { Stage, useStatus } from "../status-hook"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; const TestModelPage = () => { - const [{ stage }] = useStatus(); + const [{ stage }] = useMlStatus(); return ( - {stage === Stage.TrainingComplete ? ( + {stage === MlStage.TrainingComplete ? ( <> From e57cb5c53a6fba2da4f3a4cce694d776482d95cd Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Fri, 19 Jul 2024 09:13:51 +0100 Subject: [PATCH 073/172] Use connection library for USB connection (#276) We'll try it for bluetooth too in a bit but that will be the first real client so API tweaks are likely. --- package-lock.json | 69 +++-- package.json | 4 +- src/connect-actions.ts | 26 +- src/device/board-id.ts | 58 ---- src/device/board-serial-info.test.ts | 52 ---- src/device/board-serial-info.ts | 35 --- src/device/constants.ts | 80 ----- src/device/dap-wrapper.ts | 417 --------------------------- src/device/device.ts | 38 --- src/device/get-hex-file.ts | 30 +- src/device/microbit-usb.ts | 112 ------- src/device/partial-flashing-utils.ts | 128 -------- src/logging/NullLoggingProvider.tsx | 14 - 13 files changed, 88 insertions(+), 975 deletions(-) delete mode 100644 src/device/board-id.ts delete mode 100644 src/device/board-serial-info.test.ts delete mode 100644 src/device/board-serial-info.ts delete mode 100644 src/device/constants.ts delete mode 100644 src/device/dap-wrapper.ts delete mode 100644 src/device/device.ts delete mode 100644 src/device/microbit-usb.ts delete mode 100644 src/device/partial-flashing-utils.ts delete mode 100644 src/logging/NullLoggingProvider.tsx diff --git a/package-lock.json b/package-lock.json index d01377be0..3e4a96510 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@microbit/microbit-connection": "^0.0.0-alpha.6", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", @@ -22,7 +23,6 @@ "browser-lang": "^0.2.1", "chart.js": "^4.2.1", "d3": "^7.8.5", - "dapjs": "^2.3.0", "framer-motion": "^10.2.4", "js-cookie": "^3.0.4", "postcss": "^8.4.23", @@ -51,8 +51,6 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/three": "^0.152.0", - "@types/w3c-web-usb": "^1.0.6", - "@types/web-bluetooth": "^0.0.17", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", @@ -4357,6 +4355,43 @@ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, + "node_modules/@microbit/microbit-connection": { + "version": "0.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.6.tgz", + "integrity": "sha512-lvIG+IsMlNsKOnE/k6i8QAMfZ7p8aryy0KBsuqRHN5seBkkPpCWbMT9tYLD3rerE3SGm1+xDz+nFVwLa7CvJNg==", + "dependencies": { + "@microbit/microbit-universal-hex": "^0.2.2", + "@types/web-bluetooth": "^0.0.20", + "dapjs": "github:microbit-matt-hillsdon/dapjs#v2.3.0-microbit.2", + "nrf-intel-hex": "^1.4.0" + } + }, + "node_modules/@microbit/microbit-connection/node_modules/dapjs": { + "version": "2.3.0-microbit.2", + "resolved": "git+ssh://git@github.com/microbit-matt-hillsdon/dapjs.git#4529bf7a8dcb124b0a7be2bd87a5655557796456", + "license": "MIT", + "dependencies": { + "@types/node-hid": "^1.2.0", + "@types/usb": "^1.5.1", + "@types/w3c-web-usb": "^1.0.10" + }, + "engines": { + "node": ">=8.14.0" + } + }, + "node_modules/@microbit/microbit-universal-hex": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@microbit/microbit-universal-hex/-/microbit-universal-hex-0.2.2.tgz", + "integrity": "sha512-qyFt8ATgxAyPkNz9Yado4HXEeCctwP/8L1/v2hFLeVUqw/HFqVqV4piJbqRMmyOefMcQ9OyVPhLXjtbKn9063Q==", + "engines": { + "node": ">=8.5", + "npm": ">=6.0", + "yarn": "^1.0" + }, + "peerDependencies": { + "tslib": ">=1.11.1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -6029,10 +6064,9 @@ "integrity": "sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==" }, "node_modules/@types/web-bluetooth": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz", - "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==", - "dev": true + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" }, "node_modules/@types/webxr": { "version": "0.5.10", @@ -7838,19 +7872,6 @@ "node": ">=12" } }, - "node_modules/dapjs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/dapjs/-/dapjs-2.3.0.tgz", - "integrity": "sha512-quanzq7+2xnqgGqqYgARz9o3iBcZ3Ir5r5mTA7WPsjrp9ilEqqCToSFGTL+8HuGP35dUIL7O+yMBloYHhHgZDA==", - "dependencies": { - "@types/node-hid": "^1.2.0", - "@types/usb": "^1.5.1", - "@types/w3c-web-usb": "^1.0.4" - }, - "engines": { - "node": ">=8.14.0" - } - }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -11188,6 +11209,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nrf-intel-hex": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/nrf-intel-hex/-/nrf-intel-hex-1.4.0.tgz", + "integrity": "sha512-q3+GGRIpe0VvCjUP1zaqW5rk6IpCZzhD0lu7Sguo1bgWwFcA9kZRjsaKUb0jBQMnefyOl5o0BBGAxvqMqYx8Sg==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nwsapi": { "version": "2.2.10", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", diff --git a/package.json b/package.json index c243bf545..39955910a 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,6 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/three": "^0.152.0", - "@types/w3c-web-usb": "^1.0.6", - "@types/web-bluetooth": "^0.0.17", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", @@ -63,6 +61,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@microbit/microbit-connection": "^0.0.0-alpha.6", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", @@ -71,7 +70,6 @@ "browser-lang": "^0.2.1", "chart.js": "^4.2.1", "d3": "^7.8.5", - "dapjs": "^2.3.0", "framer-motion": "^10.2.4", "js-cookie": "^3.0.4", "postcss": "^8.4.23", diff --git a/src/connect-actions.ts b/src/connect-actions.ts index e1bd390d8..1979f526b 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -1,8 +1,8 @@ +import { MicrobitWebUSBConnection } from "@microbit/microbit-connection"; import { ConnectionFlowType } from "./connection-stage-hooks"; import { Connections } from "./connections"; import { ConnectionStatus, ProgramType } from "./connections-hooks"; -import { getHexFileUrl } from "./device/get-hex-file"; -import MicrobitWebUSBConnection from "./device/microbit-usb"; +import { getFlashDataSource } from "./device/get-hex-file"; import { Logging } from "./logging/logging"; export enum ConnectAndFlashResult { @@ -35,12 +35,12 @@ export class ConnectActions { progressCallback: (progress: number) => void ): Promise => { try { - this.device = new MicrobitWebUSBConnection(this.logging); + this.device = new MicrobitWebUSBConnection({ logging: this.logging }); await this.device.connect(); const result = await this.flashMicrobit(hexType, progressCallback); // Save remote micro:bit device id is stored for passing it to bridge micro:bit - const deviceId = this.device.getDeviceId(); + const deviceId = this.device.getDeviceId()?.toString(); if ( !!deviceId && result === ConnectAndFlashResult.Success && @@ -62,23 +62,23 @@ export class ConnectActions { } }; - flashMicrobit = async ( - hexType: ConnectionFlowType, - progressCallback: (progress: number) => void + private flashMicrobit = async ( + flowType: ConnectionFlowType, + progress: (progress: number) => void ): Promise => { if (!this.device) { return ConnectAndFlashResult.Failed; } - const deviceVersion = this.device.getBoardVersion(); - const hexUrl = deviceVersion - ? getHexFileUrl(deviceVersion, hexType) - : deviceVersion; + const data = getFlashDataSource(flowType); - if (!hexUrl) { + if (!data) { return ConnectAndFlashResult.ErrorMicrobitUnsupported; } try { - await this.device.flashHex(hexUrl, progressCallback); + await this.device.flash(data, { + partial: true, + progress: (v) => progress(v ?? 100), + }); return ConnectAndFlashResult.Success; } catch (e) { this.logging.error(`Flashing failed: ${JSON.stringify(e)}`); diff --git a/src/device/board-id.ts b/src/device/board-id.ts deleted file mode 100644 index 6df8c2744..000000000 --- a/src/device/board-id.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * (c) 2021, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ - -/** - * Validates micro:bit board IDs. - */ -export class BoardId { - private static v1Normalized = new BoardId(0x9900); - private static v2Normalized = new BoardId(0x9903); - - constructor(public id: number) { - if (!this.isV1() && !this.isV2()) { - throw new Error(`Could not recognise the Board ID ${id.toString(16)}`); - } - } - - isV1(): boolean { - return this.id === 0x9900 || this.id === 0x9901; - } - - isV2(): boolean { - return ( - this.id === 0x9903 || - this.id === 0x9904 || - this.id === 0x9905 || - this.id === 0x9906 - ); - } - - /** - * Return the board ID using the default ID for the board type. - * Used to integrate with MicropythonFsHex. - */ - normalize() { - return this.isV1() ? BoardId.v1Normalized : BoardId.v2Normalized; - } - - /** - * toString matches the input to parse. - * - * @returns the ID as a string. - */ - toString() { - return this.id.toString(16); - } - - /** - * @param value The ID as a hex string with no 0x prefix (e.g. 9900). - * @returns the valid board ID - * @throws if the ID isn't known. - */ - static parse(value: string): BoardId { - return new BoardId(parseInt(value, 16)); - } -} diff --git a/src/device/board-serial-info.test.ts b/src/device/board-serial-info.test.ts deleted file mode 100644 index 60c383344..000000000 --- a/src/device/board-serial-info.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * (c) 2021, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ -import { BoardId } from "./board-id"; -import { BoardSerialInfo } from "./board-serial-info"; -import { vi } from "vitest"; - -describe("BoardSerialInfo", () => { - const valid = { - serialNumber: "9904360251974e450039900a00000041000000009796990b", - } as USBDevice; - const weirdLength = { - serialNumber: "9904360251974e450039900a000000410000000097969", - } as USBDevice; - const missing = { serialNumber: "" } as USBDevice; - const log = vi.fn(); - afterEach(() => { - log.mockReset(); - }); - - it("throws if serialNumber missing", () => { - expect(() => BoardSerialInfo.parse(missing, log)).toThrowError(); - - expect(log.mock.calls).toEqual([]); - }); - - it("parses serials", () => { - const result = BoardSerialInfo.parse(valid, log); - expect(result).toEqual({ - id: BoardId.parse("9904"), - familyId: "3602", - hic: "9796990b", - }); - - expect(log.mock.calls).toEqual([]); - }); - - it("logs if unexpected length", () => { - const result = BoardSerialInfo.parse(weirdLength, log); - expect(result).toEqual({ - id: BoardId.parse("9904"), - familyId: "3602", - hic: "00097969", - }); - - expect(log.mock.calls).toEqual([ - ["USB serial number unexpected length: 45"], - ]); - }); -}); diff --git a/src/device/board-serial-info.ts b/src/device/board-serial-info.ts deleted file mode 100644 index 8651992cc..000000000 --- a/src/device/board-serial-info.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * (c) 2021, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ -import { BoardId } from "./board-id"; - -export class BoardSerialInfo { - constructor( - public id: BoardId, - public familyId: string, - public hic: string - ) {} - static parse(device: USBDevice, log: (msg: string) => void) { - const serial = device.serialNumber; - if (!serial) { - throw new Error("Could not detected ID from connected board."); - } - if (serial.length !== 48) { - log(`USB serial number unexpected length: ${serial.length}`); - } - const id = serial.substring(0, 4); - const familyId = serial.substring(4, 8); - const hic = serial.slice(-8); - return new BoardSerialInfo(BoardId.parse(id), familyId, hic); - } - - eq(other: BoardSerialInfo) { - return ( - other.id === this.id && - other.familyId === this.familyId && - other.hic === this.hic - ); - } -} diff --git a/src/device/constants.ts b/src/device/constants.ts deleted file mode 100644 index 1ad71a495..000000000 --- a/src/device/constants.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * (c) 2021, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ - -// https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/constants.ts -// https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/constants.ts - -// CRA's build tooling doesn't support const enums so we've converted them to prefixed constants here. -// If we move this to a separate library then we can replace them. -// In the meantime we should prune the list below to what we actually use. - -// FICR Registers -export const FICR = { - CODEPAGESIZE: 0x10000000 | 0x10, - CODESIZE: 0x10000000 | 0x14, -}; - -export const DapCmd = { - DAP_INFO: 0x00, - DAP_CONNECT: 0x02, - DAP_DISCONNECT: 0x03, - DAP_TRANSFER: 0x05, - DAP_TRANSFER_BLOCK: 0x06, - // Many more. -}; - -export const Csw = { - CSW_SIZE: 0x00000007, - CSW_SIZE32: 0x00000002, - CSW_ADDRINC: 0x00000030, - CSW_SADDRINC: 0x00000010, - CSW_DBGSTAT: 0x00000040, - CSW_HPROT: 0x02000000, - CSW_MSTRDBG: 0x20000000, - CSW_RESERVED: 0x01000000, - CSW_VALUE: -1, // see below - // Many more. -}; -Csw.CSW_VALUE = - Csw.CSW_RESERVED | - Csw.CSW_MSTRDBG | - Csw.CSW_HPROT | - Csw.CSW_DBGSTAT | - Csw.CSW_SADDRINC; - -export const DapVal = { - AP_ACC: 1 << 0, - READ: 1 << 1, - WRITE: 0 << 1, - // More. -}; - -export const ApReg = { - CSW: 0x00, - TAR: 0x04, - DRW: 0x0c, - // More. -}; - -export const CortexSpecialReg = { - // Debug Exception and Monitor Control Register - DEMCR: 0xe000edfc, - // DWTENA in armv6 architecture reference manual - DEMCR_VC_CORERESET: 1 << 0, - - // CPUID Register - CPUID: 0xe000ed00, - - // Debug Halting Control and Status Register - DHCSR: 0xe000edf0, - S_RESET_ST: 1 << 25, - - NVIC_AIRCR: 0xe000ed0c, - NVIC_AIRCR_VECTKEY: 0x5fa << 16, - NVIC_AIRCR_SYSRESETREQ: 1 << 2, - - // Many more. -}; diff --git a/src/device/dap-wrapper.ts b/src/device/dap-wrapper.ts deleted file mode 100644 index ee9ac97c4..000000000 --- a/src/device/dap-wrapper.ts +++ /dev/null @@ -1,417 +0,0 @@ -/** - * (c) 2021, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ -import { CortexM, DAPLink, WebUSB } from "dapjs"; -import { Logging } from "../logging/logging"; -import { - ApReg, - CortexSpecialReg, - Csw, - DapCmd, - DapVal, - FICR, -} from "./constants"; -import { - apReg, - bufferConcat, - CoreRegister, - regRequest, -} from "./partial-flashing-utils"; -import { BoardSerialInfo } from "./board-serial-info"; - -export class DAPWrapper { - transport: WebUSB; - daplink: DAPLink; - cortexM: CortexM; - - _pageSize: number | undefined; - _numPages: number | undefined; - - private loggedBoardSerialInfo: BoardSerialInfo | undefined; - - private initialConnectionComplete: boolean = false; - - constructor(public device: USBDevice, private logging: Logging) { - this.transport = new WebUSB(this.device); - this.daplink = new DAPLink(this.transport); - this.cortexM = new CortexM(this.transport); - } - - /** - * The page size. Throws if we've not connected. - */ - get pageSize(): number { - if (this._pageSize === undefined) { - throw new Error("pageSize not defined until connected"); - } - return this._pageSize; - } - - /** - * The number of pages. Throws if we've not connected. - */ - get numPages() { - if (this._numPages === undefined) { - throw new Error("numPages not defined until connected"); - } - return this._numPages; - } - - get boardSerialInfo(): BoardSerialInfo { - return BoardSerialInfo.parse( - this.device, - this.logging.log.bind(this.logging) - ); - } - - // Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L119 - async reconnectAsync(): Promise { - if (this.initialConnectionComplete) { - await this.disconnectAsync(); - - this.transport = new WebUSB(this.device); - this.daplink = new DAPLink(this.transport); - this.cortexM = new CortexM(this.transport); - } else { - this.initialConnectionComplete = true; - } - - await this.daplink.connect(); - await this.cortexM.connect(); - - this.logging.event({ - type: "WebUSB-info", - message: "connected", - }); - - const serialInfo = this.boardSerialInfo; - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - this.logging.log(`Detected board ID ${serialInfo.id}`); - - if ( - !this.loggedBoardSerialInfo || - !this.loggedBoardSerialInfo.eq(this.boardSerialInfo) - ) { - this.loggedBoardSerialInfo = this.boardSerialInfo; - this.logging.event({ - type: "WebUSB-info", - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - message: "board-id/" + this.boardSerialInfo.id, - }); - this.logging.event({ - type: "WebUSB-info", - message: - "board-family-hic/" + - this.boardSerialInfo.familyId + - this.boardSerialInfo.hic, - }); - } - - this._pageSize = await this.cortexM.readMem32(FICR.CODEPAGESIZE); - this._numPages = await this.cortexM.readMem32(FICR.CODESIZE); - } - - async startSerial(listener: (data: string) => void): Promise { - const currentBaud = await this.daplink.getSerialBaudrate(); - if (currentBaud !== 115200) { - // Changing the baud rate causes a micro:bit reset, so only do it if necessary - await this.daplink.setSerialBaudrate(115200); - } - this.daplink.on(DAPLink.EVENT_SERIAL_DATA, listener); - await this.daplink.startSerialRead(1); - } - - stopSerial(listener: (data: string) => void): void { - this.daplink.stopSerialRead(); - this.daplink.removeListener(DAPLink.EVENT_SERIAL_DATA, listener); - } - - async disconnectAsync(): Promise { - if ( - this.device.opened && - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - (this.transport as any).interfaceNumber !== undefined - ) { - return this.daplink.disconnect(); - } - } - - // Send a packet to the micro:bit directly via WebUSB and return the response. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/transport/cmsis_dap.ts#L161 - private async send(packet: number[]): Promise { - const array = Uint8Array.from(packet); - await this.transport.write(array.buffer); - - const response = await this.transport.read(); - return new Uint8Array(response.buffer); - } - - // Send a command along with relevant data to the micro:bit directly via WebUSB and handle the response. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/transport/cmsis_dap.ts#L74 - private async cmdNums( - op: number /* DapCmd */, - data: number[] - ): Promise { - data.unshift(op); - - const buf = await this.send(data); - - if (buf[0] !== op) { - throw new Error(`Bad response for ${op} -> ${buf[0]}`); - } - - switch (op) { - case DapCmd.DAP_CONNECT: - case DapCmd.DAP_INFO: - case DapCmd.DAP_TRANSFER: - case DapCmd.DAP_TRANSFER_BLOCK: - break; - default: - if (buf[1] !== 0) { - throw new Error(`Bad status for ${op} -> ${buf[1]}`); - } - } - - return buf; - } - - // Read a certain register a specified amount of times. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/dap.ts#L117 - private async readRegRepeat( - regId: number /* Reg */, - cnt: number - ): Promise { - const request = regRequest(regId); - const sendargs = [0, cnt]; - - for (let i = 0; i < cnt; ++i) { - sendargs.push(request); - } - - // Transfer the read requests to the micro:bit and retrieve the data read. - const buf = await this.cmdNums(DapCmd.DAP_TRANSFER, sendargs); - - if (buf[1] !== cnt) { - throw new Error("(many) Bad #trans " + buf[1]); - } else if (buf[2] !== 1) { - throw new Error("(many) Bad transfer status " + buf[2]); - } - - return buf.subarray(3, 3 + cnt * 4); - } - - // Write to a certain register a specified amount of data. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/dap/dap.ts#L138 - private async writeRegRepeat( - regId: number /* Reg */, - data: Uint32Array - ): Promise { - const request = regRequest(regId, true); - const sendargs = [0, data.length, 0, request]; - - data.forEach((d) => { - // separate d into bytes - sendargs.push( - d & 0xff, - (d >> 8) & 0xff, - (d >> 16) & 0xff, - (d >> 24) & 0xff - ); - }); - - // Transfer the write requests to the micro:bit and retrieve the response status. - const buf = await this.cmdNums(DapCmd.DAP_TRANSFER_BLOCK, sendargs); - - if (buf[3] !== 1) { - throw new Error("(many-wr) Bad transfer status " + buf[2]); - } - } - - // Core functionality reading a block of data from micro:bit RAM at a specified address. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L181 - private async readBlockCore( - addr: number, - words: number - ): Promise { - // Set up CMSIS-DAP to read/write from/to the RAM address addr using the register - // ApReg.DRW to write to or read from. - await this.cortexM.writeAP(ApReg.CSW, Csw.CSW_VALUE | Csw.CSW_SIZE32); - await this.cortexM.writeAP(ApReg.TAR, addr); - - let lastSize = words % 15; - if (lastSize === 0) { - lastSize = 15; - } - - const blocks = []; - - for (let i = 0; i < Math.ceil(words / 15); i++) { - const b: Uint8Array = await this.readRegRepeat( - apReg(ApReg.DRW, DapVal.READ), - i === blocks.length - 1 ? lastSize : 15 - ); - blocks.push(b); - } - - return bufferConcat(blocks).subarray(0, words * 4); - } - - // Core functionality writing a block of data to micro:bit RAM at a specified address. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L205 - private async writeBlockCore( - addr: number, - words: Uint32Array - ): Promise { - try { - // Set up CMSIS-DAP to read/write from/to the RAM address addr using the register ApReg.DRW to write to or read from. - await this.cortexM.writeAP(ApReg.CSW, Csw.CSW_VALUE | Csw.CSW_SIZE32); - await this.cortexM.writeAP(ApReg.TAR, addr); - - await this.writeRegRepeat(apReg(ApReg.DRW, DapVal.WRITE), words); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (e.dapWait) { - // Retry after a delay if required. - this.logging.log(`Transfer wait, write block`); - await new Promise((resolve) => setTimeout(resolve, 100)); - return await this.writeBlockCore(addr, words); - } else { - throw e; - } - } - } - - // Reads a block of data from micro:bit RAM at a specified address. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/memory/memory.ts#L143 - async readBlockAsync(addr: number, words: number): Promise { - const bufs = []; - const end = addr + words * 4; - let ptr = addr; - - // Read a single page at a time. - while (ptr < end) { - let nextptr = ptr + this.pageSize; - if (ptr === addr) { - nextptr &= ~(this.pageSize - 1); - } - const len = Math.min(nextptr - ptr, end - ptr); - bufs.push(await this.readBlockCore(ptr, len >> 2)); - ptr = nextptr; - } - const result = bufferConcat(bufs); - return result.subarray(0, words * 4); - } - - // Writes a block of data to micro:bit RAM at a specified address. - async writeBlockAsync(address: number, data: Uint32Array): Promise { - const payloadSize = this.transport.packetSize - 8; - if (data.buffer.byteLength > payloadSize) { - let start = 0; - let end = payloadSize; - - // Split write up into smaller writes whose data can each be held in a single packet. - while (start !== end) { - const temp = new Uint32Array(data.buffer.slice(start, end)); - await this.writeBlockCore(address + start, temp); - - start = end; - end = Math.min(data.buffer.byteLength, end + payloadSize); - } - } else { - await this.writeBlockCore(address, data); - } - } - - // Execute code at a certain address with specified values in the registers. - // Waits for execution to halt. - async executeAsync( - address: number, - code: Uint32Array, - sp: number, - pc: number, - lr: number, - ...registers: number[] - ) { - if (registers.length > 12) { - throw new Error( - `Only 12 general purpose registers but got ${registers.length} values` - ); - } - - await this.cortexM.halt(true); - await this.writeBlockAsync(address, code); - await this.cortexM.writeCoreRegister(CoreRegister.PC, pc); - await this.cortexM.writeCoreRegister(CoreRegister.LR, lr); - await this.cortexM.writeCoreRegister(CoreRegister.SP, sp); - for (let i = 0; i < registers.length; ++i) { - await this.cortexM.writeCoreRegister(i, registers[i]); - } - await this.cortexM.resume(true); - return this.waitForHalt(); - } - - // Checks whether the micro:bit has halted or timeout has been reached. - // Recurses otherwise. - private async waitForHaltCore( - halted: boolean, - deadline: number - ): Promise { - if (new Date().getTime() > deadline) { - throw new Error("timeout"); - } - if (!halted) { - const isHalted = await this.cortexM.isHalted(); - // NB this is a Promise so no stack risk. - return this.waitForHaltCore(isHalted, deadline); - } - } - - // Initial function to call to wait for the micro:bit halt. - async waitForHalt(timeToWait = 10000): Promise { - const deadline = new Date().getTime() + timeToWait; - return this.waitForHaltCore(false, deadline); - } - - // Resets the micro:bit in software by writing to NVIC_AIRCR. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L347 - private async softwareReset() { - await this.cortexM.writeMem32( - CortexSpecialReg.NVIC_AIRCR, - CortexSpecialReg.NVIC_AIRCR_VECTKEY | - CortexSpecialReg.NVIC_AIRCR_SYSRESETREQ - ); - - // wait for the system to come out of reset - let dhcsr = await this.cortexM.readMem32(CortexSpecialReg.DHCSR); - - while ((dhcsr & CortexSpecialReg.S_RESET_ST) !== 0) { - dhcsr = await this.cortexM.readMem32(CortexSpecialReg.DHCSR); - } - } - - // Reset the micro:bit, possibly halting the core on reset. - // Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L248 - async reset(halt = false) { - if (halt) { - await this.cortexM.halt(true); - - // VC_CORERESET causes the core to halt on reset. - const demcr = await this.cortexM.readMem32(CortexSpecialReg.DEMCR); - await this.cortexM.writeMem32( - CortexSpecialReg.DEMCR, - CortexSpecialReg.DEMCR | CortexSpecialReg.DEMCR_VC_CORERESET - ); - - await this.softwareReset(); - await this.waitForHalt(); - - // Unset the VC_CORERESET bit - await this.cortexM.writeMem32(CortexSpecialReg.DEMCR, demcr); - } else { - await this.softwareReset(); - } - } -} diff --git a/src/device/device.ts b/src/device/device.ts deleted file mode 100644 index f2ea60e29..000000000 --- a/src/device/device.ts +++ /dev/null @@ -1,38 +0,0 @@ -export type MicrobitVersion = 1 | 2; - -export type Button = "A" | "B"; - -export type ButtonState = - | ButtonStates.Released - | ButtonStates.Pressed - | ButtonStates.LongPressed; - -export enum ButtonStates { - Released, - Pressed, - LongPressed, -} - -export const microbitServicesUUID = { - uart: "6e400001-b5a3-f393-e0a9-e50e24dcca9e", - accelerometer: "e95d0753-251d-470a-a062-fa1922dfa9a8", - deviceInfo: "0000180a-0000-1000-8000-00805f9b34fb", - led: "e95dd91d-251d-470a-a062-fa1922dfa9a8", - io: "e95d127b-251d-470a-a062-fa1922dfa9a8", - button: "e95d9882-251d-470a-a062-fa1922dfa9a8", -}; - -export const microbitCharacteristicsUUID = { - buttonA: "e95dda90-251d-470a-a062-fa1922dfa9a8", - buttonB: "e95dda91-251d-470a-a062-fa1922dfa9a8", - accelerometer: "e95dca4b-251d-470a-a062-fa1922dfa9a8", - // TODO: To remove io? Used for controlling pins - ioData: "e95d8d00-251d-470a-a062-fa1922dfa9a8", - // Allows the state of any|all LEDs in the 5x5 grid to be set to on or off with a single GATT operation. - ledMatrixState: "e95d7b77-251d-470a-a062-fa1922dfa9a8", - modelNumber: "00002a24-0000-1000-8000-00805f9b34fb", - // Used to listen for data from the micro:bit. - uartDataTX: "6e400002-b5a3-f393-e0a9-e50e24dcca9e", - // Used for sending data to the micro:bit - uartDataRX: "6e400003-b5a3-f393-e0a9-e50e24dcca9e", -}; diff --git a/src/device/get-hex-file.ts b/src/device/get-hex-file.ts index 81ea91527..d2e02dea1 100644 --- a/src/device/get-hex-file.ts +++ b/src/device/get-hex-file.ts @@ -1,18 +1,22 @@ import { ConnectionFlowType } from "../connection-stage-hooks"; -import { MicrobitVersion } from "./device"; +import { + BoardVersion, + FlashDataError, + FlashDataSource, +} from "@microbit/microbit-connection"; export const getHexFileUrl = ( - version: MicrobitVersion | "universal", + version: BoardVersion | "universal", type: ConnectionFlowType | "radio-remote-dev" | "radio-local" ): string | undefined => { if (type === ConnectionFlowType.Bluetooth) { return { - 1: "firmware/ml-microbit-cpp-version-combined.hex", - 2: "firmware/MICROBIT.hex", + V1: "firmware/ml-microbit-cpp-version-combined.hex", + V2: "firmware/MICROBIT.hex", universal: "firmware/universal-hex.hex", }[version]; } - if (version !== 2) { + if (version !== "V2") { return undefined; } return { @@ -22,3 +26,19 @@ export const getHexFileUrl = ( "radio-local": "firmware/local-sensors-v0.2.1.hex", }[type]; }; + +export const getFlashDataSource = ( + type: ConnectionFlowType | "radio-remote-dev" | "radio-local" +): FlashDataSource => { + return async (boardVersion: BoardVersion) => { + const url = getHexFileUrl(boardVersion, type); + if (!url) { + throw new FlashDataError("No hex for board version"); + } + const response = await fetch(url); + if (!response.ok) { + throw new FlashDataError(`Failed to fetch ${response.status}`); + } + return response.text(); + }; +}; diff --git a/src/device/microbit-usb.ts b/src/device/microbit-usb.ts deleted file mode 100644 index d76bc8910..000000000 --- a/src/device/microbit-usb.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - -import { DAPLink } from "dapjs"; -import { Logging } from "../logging/logging"; -import { DAPWrapper } from "./dap-wrapper"; -import { MicrobitVersion } from "./device"; - -export const CortexSpecialReg = { - // Debug Halting Control and Status Register - DHCSR: 0xe000edf0, - S_RESET_ST: 1 << 25, - - NVIC_AIRCR: 0xe000ed0c, - NVIC_AIRCR_VECTKEY: 0x5fa << 16, - NVIC_AIRCR_SYSRESETREQ: 1 << 2, - - // Many more. -}; - -/** - * A USB connection to a micro:bit. - */ -class MicrobitWebUSBConnection { - private logging: Logging; - private device: USBDevice | undefined; // Undefined if disconnected - private connection: DAPWrapper | undefined; - - /** - * Creates a new MicrobitUSB object. - */ - constructor(logging: Logging) { - this.logging = logging; - } - - public async connect() { - const device = await this.chooseDevice(); - this.connection = new DAPWrapper(device, this.logging); - } - - private async chooseDevice(): Promise { - if (this.device) { - return this.device; - } - this.device = await navigator.usb.requestDevice({ - filters: [{ vendorId: 3368, productId: 516 }], - }); - return this.device; - } - - private logError(message: string, e: unknown) { - this.logging.error(`${message}: ${JSON.stringify(e)}`); - } - - getBoardVersion(): MicrobitVersion | null { - if (!this.connection) { - return null; - } - const boardId = this.connection.boardSerialInfo.id; - return boardId.isV1() ? 1 : boardId.isV2() ? 2 : null; - } - - getDeviceId(): string | null { - if (!this.connection) { - return null; - } - return this.connection.boardSerialInfo.id.toString(); - } - - /** - * Resets the micro:bit in software by writing to NVIC_AIRCR. - * Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/cortex/cortex.ts#L347 - */ - public async softwareReset(): Promise { - await this.connection?.reset(); - } - - /** - * Flashes a .hex file to the micro:bit. - * @param {string} url The hex file to flash. (As a link) - * @param {(progress: number) => void} progressCallback A callback for progress. - */ - public async flashHex( - url: string, - progressCallback: (progress: number) => void - ): Promise { - if (!this.connection) { - throw new Error("Must be connected now"); - } - const hexFile = await fetch(url); - const buffer = await hexFile.arrayBuffer(); - const target = new DAPLink(this.connection?.transport); - - target.on(DAPLink.EVENT_PROGRESS, (progress: number) => { - progressCallback(progress); - }); - - try { - await target.connect(); - await target.flash(buffer); - await target.disconnect(); - } catch (e) { - this.logError("Failed to flash hex", e); - throw e; - } - } -} - -export default MicrobitWebUSBConnection; diff --git a/src/device/partial-flashing-utils.ts b/src/device/partial-flashing-utils.ts deleted file mode 100644 index cc9221921..000000000 --- a/src/device/partial-flashing-utils.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * (c) 2021, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ -import { DapVal } from "./constants"; - -// Represents the micro:bit's core registers -// Drawn from https://armmbed.github.io/dapjs/docs/enums/coreregister.html -export const CoreRegister = { - SP: 13, - LR: 14, - PC: 15, -}; - -export const read32FromUInt8Array = (data: Uint8Array, i: number): number => { - return ( - (data[i] | - (data[i + 1] << 8) | - (data[i + 2] << 16) | - (data[i + 3] << 24)) >>> - 0 - ); -}; - -export const bufferConcat = (bufs: Uint8Array[]): Uint8Array => { - let len = 0; - for (const b of bufs) { - len += b.length; - } - const r = new Uint8Array(len); - len = 0; - for (const b of bufs) { - r.set(b, len); - len += b.length; - } - return r; -}; - -// Returns the MurmurHash of the data passed to it, used for checksum calculation. -// Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L14 -export const murmur3_core = (data: Uint8Array): [number, number] => { - let h0 = 0x2f9be6cc; - let h1 = 0x1ec3a6c8; - - for (let i = 0; i < data.byteLength; i += 4) { - let k = read32FromUInt8Array(data, i) >>> 0; - k = Math.imul(k, 0xcc9e2d51); - k = (k << 15) | (k >>> 17); - k = Math.imul(k, 0x1b873593); - - h0 ^= k; - h1 ^= k; - h0 = (h0 << 13) | (h0 >>> 19); - h1 = (h1 << 13) | (h1 >>> 19); - h0 = (Math.imul(h0, 5) + 0xe6546b64) >>> 0; - h1 = (Math.imul(h1, 5) + 0xe6546b64) >>> 0; - } - return [h0, h1]; -}; - -// Returns a representation of an Access Port Register. -// Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/util.ts#L63 -export const apReg = (r: number, mode: number): number /* Reg */ => { - const v = r | mode | DapVal.AP_ACC; - return 4 + ((v & 0x0c) >> 2); -}; - -// Returns a code representing a request to read/write a certain register. -// Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/util.ts#L92 -export const regRequest = (regId: number, isWrite: boolean = false): number => { - let request = !isWrite ? 1 << 1 /* READ */ : 0 << 1; /* WRITE */ - - if (regId < 4) { - request |= 0 << 0 /* DP_ACC */; - } else { - request |= 1 << 0 /* AP_ACC */; - } - - request |= (regId & 3) << 2; - - return request; -}; - -export class Page { - constructor(readonly targetAddr: number, readonly data: Uint8Array) {} -} - -// Split buffer into pages, each of pageSize size. -// Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L209 -export const pageAlignBlocks = ( - buffer: Uint8Array, - targetAddr: number, - pageSize: number -): Page[] => { - const unaligned = new Uint8Array(buffer); - const pages = []; - for (let i = 0; i < unaligned.byteLength; ) { - const newbuf = new Uint8Array(pageSize).fill(0xff); - const startPad = (targetAddr + i) & (pageSize - 1); - const newAddr = targetAddr + i - startPad; - for (; i < unaligned.byteLength; ++i) { - if (targetAddr + i >= newAddr + pageSize) break; - newbuf[targetAddr + i - newAddr] = unaligned[i]; - } - const page = new Page(newAddr, newbuf); - pages.push(page); - } - return pages; -}; - -// Filter out all pages whose calculated checksum matches the corresponding checksum passed as an argument. -// Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L523 -export const onlyChanged = ( - pages: Page[], - checksums: Uint8Array, - pageSize: number -): Page[] => { - return pages.filter((page) => { - const idx = page.targetAddr / pageSize; - if (idx * 8 + 8 > checksums.length) return true; // out of range? - const c0 = read32FromUInt8Array(checksums, idx * 8); - const c1 = read32FromUInt8Array(checksums, idx * 8 + 4); - const ch = murmur3_core(page.data); - if (c0 === ch[0] && c1 === ch[1]) return false; - return true; - }); -}; diff --git a/src/logging/NullLoggingProvider.tsx b/src/logging/NullLoggingProvider.tsx deleted file mode 100644 index 8d8ae1c88..000000000 --- a/src/logging/NullLoggingProvider.tsx +++ /dev/null @@ -1,14 +0,0 @@ -/** - * (c) 2022, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ -import { ReactNode } from "react"; -import { NullLogging } from "../deployment/default/logging"; -import { LoggingProvider } from "./logging-hooks"; - -const NullLoggingProvider = ({ children }: { children: ReactNode }) => ( - {children} -); - -export default NullLoggingProvider; From b6fcf0030695eb2aaa665bb1649fc3a5b9b035b6 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 18 Jul 2024 13:06:42 +0100 Subject: [PATCH 074/172] Hook up error dialogs for when first connecting fails --- src/connection-stage-actions.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 4f1b8b960..6105893ce 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -132,6 +132,18 @@ export const getUpdatedConnState = ( ? ConnectionFlowStep.ConnectingMicrobits : ConnectionFlowStep.ConnectBattery, }; + case ConnEvent.TryAgainBluetoothConnect: + return { ...state, step: ConnectionFlowStep.TryAgainBluetoothConnect }; + case ConnEvent.TryAgainReplugMicrobit: + return { ...state, step: ConnectionFlowStep.TryAgainReplugMicrobit }; + case ConnEvent.TryAgainCloseTabs: + return { ...state, step: ConnectionFlowStep.TryAgainCloseTabs }; + case ConnEvent.TryAgainSelectMicrobit: + return { ...state, step: ConnectionFlowStep.TryAgainSelectMicrobit }; + case ConnEvent.BadFirmware: + return { ...state, step: ConnectionFlowStep.BadFirmware }; + case ConnEvent.MicrobitUnsupported: + return { ...state, step: ConnectionFlowStep.MicrobitUnsupported }; case ConnEvent.TryAgain: return { ...state, From 0366b520e4a2c0a32730d1b3316eeb98134cc5ed Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 18 Jul 2024 15:34:27 +0100 Subject: [PATCH 075/172] Reconnect button logic --- src/components/ConnectionFlowDialogs.tsx | 21 ++++----- src/components/LiveGraphPanel.tsx | 50 ++++++++++++++------- src/connect-actions.ts | 19 ++++---- src/connection-stage-actions.ts | 38 ++++++++++------ src/connection-stage-hooks.tsx | 11 +++-- src/connections-hooks.tsx | 31 ++++++++++--- src/connections.ts | 57 ++++++++++++++++++------ 7 files changed, 156 insertions(+), 71 deletions(-) diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index c33ff0765..406773506 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -31,15 +31,19 @@ const ConnectionDialogs = () => { const [bluetoothPattern, setBluetoothPattern] = useState( Array(25).fill(false) ); + const onClose = useCallback(() => { + dispatch(ConnEvent.Close); + onCloseDialog(); + }, [dispatch, onCloseDialog]); useEffect(() => { - if ( - stage.step === ConnectionFlowStep.Start || - stage.step === ConnectionFlowStep.WebUsbBluetoothUnsupported - ) { + if (stage.step !== ConnectionFlowStep.None && !isOpen) { onOpen(); } - }, [onOpen, stage]); + if (stage.step === ConnectionFlowStep.None && isOpen) { + onClose(); + } + }, [isOpen, onClose, onOpen, stage]); const progressCallback = useCallback( (progress: number) => { @@ -85,10 +89,7 @@ const ConnectionDialogs = () => { () => dispatch(ConnEvent.InstructManualFlashing), [dispatch] ); - const onClose = useCallback(() => { - dispatch(ConnEvent.Close); - onCloseDialog(); - }, [dispatch, onCloseDialog]); + const dialogCommonProps = { isOpen, onClose }; switch (stage.step) { @@ -161,7 +162,7 @@ const ConnectionDialogs = () => { actions.connectBluetooth(onClose)} + onNextClick={actions.connectBluetooth} /> ); } diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 4903e65b3..f2ffd1b38 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -1,25 +1,32 @@ import { Button, HStack, Portal, Text } from "@chakra-ui/react"; -import { useCallback, useRef } from "react"; +import { useMemo, useRef } from "react"; import { MdBolt } from "react-icons/md"; import { FormattedMessage } from "react-intl"; -import { useConnections } from "../connections-hooks"; +import { useConnectionStage } from "../connection-stage-hooks"; +import { ConnectionStatus, useConnections } from "../connections-hooks"; import InfoToolTip from "./InfoToolTip"; import LiveGraph from "./LiveGraph"; -import { useConnectionStage } from "../connection-stage-hooks"; const LiveGraphPanel = () => { const { actions } = useConnectionStage(); - const { isInputConnected } = useConnections(); + const { inputConnection } = useConnections(); const parentPortalRef = useRef(null); - const handleDisconnect = useCallback(() => { - actions.disconnect(); - }, [actions]); + const connectBtnConfig = useMemo( + () => + inputConnection.status === ConnectionStatus.None || + inputConnection.status === ConnectionStatus.Connecting + ? { + textId: "footer.connectButton", + onClick: actions.start, + } + : { + textId: "actions.reconnect", + onClick: actions.reconnect, + }, + [actions.reconnect, actions.start, inputConnection] + ); - const handleConnect = useCallback(() => { - // TODO: Handle incompatibility dialog and reconnection - actions.start(); - }, [actions]); return ( { > - {isInputConnected ? ( - ) : ( - )} + {inputConnection.status === ConnectionStatus.Reconnecting && ( + + + + )} => { - const programType = ProgramType.Input; + const program = ProgramType.Input; + this.connections.setConnectingOrReconnecting(program, "radio"); // TODO: Use deviceId to assign to connect microbits - const deviceId = this.connections.getRemoteDeviceId(programType); + const deviceId = this.connections.getRemoteDeviceId(program); if (!deviceId) { throw new Error("Radio bridge device id not set"); } await delay(5000); - this.connections.setConnection(programType, { - status: ConnectionStatus.Connected, - type: "radio", - }); + this.connections.setConnected(program, "radio"); return RadioConnectResult.Success; }; // TODO: Replace with real connecting logic connectBluetooth = async (): Promise => { + const program = ProgramType.Input; + this.connections.setConnectingOrReconnecting(program, "bluetooth"); + await delay(5000); const isSuccess = true; if (isSuccess) { - this.connections.setConnection(ProgramType.Input, { - status: ConnectionStatus.Connected, - type: "bluetooth", - }); + this.connections.setConnected(program, "bluetooth"); return BluetoothConnectResult.Success; } return BluetoothConnectResult.Failed; diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 6105893ce..666dc5ab6 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -9,6 +9,7 @@ import { ConnectionFlowType, ConnectionStage, } from "./connection-stage-hooks"; +import { ConnectionsState, ProgramType } from "./connections-hooks"; type Stage = Pick; @@ -16,7 +17,8 @@ export class ConnectionStageActions { constructor( private actions: ConnectActions, private stage: ConnectionStage, - private setStage: (stage: ConnectionStage) => void + private setStage: (stage: ConnectionStage) => void, + private connectionsState: ConnectionsState ) {} start = () => { @@ -41,6 +43,8 @@ export class ConnectionStageActions { ) { return this.dispatchEvent(ConnEvent.InstructManualFlashing); } + // TODO: Not sure if this is a good way of error handling because it means + // there are 2 levels of switch statements to go through to provide UI switch (result) { case ConnectAndFlashResult.ErrorMicrobitUnsupported: return this.dispatchEvent(ConnEvent.MicrobitUnsupported); @@ -53,30 +57,45 @@ export class ConnectionStageActions { case ConnectAndFlashResult.Failed: return this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); case ConnectAndFlashResult.Success: - this.dispatchEvent(ConnEvent.FlashingComplete); break; } // TODO: not sure if connecting microbits should be triggered here if (this.stage.type === ConnectionFlowType.RadioBridge) { - await this.actions.connectMicrobitsSerial(); + return await this.connectMicrobits(); } + return this.dispatchEvent(ConnEvent.FlashingComplete); }; - connectBluetooth = async (onSuccess: () => void) => { + connectBluetooth = async () => { this.dispatchEvent(ConnEvent.ConnectingBluetooth); const result = await this.actions.connectBluetooth(); if (result === BluetoothConnectResult.Success) { - onSuccess(); + this.dispatchEvent(ConnEvent.Close); } else { this.dispatchEvent(ConnEvent.TryAgainBluetoothConnect); } }; + connectMicrobits = async () => { + this.dispatchEvent(ConnEvent.ConnectingMicrobits); + await this.actions.connectMicrobitsSerial(); + this.dispatchEvent(ConnEvent.Close); + }; + disconnect = () => this.actions.disconnect(); getDeviceId = () => { return this.actions.device?.getDeviceId(); }; + + reconnect = async () => { + const program = ProgramType.Input; + if (this.connectionsState[program]?.type === "bluetooth") { + await this.connectBluetooth(); + } else { + await this.connectMicrobits(); + } + }; } export const getUpdatedConnState = ( @@ -94,6 +113,7 @@ export const getUpdatedConnState = ( }; case ConnEvent.Close: return { ...state, step: ConnectionFlowStep.None }; + case ConnEvent.FlashingComplete: case ConnEvent.SkipFlashing: return { ...state, step: ConnectionFlowStep.ConnectBattery }; case ConnEvent.FlashingInProgress: @@ -124,14 +144,6 @@ export const getUpdatedConnState = ( step: ConnectionFlowStep.Start, type: ConnectionFlowType.Bluetooth, }; - case ConnEvent.FlashingComplete: - return { - ...state, - step: - state.type === ConnectionFlowType.RadioRemote - ? ConnectionFlowStep.ConnectingMicrobits - : ConnectionFlowStep.ConnectBattery, - }; case ConnEvent.TryAgainBluetoothConnect: return { ...state, step: ConnectionFlowStep.TryAgainBluetoothConnect }; case ConnEvent.TryAgainReplugMicrobit: diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 65a8ffdd5..363d72a1e 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -124,13 +124,18 @@ export const useConnectionStage = (): { } const [stage, setStage] = connectionStageContextValue; const logging = useLogging(); - const { connections } = useConnections(); + const { connections, state: connectionsState } = useConnections(); const actions = useMemo(() => { const connectActions = new ConnectActions(logging, connections); - return new ConnectionStageActions(connectActions, stage, setStage); - }, [logging, connections, stage, setStage]); + return new ConnectionStageActions( + connectActions, + stage, + setStage, + connectionsState + ); + }, [logging, connections, stage, setStage, connectionsState]); return { stage, diff --git a/src/connections-hooks.tsx b/src/connections-hooks.tsx index c3f5541f6..9f0d0d169 100644 --- a/src/connections-hooks.tsx +++ b/src/connections-hooks.tsx @@ -2,10 +2,13 @@ import { ReactNode, createContext, useContext, useMemo, useState } from "react"; import { Connections } from "./connections"; export enum ConnectionStatus { - Disconnected, + None, // Have not been connected before + Connecting, Connected, + Disconnected, + Reconnecting, } - +export type ConnectionType = "bluetooth" | "radio"; export interface BluetoothConnection { status: ConnectionStatus; type: "bluetooth"; @@ -20,11 +23,11 @@ export interface RadioConnection { export type Connection = BluetoothConnection | RadioConnection; export enum ProgramType { - Input, + Input = "input", } export interface ConnectionsState { - [ProgramType.Input]?: Connection; + [ProgramType.Input]: Connection; } type ConnectionsContextValue = [ @@ -40,8 +43,17 @@ interface ConnectionsProviderProps { children: ReactNode; } +const initialConnectionsState: ConnectionsState = { + input: { + type: "bluetooth", + status: ConnectionStatus.None, + }, +}; + export const ConnectionsProvider = ({ children }: ConnectionsProviderProps) => { - const connectionsContextValue = useState({}); + const connectionsContextValue = useState( + initialConnectionsState + ); return ( {children} @@ -50,7 +62,9 @@ export const ConnectionsProvider = ({ children }: ConnectionsProviderProps) => { }; export const useConnections = (): { + state: ConnectionsState; connections: Connections; + inputConnection: Connection; isInputConnected: boolean; } => { const connectionsContextValue = useContext(ConnectionsContext); @@ -66,5 +80,10 @@ export const useConnections = (): { () => conn.isConnected(ProgramType.Input), [conn] ); - return { connections: conn, isInputConnected }; + return { + state, + connections: conn, + inputConnection: state[ProgramType.Input], + isInputConnected, + }; }; diff --git a/src/connections.ts b/src/connections.ts index c6e81f835..19eaffa4b 100644 --- a/src/connections.ts +++ b/src/connections.ts @@ -1,6 +1,7 @@ import { Connection, ConnectionStatus, + ConnectionType, ConnectionsState, ProgramType, RadioConnection, @@ -12,27 +13,38 @@ export class Connections { private setConnections: (state: ConnectionsState) => void ) {} + private isValidConnection = (v: unknown): v is Connection => { + if (typeof v !== "object") { + return false; + } + const objValue = v as object; + if ( + !("status" in objValue) || + !("type" in objValue) || + !( + objValue.type === "bluetooth" || + (objValue.type === "radio" && "remoteDeviceId" in objValue) + ) + ) { + return false; + } + return true; + }; + setConnection = ( program: ProgramType, conn: { status?: ConnectionStatus; - type?: "bluetooth" | "radio"; + type?: ConnectionType; remoteDeviceId?: RadioConnection["remoteDeviceId"]; } ) => { + const existingConn = this.connections[program]; const newConnection = { - status: ConnectionStatus.Disconnected, - ...this.connections[program], + ...existingConn, ...conn, } as Connection; - if ( - !("status" in newConnection) || - !("type" in newConnection) || - !( - newConnection.type === "bluetooth" || - (newConnection.type === "radio" && !!newConnection.remoteDeviceId) - ) - ) { + if (!this.isValidConnection(newConnection)) { throw new Error( `Invalid new connection state: ${JSON.stringify(newConnection)}` ); @@ -40,16 +52,35 @@ export class Connections { this.setConnections({ [program]: newConnection }); }; + setConnectingOrReconnecting = ( + program: ProgramType, + type: ConnectionType + ) => { + const existingConn = this.connections[program]; + // Update status to reconnecting if it has been connected before + this.setConnection(program, { + type, + status: + existingConn.status === ConnectionStatus.None + ? ConnectionStatus.Connecting + : ConnectionStatus.Reconnecting, + }); + }; + + setConnected = (program: ProgramType, type: ConnectionType) => { + this.setConnection(program, { status: ConnectionStatus.Connected, type }); + }; + getRemoteDeviceId = ( programType: ProgramType ): RadioConnection["remoteDeviceId"] | undefined => { const inputConnection = this.connections[programType]; - return inputConnection?.type === "radio" + return inputConnection.type === "radio" ? inputConnection.remoteDeviceId : undefined; }; isConnected = (programType: ProgramType): boolean => { - return this.connections[programType]?.status === ConnectionStatus.Connected; + return this.connections[programType].status === ConnectionStatus.Connected; }; } From 010b4857d2171e7876582519fd4caf93f6661245 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 19 Jul 2024 09:55:29 +0100 Subject: [PATCH 076/172] WIP Reconnect dialogs --- src/components/ConnectionFlowDialogs.tsx | 11 ++- src/components/ConnectionLostDialog.tsx | 73 ++++++++++++++++++++ src/connect-actions.ts | 65 +++++++---------- src/connection-stage-actions.ts | 88 +++++++++++++++++++----- src/connection-stage-hooks.tsx | 18 +++-- src/connections-hooks.tsx | 12 +++- src/connections.ts | 27 +++++--- 7 files changed, 217 insertions(+), 77 deletions(-) create mode 100644 src/components/ConnectionLostDialog.tsx diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index 406773506..2ab9c251f 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -231,11 +231,20 @@ const ConnectionDialogs = () => { ); } case ConnectionFlowStep.WebUsbBluetoothUnsupported: { - console.log("here"); return ( ); } + // TODO: Reconnect dialogs + case ConnectionFlowStep.ReconnectAutoFail: { + return <>; + } + case ConnectionFlowStep.ReconnectManualFail: { + return <>; + } + case ConnectionFlowStep.ReconnectFailedTwice: { + return <>; + } } }; diff --git a/src/components/ConnectionLostDialog.tsx b/src/components/ConnectionLostDialog.tsx new file mode 100644 index 000000000..6a3880c69 --- /dev/null +++ b/src/components/ConnectionLostDialog.tsx @@ -0,0 +1,73 @@ +import { + Button, + HStack, + Heading, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; + +interface ConnectionLostDialogProps { + isOpen: boolean; + onClose: () => void; + reconnect: () => void; +} + +const ConnectionLostDialog = ({ + isOpen, + onClose, +}: ConnectionLostDialogProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + {onLinkClick && linkTextId && ( + + )} + + + + + + + + + ); +}; + +export default ConnectionLostDialog; diff --git a/src/connect-actions.ts b/src/connect-actions.ts index a270db4c6..22558d937 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -14,14 +14,15 @@ export enum ConnectAndFlashResult { ErrorUnableToClaimInterface, } -export enum BluetoothConnectResult { - Success, - Failed, -} +type ConnectAndFlashFailResult = Exclude< + ConnectAndFlashResult, + ConnectAndFlashResult.Success +>; -export enum RadioConnectResult { +export enum ConnectResult { Success, - Failed, + ManualConnectFailed, + AutomaticConnectFailed, } const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); @@ -33,31 +34,25 @@ export class ConnectActions { requestUSBConnectionAndFlash = async ( hexType: ConnectionFlowType, progressCallback: (progress: number) => void - ): Promise => { + ): Promise< + | { result: ConnectAndFlashResult.Success; deviceId: string } + | { result: ConnectAndFlashFailResult; deviceId?: string } + > => { try { this.device = new MicrobitWebUSBConnection({ logging: this.logging }); await this.device.connect(); const result = await this.flashMicrobit(hexType, progressCallback); - // Save remote micro:bit device id is stored for passing it to bridge micro:bit const deviceId = this.device.getDeviceId()?.toString(); - if ( - !!deviceId && - result === ConnectAndFlashResult.Success && - hexType === ConnectionFlowType.RadioRemote - ) { - this.connections.setConnection(ProgramType.Input, { - type: "radio", - remoteDeviceId: deviceId, - }); + if (!deviceId) { + return { result: ConnectAndFlashResult.Failed }; } - - return result; + return { result, deviceId }; } catch (e) { this.logging.error( `USB request device failed/cancelled: ${JSON.stringify(e)}` ); - return this.handleConnectAndFlashError(e); + return { result: this.handleConnectAndFlashError(e) }; } }; @@ -87,7 +82,7 @@ export class ConnectActions { private handleConnectAndFlashError = ( err: unknown - ): ConnectAndFlashResult => { + ): ConnectAndFlashFailResult => { // We might get Error objects as Promise rejection arguments if ( typeof err === "object" && @@ -123,33 +118,25 @@ export class ConnectActions { }; // TODO: Replace with real connecting logic - connectMicrobitsSerial = async (): Promise => { - const program = ProgramType.Input; - this.connections.setConnectingOrReconnecting(program, "radio"); + connectMicrobitsSerial = async (deviceId: string): Promise => { + await delay(5000); // TODO: Use deviceId to assign to connect microbits - const deviceId = this.connections.getRemoteDeviceId(program); if (!deviceId) { - throw new Error("Radio bridge device id not set"); + return ConnectResult.ManualConnectFailed; } - - await delay(5000); - this.connections.setConnected(program, "radio"); - return RadioConnectResult.Success; + return ConnectResult.ManualConnectFailed; }; // TODO: Replace with real connecting logic - connectBluetooth = async (): Promise => { - const program = ProgramType.Input; - this.connections.setConnectingOrReconnecting(program, "bluetooth"); - + connectBluetooth = async (): Promise => { await delay(5000); - const isSuccess = true; - if (isSuccess) { - this.connections.setConnected(program, "bluetooth"); - return BluetoothConnectResult.Success; + + const hasFailed = false; + if (hasFailed) { + return ConnectResult.ManualConnectFailed; } - return BluetoothConnectResult.Failed; + return ConnectResult.Success; }; // TODO: Replace with real disconnect logic diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 666dc5ab6..e0e9d91ab 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -1,5 +1,5 @@ import { - BluetoothConnectResult, + ConnectResult, ConnectActions, ConnectAndFlashResult, } from "./connect-actions"; @@ -9,16 +9,20 @@ import { ConnectionFlowType, ConnectionStage, } from "./connection-stage-hooks"; +import { Connections } from "./connections"; import { ConnectionsState, ProgramType } from "./connections-hooks"; type Stage = Pick; export class ConnectionStageActions { + private program: ProgramType = ProgramType.Input; + constructor( private actions: ConnectActions, private stage: ConnectionStage, private setStage: (stage: ConnectionStage) => void, - private connectionsState: ConnectionsState + private connectionsState: ConnectionsState, + private connections: Connections ) {} start = () => { @@ -33,10 +37,12 @@ export class ConnectionStageActions { progressCallback: (progress: number) => void ) => { this.dispatchEvent(ConnEvent.WebUsbChooseMicrobit); - const result = await this.actions.requestUSBConnectionAndFlash( - this.stage.type, - progressCallback - ); + const { result, deviceId } = + await this.actions.requestUSBConnectionAndFlash( + this.stage.type, + progressCallback + ); + if ( this.stage.type === ConnectionFlowType.Bluetooth && result !== ConnectAndFlashResult.Success @@ -59,27 +65,60 @@ export class ConnectionStageActions { case ConnectAndFlashResult.Success: break; } + + // Save remote micro:bit device id for passing it to bridge micro:bit + if (this.stage.type === ConnectionFlowType.RadioRemote) { + this.connections.setConnection(ProgramType.Input, { + type: "radio", + remoteDeviceId: deviceId, + }); + } + // TODO: not sure if connecting microbits should be triggered here if (this.stage.type === ConnectionFlowType.RadioBridge) { return await this.connectMicrobits(); } - return this.dispatchEvent(ConnEvent.FlashingComplete); + return this.dispatchEvent(ConnEvent.ConnectBattery); }; connectBluetooth = async () => { this.dispatchEvent(ConnEvent.ConnectingBluetooth); + this.connections.setConnectingOrReconnecting(this.program, "bluetooth"); const result = await this.actions.connectBluetooth(); - if (result === BluetoothConnectResult.Success) { - this.dispatchEvent(ConnEvent.Close); - } else { - this.dispatchEvent(ConnEvent.TryAgainBluetoothConnect); - } + this.handleConnectResult(result); }; connectMicrobits = async () => { this.dispatchEvent(ConnEvent.ConnectingMicrobits); - await this.actions.connectMicrobitsSerial(); - this.dispatchEvent(ConnEvent.Close); + this.connections.setConnectingOrReconnecting(this.program, "radio"); + const deviceId = this.connections.getRemoteDeviceId(this.program); + if (deviceId) { + const result = await this.actions.connectMicrobitsSerial(deviceId); + this.handleConnectResult(result); + } else { + this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); + } + }; + + handleConnectResult = (result: ConnectResult) => { + if (result === ConnectResult.Success) { + this.connections.setConnected(this.program); + return this.dispatchEvent(ConnEvent.Close); + } + const reconnectFailStreak = this.connections.setConnectionFailedStreak( + this.program + ); + if (reconnectFailStreak === 0) { + return this.dispatchEvent(ConnEvent.ConnectFailed); + } + if (reconnectFailStreak === 1) { + return this.dispatchEvent( + result === ConnectResult.ManualConnectFailed + ? ConnEvent.ReconnectManualFail + : ConnEvent.ReconnectAutoFail + ); + } + return this.dispatchEvent(ConnEvent.ReconnectFailedTwice); }; disconnect = () => this.actions.disconnect(); @@ -89,8 +128,7 @@ export class ConnectionStageActions { }; reconnect = async () => { - const program = ProgramType.Input; - if (this.connectionsState[program]?.type === "bluetooth") { + if (this.connectionsState[this.program]?.type === "bluetooth") { await this.connectBluetooth(); } else { await this.connectMicrobits(); @@ -113,7 +151,7 @@ export const getUpdatedConnState = ( }; case ConnEvent.Close: return { ...state, step: ConnectionFlowStep.None }; - case ConnEvent.FlashingComplete: + case ConnEvent.ConnectBattery: case ConnEvent.SkipFlashing: return { ...state, step: ConnectionFlowStep.ConnectBattery }; case ConnEvent.FlashingInProgress: @@ -144,8 +182,14 @@ export const getUpdatedConnState = ( step: ConnectionFlowStep.Start, type: ConnectionFlowType.Bluetooth, }; - case ConnEvent.TryAgainBluetoothConnect: - return { ...state, step: ConnectionFlowStep.TryAgainBluetoothConnect }; + case ConnEvent.ConnectFailed: + return { + ...state, + step: + state.type === ConnectionFlowType.Bluetooth + ? ConnectionFlowStep.TryAgainBluetoothConnect + : ConnectionFlowStep.TryAgainReplugMicrobit, + }; case ConnEvent.TryAgainReplugMicrobit: return { ...state, step: ConnectionFlowStep.TryAgainReplugMicrobit }; case ConnEvent.TryAgainCloseTabs: @@ -164,6 +208,12 @@ export const getUpdatedConnState = ( ? ConnectionFlowStep.ConnectBluetoothTutorial : ConnectionFlowStep.ConnectCable, }; + case ConnEvent.ReconnectAutoFail: + return { ...state, step: ConnectionFlowStep.ReconnectAutoFail }; + case ConnEvent.ReconnectManualFail: + return { ...state, step: ConnectionFlowStep.ReconnectManualFail }; + case ConnEvent.ReconnectFailedTwice: + return { ...state, step: ConnectionFlowStep.ReconnectFailedTwice }; default: return state; } diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 363d72a1e..805a68cf3 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -44,6 +44,10 @@ export enum ConnectionFlowStep { BadFirmware, MicrobitUnsupported, WebUsbBluetoothUnsupported, + + ReconnectAutoFail, + ReconnectManualFail, + ReconnectFailedTwice, } export enum ConnEvent { @@ -60,7 +64,7 @@ export enum ConnEvent { // Web USB Flashing events WebUsbChooseMicrobit, FlashingInProgress, - FlashingComplete, + ConnectBattery, // Web USB Flashing failure events TryAgainReplugMicrobit, @@ -73,11 +77,14 @@ export enum ConnEvent { // Bluetooth connection event ConnectingBluetooth, - // Bluetooth connection failure event - TryAgainBluetoothConnect, - // Connecting microbits for radio connection ConnectingMicrobits, + + // Connection failure event + ConnectFailed, + ReconnectAutoFail, + ReconnectManualFail, + ReconnectFailedTwice, } type ConnectionStageContextValue = [ @@ -133,7 +140,8 @@ export const useConnectionStage = (): { connectActions, stage, setStage, - connectionsState + connectionsState, + connections ); }, [logging, connections, stage, setStage, connectionsState]); diff --git a/src/connections-hooks.tsx b/src/connections-hooks.tsx index 9f0d0d169..22990f362 100644 --- a/src/connections-hooks.tsx +++ b/src/connections-hooks.tsx @@ -9,12 +9,17 @@ export enum ConnectionStatus { Reconnecting, } export type ConnectionType = "bluetooth" | "radio"; -export interface BluetoothConnection { + +interface ConnectionBase { status: ConnectionStatus; + // Number of times there have been consecutive reconnect fails + // for determining which reconnection dialog to show + reconnectFailStreak: number; +} +export interface BluetoothConnection extends ConnectionBase { type: "bluetooth"; } -export interface RadioConnection { - status: ConnectionStatus; +export interface RadioConnection extends ConnectionBase { type: "radio"; // Remote micro:bit device id is stored for passing it to bridge micro:bit remoteDeviceId: string; @@ -47,6 +52,7 @@ const initialConnectionsState: ConnectionsState = { input: { type: "bluetooth", status: ConnectionStatus.None, + reconnectFailStreak: 0, }, }; diff --git a/src/connections.ts b/src/connections.ts index 19eaffa4b..94de29738 100644 --- a/src/connections.ts +++ b/src/connections.ts @@ -21,6 +21,7 @@ export class Connections { if ( !("status" in objValue) || !("type" in objValue) || + !("reconnectFailStreak" in objValue) || !( objValue.type === "bluetooth" || (objValue.type === "radio" && "remoteDeviceId" in objValue) @@ -31,14 +32,7 @@ export class Connections { return true; }; - setConnection = ( - program: ProgramType, - conn: { - status?: ConnectionStatus; - type?: ConnectionType; - remoteDeviceId?: RadioConnection["remoteDeviceId"]; - } - ) => { + setConnection = (program: ProgramType, conn: Partial) => { const existingConn = this.connections[program]; const newConnection = { ...existingConn, @@ -67,8 +61,8 @@ export class Connections { }); }; - setConnected = (program: ProgramType, type: ConnectionType) => { - this.setConnection(program, { status: ConnectionStatus.Connected, type }); + setConnected = (program: ProgramType) => { + this.setConnection(program, { status: ConnectionStatus.Connected }); }; getRemoteDeviceId = ( @@ -83,4 +77,17 @@ export class Connections { isConnected = (programType: ProgramType): boolean => { return this.connections[programType].status === ConnectionStatus.Connected; }; + + setConnectionFailedStreak = (programType: ProgramType): number => { + const existingConn = this.connections[programType]; + const reconnectFailStreak = + existingConn.status === ConnectionStatus.Reconnecting + ? existingConn.reconnectFailStreak + 1 + : existingConn.reconnectFailStreak; + this.setConnection(programType, { + status: ConnectionStatus.Disconnected, + reconnectFailStreak, + }); + return reconnectFailStreak; + }; } From b63d31b436f1c68b2fc757a1607181d7c228d1ea Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 19 Jul 2024 11:18:26 +0100 Subject: [PATCH 077/172] Replace error handling with codes from DeviceError --- src/connect-actions.ts | 48 ++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 22558d937..f719b1f49 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -1,4 +1,7 @@ -import { MicrobitWebUSBConnection } from "@microbit/microbit-connection"; +import { + DeviceError, + MicrobitWebUSBConnection, +} from "@microbit/microbit-connection"; import { ConnectionFlowType } from "./connection-stage-hooks"; import { Connections } from "./connections"; import { ConnectionStatus, ProgramType } from "./connections-hooks"; @@ -83,38 +86,19 @@ export class ConnectActions { private handleConnectAndFlashError = ( err: unknown ): ConnectAndFlashFailResult => { - // We might get Error objects as Promise rejection arguments - if ( - typeof err === "object" && - err !== null && - !("message" in err) && - "promise" in err && - "reason" in err - ) { - err = err.reason; - } - if ( - typeof err !== "object" || - err === null || - !(typeof err === "object" && "message" in err) - ) { - return ConnectAndFlashResult.Failed; - } - - const errMessage = err.message as string; - - // This is somewhat fragile but worth it for scenario specific errors. - // These messages changed to be prefixed in 2023 so we've relaxed the checks. - if (/No valid interfaces found/.test(errMessage)) { - // This comes from DAPjs's WebUSB open. - return ConnectAndFlashResult.ErrorBadFirmware; - } else if (/No device selected/.test(errMessage)) { - return ConnectAndFlashResult.ErrorNoDeviceSelected; - } else if (/Unable to claim interface/.test(errMessage)) { - return ConnectAndFlashResult.ErrorUnableToClaimInterface; - } else { - return ConnectAndFlashResult.Failed; + if (err instanceof DeviceError) { + switch (err.code) { + case "clear-connect": + return ConnectAndFlashResult.ErrorUnableToClaimInterface; + case "no-device-selected": + return ConnectAndFlashResult.ErrorNoDeviceSelected; + case "update-req": + return ConnectAndFlashResult.ErrorBadFirmware; + default: + return ConnectAndFlashResult.Failed; + } } + return ConnectAndFlashResult.Failed; }; // TODO: Replace with real connecting logic From 4d808a6c4059e5b4e2b99f750ba78707d7dedf61 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon Date: Fri, 19 Jul 2024 11:40:50 +0100 Subject: [PATCH 078/172] Baby steps towards bluetooth support We connect and log the data --- src/connect-actions.ts | 44 +++++++++++++++++++++++---------- src/connection-stage-actions.ts | 2 +- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/connect-actions.ts b/src/connect-actions.ts index f719b1f49..12ccfba93 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -1,5 +1,7 @@ import { DeviceError, + ConnectionStatus as DeviceConnectionStatus, + MicrobitWebBluetoothConnection, MicrobitWebUSBConnection, } from "@microbit/microbit-connection"; import { ConnectionFlowType } from "./connection-stage-hooks"; @@ -7,6 +9,7 @@ import { Connections } from "./connections"; import { ConnectionStatus, ProgramType } from "./connections-hooks"; import { getFlashDataSource } from "./device/get-hex-file"; import { Logging } from "./logging/logging"; +import { AccelerometerDataEvent } from "../../connection/build/accelerometer"; export enum ConnectAndFlashResult { Success, @@ -31,8 +34,16 @@ export enum ConnectResult { const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); export class ConnectActions { - public device: MicrobitWebUSBConnection | undefined; - constructor(private logging: Logging, private connections: Connections) {} + public usb: MicrobitWebUSBConnection; + public bluetooth: MicrobitWebBluetoothConnection; + private accelerometerListener = (e: AccelerometerDataEvent) => { + console.log(e.data); + }; + + constructor(private logging: Logging, private connections: Connections) { + this.usb = new MicrobitWebUSBConnection({ logging }); + this.bluetooth = new MicrobitWebBluetoothConnection({ logging }); + } requestUSBConnectionAndFlash = async ( hexType: ConnectionFlowType, @@ -42,11 +53,12 @@ export class ConnectActions { | { result: ConnectAndFlashFailResult; deviceId?: string } > => { try { - this.device = new MicrobitWebUSBConnection({ logging: this.logging }); - await this.device.connect(); + // TODO: move this to init point + await this.usb.initialize(); + await this.usb.connect(); const result = await this.flashMicrobit(hexType, progressCallback); // Save remote micro:bit device id is stored for passing it to bridge micro:bit - const deviceId = this.device.getDeviceId()?.toString(); + const deviceId = this.usb.getDeviceId()?.toString(); if (!deviceId) { return { result: ConnectAndFlashResult.Failed }; } @@ -63,7 +75,7 @@ export class ConnectActions { flowType: ConnectionFlowType, progress: (progress: number) => void ): Promise => { - if (!this.device) { + if (!this.usb) { return ConnectAndFlashResult.Failed; } const data = getFlashDataSource(flowType); @@ -72,7 +84,7 @@ export class ConnectActions { return ConnectAndFlashResult.ErrorMicrobitUnsupported; } try { - await this.device.flash(data, { + await this.usb.flash(data, { partial: true, progress: (v) => progress(v ?? 100), }); @@ -114,13 +126,19 @@ export class ConnectActions { // TODO: Replace with real connecting logic connectBluetooth = async (): Promise => { - await delay(5000); - - const hasFailed = false; - if (hasFailed) { - return ConnectResult.ManualConnectFailed; + // TODO: move this to init point + await this.bluetooth.initialize(); + this.bluetooth.addEventListener( + "accelerometerdatachanged", + this.accelerometerListener + ); + await this.bluetooth.connect({ + // TODO: name + }); + if (this.bluetooth.status === DeviceConnectionStatus.CONNECTED) { + return ConnectResult.Success; } - return ConnectResult.Success; + return ConnectResult.ManualConnectFailed; }; // TODO: Replace with real disconnect logic diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index e0e9d91ab..82d3e3c74 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -124,7 +124,7 @@ export class ConnectionStageActions { disconnect = () => this.actions.disconnect(); getDeviceId = () => { - return this.actions.device?.getDeviceId(); + return this.actions.usb?.getDeviceId(); }; reconnect = async () => { From b8f685a3e26dc22c34b829487e1b282479d6e7d3 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 19 Jul 2024 12:36:33 +0100 Subject: [PATCH 079/172] Reconnect error dialog UI --- src/components/ConnectionFlowDialogs.tsx | 24 ++-- src/components/ConnectionLostDialog.tsx | 73 ------------ src/components/ReconnectErrorDialog.tsx | 138 +++++++++++++++++++++++ src/connection-stage-hooks.tsx | 6 +- 4 files changed, 154 insertions(+), 87 deletions(-) delete mode 100644 src/components/ConnectionLostDialog.tsx create mode 100644 src/components/ReconnectErrorDialog.tsx diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index 2ab9c251f..099bf4f2f 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -22,6 +22,7 @@ import TryAgainDialog from "./TryAgainDialog"; import UnsupportedMicrobitDialog from "./UnsupportedMicrobitDialog"; import WebUsbBluetoothUnsupportedDialog from "./WebUsbBluetoothUnsupportedDialog"; import WhatYouWillNeedDialog from "./WhatYouWillNeedDialog"; +import ReconnectErrorDialog from "./ReconnectErrorDialog"; const ConnectionDialogs = () => { const { stage, actions } = useConnectionStage(); @@ -213,8 +214,7 @@ const ConnectionDialogs = () => { case ConnectionFlowStep.BadFirmware: { return ( @@ -223,24 +223,26 @@ const ConnectionDialogs = () => { case ConnectionFlowStep.MicrobitUnsupported: { return ( ); } case ConnectionFlowStep.WebUsbBluetoothUnsupported: { - return ( - - ); + return ; } // TODO: Reconnect dialogs + case ConnectionFlowStep.ReconnectManualFail: case ConnectionFlowStep.ReconnectAutoFail: { - return <>; - } - case ConnectionFlowStep.ReconnectManualFail: { - return <>; + return ( + + ); } case ConnectionFlowStep.ReconnectFailedTwice: { return <>; diff --git a/src/components/ConnectionLostDialog.tsx b/src/components/ConnectionLostDialog.tsx deleted file mode 100644 index 6a3880c69..000000000 --- a/src/components/ConnectionLostDialog.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { - Button, - HStack, - Heading, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalOverlay, - Text, - VStack, -} from "@chakra-ui/react"; -import { FormattedMessage } from "react-intl"; - -interface ConnectionLostDialogProps { - isOpen: boolean; - onClose: () => void; - reconnect: () => void; -} - -const ConnectionLostDialog = ({ - isOpen, - onClose, -}: ConnectionLostDialogProps) => { - return ( - - - - - - - - - - - - - - - - - - - - - {onLinkClick && linkTextId && ( - - )} - - - - - - - - - ); -}; - -export default ConnectionLostDialog; diff --git a/src/components/ReconnectErrorDialog.tsx b/src/components/ReconnectErrorDialog.tsx new file mode 100644 index 000000000..f1d26480b --- /dev/null +++ b/src/components/ReconnectErrorDialog.tsx @@ -0,0 +1,138 @@ +import { ExternalLinkIcon } from "@chakra-ui/icons"; +import { + Button, + HStack, + Heading, + ListItem, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + UnorderedList, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import { + ConnectionFlowStep, + ConnectionFlowType, +} from "../connection-stage-hooks"; + +interface ReconnectErrorDialogProps { + isOpen: boolean; + onClose: () => void; + onReconnect: () => void; + flowType: ConnectionFlowType; + errorStep: + | ConnectionFlowStep.ReconnectManualFail + | ConnectionFlowStep.ReconnectAutoFail; +} + +const contentConfig = { + bluetooth: { + listHeading: "disconnectedWarning.bluetooth2", + bullets: [ + "disconnectedWarning.bluetooth3", + "disconnectedWarning.bluetooth4", + ], + }, + bridge: { + listHeading: "connectMB.usbTryAgain.replugMicrobit2", + bullets: [ + "connectMB.usbTryAgain.replugMicrobit3", + "connectMB.usbTryAgain.replugMicrobit4", + ], + }, + remote: { + listHeading: "disconnectedWarning.bluetooth2", + bullets: [ + "disconnectedWarning.bluetooth3", + "disconnectedWarning.bluetooth4", + ], + }, +}; + +const errorTextIdPrefixConfig = { + [ConnectionFlowStep.ReconnectAutoFail]: "disconnectedWarning", + [ConnectionFlowStep.ReconnectManualFail]: "reconnectFailed", +}; + +const ReconnectErrorDialog = ({ + isOpen, + onClose, + onReconnect, + flowType, + errorStep, +}: ReconnectErrorDialogProps) => { + const errorTextIdPrefix = errorTextIdPrefixConfig[errorStep]; + return ( + + + + + + + + + + + + + + + + + + {contentConfig[flowType].bullets.map((textId) => ( + + + + + + ))} + + + + + + + + + + + + + + + ); +}; + +export default ReconnectErrorDialog; diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 805a68cf3..1a10d4248 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -5,9 +5,9 @@ import { useConnections } from "./connections-hooks"; import { ConnectActions } from "./connect-actions"; export enum ConnectionFlowType { - Bluetooth, - RadioBridge, - RadioRemote, + Bluetooth = "bluetooth", + RadioBridge = "bridge", + RadioRemote = "remote", } export interface ConnectionStage { From e4376b9a8709fa615e88dfba6848e519300549b7 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 19 Jul 2024 13:41:26 +0100 Subject: [PATCH 080/172] Failed reconnect dialog --- src/components/ConnectionFlowDialogs.tsx | 17 ++++----- src/components/ReconnectErrorDialog.tsx | 18 ++-------- src/components/TroubleshootingLink.tsx | 29 ++++++++++++++++ src/components/WhatYouWillNeedDialog.tsx | 44 +++++++++--------------- src/connection-stage-actions.ts | 4 +++ 5 files changed, 59 insertions(+), 53 deletions(-) create mode 100644 src/components/TroubleshootingLink.tsx diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index 099bf4f2f..425cde750 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -66,8 +66,6 @@ const ConnectionDialogs = () => { } } - // TODO: Flag reconnect failed - const reconnectFailed = false; const onSwitchTypeClick = useCallback( () => dispatch(ConnEvent.Switch), [dispatch] @@ -94,10 +92,13 @@ const ConnectionDialogs = () => { const dialogCommonProps = { isOpen, onClose }; switch (stage.step) { + case ConnectionFlowStep.ReconnectFailedTwice: case ConnectionFlowStep.Start: { return ( { : undefined } onNextClick={onNextClick} - reconnect={reconnectFailed} + reconnect={stage.step === ConnectionFlowStep.ReconnectFailedTwice} /> ); } @@ -204,8 +205,7 @@ const ConnectionDialogs = () => { case ConnectionFlowStep.TryAgainCloseTabs: { return ( @@ -238,15 +238,12 @@ const ConnectionDialogs = () => { return ( ); } - case ConnectionFlowStep.ReconnectFailedTwice: { - return <>; - } } }; diff --git a/src/components/ReconnectErrorDialog.tsx b/src/components/ReconnectErrorDialog.tsx index f1d26480b..4419e8f0f 100644 --- a/src/components/ReconnectErrorDialog.tsx +++ b/src/components/ReconnectErrorDialog.tsx @@ -1,4 +1,3 @@ -import { ExternalLinkIcon } from "@chakra-ui/icons"; import { Button, HStack, @@ -19,6 +18,7 @@ import { ConnectionFlowStep, ConnectionFlowType, } from "../connection-stage-hooks"; +import TroubleshootingLink from "./TroubleshootingLink"; interface ReconnectErrorDialogProps { isOpen: boolean; @@ -105,21 +105,7 @@ const ReconnectErrorDialog = ({ - + + ); +}; + +export default TroubleshootingLink; diff --git a/src/components/WhatYouWillNeedDialog.tsx b/src/components/WhatYouWillNeedDialog.tsx index 9c23f9ba9..0098db768 100644 --- a/src/components/WhatYouWillNeedDialog.tsx +++ b/src/components/WhatYouWillNeedDialog.tsx @@ -1,21 +1,17 @@ import { Grid, GridItem, Image, Text, VStack } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import ConnectContainerDialog, { - ConnectContainerDialogProps, -} from "./ConnectContainerDialog"; import batteryPackImage from "../images/stylised-battery-pack.svg"; import microbitImage from "../images/stylised-microbit-black.svg"; import twoMicrobitsImage from "../images/stylised-two-microbits-black.svg"; import usbCableImage from "../images/stylised-usb-cable.svg"; import computerImage from "../images/stylised_computer.svg"; import computerBluetoothImage from "../images/stylised_computer_w_bluetooth.svg"; -import { ConnectionFlowType } from "../connection-stage-hooks"; +import ConnectContainerDialog, { + ConnectContainerDialogProps, +} from "./ConnectContainerDialog"; -const whatYouWillNeedRadioConfig = { - headingId: "connectMB.radioStart.heading", - reconnectHeadingId: "reconnectFailed.radioHeading", - linkTextId: "connectMB.radioStart.switchBluetooth", - items: [ +const itemsConfig = { + radio: [ { imgSrc: twoMicrobitsImage, titleId: "connectMB.radioStart.requirements1", @@ -35,13 +31,7 @@ const whatYouWillNeedRadioConfig = { subtitleId: "connectMB.radioStart.requirements4.subtitle", }, ], -}; - -const whatYouWillNeedBluetoothConfig = { - headingId: "connectMB.bluetoothStart.heading", - reconnectHeadingId: "reconnectFailed.bluetoothHeading", - linkTextId: "connectMB.bluetoothStart.switchRadio", - items: [ + bluetooth: [ { imgSrc: microbitImage, titleId: "connectMB.bluetoothStart.requirements1", @@ -69,7 +59,7 @@ export interface WhatYouWillNeedDialogProps "children" | "onBack" | "headingId" > { reconnect: boolean; - type: ConnectionFlowType; + type: "radio" | "bluetooth"; } const WhatYouWillNeedDialog = ({ @@ -77,17 +67,17 @@ const WhatYouWillNeedDialog = ({ type, ...props }: WhatYouWillNeedDialogProps) => { - const configs = { - [ConnectionFlowType.Bluetooth]: whatYouWillNeedBluetoothConfig, - [ConnectionFlowType.RadioRemote]: whatYouWillNeedRadioConfig, - [ConnectionFlowType.RadioBridge]: whatYouWillNeedRadioConfig, - }; - const { items, headingId, reconnectHeadingId, linkTextId } = configs[type]; return ( {reconnect && ( @@ -96,11 +86,11 @@ const WhatYouWillNeedDialog = ({ )} - {items.map(({ imgSrc, titleId, subtitleId }) => { + {itemsConfig[type].map(({ imgSrc, titleId, subtitleId }) => { return ( diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 82d3e3c74..65898a82d 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -144,6 +144,10 @@ export const getUpdatedConnState = ( case ConnEvent.Start: return { ...state, + type: + state.type === ConnectionFlowType.RadioBridge + ? ConnectionFlowType.RadioRemote + : ConnectionFlowType.Bluetooth, step: !state.isWebBluetoothSupported && !state.isWebUsbSupported ? ConnectionFlowStep.WebUsbBluetoothUnsupported From 4360a77449c50a06907230a9e4f99445c9e0ec02 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 19 Jul 2024 13:42:33 +0100 Subject: [PATCH 081/172] Temporarily suppress eslint so CI would pass --- src/connect-actions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 12ccfba93..64fdcecf2 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -37,6 +37,7 @@ export class ConnectActions { public usb: MicrobitWebUSBConnection; public bluetooth: MicrobitWebBluetoothConnection; private accelerometerListener = (e: AccelerometerDataEvent) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access console.log(e.data); }; From e60449a1cee1811608817895625de5b2c8a71d66 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 19 Jul 2024 13:50:36 +0100 Subject: [PATCH 082/172] Generalise troubleshoot link component -> external link component --- .../{TroubleshootingLink.tsx => ExternalLink.tsx} | 9 +++++---- src/components/ReconnectErrorDialog.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) rename src/components/{TroubleshootingLink.tsx => ExternalLink.tsx} (67%) diff --git a/src/components/TroubleshootingLink.tsx b/src/components/ExternalLink.tsx similarity index 67% rename from src/components/TroubleshootingLink.tsx rename to src/components/ExternalLink.tsx index 9ff0019fc..993ea2439 100644 --- a/src/components/TroubleshootingLink.tsx +++ b/src/components/ExternalLink.tsx @@ -2,11 +2,12 @@ import { ExternalLinkIcon } from "@chakra-ui/icons"; import { Button } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -interface TroubleshootingLinkProps { +interface ExternalLinkProps { textId: string; + href: string; } -const TroubleshootingLink = ({ textId }: TroubleshootingLinkProps) => { +const ExternalLink = ({ textId, href }: ExternalLinkProps) => { return ( @@ -56,15 +58,15 @@ const LiveGraphPanel = () => { variant="primary" size="sm" isDisabled={ - inputConnection.status === ConnectionStatus.Reconnecting || - inputConnection.status === ConnectionStatus.Connecting + stage.status === ConnectionStatus.Reconnecting || + stage.status === ConnectionStatus.Connecting } onClick={connectBtnConfig.onClick} > )} - {inputConnection.status === ConnectionStatus.Reconnecting && ( + {stage.status === ConnectionStatus.Reconnecting && ( diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index d0adf3776..078114038 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -2,7 +2,6 @@ import { Button, HStack, StackProps, useDisclosure } from "@chakra-ui/react"; import { useCallback, useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; -import { useConnections } from "../connections-hooks"; import { useGestureActions } from "../gestures-hooks"; import { createStepPageUrl } from "../urls"; import StartOverWarningDialog from "./StartOverWarningDialog"; @@ -16,8 +15,7 @@ const StartResumeActions = ({ ...props }: Partial) => { ); const startOverWarningDialogDisclosure = useDisclosure(); const navigate = useNavigate(); - const { actions: connectionStageActions } = useConnectionStage(); - const { isInputConnected } = useConnections(); + const { actions: connStageActions, isConnected } = useConnectionStage(); const handleNavigateToAddData = useCallback(() => { navigate(createStepPageUrl("add-data")); @@ -25,17 +23,12 @@ const StartResumeActions = ({ ...props }: Partial) => { const handleStartNewSession = useCallback(() => { gestureActions.deleteAllGestures(); - if (isInputConnected) { + if (isConnected) { handleNavigateToAddData(); } else { - connectionStageActions.start(); + connStageActions.start(); } - }, [ - gestureActions, - connectionStageActions, - handleNavigateToAddData, - isInputConnected, - ]); + }, [gestureActions, connStageActions, handleNavigateToAddData, isConnected]); const onClickStartNewSession = useCallback(() => { if (hasExistingSession) { diff --git a/src/connect-actions.ts b/src/connect-actions.ts index e930daeba..8ac86242a 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -1,13 +1,11 @@ import { - DeviceError, + AccelerometerDataEvent, ConnectionStatus as DeviceConnectionStatus, + DeviceError, MicrobitWebBluetoothConnection, MicrobitWebUSBConnection, - AccelerometerDataEvent, } from "@microbit/microbit-connection"; import { ConnectionFlowType } from "./connection-stage-hooks"; -import { Connections } from "./connections"; -import { ConnectionStatus, ProgramType } from "./connections-hooks"; import { getFlashDataSource } from "./device/get-hex-file"; import { Logging } from "./logging/logging"; @@ -20,7 +18,7 @@ export enum ConnectAndFlashResult { ErrorUnableToClaimInterface = "ErrorUnableToClaimInterface", } -type ConnectAndFlashFailResult = Exclude< +export type ConnectAndFlashFailResult = Exclude< ConnectAndFlashResult, ConnectAndFlashResult.Success >; @@ -34,14 +32,13 @@ export enum ConnectResult { const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); export class ConnectActions { - public usb: MicrobitWebUSBConnection; - public bluetooth: MicrobitWebBluetoothConnection; + private usb: MicrobitWebUSBConnection; + private bluetooth: MicrobitWebBluetoothConnection; private accelerometerListener = (e: AccelerometerDataEvent) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access console.log(e.data); }; - constructor(private logging: Logging, private connections: Connections) { + constructor(private logging: Logging) { this.usb = new MicrobitWebUSBConnection({ logging }); this.bluetooth = new MicrobitWebBluetoothConnection({ logging }); } @@ -50,8 +47,8 @@ export class ConnectActions { hexType: ConnectionFlowType, progressCallback: (progress: number) => void ): Promise< - | { result: ConnectAndFlashResult.Success; deviceId: string } - | { result: ConnectAndFlashFailResult; deviceId?: string } + | { result: ConnectAndFlashResult.Success; deviceId: number } + | { result: ConnectAndFlashFailResult; deviceId?: number } > => { try { // TODO: move this to init point @@ -59,7 +56,7 @@ export class ConnectActions { await this.usb.connect(); const result = await this.flashMicrobit(hexType, progressCallback); // Save remote micro:bit device id is stored for passing it to bridge micro:bit - const deviceId = this.usb.getDeviceId()?.toString(); + const deviceId = this.usb.getDeviceId(); if (!deviceId) { return { result: ConnectAndFlashResult.Failed }; } @@ -115,7 +112,7 @@ export class ConnectActions { }; // TODO: Replace with real connecting logic - connectMicrobitsSerial = async (deviceId: string): Promise => { + connectMicrobitsSerial = async (deviceId: number): Promise => { await delay(5000); // TODO: Use deviceId to assign to connect microbits @@ -143,9 +140,7 @@ export class ConnectActions { }; // TODO: Replace with real disconnect logic - disconnect = () => { - this.connections.setConnection(ProgramType.Input, { - status: ConnectionStatus.Disconnected, - }); + disconnect = async () => { + await delay(5000); }; } diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 65898a82d..8018c461a 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -1,52 +1,60 @@ import { - ConnectResult, ConnectActions, + ConnectAndFlashFailResult, ConnectAndFlashResult, + ConnectResult, } from "./connect-actions"; import { ConnEvent, ConnectionFlowStep, ConnectionFlowType, ConnectionStage, + ConnectionStatus, + ConnectionType, } from "./connection-stage-hooks"; -import { Connections } from "./connections"; -import { ConnectionsState, ProgramType } from "./connections-hooks"; -type Stage = Pick; +type FlowStage = Pick; export class ConnectionStageActions { - private program: ProgramType = ProgramType.Input; - constructor( private actions: ConnectActions, private stage: ConnectionStage, - private setStage: (stage: ConnectionStage) => void, - private connectionsState: ConnectionsState, - private connections: Connections + private setStage: (stage: ConnectionStage) => void ) {} - start = () => { - this.dispatchEvent(ConnEvent.Start); - }; + start = () => this.dispatchEvent(ConnEvent.Start); dispatchEvent = (event: ConnEvent) => { this.setStage(getUpdatedConnState(this.stage, event)); }; connectAndflashMicrobit = async ( - progressCallback: (progress: number) => void + progressCallback: (progress: number) => void, + onSuccess: (deviceId: number) => void ) => { this.dispatchEvent(ConnEvent.WebUsbChooseMicrobit); const { result, deviceId } = await this.actions.requestUSBConnectionAndFlash( - this.stage.type, + this.stage.flowType, progressCallback ); + if (result !== ConnectAndFlashResult.Success) { + return this.handleConnectAndFlashFail(result); + } + // Save device id for connecting micro:bits via serial, or to show bluetooth pattern + this.setStage({ + ...this.stage, + deviceIds: [...this.stage.deviceIds, deviceId], + }); + onSuccess(deviceId); + if (this.stage.flowType === ConnectionFlowType.RadioBridge) { + return await this.connectMicrobits(); + } + return this.dispatchEvent(ConnEvent.ConnectBattery); + }; - if ( - this.stage.type === ConnectionFlowType.Bluetooth && - result !== ConnectAndFlashResult.Success - ) { + private handleConnectAndFlashFail = (result: ConnectAndFlashFailResult) => { + if (this.stage.flowType === ConnectionFlowType.Bluetooth) { return this.dispatchEvent(ConnEvent.InstructManualFlashing); } // TODO: Not sure if this is a good way of error handling because it means @@ -60,54 +68,47 @@ export class ConnectionStageActions { return this.dispatchEvent(ConnEvent.TryAgainSelectMicrobit); case ConnectAndFlashResult.ErrorUnableToClaimInterface: return this.dispatchEvent(ConnEvent.TryAgainCloseTabs); - case ConnectAndFlashResult.Failed: + default: return this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); - case ConnectAndFlashResult.Success: - break; - } - - // Save remote micro:bit device id for passing it to bridge micro:bit - if (this.stage.type === ConnectionFlowType.RadioRemote) { - this.connections.setConnection(ProgramType.Input, { - type: "radio", - remoteDeviceId: deviceId, - }); } - - // TODO: not sure if connecting microbits should be triggered here - if (this.stage.type === ConnectionFlowType.RadioBridge) { - return await this.connectMicrobits(); - } - return this.dispatchEvent(ConnEvent.ConnectBattery); }; connectBluetooth = async () => { this.dispatchEvent(ConnEvent.ConnectingBluetooth); - this.connections.setConnectingOrReconnecting(this.program, "bluetooth"); + this.onConnectingOrReconnectingStatus("bluetooth"); const result = await this.actions.connectBluetooth(); this.handleConnectResult(result); }; connectMicrobits = async () => { this.dispatchEvent(ConnEvent.ConnectingMicrobits); - this.connections.setConnectingOrReconnecting(this.program, "radio"); - const deviceId = this.connections.getRemoteDeviceId(this.program); - if (deviceId) { - const result = await this.actions.connectMicrobitsSerial(deviceId); + this.onConnectingOrReconnectingStatus("radio"); + const deviceId = this.stage.deviceIds; + if (deviceId.length > 0) { + const result = await this.actions.connectMicrobitsSerial(deviceId[0]); this.handleConnectResult(result); } else { this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); } }; - handleConnectResult = (result: ConnectResult) => { + private onConnectingOrReconnectingStatus = (connType: ConnectionType) => { + this.setStage({ + ...this.stage, + connType, + status: + this.stage.status === ConnectionStatus.None + ? ConnectionStatus.Connecting + : ConnectionStatus.Reconnecting, + }); + }; + + private handleConnectResult = (result: ConnectResult) => { if (result === ConnectResult.Success) { - this.connections.setConnected(this.program); + this.onConnected(); return this.dispatchEvent(ConnEvent.Close); } - const reconnectFailStreak = this.connections.setConnectionFailedStreak( - this.program - ); + const reconnectFailStreak = this.setDisconnectedAndRecordFailStreak(); if (reconnectFailStreak === 0) { return this.dispatchEvent(ConnEvent.ConnectFailed); } @@ -121,14 +122,38 @@ export class ConnectionStageActions { return this.dispatchEvent(ConnEvent.ReconnectFailedTwice); }; - disconnect = () => this.actions.disconnect(); + private onConnected = () => { + this.setStage({ + ...this.stage, + status: ConnectionStatus.Connected, + reconnectFailStreak: 0, + }); + }; + + private setDisconnectedAndRecordFailStreak = () => { + const reconnectFailStreak = + this.stage.status === ConnectionStatus.Reconnecting + ? this.stage.reconnectFailStreak + 1 + : this.stage.reconnectFailStreak; + this.setStage({ + ...this.stage, + reconnectFailStreak, + status: ConnectionStatus.Disconnected, + }); + return reconnectFailStreak; + }; - getDeviceId = () => { - return this.actions.usb?.getDeviceId(); + disconnect = async () => { + await this.actions.disconnect(); + this.setStage({ + ...this.stage, + status: ConnectionStatus.Disconnected, + deviceIds: [], + }); }; reconnect = async () => { - if (this.connectionsState[this.program]?.type === "bluetooth") { + if (this.stage.connType === "bluetooth") { await this.connectBluetooth(); } else { await this.connectMicrobits(); @@ -144,30 +169,30 @@ export const getUpdatedConnState = ( case ConnEvent.Start: return { ...state, - type: - state.type === ConnectionFlowType.RadioBridge + flowType: + state.flowType === ConnectionFlowType.RadioBridge ? ConnectionFlowType.RadioRemote : ConnectionFlowType.Bluetooth, - step: + flowStep: !state.isWebBluetoothSupported && !state.isWebUsbSupported ? ConnectionFlowStep.WebUsbBluetoothUnsupported : ConnectionFlowStep.Start, }; case ConnEvent.Close: - return { ...state, step: ConnectionFlowStep.None }; + return { ...state, flowStep: ConnectionFlowStep.None }; case ConnEvent.ConnectBattery: case ConnEvent.SkipFlashing: - return { ...state, step: ConnectionFlowStep.ConnectBattery }; + return { ...state, flowStep: ConnectionFlowStep.ConnectBattery }; case ConnEvent.FlashingInProgress: - return { ...state, step: ConnectionFlowStep.FlashingInProgress }; + return { ...state, flowStep: ConnectionFlowStep.FlashingInProgress }; case ConnEvent.InstructManualFlashing: - return { ...state, step: ConnectionFlowStep.ManualFlashingTutorial }; + return { ...state, flowStep: ConnectionFlowStep.ManualFlashingTutorial }; case ConnEvent.WebUsbChooseMicrobit: - return { ...state, step: ConnectionFlowStep.WebUsbChooseMicrobit }; + return { ...state, flowStep: ConnectionFlowStep.WebUsbChooseMicrobit }; case ConnEvent.ConnectingBluetooth: - return { ...state, step: ConnectionFlowStep.ConnectingBluetooth }; + return { ...state, flowStep: ConnectionFlowStep.ConnectingBluetooth }; case ConnEvent.ConnectingMicrobits: - return { ...state, step: ConnectionFlowStep.ConnectingMicrobits }; + return { ...state, flowStep: ConnectionFlowStep.ConnectingMicrobits }; case ConnEvent.Next: return { ...state, ...getNextStage(state, 1) }; case ConnEvent.Back: @@ -175,98 +200,117 @@ export const getUpdatedConnState = ( case ConnEvent.Switch: return { ...state, - type: - state.type === ConnectionFlowType.Bluetooth + flowType: + state.flowType === ConnectionFlowType.Bluetooth ? ConnectionFlowType.RadioRemote : ConnectionFlowType.Bluetooth, }; case ConnEvent.GoToBluetoothStart: return { ...state, - step: ConnectionFlowStep.Start, - type: ConnectionFlowType.Bluetooth, + flowStep: ConnectionFlowStep.Start, + flowType: ConnectionFlowType.Bluetooth, }; case ConnEvent.ConnectFailed: return { ...state, - step: - state.type === ConnectionFlowType.Bluetooth + flowStep: + state.flowType === ConnectionFlowType.Bluetooth ? ConnectionFlowStep.TryAgainBluetoothConnect : ConnectionFlowStep.TryAgainReplugMicrobit, }; case ConnEvent.TryAgainReplugMicrobit: - return { ...state, step: ConnectionFlowStep.TryAgainReplugMicrobit }; + return { ...state, flowStep: ConnectionFlowStep.TryAgainReplugMicrobit }; case ConnEvent.TryAgainCloseTabs: - return { ...state, step: ConnectionFlowStep.TryAgainCloseTabs }; + return { ...state, flowStep: ConnectionFlowStep.TryAgainCloseTabs }; case ConnEvent.TryAgainSelectMicrobit: - return { ...state, step: ConnectionFlowStep.TryAgainSelectMicrobit }; + return { ...state, flowStep: ConnectionFlowStep.TryAgainSelectMicrobit }; case ConnEvent.BadFirmware: - return { ...state, step: ConnectionFlowStep.BadFirmware }; + return { ...state, flowStep: ConnectionFlowStep.BadFirmware }; case ConnEvent.MicrobitUnsupported: - return { ...state, step: ConnectionFlowStep.MicrobitUnsupported }; + return { ...state, flowStep: ConnectionFlowStep.MicrobitUnsupported }; case ConnEvent.TryAgain: return { ...state, - step: - state.step === ConnectionFlowStep.TryAgainBluetoothConnect + flowStep: + state.flowStep === ConnectionFlowStep.TryAgainBluetoothConnect ? ConnectionFlowStep.ConnectBluetoothTutorial : ConnectionFlowStep.ConnectCable, }; case ConnEvent.ReconnectAutoFail: - return { ...state, step: ConnectionFlowStep.ReconnectAutoFail }; + return { ...state, flowStep: ConnectionFlowStep.ReconnectAutoFail }; case ConnEvent.ReconnectManualFail: - return { ...state, step: ConnectionFlowStep.ReconnectManualFail }; + return { ...state, flowStep: ConnectionFlowStep.ReconnectManualFail }; case ConnEvent.ReconnectFailedTwice: - return { ...state, step: ConnectionFlowStep.ReconnectFailedTwice }; + return { ...state, flowStep: ConnectionFlowStep.ReconnectFailedTwice }; default: return state; } }; -const getStagesOrder = (state: ConnectionStage): Stage[] => { +const getStagesOrder = (state: ConnectionStage): FlowStage[] => { const { RadioRemote, RadioBridge, Bluetooth } = ConnectionFlowType; - if (state.type === ConnectionFlowType.Bluetooth) { + if (state.flowType === ConnectionFlowType.Bluetooth) { return [ - { step: ConnectionFlowStep.Start, type: Bluetooth }, - { step: ConnectionFlowStep.ConnectCable, type: Bluetooth }, + { flowStep: ConnectionFlowStep.Start, flowType: Bluetooth }, + { flowStep: ConnectionFlowStep.ConnectCable, flowType: Bluetooth }, // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. !state.isWebUsbSupported || - state.step === ConnectionFlowStep.ManualFlashingTutorial - ? { step: ConnectionFlowStep.ManualFlashingTutorial, type: Bluetooth } - : { step: ConnectionFlowStep.WebUsbFlashingTutorial, type: Bluetooth }, - { step: ConnectionFlowStep.ConnectBattery, type: Bluetooth }, - { step: ConnectionFlowStep.EnterBluetoothPattern, type: Bluetooth }, - { step: ConnectionFlowStep.ConnectBluetoothTutorial, type: Bluetooth }, + state.flowStep === ConnectionFlowStep.ManualFlashingTutorial + ? { + flowStep: ConnectionFlowStep.ManualFlashingTutorial, + flowType: Bluetooth, + } + : { + flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, + flowType: Bluetooth, + }, + { flowStep: ConnectionFlowStep.ConnectBattery, flowType: Bluetooth }, + { + flowStep: ConnectionFlowStep.EnterBluetoothPattern, + flowType: Bluetooth, + }, + { + flowStep: ConnectionFlowStep.ConnectBluetoothTutorial, + flowType: Bluetooth, + }, ]; } return [ - { step: ConnectionFlowStep.Start, type: RadioRemote }, - { step: ConnectionFlowStep.ConnectCable, type: RadioRemote }, - { step: ConnectionFlowStep.WebUsbFlashingTutorial, type: RadioRemote }, - { step: ConnectionFlowStep.ConnectBattery, type: RadioRemote }, - { step: ConnectionFlowStep.ConnectCable, type: RadioBridge }, - { step: ConnectionFlowStep.WebUsbFlashingTutorial, type: RadioBridge }, + { flowStep: ConnectionFlowStep.Start, flowType: RadioRemote }, + { flowStep: ConnectionFlowStep.ConnectCable, flowType: RadioRemote }, + { + flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, + flowType: RadioRemote, + }, + { flowStep: ConnectionFlowStep.ConnectBattery, flowType: RadioRemote }, + { flowStep: ConnectionFlowStep.ConnectCable, flowType: RadioBridge }, + { + flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, + flowType: RadioBridge, + }, ]; }; -const getStageAndTypeIdx = ({ step, type }: Stage, order: Stage[]) => { +const getFlowStageIdx = ( + { flowStep, flowType }: FlowStage, + order: FlowStage[] +) => { for (let idx = 0; idx < order.length; idx++) { const currStage = order[idx]; - if (currStage.step === step && currStage.type === type) { + if (currStage.flowStep === flowStep && currStage.flowType === flowType) { return idx; } } throw new Error("Should be able to match stage and type again order"); }; -const getNextStage = (stage: ConnectionStage, step: number): Stage => { +const getNextStage = (stage: ConnectionStage, increment: number): FlowStage => { const order = getStagesOrder(stage); - const curr = { step: stage.step, type: stage.type }; - const currIdx = getStageAndTypeIdx(curr, order); - const newIdx = currIdx + step; - // If impossible step stage, stick to current step + const currIdx = getFlowStageIdx(stage, order); + const newIdx = currIdx + increment; if (newIdx === order.length || newIdx < 0) { - return curr; + throw new Error("Impossible step stage"); } return order[newIdx]; }; diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 1a10d4248..df390d706 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -1,7 +1,6 @@ import { ReactNode, createContext, useContext, useMemo, useState } from "react"; import { ConnectionStageActions } from "./connection-stage-actions"; import { useLogging } from "./logging/logging-hooks"; -import { useConnections } from "./connections-hooks"; import { ConnectActions } from "./connect-actions"; export enum ConnectionFlowType { @@ -10,13 +9,32 @@ export enum ConnectionFlowType { RadioRemote = "remote", } -export interface ConnectionStage { - step: ConnectionFlowStep; - type: ConnectionFlowType; +export enum ConnectionStatus { + None, // Have not been connected before + Connecting, + Connected, + Disconnected, + Reconnecting, +} - // TODO: Separate into different hook - isWebUsbSupported: boolean; +export type ConnectionType = "bluetooth" | "radio"; + +export interface ConnectionStage { + // For connection flow + flowStep: ConnectionFlowStep; + flowType: ConnectionFlowType; + // Number of times there have been consecutive reconnect fails + // for determining which reconnection dialog to show + reconnectFailStreak: number; + + // Connection details + deviceIds: number[]; + status: ConnectionStatus; + connType: ConnectionType; + + // Compatibility isWebBluetoothSupported: boolean; + isWebUsbSupported: boolean; } export enum ConnectionFlowStep { @@ -99,21 +117,23 @@ interface ConnectionStageProviderProps { children: ReactNode; } +const initialConnectionStageValue: ConnectionStage = { + flowStep: ConnectionFlowStep.None, + flowType: ConnectionFlowType.Bluetooth, + reconnectFailStreak: 0, + deviceIds: [], + status: ConnectionStatus.Disconnected, + connType: "bluetooth", + isWebBluetoothSupported: true, + isWebUsbSupported: true, +}; + export const ConnectionStageProvider = ({ children, }: ConnectionStageProviderProps) => { - // TODO: Check bt and usb compatibility - const isWebBluetoothSupported = true; - const isWebUsbSupported = true; - - const connectionStageContextValue = useState({ - type: isWebBluetoothSupported - ? ConnectionFlowType.Bluetooth - : ConnectionFlowType.RadioRemote, - step: ConnectionFlowStep.None, - isWebUsbSupported, - isWebBluetoothSupported, - }); + const connectionStageContextValue = useState( + initialConnectionStageValue + ); return ( {children} @@ -124,6 +144,7 @@ export const ConnectionStageProvider = ({ export const useConnectionStage = (): { stage: ConnectionStage; actions: ConnectionStageActions; + isConnected: boolean; } => { const connectionStageContextValue = useContext(ConnectionStageContext); if (!connectionStageContextValue) { @@ -131,22 +152,20 @@ export const useConnectionStage = (): { } const [stage, setStage] = connectionStageContextValue; const logging = useLogging(); - const { connections, state: connectionsState } = useConnections(); const actions = useMemo(() => { - const connectActions = new ConnectActions(logging, connections); + const connectActions = new ConnectActions(logging); + return new ConnectionStageActions(connectActions, stage, setStage); + }, [logging, stage, setStage]); - return new ConnectionStageActions( - connectActions, - stage, - setStage, - connectionsState, - connections - ); - }, [logging, connections, stage, setStage, connectionsState]); + const isConnected = useMemo( + () => stage.status === ConnectionStatus.Connected, + [stage.status] + ); return { stage, actions, + isConnected, }; }; diff --git a/src/connections-hooks.tsx b/src/connections-hooks.tsx deleted file mode 100644 index 22990f362..000000000 --- a/src/connections-hooks.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { ReactNode, createContext, useContext, useMemo, useState } from "react"; -import { Connections } from "./connections"; - -export enum ConnectionStatus { - None, // Have not been connected before - Connecting, - Connected, - Disconnected, - Reconnecting, -} -export type ConnectionType = "bluetooth" | "radio"; - -interface ConnectionBase { - status: ConnectionStatus; - // Number of times there have been consecutive reconnect fails - // for determining which reconnection dialog to show - reconnectFailStreak: number; -} -export interface BluetoothConnection extends ConnectionBase { - type: "bluetooth"; -} -export interface RadioConnection extends ConnectionBase { - type: "radio"; - // Remote micro:bit device id is stored for passing it to bridge micro:bit - remoteDeviceId: string; -} - -export type Connection = BluetoothConnection | RadioConnection; - -export enum ProgramType { - Input = "input", -} - -export interface ConnectionsState { - [ProgramType.Input]: Connection; -} - -type ConnectionsContextValue = [ - ConnectionsState, - setConnectionsState: (state: ConnectionsState) => void -]; - -export const ConnectionsContext = createContext( - null -); - -interface ConnectionsProviderProps { - children: ReactNode; -} - -const initialConnectionsState: ConnectionsState = { - input: { - type: "bluetooth", - status: ConnectionStatus.None, - reconnectFailStreak: 0, - }, -}; - -export const ConnectionsProvider = ({ children }: ConnectionsProviderProps) => { - const connectionsContextValue = useState( - initialConnectionsState - ); - return ( - - {children} - - ); -}; - -export const useConnections = (): { - state: ConnectionsState; - connections: Connections; - inputConnection: Connection; - isInputConnected: boolean; -} => { - const connectionsContextValue = useContext(ConnectionsContext); - if (!connectionsContextValue) { - throw new Error("Missing provider"); - } - const [state, setState] = connectionsContextValue; - const conn = useMemo( - () => new Connections(state, setState), - [state, setState] - ); - const isInputConnected = useMemo( - () => conn.isConnected(ProgramType.Input), - [conn] - ); - return { - state, - connections: conn, - inputConnection: state[ProgramType.Input], - isInputConnected, - }; -}; diff --git a/src/connections.ts b/src/connections.ts deleted file mode 100644 index 94de29738..000000000 --- a/src/connections.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { - Connection, - ConnectionStatus, - ConnectionType, - ConnectionsState, - ProgramType, - RadioConnection, -} from "./connections-hooks"; - -export class Connections { - constructor( - private connections: ConnectionsState, - private setConnections: (state: ConnectionsState) => void - ) {} - - private isValidConnection = (v: unknown): v is Connection => { - if (typeof v !== "object") { - return false; - } - const objValue = v as object; - if ( - !("status" in objValue) || - !("type" in objValue) || - !("reconnectFailStreak" in objValue) || - !( - objValue.type === "bluetooth" || - (objValue.type === "radio" && "remoteDeviceId" in objValue) - ) - ) { - return false; - } - return true; - }; - - setConnection = (program: ProgramType, conn: Partial) => { - const existingConn = this.connections[program]; - const newConnection = { - ...existingConn, - ...conn, - } as Connection; - if (!this.isValidConnection(newConnection)) { - throw new Error( - `Invalid new connection state: ${JSON.stringify(newConnection)}` - ); - } - this.setConnections({ [program]: newConnection }); - }; - - setConnectingOrReconnecting = ( - program: ProgramType, - type: ConnectionType - ) => { - const existingConn = this.connections[program]; - // Update status to reconnecting if it has been connected before - this.setConnection(program, { - type, - status: - existingConn.status === ConnectionStatus.None - ? ConnectionStatus.Connecting - : ConnectionStatus.Reconnecting, - }); - }; - - setConnected = (program: ProgramType) => { - this.setConnection(program, { status: ConnectionStatus.Connected }); - }; - - getRemoteDeviceId = ( - programType: ProgramType - ): RadioConnection["remoteDeviceId"] | undefined => { - const inputConnection = this.connections[programType]; - return inputConnection.type === "radio" - ? inputConnection.remoteDeviceId - : undefined; - }; - - isConnected = (programType: ProgramType): boolean => { - return this.connections[programType].status === ConnectionStatus.Connected; - }; - - setConnectionFailedStreak = (programType: ProgramType): number => { - const existingConn = this.connections[programType]; - const reconnectFailStreak = - existingConn.status === ConnectionStatus.Reconnecting - ? existingConn.reconnectFailStreak + 1 - : existingConn.reconnectFailStreak; - this.setConnection(programType, { - status: ConnectionStatus.Disconnected, - reconnectFailStreak, - }); - return reconnectFailStreak; - }; -} diff --git a/src/ml-hooks.tsx b/src/ml-hooks.tsx index 5d4c73c0b..1cbdbb73b 100644 --- a/src/ml-hooks.tsx +++ b/src/ml-hooks.tsx @@ -1,27 +1,27 @@ import { useMemo } from "react"; -import { useConnections } from "./connections-hooks"; import { useGestureData } from "./gestures-hooks"; import { useLogging } from "./logging/logging-hooks"; import { useMlStatus } from "./ml-status-hooks"; import { MlActions } from "./ml-actions"; +import { useConnectionStage } from "./connection-stage-hooks"; export const useMlActions = () => { const [gestures, setGestures] = useGestureData(); const [status, setStatus] = useMlStatus(); const logger = useLogging(); - const { isInputConnected } = useConnections(); + const { isConnected } = useConnectionStage(); const actions = useMemo( () => new MlActions( logger, - isInputConnected, + isConnected, gestures, setGestures, status, setStatus ), - [gestures, isInputConnected, logger, setGestures, setStatus, status] + [gestures, isConnected, logger, setGestures, setStatus, status] ); return actions; }; From a4804e212a38890974c1f4e9d2054641c2fcb688 Mon Sep 17 00:00:00 2001 From: Grace Date: Fri, 19 Jul 2024 18:08:17 +0100 Subject: [PATCH 086/172] Store device ids and microbit name and use them for bluetooth connection flow to infer pattern and for connecting --- src/bt-pattern-utils.ts | 34 ++++++++++--- src/components/ConnectionFlowDialogs.tsx | 31 +++++++----- .../EnterBluetoothPatternDialog.tsx | 22 +++++--- src/connect-actions.ts | 9 ++-- src/connection-stage-actions.ts | 50 +++++++++++++++---- src/connection-stage-hooks.tsx | 9 +++- 6 files changed, 111 insertions(+), 44 deletions(-) diff --git a/src/bt-pattern-utils.ts b/src/bt-pattern-utils.ts index 8b9a7db99..957596425 100644 --- a/src/bt-pattern-utils.ts +++ b/src/bt-pattern-utils.ts @@ -22,7 +22,9 @@ const microbitBluetoothCodebook: string[][] = [ ["z", "u", "z", "u", "z"], ]; -const microbitNameToBluetoothPattern = (name: string): BluetoothPattern => { +export const microbitNameToBluetoothPattern = ( + name: string +): BluetoothPattern => { const pattern: BluetoothPattern = new Array(25).fill(true); // if wrong name length, return empty pattern @@ -50,7 +52,7 @@ const deviceIdToNameCodebook = [ ["z", "v", "g", "p", "t"], ]; -const deviceIdToMicrobitName = (deviceId: number): string => { +export const deviceIdToMicrobitName = (deviceId: number): string => { let d = microbitNameLength; let ld = 1; let name = ""; @@ -65,9 +67,27 @@ const deviceIdToMicrobitName = (deviceId: number): string => { return name; }; -export const deviceIdToBluetoothPattern = ( - deviceId: number -): BluetoothPattern => { - const name = deviceIdToMicrobitName(deviceId); - return microbitNameToBluetoothPattern(name); +/** + * Converts a pairing pattern to a name. + * See guide on microbit names to understand how a pattern is turned into a name + * https://support.microbit.org/support/solutions/articles/19000067679-how-to-find-the-name-of-your-micro-bit + * @param {boolean[]} pattern The pattern to convert. + * @returns {string} The name of the micro:bit. + */ +export const microbitPatternToName = (pattern: boolean[]): string => { + const code: string[] = [" ", " ", " ", " ", " "]; + + for (let col = 0; col < microbitNameLength; col++) { + for (let row = 0; row < microbitNameLength; row++) { + if (pattern[row * microbitNameLength + col]) { + // Find the first vertical on/true in each column + code[col] = microbitBluetoothCodebook[row][col]; // Use code-book to find char + break; // Rest of column is irrelevant + } + // If we get to here the pattern is not legal, and the returned name + // will not match any microbit. + } + } + + return code.join(""); }; diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index 1f7f2a3de..fe065aa8c 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -1,13 +1,10 @@ import { useDisclosure } from "@chakra-ui/react"; import { useCallback, useEffect, useState } from "react"; -import { - BluetoothPattern, - deviceIdToBluetoothPattern, -} from "../bt-pattern-utils"; import { ConnEvent, ConnectionFlowStep, ConnectionFlowType, + ConnectionStage, useConnectionStage, } from "../connection-stage-hooks"; import BrokenFirmwareDialog from "./BrokenFirmwareDialog"; @@ -30,10 +27,8 @@ const ConnectionDialogs = () => { const dispatch = actions.dispatchEvent; const [flashProgress, setFlashProgress] = useState(0); const { isOpen, onClose: onCloseDialog, onOpen } = useDisclosure(); - const [bluetoothPattern, setBluetoothPattern] = useState( - stage.deviceIds.length > 0 - ? deviceIdToBluetoothPattern(stage.deviceIds[0]) - : Array(25).fill(false) + const [microbitName, setMicrobitName] = useState( + stage.microbitNames.length > 0 ? stage.microbitNames[0] : undefined ); const onClose = useCallback(() => { dispatch(ConnEvent.Close); @@ -59,15 +54,25 @@ const ConnectionDialogs = () => { [dispatch, stage.flowStep] ); - const onFlashSuccess = useCallback((deviceId: number) => { - // Infer pattern from device id saves the user from entering the pattern - setBluetoothPattern(deviceIdToBluetoothPattern(deviceId)); + const onFlashSuccess = useCallback(({ microbitNames }: ConnectionStage) => { + // Inferring microbit name saves the user from entering the pattern + if (microbitNames.length > 0) { + setMicrobitName(microbitNames[0]); + } }, []); async function connectAndFlash(): Promise { await actions.connectAndflashMicrobit(progressCallback, onFlashSuccess); } + const onChangeMicrobitName = useCallback( + (name: string) => { + actions.setMicrobitName(name); + setMicrobitName(name); + }, + [actions] + ); + const onSwitchTypeClick = useCallback( () => dispatch(ConnEvent.Switch), [dispatch] @@ -158,8 +163,8 @@ const ConnectionDialogs = () => { {...dialogCommonProps} onBackClick={onBackClick} onNextClick={onNextClick} - bluetoothPattern={bluetoothPattern} - setBluetoothPattern={setBluetoothPattern} + microbitName={microbitName} + onChangeMicrobitName={onChangeMicrobitName} /> ); } diff --git a/src/components/EnterBluetoothPatternDialog.tsx b/src/components/EnterBluetoothPatternDialog.tsx index 89e70a4a4..5a4e076cf 100644 --- a/src/components/EnterBluetoothPatternDialog.tsx +++ b/src/components/EnterBluetoothPatternDialog.tsx @@ -5,22 +5,31 @@ import BluetoothPatternInput from "./BluetoothPatternInput"; import ConnectContainerDialog, { ConnectContainerDialogProps, } from "./ConnectContainerDialog"; -import { BluetoothPattern } from "../bt-pattern-utils"; +import { + BluetoothPattern, + microbitNameToBluetoothPattern, + microbitPatternToName, +} from "../bt-pattern-utils"; export interface EnterBluetoothPatternDialogProps extends Omit { - setBluetoothPattern: (pattern: BluetoothPattern) => void; - bluetoothPattern: BluetoothPattern; + onChangeMicrobitName: (name: string) => void; + microbitName: string | undefined; } const EnterBluetoothPatternDialog = ({ onNextClick, onBackClick, - setBluetoothPattern, - bluetoothPattern, + onChangeMicrobitName, + microbitName, ...props }: EnterBluetoothPatternDialogProps) => { const [showInvalid, setShowInvalid] = useState(false); + const [bluetoothPattern, setBluetoothPattern] = useState( + microbitName + ? microbitNameToBluetoothPattern(microbitName) + : Array(25).fill(false) + ); const handleNextClick = useCallback(() => { if (!isPatternValid(bluetoothPattern)) { @@ -38,9 +47,10 @@ const EnterBluetoothPatternDialog = ({ const handlePatternChange = useCallback( (newPattern: BluetoothPattern) => { setBluetoothPattern(newPattern); + onChangeMicrobitName(microbitPatternToName(newPattern)); setShowInvalid(false); }, - [setBluetoothPattern] + [onChangeMicrobitName] ); return ( diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 8ac86242a..813da00f8 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -122,17 +122,16 @@ export class ConnectActions { return ConnectResult.ManualConnectFailed; }; - // TODO: Replace with real connecting logic - connectBluetooth = async (): Promise => { + connectBluetooth = async ( + name: string | undefined + ): Promise => { // TODO: move this to init point await this.bluetooth.initialize(); this.bluetooth.addEventListener( "accelerometerdatachanged", this.accelerometerListener ); - await this.bluetooth.connect({ - // TODO: name - }); + await this.bluetooth.connect({ name }); if (this.bluetooth.status === DeviceConnectionStatus.CONNECTED) { return ConnectResult.Success; } diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 8018c461a..7cfd86068 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -1,3 +1,4 @@ +import { deviceIdToMicrobitName } from "./bt-pattern-utils"; import { ConnectActions, ConnectAndFlashFailResult, @@ -30,7 +31,7 @@ export class ConnectionStageActions { connectAndflashMicrobit = async ( progressCallback: (progress: number) => void, - onSuccess: (deviceId: number) => void + onSuccess: (stage: ConnectionStage) => void ) => { this.dispatchEvent(ConnEvent.WebUsbChooseMicrobit); const { result, deviceId } = @@ -41,18 +42,36 @@ export class ConnectionStageActions { if (result !== ConnectAndFlashResult.Success) { return this.handleConnectAndFlashFail(result); } - // Save device id for connecting micro:bits via serial, or to show bluetooth pattern - this.setStage({ - ...this.stage, - deviceIds: [...this.stage.deviceIds, deviceId], - }); - onSuccess(deviceId); + // Store radio/bluetooth details. Radio is essential to pass to micro:bit 2. + // Bluetooth saves the user from entering the pattern. + const newStage = this.storeDetectedDetails(deviceId); + onSuccess(newStage); + if (this.stage.flowType === ConnectionFlowType.RadioBridge) { return await this.connectMicrobits(); } return this.dispatchEvent(ConnEvent.ConnectBattery); }; + private storeDetectedDetails = (deviceId: number): ConnectionStage => { + const existingDeviceIds = this.stage.detectedDeviceIds; + const existingNames = this.stage.microbitNames; + const name = deviceIdToMicrobitName(deviceId); + const newStage = { + ...this.stage, + // Only store two device ids and names at any given time as only + // a maximum of two device infos are needed for radio connection + ...(existingDeviceIds.length === 2 + ? { detectedDeviceIds: [deviceId], microbitNames: [name] } + : { + detectedDeviceIds: [...existingDeviceIds, deviceId], + microbitNames: [...existingNames, name], + }), + }; + this.setStage(newStage); + return newStage; + }; + private handleConnectAndFlashFail = (result: ConnectAndFlashFailResult) => { if (this.stage.flowType === ConnectionFlowType.Bluetooth) { return this.dispatchEvent(ConnEvent.InstructManualFlashing); @@ -73,17 +92,27 @@ export class ConnectionStageActions { } }; + setMicrobitName = (name: string) => { + const microbitNames = [...this.stage.microbitNames]; + microbitNames[0] = name; + this.setStage({ ...this.stage, microbitNames }); + }; + connectBluetooth = async () => { this.dispatchEvent(ConnEvent.ConnectingBluetooth); this.onConnectingOrReconnectingStatus("bluetooth"); - const result = await this.actions.connectBluetooth(); + const result = await this.actions.connectBluetooth( + this.stage.microbitNames.length > 0 + ? this.stage.microbitNames[0] + : undefined + ); this.handleConnectResult(result); }; connectMicrobits = async () => { this.dispatchEvent(ConnEvent.ConnectingMicrobits); this.onConnectingOrReconnectingStatus("radio"); - const deviceId = this.stage.deviceIds; + const deviceId = this.stage.detectedDeviceIds; if (deviceId.length > 0) { const result = await this.actions.connectMicrobitsSerial(deviceId[0]); this.handleConnectResult(result); @@ -148,7 +177,6 @@ export class ConnectionStageActions { this.setStage({ ...this.stage, status: ConnectionStatus.Disconnected, - deviceIds: [], }); }; @@ -234,7 +262,7 @@ export const getUpdatedConnState = ( ...state, flowStep: state.flowStep === ConnectionFlowStep.TryAgainBluetoothConnect - ? ConnectionFlowStep.ConnectBluetoothTutorial + ? ConnectionFlowStep.EnterBluetoothPattern : ConnectionFlowStep.ConnectCable, }; case ConnEvent.ReconnectAutoFail: diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index df390d706..110ef647d 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -28,7 +28,11 @@ export interface ConnectionStage { reconnectFailStreak: number; // Connection details - deviceIds: number[]; + // Detected device id may not be synced with micro:bit name if the user changes + // the bluetooth pattern, because it is not possible to compute device id from + // micro:bit name + detectedDeviceIds: number[]; + microbitNames: string[]; status: ConnectionStatus; connType: ConnectionType; @@ -121,7 +125,8 @@ const initialConnectionStageValue: ConnectionStage = { flowStep: ConnectionFlowStep.None, flowType: ConnectionFlowType.Bluetooth, reconnectFailStreak: 0, - deviceIds: [], + detectedDeviceIds: [], + microbitNames: [], status: ConnectionStatus.Disconnected, connType: "bluetooth", isWebBluetoothSupported: true, From 706f6cab8064260efb0cc5c305647b2c6e3fc503 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 22 Jul 2024 09:27:06 +0100 Subject: [PATCH 087/172] Ensure connecting dialog is shown when bluetooth connecting --- src/connection-stage-actions.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 7cfd86068..1cc23aa93 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -26,7 +26,7 @@ export class ConnectionStageActions { start = () => this.dispatchEvent(ConnEvent.Start); dispatchEvent = (event: ConnEvent) => { - this.setStage(getUpdatedConnState(this.stage, event)); + this.setStage(getUpdatedConnStage(this.stage, event)); }; connectAndflashMicrobit = async ( @@ -99,8 +99,7 @@ export class ConnectionStageActions { }; connectBluetooth = async () => { - this.dispatchEvent(ConnEvent.ConnectingBluetooth); - this.onConnectingOrReconnectingStatus("bluetooth"); + this.onConnectingOrReconnecting("bluetooth"); const result = await this.actions.connectBluetooth( this.stage.microbitNames.length > 0 ? this.stage.microbitNames[0] @@ -110,8 +109,7 @@ export class ConnectionStageActions { }; connectMicrobits = async () => { - this.dispatchEvent(ConnEvent.ConnectingMicrobits); - this.onConnectingOrReconnectingStatus("radio"); + this.onConnectingOrReconnecting("radio"); const deviceId = this.stage.detectedDeviceIds; if (deviceId.length > 0) { const result = await this.actions.connectMicrobitsSerial(deviceId[0]); @@ -121,9 +119,15 @@ export class ConnectionStageActions { } }; - private onConnectingOrReconnectingStatus = (connType: ConnectionType) => { + private onConnectingOrReconnecting = (connType: ConnectionType) => { + const nextStage = getUpdatedConnStage( + this.stage, + connType === "bluetooth" + ? ConnEvent.ConnectingBluetooth + : ConnEvent.ConnectingMicrobits + ); this.setStage({ - ...this.stage, + ...nextStage, connType, status: this.stage.status === ConnectionStatus.None @@ -189,7 +193,7 @@ export class ConnectionStageActions { }; } -export const getUpdatedConnState = ( +export const getUpdatedConnStage = ( state: ConnectionStage, event: ConnEvent ): ConnectionStage => { From a14420a863e09b6436b6d4ad75648beca96d438b Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 22 Jul 2024 11:20:53 +0100 Subject: [PATCH 088/172] Navigate to add data page after connecting --- src/connection-stage-actions.ts | 9 ++-- src/connection-stage-hooks.tsx | 82 ++++++++++++++++++++------------- src/pages/AddDataPage.tsx | 8 ++-- 3 files changed, 60 insertions(+), 39 deletions(-) diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 1cc23aa93..74c183bbb 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -1,3 +1,4 @@ +import { NavigateFunction } from "react-router"; import { deviceIdToMicrobitName } from "./bt-pattern-utils"; import { ConnectActions, @@ -13,12 +14,14 @@ import { ConnectionStatus, ConnectionType, } from "./connection-stage-hooks"; +import { createStepPageUrl } from "./urls"; type FlowStage = Pick; export class ConnectionStageActions { constructor( private actions: ConnectActions, + private navigate: NavigateFunction, private stage: ConnectionStage, private setStage: (stage: ConnectionStage) => void ) {} @@ -138,8 +141,7 @@ export class ConnectionStageActions { private handleConnectResult = (result: ConnectResult) => { if (result === ConnectResult.Success) { - this.onConnected(); - return this.dispatchEvent(ConnEvent.Close); + return this.onConnected(); } const reconnectFailStreak = this.setDisconnectedAndRecordFailStreak(); if (reconnectFailStreak === 0) { @@ -157,10 +159,11 @@ export class ConnectionStageActions { private onConnected = () => { this.setStage({ - ...this.stage, + ...getUpdatedConnStage(this.stage, ConnEvent.Close), status: ConnectionStatus.Connected, reconnectFailStreak: 0, }); + this.navigate(createStepPageUrl("add-data")); }; private setDisconnectedAndRecordFailStreak = () => { diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 110ef647d..16af481e2 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -1,7 +1,15 @@ -import { ReactNode, createContext, useContext, useMemo, useState } from "react"; +import { + ReactNode, + createContext, + useContext, + useEffect, + useMemo, + useState, +} from "react"; import { ConnectionStageActions } from "./connection-stage-actions"; import { useLogging } from "./logging/logging-hooks"; import { ConnectActions } from "./connect-actions"; +import { useNavigate } from "react-router"; export enum ConnectionFlowType { Bluetooth = "bluetooth", @@ -10,11 +18,11 @@ export enum ConnectionFlowType { } export enum ConnectionStatus { - None, // Have not been connected before - Connecting, - Connected, - Disconnected, - Reconnecting, + None = "None", // Have not been connected before + Connecting = "Connecting", + Connected = "Connected", + Disconnected = "Disconnected", + Reconnecting = "Reconnecting", } export type ConnectionType = "bluetooth" | "radio"; @@ -43,33 +51,33 @@ export interface ConnectionStage { export enum ConnectionFlowStep { // Happy flow stages - None, - Start, - ConnectCable, - WebUsbFlashingTutorial, - ManualFlashingTutorial, - ConnectBattery, - EnterBluetoothPattern, - ConnectBluetoothTutorial, + None = "None", + Start = "Start", + ConnectCable = "ConnectCable", + WebUsbFlashingTutorial = "WebUsbFlashingTutorial", + ManualFlashingTutorial = "ManualFlashingTutorial", + ConnectBattery = "ConnectBattery", + EnterBluetoothPattern = "EnterBluetoothPattern", + ConnectBluetoothTutorial = "ConnectBluetoothTutorial", // Stages that are not user-controlled - WebUsbChooseMicrobit, - ConnectingBluetooth, - ConnectingMicrobits, - FlashingInProgress, + WebUsbChooseMicrobit = "WebUsbChooseMicrobit", + ConnectingBluetooth = "ConnectingBluetooth", + ConnectingMicrobits = "ConnectingMicrobits", + FlashingInProgress = "FlashingInProgress", // Failure stages - TryAgainReplugMicrobit, - TryAgainCloseTabs, - TryAgainSelectMicrobit, - TryAgainBluetoothConnect, - BadFirmware, - MicrobitUnsupported, - WebUsbBluetoothUnsupported, - - ReconnectAutoFail, - ReconnectManualFail, - ReconnectFailedTwice, + TryAgainReplugMicrobit = "TryAgainReplugMicrobit", + TryAgainCloseTabs = "TryAgainCloseTabs", + TryAgainSelectMicrobit = "TryAgainSelectMicrobit", + TryAgainBluetoothConnect = "TryAgainBluetoothConnect", + BadFirmware = "BadFirmware", + MicrobitUnsupported = "MicrobitUnsupported", + WebUsbBluetoothUnsupported = "WebUsbBluetoothUnsupported", + + ReconnectAutoFail = "ReconnectAutoFail", + ReconnectManualFail = "ReconnectManualFail", + ReconnectFailedTwice = "ReconnectFailedTwice", } export enum ConnEvent { @@ -127,7 +135,7 @@ const initialConnectionStageValue: ConnectionStage = { reconnectFailStreak: 0, detectedDeviceIds: [], microbitNames: [], - status: ConnectionStatus.Disconnected, + status: ConnectionStatus.None, connType: "bluetooth", isWebBluetoothSupported: true, isWebUsbSupported: true, @@ -157,11 +165,21 @@ export const useConnectionStage = (): { } const [stage, setStage] = connectionStageContextValue; const logging = useLogging(); + const navigate = useNavigate(); const actions = useMemo(() => { const connectActions = new ConnectActions(logging); - return new ConnectionStageActions(connectActions, stage, setStage); - }, [logging, stage, setStage]); + return new ConnectionStageActions( + connectActions, + navigate, + stage, + setStage + ); + }, [logging, navigate, stage, setStage]); + + useEffect(() => { + console.log(stage); + }, [stage]); const isConnected = useMemo( () => stage.status === ConnectionStatus.Connected, diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index ee8e32ab2..28fdcb468 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -28,13 +28,14 @@ import { } from "../gestures-hooks"; import { addDataConfig } from "../pages-config"; import { createStepPageUrl } from "../urls"; +import { useConnectionStage } from "../connection-stage-hooks"; const AddDataPage = () => { const intl = useIntl(); const navigate = useNavigate(); const [gestures] = useGestureData(); const actions = useGestureActions(); - const isInputConnected = true; + const { isConnected } = useConnectionStage(); const hasSufficientData = useMemo( () => hasSufficientDataForTraining(gestures.data), @@ -64,7 +65,7 @@ const AddDataPage = () => { return ( - {noStoredData && !isInputConnected ? ( + {noStoredData && !isConnected ? ( ) : ( @@ -85,8 +86,7 @@ const AddDataPage = () => { leftIcon={} onClick={handleAddNewGesture} isDisabled={ - !isInputConnected || - gestures.data.some((g) => g.name.length === 0) + !isConnected || gestures.data.some((g) => g.name.length === 0) } > From 2269b20baa75594d2b984b235d476b5d161c836d Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 22 Jul 2024 11:54:33 +0100 Subject: [PATCH 089/172] Remove debugging console.log --- src/connection-stage-hooks.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 16af481e2..efe455913 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -177,10 +177,6 @@ export const useConnectionStage = (): { ); }, [logging, navigate, stage, setStage]); - useEffect(() => { - console.log(stage); - }, [stage]); - const isConnected = useMemo( () => stage.status === ConnectionStatus.Connected, [stage.status] From e74065c13c374f1f3d7a17c7e3e557b9f2bc4408 Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 22 Jul 2024 12:27:20 +0100 Subject: [PATCH 090/172] Update connectionStage state data structure for holding device id/name depending on connection type --- src/components/ConnectionFlowDialogs.tsx | 11 ++-- src/connection-stage-actions.ts | 68 +++++++++++++----------- src/connection-stage-hooks.tsx | 63 +++++++++++----------- 3 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index fe065aa8c..90f62fa19 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -28,7 +28,7 @@ const ConnectionDialogs = () => { const [flashProgress, setFlashProgress] = useState(0); const { isOpen, onClose: onCloseDialog, onOpen } = useDisclosure(); const [microbitName, setMicrobitName] = useState( - stage.microbitNames.length > 0 ? stage.microbitNames[0] : undefined + actions.getMicrobitName() ); const onClose = useCallback(() => { dispatch(ConnEvent.Close); @@ -54,10 +54,11 @@ const ConnectionDialogs = () => { [dispatch, stage.flowStep] ); - const onFlashSuccess = useCallback(({ microbitNames }: ConnectionStage) => { + const onFlashSuccess = useCallback((stage: ConnectionStage) => { // Inferring microbit name saves the user from entering the pattern - if (microbitNames.length > 0) { - setMicrobitName(microbitNames[0]); + // for bluetooth connection flow + if (stage.connType === "bluetooth" && stage.microbitName) { + setMicrobitName(stage.microbitName); } }, []); @@ -67,7 +68,7 @@ const ConnectionDialogs = () => { const onChangeMicrobitName = useCallback( (name: string) => { - actions.setMicrobitName(name); + actions.onChangeMicrobitName(name); setMicrobitName(name); }, [actions] diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 74c183bbb..45da2f753 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -47,8 +47,7 @@ export class ConnectionStageActions { } // Store radio/bluetooth details. Radio is essential to pass to micro:bit 2. // Bluetooth saves the user from entering the pattern. - const newStage = this.storeDetectedDetails(deviceId); - onSuccess(newStage); + onSuccess({ ...this.stage, ...this.getDetectedDetails(deviceId) }); if (this.stage.flowType === ConnectionFlowType.RadioBridge) { return await this.connectMicrobits(); @@ -56,23 +55,19 @@ export class ConnectionStageActions { return this.dispatchEvent(ConnEvent.ConnectBattery); }; - private storeDetectedDetails = (deviceId: number): ConnectionStage => { - const existingDeviceIds = this.stage.detectedDeviceIds; - const existingNames = this.stage.microbitNames; - const name = deviceIdToMicrobitName(deviceId); - const newStage = { - ...this.stage, - // Only store two device ids and names at any given time as only - // a maximum of two device infos are needed for radio connection - ...(existingDeviceIds.length === 2 - ? { detectedDeviceIds: [deviceId], microbitNames: [name] } - : { - detectedDeviceIds: [...existingDeviceIds, deviceId], - microbitNames: [...existingNames, name], - }), - }; - this.setStage(newStage); - return newStage; + private getDetectedDetails = (deviceId: number): Partial => { + switch (this.stage.flowType) { + case ConnectionFlowType.Bluetooth: { + const microbitName = deviceIdToMicrobitName(deviceId); + return { deviceId, microbitName }; + } + case ConnectionFlowType.RadioBridge: { + return { bridgeDeviceId: deviceId }; + } + case ConnectionFlowType.RadioRemote: { + return { remoteDeviceId: deviceId }; + } + } }; private handleConnectAndFlashFail = (result: ConnectAndFlashFailResult) => { @@ -95,27 +90,38 @@ export class ConnectionStageActions { } }; - setMicrobitName = (name: string) => { - const microbitNames = [...this.stage.microbitNames]; - microbitNames[0] = name; - this.setStage({ ...this.stage, microbitNames }); + onChangeMicrobitName = (name: string) => { + if (this.stage.connType !== "bluetooth") { + throw new Error("Microbit name can only be set for bluetooth flow"); + } + this.setStage({ + ...this.stage, + connType: "bluetooth", + // It is not possible to compute device id from micro:bit name + // so to remove confusion, device id is removed from state + deviceId: undefined, + microbitName: name, + }); + }; + + getMicrobitName = () => { + return this.stage.connType === "bluetooth" + ? this.stage.microbitName + : undefined; }; connectBluetooth = async () => { this.onConnectingOrReconnecting("bluetooth"); - const result = await this.actions.connectBluetooth( - this.stage.microbitNames.length > 0 - ? this.stage.microbitNames[0] - : undefined - ); + const result = await this.actions.connectBluetooth(this.getMicrobitName()); this.handleConnectResult(result); }; connectMicrobits = async () => { this.onConnectingOrReconnecting("radio"); - const deviceId = this.stage.detectedDeviceIds; - if (deviceId.length > 0) { - const result = await this.actions.connectMicrobitsSerial(deviceId[0]); + if (this.stage.connType === "radio" && this.stage.remoteDeviceId) { + const result = await this.actions.connectMicrobitsSerial( + this.stage.remoteDeviceId + ); this.handleConnectResult(result); } else { this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index efe455913..682ed6cef 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -1,11 +1,4 @@ -import { - ReactNode, - createContext, - useContext, - useEffect, - useMemo, - useState, -} from "react"; +import { ReactNode, createContext, useContext, useMemo, useState } from "react"; import { ConnectionStageActions } from "./connection-stage-actions"; import { useLogging } from "./logging/logging-hooks"; import { ConnectActions } from "./connect-actions"; @@ -27,28 +20,6 @@ export enum ConnectionStatus { export type ConnectionType = "bluetooth" | "radio"; -export interface ConnectionStage { - // For connection flow - flowStep: ConnectionFlowStep; - flowType: ConnectionFlowType; - // Number of times there have been consecutive reconnect fails - // for determining which reconnection dialog to show - reconnectFailStreak: number; - - // Connection details - // Detected device id may not be synced with micro:bit name if the user changes - // the bluetooth pattern, because it is not possible to compute device id from - // micro:bit name - detectedDeviceIds: number[]; - microbitNames: string[]; - status: ConnectionStatus; - connType: ConnectionType; - - // Compatibility - isWebBluetoothSupported: boolean; - isWebUsbSupported: boolean; -} - export enum ConnectionFlowStep { // Happy flow stages None = "None", @@ -117,6 +88,36 @@ export enum ConnEvent { ReconnectFailedTwice, } +interface ConnectionStageBase { + // For connection flow + flowStep: ConnectionFlowStep; + flowType: ConnectionFlowType; + // Number of times there have been consecutive reconnect fails + // for determining which reconnection dialog to show + reconnectFailStreak: number; + + // Compatibility + isWebBluetoothSupported: boolean; + isWebUsbSupported: boolean; + + // Connection state + status: ConnectionStatus; +} + +interface BluetoothConnectionStage extends ConnectionStageBase { + connType: "bluetooth"; + deviceId?: number; + microbitName?: string; +} + +interface RadioConnectionStage extends ConnectionStageBase { + connType: "radio"; + bridgeDeviceId?: number; + remoteDeviceId?: number; +} + +export type ConnectionStage = BluetoothConnectionStage | RadioConnectionStage; + type ConnectionStageContextValue = [ ConnectionStage, (state: ConnectionStage) => void @@ -133,8 +134,6 @@ const initialConnectionStageValue: ConnectionStage = { flowStep: ConnectionFlowStep.None, flowType: ConnectionFlowType.Bluetooth, reconnectFailStreak: 0, - detectedDeviceIds: [], - microbitNames: [], status: ConnectionStatus.None, connType: "bluetooth", isWebBluetoothSupported: true, From 85cf5e8b5bb0eaf1a31b9867fa8efb4f6d348edf Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 22 Jul 2024 15:12:02 +0100 Subject: [PATCH 091/172] Store microbit name in local storage --- src/components/ConnectionFlowDialogs.tsx | 32 ++++--- src/connection-stage-actions.ts | 74 ++++++++-------- src/connection-stage-hooks.tsx | 106 +++++++++++++---------- 3 files changed, 119 insertions(+), 93 deletions(-) diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index 90f62fa19..f560c6ae0 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -28,7 +28,7 @@ const ConnectionDialogs = () => { const [flashProgress, setFlashProgress] = useState(0); const { isOpen, onClose: onCloseDialog, onOpen } = useDisclosure(); const [microbitName, setMicrobitName] = useState( - actions.getMicrobitName() + stage.bluetoothMicrobitName ); const onClose = useCallback(() => { dispatch(ConnEvent.Close); @@ -54,18 +54,6 @@ const ConnectionDialogs = () => { [dispatch, stage.flowStep] ); - const onFlashSuccess = useCallback((stage: ConnectionStage) => { - // Inferring microbit name saves the user from entering the pattern - // for bluetooth connection flow - if (stage.connType === "bluetooth" && stage.microbitName) { - setMicrobitName(stage.microbitName); - } - }, []); - - async function connectAndFlash(): Promise { - await actions.connectAndflashMicrobit(progressCallback, onFlashSuccess); - } - const onChangeMicrobitName = useCallback( (name: string) => { actions.onChangeMicrobitName(name); @@ -74,6 +62,24 @@ const ConnectionDialogs = () => { [actions] ); + const onFlashSuccess = useCallback( + async (newStage: ConnectionStage) => { + // Inferring microbit name saves the user from entering the pattern + // for bluetooth connection flow + if (newStage.bluetoothMicrobitName) { + setMicrobitName(newStage.bluetoothMicrobitName); + } + if (newStage.flowType === ConnectionFlowType.RadioBridge) { + await actions.connectMicrobits(); + } + }, + [actions] + ); + + async function connectAndFlash(): Promise { + await actions.connectAndflashMicrobit(progressCallback, onFlashSuccess); + } + const onSwitchTypeClick = useCallback( () => dispatch(ConnEvent.Switch), [dispatch] diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 45da2f753..320574b5c 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -45,29 +45,48 @@ export class ConnectionStageActions { if (result !== ConnectAndFlashResult.Success) { return this.handleConnectAndFlashFail(result); } - // Store radio/bluetooth details. Radio is essential to pass to micro:bit 2. - // Bluetooth saves the user from entering the pattern. - onSuccess({ ...this.stage, ...this.getDetectedDetails(deviceId) }); - if (this.stage.flowType === ConnectionFlowType.RadioBridge) { - return await this.connectMicrobits(); - } - return this.dispatchEvent(ConnEvent.ConnectBattery); + this.onFlashSuccess(deviceId, onSuccess); }; - private getDetectedDetails = (deviceId: number): Partial => { + private onFlashSuccess = ( + deviceId: number, + onSuccess: (stage: ConnectionStage) => void + ) => { + let newStage = this.stage; + // Store radio/bluetooth details. Radio is essential to pass to micro:bit 2. + // Bluetooth saves the user from entering the pattern. switch (this.stage.flowType) { case ConnectionFlowType.Bluetooth: { const microbitName = deviceIdToMicrobitName(deviceId); - return { deviceId, microbitName }; + newStage = { + ...this.stage, + flowStep: ConnectionFlowStep.ConnectBattery, + bluetoothDeviceId: deviceId, + bluetoothMicrobitName: microbitName, + }; + break; } case ConnectionFlowType.RadioBridge: { - return { bridgeDeviceId: deviceId }; + newStage = { + ...this.stage, + flowStep: ConnectionFlowStep.ConnectingMicrobits, + radioBridgeDeviceId: deviceId, + ...this.getConnectingOrReconnectingStage("radio"), + }; + break; } case ConnectionFlowType.RadioRemote: { - return { remoteDeviceId: deviceId }; + newStage = { + ...this.stage, + flowStep: ConnectionFlowStep.ConnectBattery, + radioRemoteDeviceId: deviceId, + }; + break; } } + onSuccess(newStage); + this.setStage(newStage); }; private handleConnectAndFlashFail = (result: ConnectAndFlashFailResult) => { @@ -99,28 +118,22 @@ export class ConnectionStageActions { connType: "bluetooth", // It is not possible to compute device id from micro:bit name // so to remove confusion, device id is removed from state - deviceId: undefined, - microbitName: name, + bluetoothDeviceId: undefined, + bluetoothMicrobitName: name, }); }; - getMicrobitName = () => { - return this.stage.connType === "bluetooth" - ? this.stage.microbitName - : undefined; - }; - connectBluetooth = async () => { - this.onConnectingOrReconnecting("bluetooth"); - const result = await this.actions.connectBluetooth(this.getMicrobitName()); + const result = await this.actions.connectBluetooth( + this.stage.bluetoothMicrobitName + ); this.handleConnectResult(result); }; connectMicrobits = async () => { - this.onConnectingOrReconnecting("radio"); - if (this.stage.connType === "radio" && this.stage.remoteDeviceId) { + if (this.stage.connType === "radio" && this.stage.radioRemoteDeviceId) { const result = await this.actions.connectMicrobitsSerial( - this.stage.remoteDeviceId + this.stage.radioRemoteDeviceId ); this.handleConnectResult(result); } else { @@ -128,21 +141,14 @@ export class ConnectionStageActions { } }; - private onConnectingOrReconnecting = (connType: ConnectionType) => { - const nextStage = getUpdatedConnStage( - this.stage, - connType === "bluetooth" - ? ConnEvent.ConnectingBluetooth - : ConnEvent.ConnectingMicrobits - ); - this.setStage({ - ...nextStage, + private getConnectingOrReconnectingStage = (connType: ConnectionType) => { + return { connType, status: this.stage.status === ConnectionStatus.None ? ConnectionStatus.Connecting : ConnectionStatus.Reconnecting, - }); + }; }; private handleConnectResult = (result: ConnectResult) => { diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 682ed6cef..16664a676 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -1,8 +1,16 @@ -import { ReactNode, createContext, useContext, useMemo, useState } from "react"; +import { + ReactNode, + createContext, + useCallback, + useContext, + useMemo, + useState, +} from "react"; import { ConnectionStageActions } from "./connection-stage-actions"; import { useLogging } from "./logging/logging-hooks"; import { ConnectActions } from "./connect-actions"; import { useNavigate } from "react-router"; +import { useStorage } from "./hooks/use-storage"; export enum ConnectionFlowType { Bluetooth = "bluetooth", @@ -53,42 +61,42 @@ export enum ConnectionFlowStep { export enum ConnEvent { // User triggered events - Start, - Switch, - Next, - Back, - SkipFlashing, - TryAgain, - GoToBluetoothStart, - Close, + Start = "Start", + Switch = "Switch", + Next = "Next", + Back = "Back", + SkipFlashing = "SkipFlashing", + TryAgain = "TryAgain", + GoToBluetoothStart = "GoToBluetoothStart", + Close = "Close", // Web USB Flashing events - WebUsbChooseMicrobit, - FlashingInProgress, - ConnectBattery, + WebUsbChooseMicrobit = "WebUsbChooseMicrobit", + FlashingInProgress = "FlashingInProgress", + ConnectBattery = "ConnectBattery", // Web USB Flashing failure events - TryAgainReplugMicrobit, - TryAgainCloseTabs, - TryAgainSelectMicrobit, - InstructManualFlashing, - BadFirmware, - MicrobitUnsupported, + TryAgainReplugMicrobit = "TryAgainReplugMicrobit", + TryAgainCloseTabs = "TryAgainCloseTabs", + TryAgainSelectMicrobit = "TryAgainSelectMicrobit", + InstructManualFlashing = "InstructManualFlashing", + BadFirmware = "BadFirmware", + MicrobitUnsupported = "MicrobitUnsupported", // Bluetooth connection event - ConnectingBluetooth, + ConnectingBluetooth = "ConnectingBluetooth", // Connecting microbits for radio connection - ConnectingMicrobits, + ConnectingMicrobits = "ConnectingMicrobits", // Connection failure event - ConnectFailed, - ReconnectAutoFail, - ReconnectManualFail, - ReconnectFailedTwice, + ConnectFailed = "ConnectFailed", + ReconnectAutoFail = "ReconnectAutoFail", + ReconnectManualFail = "ReconnectManualFail", + ReconnectFailedTwice = "ReconnectFailedTwice", } -interface ConnectionStageBase { +export interface ConnectionStage { // For connection flow flowStep: ConnectionFlowStep; flowType: ConnectionFlowType; @@ -102,22 +110,13 @@ interface ConnectionStageBase { // Connection state status: ConnectionStatus; + connType: "bluetooth" | "radio"; + bluetoothDeviceId?: number; + bluetoothMicrobitName?: string; + radioBridgeDeviceId?: number; + radioRemoteDeviceId?: number; } -interface BluetoothConnectionStage extends ConnectionStageBase { - connType: "bluetooth"; - deviceId?: number; - microbitName?: string; -} - -interface RadioConnectionStage extends ConnectionStageBase { - connType: "radio"; - bridgeDeviceId?: number; - remoteDeviceId?: number; -} - -export type ConnectionStage = BluetoothConnectionStage | RadioConnectionStage; - type ConnectionStageContextValue = [ ConnectionStage, (state: ConnectionStage) => void @@ -130,24 +129,40 @@ interface ConnectionStageProviderProps { children: ReactNode; } -const initialConnectionStageValue: ConnectionStage = { +const getInitialConnectionStageValue = ( + microbitName: string | undefined +): ConnectionStage => ({ flowStep: ConnectionFlowStep.None, flowType: ConnectionFlowType.Bluetooth, reconnectFailStreak: 0, status: ConnectionStatus.None, + bluetoothMicrobitName: microbitName, connType: "bluetooth", isWebBluetoothSupported: true, isWebUsbSupported: true, -}; +}); export const ConnectionStageProvider = ({ children, }: ConnectionStageProviderProps) => { - const connectionStageContextValue = useState( - initialConnectionStageValue + const [val, setMicrobitName] = useStorage<{ + value?: string; + }>("local", "microbitName", { value: undefined }); + const [connectionStage, setConnStage] = useState( + getInitialConnectionStageValue(val.value) ); + const setConnectionStage = useCallback( + (connStage: ConnectionStage) => { + setMicrobitName({ value: connStage.bluetoothMicrobitName }); + setConnStage(connStage); + }, + [setMicrobitName] + ); + return ( - + {children} ); @@ -167,9 +182,8 @@ export const useConnectionStage = (): { const navigate = useNavigate(); const actions = useMemo(() => { - const connectActions = new ConnectActions(logging); return new ConnectionStageActions( - connectActions, + new ConnectActions(logging), navigate, stage, setStage From c00f9aa90e728e681277fc93d8105e516bf6a5cb Mon Sep 17 00:00:00 2001 From: Grace Date: Mon, 22 Jul 2024 16:02:13 +0100 Subject: [PATCH 092/172] Refactor to no longer need conn event and fix bt and radio conn flow --- src/components/ConnectionFlowDialogs.tsx | 67 +++---- src/connect-actions.ts | 2 +- src/connection-stage-actions.ts | 215 ++++++++++------------- src/connection-stage-hooks.tsx | 37 ---- 4 files changed, 118 insertions(+), 203 deletions(-) diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index f560c6ae0..cbaf6fd9f 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -1,7 +1,6 @@ import { useDisclosure } from "@chakra-ui/react"; import { useCallback, useEffect, useState } from "react"; import { - ConnEvent, ConnectionFlowStep, ConnectionFlowType, ConnectionStage, @@ -24,16 +23,15 @@ import WhatYouWillNeedDialog from "./WhatYouWillNeedDialog"; const ConnectionDialogs = () => { const { stage, actions } = useConnectionStage(); - const dispatch = actions.dispatchEvent; const [flashProgress, setFlashProgress] = useState(0); const { isOpen, onClose: onCloseDialog, onOpen } = useDisclosure(); const [microbitName, setMicrobitName] = useState( stage.bluetoothMicrobitName ); const onClose = useCallback(() => { - dispatch(ConnEvent.Close); + actions.setFlowStep(ConnectionFlowStep.None); onCloseDialog(); - }, [dispatch, onCloseDialog]); + }, [actions, onCloseDialog]); useEffect(() => { if (stage.flowStep !== ConnectionFlowStep.None && !isOpen) { @@ -47,11 +45,11 @@ const ConnectionDialogs = () => { const progressCallback = useCallback( (progress: number) => { if (stage.flowStep !== ConnectionFlowStep.FlashingInProgress) { - dispatch(ConnEvent.FlashingInProgress); + actions.setFlowStep(ConnectionFlowStep.FlashingInProgress); } setFlashProgress(progress * 100); }, - [dispatch, stage.flowStep] + [actions, stage.flowStep] ); const onChangeMicrobitName = useCallback( @@ -79,28 +77,13 @@ const ConnectionDialogs = () => { async function connectAndFlash(): Promise { await actions.connectAndflashMicrobit(progressCallback, onFlashSuccess); } - - const onSwitchTypeClick = useCallback( - () => dispatch(ConnEvent.Switch), - [dispatch] - ); - const onBackClick = useCallback(() => dispatch(ConnEvent.Back), [dispatch]); - const onNextClick = useCallback(() => dispatch(ConnEvent.Next), [dispatch]); const onSkip = useCallback( - () => dispatch(ConnEvent.SkipFlashing), - [dispatch] - ); - const onTryAgain = useCallback( - () => dispatch(ConnEvent.TryAgain), - [dispatch] - ); - const onStartBluetooth = useCallback( - () => dispatch(ConnEvent.GoToBluetoothStart), - [dispatch] + () => actions.setFlowStep(ConnectionFlowStep.ConnectBattery), + [actions] ); const onInstructManualFlashing = useCallback( - () => dispatch(ConnEvent.InstructManualFlashing), - [dispatch] + () => actions.setFlowStep(ConnectionFlowStep.ManualFlashingTutorial), + [actions] ); const dialogCommonProps = { isOpen, onClose }; @@ -118,22 +101,26 @@ const ConnectionDialogs = () => { {...dialogCommonProps} onLinkClick={ stage.isWebBluetoothSupported && stage.isWebUsbSupported - ? onSwitchTypeClick + ? actions.switchFlowType : undefined } - onNextClick={onNextClick} + onNextClick={actions.onNextClick} reconnect={stage.flowStep === ConnectionFlowStep.ReconnectFailedTwice} /> ); } case ConnectionFlowStep.ConnectCable: { - const commonProps = { onBackClick, onNextClick, ...dialogCommonProps }; + const commonProps = { + onBackClick: actions.onBackClick, + onNextClick: actions.onNextClick, + ...dialogCommonProps, + }; return ( ); } @@ -141,7 +128,7 @@ const ConnectionDialogs = () => { return ( ); @@ -150,8 +137,8 @@ const ConnectionDialogs = () => { return ( ); } @@ -159,8 +146,8 @@ const ConnectionDialogs = () => { return ( ); } @@ -168,8 +155,8 @@ const ConnectionDialogs = () => { return ( @@ -179,7 +166,7 @@ const ConnectionDialogs = () => { return ( ); @@ -222,7 +209,7 @@ const ConnectionDialogs = () => { return ( ); @@ -232,7 +219,7 @@ const ConnectionDialogs = () => { ); } @@ -240,7 +227,7 @@ const ConnectionDialogs = () => { return ( ); diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 813da00f8..c184d30c9 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -119,7 +119,7 @@ export class ConnectActions { if (!deviceId) { return ConnectResult.ManualConnectFailed; } - return ConnectResult.ManualConnectFailed; + return ConnectResult.Success; }; connectBluetooth = async ( diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 320574b5c..dc7de0b6f 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -7,12 +7,10 @@ import { ConnectResult, } from "./connect-actions"; import { - ConnEvent, ConnectionFlowStep, ConnectionFlowType, ConnectionStage, ConnectionStatus, - ConnectionType, } from "./connection-stage-hooks"; import { createStepPageUrl } from "./urls"; @@ -26,17 +24,28 @@ export class ConnectionStageActions { private setStage: (stage: ConnectionStage) => void ) {} - start = () => this.dispatchEvent(ConnEvent.Start); + start = () => + this.setStage({ + ...this.stage, + flowType: + this.stage.flowType === ConnectionFlowType.RadioBridge + ? ConnectionFlowType.RadioRemote + : ConnectionFlowType.Bluetooth, + flowStep: + !this.stage.isWebBluetoothSupported && !this.stage.isWebUsbSupported + ? ConnectionFlowStep.WebUsbBluetoothUnsupported + : ConnectionFlowStep.Start, + }); - dispatchEvent = (event: ConnEvent) => { - this.setStage(getUpdatedConnStage(this.stage, event)); + setFlowStep = (step: ConnectionFlowStep) => { + this.setStage({ ...this.stage, flowStep: step }); }; connectAndflashMicrobit = async ( progressCallback: (progress: number) => void, onSuccess: (stage: ConnectionStage) => void ) => { - this.dispatchEvent(ConnEvent.WebUsbChooseMicrobit); + this.setFlowStep(ConnectionFlowStep.WebUsbChooseMicrobit); const { result, deviceId } = await this.actions.requestUSBConnectionAndFlash( this.stage.flowType, @@ -61,6 +70,7 @@ export class ConnectionStageActions { const microbitName = deviceIdToMicrobitName(deviceId); newStage = { ...this.stage, + connType: "bluetooth", flowStep: ConnectionFlowStep.ConnectBattery, bluetoothDeviceId: deviceId, bluetoothMicrobitName: microbitName, @@ -70,15 +80,17 @@ export class ConnectionStageActions { case ConnectionFlowType.RadioBridge: { newStage = { ...this.stage, + connType: "radio", flowStep: ConnectionFlowStep.ConnectingMicrobits, radioBridgeDeviceId: deviceId, - ...this.getConnectingOrReconnectingStage("radio"), + status: this.getConnectingOrReconnectingStatus(), }; break; } case ConnectionFlowType.RadioRemote: { newStage = { ...this.stage, + connType: "radio", flowStep: ConnectionFlowStep.ConnectBattery, radioRemoteDeviceId: deviceId, }; @@ -91,21 +103,22 @@ export class ConnectionStageActions { private handleConnectAndFlashFail = (result: ConnectAndFlashFailResult) => { if (this.stage.flowType === ConnectionFlowType.Bluetooth) { - return this.dispatchEvent(ConnEvent.InstructManualFlashing); + return this.setFlowStep(ConnectionFlowStep.ManualFlashingTutorial); } + // TODO: Not sure if this is a good way of error handling because it means // there are 2 levels of switch statements to go through to provide UI switch (result) { case ConnectAndFlashResult.ErrorMicrobitUnsupported: - return this.dispatchEvent(ConnEvent.MicrobitUnsupported); + return this.setFlowStep(ConnectionFlowStep.MicrobitUnsupported); case ConnectAndFlashResult.ErrorBadFirmware: - return this.dispatchEvent(ConnEvent.BadFirmware); + return this.setFlowStep(ConnectionFlowStep.BadFirmware); case ConnectAndFlashResult.ErrorNoDeviceSelected: - return this.dispatchEvent(ConnEvent.TryAgainSelectMicrobit); + return this.setFlowStep(ConnectionFlowStep.TryAgainSelectMicrobit); case ConnectAndFlashResult.ErrorUnableToClaimInterface: - return this.dispatchEvent(ConnEvent.TryAgainCloseTabs); + return this.setFlowStep(ConnectionFlowStep.TryAgainCloseTabs); default: - return this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); + return this.setFlowStep(ConnectionFlowStep.TryAgainReplugMicrobit); } }; @@ -124,6 +137,12 @@ export class ConnectionStageActions { }; connectBluetooth = async () => { + this.setStage({ + ...this.stage, + connType: "bluetooth", + flowStep: ConnectionFlowStep.ConnectingBluetooth, + status: this.getConnectingOrReconnectingStatus(), + }); const result = await this.actions.connectBluetooth( this.stage.bluetoothMicrobitName ); @@ -137,18 +156,14 @@ export class ConnectionStageActions { ); this.handleConnectResult(result); } else { - this.dispatchEvent(ConnEvent.TryAgainReplugMicrobit); + this.setFlowStep(ConnectionFlowStep.TryAgainReplugMicrobit); } }; - private getConnectingOrReconnectingStage = (connType: ConnectionType) => { - return { - connType, - status: - this.stage.status === ConnectionStatus.None - ? ConnectionStatus.Connecting - : ConnectionStatus.Reconnecting, - }; + private getConnectingOrReconnectingStatus = () => { + return this.stage.status === ConnectionStatus.None + ? ConnectionStatus.Connecting + : ConnectionStatus.Reconnecting; }; private handleConnectResult = (result: ConnectResult) => { @@ -157,21 +172,26 @@ export class ConnectionStageActions { } const reconnectFailStreak = this.setDisconnectedAndRecordFailStreak(); if (reconnectFailStreak === 0) { - return this.dispatchEvent(ConnEvent.ConnectFailed); + return this.setFlowStep( + this.stage.flowType === ConnectionFlowType.Bluetooth + ? ConnectionFlowStep.TryAgainBluetoothConnect + : ConnectionFlowStep.TryAgainReplugMicrobit + ); } if (reconnectFailStreak === 1) { - return this.dispatchEvent( + return this.setFlowStep( result === ConnectResult.ManualConnectFailed - ? ConnEvent.ReconnectManualFail - : ConnEvent.ReconnectAutoFail + ? ConnectionFlowStep.ReconnectManualFail + : ConnectionFlowStep.ReconnectAutoFail ); } - return this.dispatchEvent(ConnEvent.ReconnectFailedTwice); + return this.setFlowStep(ConnectionFlowStep.ReconnectFailedTwice); }; private onConnected = () => { this.setStage({ - ...getUpdatedConnStage(this.stage, ConnEvent.Close), + ...this.stage, + flowStep: ConnectionFlowStep.None, status: ConnectionStatus.Connected, reconnectFailStreak: 0, }); @@ -206,94 +226,41 @@ export class ConnectionStageActions { await this.connectMicrobits(); } }; -} -export const getUpdatedConnStage = ( - state: ConnectionStage, - event: ConnEvent -): ConnectionStage => { - switch (event) { - case ConnEvent.Start: - return { - ...state, - flowType: - state.flowType === ConnectionFlowType.RadioBridge - ? ConnectionFlowType.RadioRemote - : ConnectionFlowType.Bluetooth, - flowStep: - !state.isWebBluetoothSupported && !state.isWebUsbSupported - ? ConnectionFlowStep.WebUsbBluetoothUnsupported - : ConnectionFlowStep.Start, - }; - case ConnEvent.Close: - return { ...state, flowStep: ConnectionFlowStep.None }; - case ConnEvent.ConnectBattery: - case ConnEvent.SkipFlashing: - return { ...state, flowStep: ConnectionFlowStep.ConnectBattery }; - case ConnEvent.FlashingInProgress: - return { ...state, flowStep: ConnectionFlowStep.FlashingInProgress }; - case ConnEvent.InstructManualFlashing: - return { ...state, flowStep: ConnectionFlowStep.ManualFlashingTutorial }; - case ConnEvent.WebUsbChooseMicrobit: - return { ...state, flowStep: ConnectionFlowStep.WebUsbChooseMicrobit }; - case ConnEvent.ConnectingBluetooth: - return { ...state, flowStep: ConnectionFlowStep.ConnectingBluetooth }; - case ConnEvent.ConnectingMicrobits: - return { ...state, flowStep: ConnectionFlowStep.ConnectingMicrobits }; - case ConnEvent.Next: - return { ...state, ...getNextStage(state, 1) }; - case ConnEvent.Back: - return { ...state, ...getNextStage(state, -1) }; - case ConnEvent.Switch: - return { - ...state, - flowType: - state.flowType === ConnectionFlowType.Bluetooth - ? ConnectionFlowType.RadioRemote - : ConnectionFlowType.Bluetooth, - }; - case ConnEvent.GoToBluetoothStart: - return { - ...state, - flowStep: ConnectionFlowStep.Start, - flowType: ConnectionFlowType.Bluetooth, - }; - case ConnEvent.ConnectFailed: - return { - ...state, - flowStep: - state.flowType === ConnectionFlowType.Bluetooth - ? ConnectionFlowStep.TryAgainBluetoothConnect - : ConnectionFlowStep.TryAgainReplugMicrobit, - }; - case ConnEvent.TryAgainReplugMicrobit: - return { ...state, flowStep: ConnectionFlowStep.TryAgainReplugMicrobit }; - case ConnEvent.TryAgainCloseTabs: - return { ...state, flowStep: ConnectionFlowStep.TryAgainCloseTabs }; - case ConnEvent.TryAgainSelectMicrobit: - return { ...state, flowStep: ConnectionFlowStep.TryAgainSelectMicrobit }; - case ConnEvent.BadFirmware: - return { ...state, flowStep: ConnectionFlowStep.BadFirmware }; - case ConnEvent.MicrobitUnsupported: - return { ...state, flowStep: ConnectionFlowStep.MicrobitUnsupported }; - case ConnEvent.TryAgain: - return { - ...state, - flowStep: - state.flowStep === ConnectionFlowStep.TryAgainBluetoothConnect - ? ConnectionFlowStep.EnterBluetoothPattern - : ConnectionFlowStep.ConnectCable, - }; - case ConnEvent.ReconnectAutoFail: - return { ...state, flowStep: ConnectionFlowStep.ReconnectAutoFail }; - case ConnEvent.ReconnectManualFail: - return { ...state, flowStep: ConnectionFlowStep.ReconnectManualFail }; - case ConnEvent.ReconnectFailedTwice: - return { ...state, flowStep: ConnectionFlowStep.ReconnectFailedTwice }; - default: - return state; - } -}; + switchFlowType = () => { + this.setStage({ + ...this.stage, + flowType: + this.stage.flowType === ConnectionFlowType.Bluetooth + ? ConnectionFlowType.RadioRemote + : ConnectionFlowType.Bluetooth, + }); + }; + + onStartBluetoothFlow = () => { + this.setStage({ + ...this.stage, + flowStep: ConnectionFlowStep.Start, + flowType: ConnectionFlowType.Bluetooth, + }); + }; + + onNextClick = () => { + this.setStage({ ...this.stage, ...getNextStage(this.stage, 1) }); + }; + + onBackClick = () => { + this.setStage({ ...this.stage, ...getNextStage(this.stage, -1) }); + }; + + onTryAgain = () => { + this.setFlowStep( + this.stage.flowStep === ConnectionFlowStep.TryAgainBluetoothConnect + ? ConnectionFlowStep.EnterBluetoothPattern + : ConnectionFlowStep.ConnectCable + ); + }; +} const getStagesOrder = (state: ConnectionStage): FlowStage[] => { const { RadioRemote, RadioBridge, Bluetooth } = ConnectionFlowType; @@ -302,16 +269,14 @@ const getStagesOrder = (state: ConnectionStage): FlowStage[] => { { flowStep: ConnectionFlowStep.Start, flowType: Bluetooth }, { flowStep: ConnectionFlowStep.ConnectCable, flowType: Bluetooth }, // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. - !state.isWebUsbSupported || - state.flowStep === ConnectionFlowStep.ManualFlashingTutorial - ? { - flowStep: ConnectionFlowStep.ManualFlashingTutorial, - flowType: Bluetooth, - } - : { - flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, - flowType: Bluetooth, - }, + { + flowStep: + !state.isWebUsbSupported || + state.flowStep === ConnectionFlowStep.ManualFlashingTutorial + ? ConnectionFlowStep.ManualFlashingTutorial + : ConnectionFlowStep.WebUsbFlashingTutorial, + flowType: Bluetooth, + }, { flowStep: ConnectionFlowStep.ConnectBattery, flowType: Bluetooth }, { flowStep: ConnectionFlowStep.EnterBluetoothPattern, diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 16664a676..b6d5cf8eb 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -59,43 +59,6 @@ export enum ConnectionFlowStep { ReconnectFailedTwice = "ReconnectFailedTwice", } -export enum ConnEvent { - // User triggered events - Start = "Start", - Switch = "Switch", - Next = "Next", - Back = "Back", - SkipFlashing = "SkipFlashing", - TryAgain = "TryAgain", - GoToBluetoothStart = "GoToBluetoothStart", - Close = "Close", - - // Web USB Flashing events - WebUsbChooseMicrobit = "WebUsbChooseMicrobit", - FlashingInProgress = "FlashingInProgress", - ConnectBattery = "ConnectBattery", - - // Web USB Flashing failure events - TryAgainReplugMicrobit = "TryAgainReplugMicrobit", - TryAgainCloseTabs = "TryAgainCloseTabs", - TryAgainSelectMicrobit = "TryAgainSelectMicrobit", - InstructManualFlashing = "InstructManualFlashing", - BadFirmware = "BadFirmware", - MicrobitUnsupported = "MicrobitUnsupported", - - // Bluetooth connection event - ConnectingBluetooth = "ConnectingBluetooth", - - // Connecting microbits for radio connection - ConnectingMicrobits = "ConnectingMicrobits", - - // Connection failure event - ConnectFailed = "ConnectFailed", - ReconnectAutoFail = "ReconnectAutoFail", - ReconnectManualFail = "ReconnectManualFail", - ReconnectFailedTwice = "ReconnectFailedTwice", -} - export interface ConnectionStage { // For connection flow flowStep: ConnectionFlowStep; From 41a9fd179115b6df485fee72396405adce96363e Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 23 Jul 2024 09:49:52 +0100 Subject: [PATCH 093/172] WIP live graph integration with accelerometer data --- src/App.tsx | 13 ++++--- src/components/LiveGraph.tsx | 27 ++++++++++---- src/connect-actions-hooks.tsx | 64 ++++++++++++++++++++++++++++++++++ src/connect-actions.ts | 35 +++++++++++-------- src/connection-stage-hooks.tsx | 16 +++++---- 5 files changed, 121 insertions(+), 34 deletions(-) create mode 100644 src/connect-actions-hooks.tsx diff --git a/src/App.tsx b/src/App.tsx index 1307bd532..2efcfe26a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,6 +24,7 @@ import { LoggingProvider } from "./logging/logging-hooks"; import { GesturesProvider } from "./gestures-hooks"; import { MlStatusProvider } from "./ml-status-hooks"; import { ConnectionStageProvider } from "./connection-stage-hooks"; +import { ConnectProvider } from "./connect-actions-hooks"; export interface ProviderLayoutProps { children: ReactNode; @@ -42,11 +43,13 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - - {children} - - + + + + {children} + + + diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index d62ddf256..11a57ed98 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -1,6 +1,6 @@ import { HStack } from "@chakra-ui/react"; import { useSize } from "@chakra-ui/react-use-size"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { SmoothieChart, TimeSeries } from "smoothie"; import { useConnectionStage } from "../connection-stage-hooks"; @@ -18,6 +18,11 @@ const LiveGraph = () => { height: 100, }; + const lineX = useMemo(() => new TimeSeries(), []); + const lineY = useMemo(() => new TimeSeries(), []); + const lineZ = useMemo(() => new TimeSeries(), []); + const recordLines = useMemo(() => new TimeSeries(), []); + // On mount draw smoothieChart useEffect(() => { if (!canvasRef.current) { @@ -36,11 +41,6 @@ const LiveGraph = () => { interpolation: "linear", }); - const lineX = new TimeSeries(); - const lineY = new TimeSeries(); - const lineZ = new TimeSeries(); - const recordLines = new TimeSeries(); - smoothieChart.addTimeSeries(lineX, { lineWidth, strokeStyle: "#f9808e" }); smoothieChart.addTimeSeries(lineY, { lineWidth, strokeStyle: "#80f98e" }); smoothieChart.addTimeSeries(lineZ, { lineWidth, strokeStyle: "#808ef9" }); @@ -55,7 +55,7 @@ const LiveGraph = () => { return () => { smoothieChart.stop(); }; - }, []); + }, [lineX, lineY, lineZ, recordLines]); useEffect(() => { if (isConnected) { @@ -65,6 +65,19 @@ const LiveGraph = () => { } }, [chart, isConnected]); + const { connectActions } = useConnectionStage(); + + useEffect(() => { + if (isConnected) { + connectActions.addAccelerometerListener(({ data }) => { + const t = new Date().getTime(); + lineX.append(t, data.x / 1000, false); + lineY.append(t, data.y / 1000, false); + lineZ.append(t, data.z / 1000, false); + }); + } + }, [connectActions, isConnected, lineX, lineY, lineZ]); + // TODO Recording logic return ( (null); + +interface ConnectProviderProps { + children: ReactNode; +} + +export const ConnectProvider = ({ children }: ConnectProviderProps) => { + const usb = useMemo(() => new MicrobitWebUSBConnection(), []); + const bluetooth = useMemo(() => new MicrobitWebBluetoothConnection(), []); + const isInitialized = useRef(false); + + useEffect(() => { + const initialize = async () => { + await usb.initialize(); + await bluetooth.initialize(); + }; + if (!isInitialized.current) { + void initialize(); + isInitialized.current = true; + } + }, [bluetooth, usb]); + + return ( + + {children} + + ); +}; + +export const useConnectActions = (): ConnectActions => { + const connectContextValue = useContext(ConnectContext); + if (!connectContextValue) { + throw new Error("Missing provider"); + } + const { usb, bluetooth } = connectContextValue; + const logging = useLogging(); + + const connectActions = useMemo( + () => new ConnectActions(logging, usb, bluetooth), + [bluetooth, logging, usb] + ); + + return connectActions; +}; diff --git a/src/connect-actions.ts b/src/connect-actions.ts index c184d30c9..8259b5264 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -34,13 +34,14 @@ const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); export class ConnectActions { private usb: MicrobitWebUSBConnection; private bluetooth: MicrobitWebBluetoothConnection; - private accelerometerListener = (e: AccelerometerDataEvent) => { - console.log(e.data); - }; - constructor(private logging: Logging) { - this.usb = new MicrobitWebUSBConnection({ logging }); - this.bluetooth = new MicrobitWebBluetoothConnection({ logging }); + constructor( + private logging: Logging, + usb: MicrobitWebUSBConnection, + bluetooth: MicrobitWebBluetoothConnection + ) { + this.usb = usb; + this.bluetooth = bluetooth; } requestUSBConnectionAndFlash = async ( @@ -51,8 +52,6 @@ export class ConnectActions { | { result: ConnectAndFlashFailResult; deviceId?: number } > => { try { - // TODO: move this to init point - await this.usb.initialize(); await this.usb.connect(); const result = await this.flashMicrobit(hexType, progressCallback); // Save remote micro:bit device id is stored for passing it to bridge micro:bit @@ -125,12 +124,6 @@ export class ConnectActions { connectBluetooth = async ( name: string | undefined ): Promise => { - // TODO: move this to init point - await this.bluetooth.initialize(); - this.bluetooth.addEventListener( - "accelerometerdatachanged", - this.accelerometerListener - ); await this.bluetooth.connect({ name }); if (this.bluetooth.status === DeviceConnectionStatus.CONNECTED) { return ConnectResult.Success; @@ -138,8 +131,20 @@ export class ConnectActions { return ConnectResult.ManualConnectFailed; }; + addAccelerometerListener = ( + listener: (e: AccelerometerDataEvent) => void + ) => { + if (this.bluetooth instanceof MicrobitWebBluetoothConnection) { + this.bluetooth?.addEventListener("accelerometerdatachanged", listener); + } else { + throw new Error( + "`getAccelerometerData` is not supported on `MicrobitWebUSBConnection`" + ); + } + }; + // TODO: Replace with real disconnect logic disconnect = async () => { - await delay(5000); + await this.bluetooth.disconnect(); }; } diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index b6d5cf8eb..c455ca8d8 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -6,10 +6,10 @@ import { useMemo, useState, } from "react"; -import { ConnectionStageActions } from "./connection-stage-actions"; -import { useLogging } from "./logging/logging-hooks"; -import { ConnectActions } from "./connect-actions"; import { useNavigate } from "react-router"; +import { ConnectActions } from "./connect-actions"; +import { useConnectActions } from "./connect-actions-hooks"; +import { ConnectionStageActions } from "./connection-stage-actions"; import { useStorage } from "./hooks/use-storage"; export enum ConnectionFlowType { @@ -85,7 +85,7 @@ type ConnectionStageContextValue = [ (state: ConnectionStage) => void ]; -export const ConnectionStageContext = +const ConnectionStageContext = createContext(null); interface ConnectionStageProviderProps { @@ -135,23 +135,24 @@ export const useConnectionStage = (): { stage: ConnectionStage; actions: ConnectionStageActions; isConnected: boolean; + connectActions: ConnectActions; } => { const connectionStageContextValue = useContext(ConnectionStageContext); if (!connectionStageContextValue) { throw new Error("Missing provider"); } const [stage, setStage] = connectionStageContextValue; - const logging = useLogging(); const navigate = useNavigate(); + const connectActions = useConnectActions(); const actions = useMemo(() => { return new ConnectionStageActions( - new ConnectActions(logging), + connectActions, navigate, stage, setStage ); - }, [logging, navigate, stage, setStage]); + }, [connectActions, navigate, stage, setStage]); const isConnected = useMemo( () => stage.status === ConnectionStatus.Connected, @@ -162,5 +163,6 @@ export const useConnectionStage = (): { stage, actions, isConnected, + connectActions, }; }; From 671cca37ebdfa51a7f3d70d64ff34af346b272c2 Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:29:21 +0100 Subject: [PATCH 094/172] Update connection library, use button B to start recording (#277) --- package-lock.json | 14 +++++-- package.json | 2 +- src/components/AddDataGridRow.tsx | 5 ++- src/components/AddDataGridView.tsx | 48 +++++++++++++++++++---- src/components/AddDataGridWalkThrough.tsx | 12 +++++- src/components/DataRecordingGridItem.tsx | 18 +++------ src/components/RecordingDialog.tsx | 3 -- src/connect-actions.ts | 28 +++++++++---- 8 files changed, 91 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3bd775469..4c05071d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit/microbit-connection": "^0.0.0-alpha.7", + "@microbit/microbit-connection": "^0.0.0-alpha.8", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", @@ -4356,12 +4356,13 @@ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, "node_modules/@microbit/microbit-connection": { - "version": "0.0.0-alpha.7", - "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.7.tgz", - "integrity": "sha512-dhbca6Jc1+bU8cwtTWRFATd8KzPdRny2s3WZTJleHdNyYitipIBwmWl5bFFex6/u2b4rrqD2zpV3xkx3HGtycA==", + "version": "0.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.8.tgz", + "integrity": "sha512-KuQTmn/Q55lB9f95UZwcMLtpyVvhkFyUPifO6SNTVxeP6JoRYYh2txEOwM3/AlNhdkkqme0tFS+qpFlH7nRu/A==", "dependencies": { "@microbit/microbit-universal-hex": "^0.2.2", "@types/web-bluetooth": "^0.0.20", + "crelt": "^1.0.6", "dapjs": "github:microbit-matt-hillsdon/dapjs#v2.3.0-microbit.2", "nrf-intel-hex": "^1.4.0" } @@ -7430,6 +7431,11 @@ "node": ">=10" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", diff --git a/package.json b/package.json index 7d56cd26c..9b77a2b0d 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit/microbit-connection": "^0.0.0-alpha.7", + "@microbit/microbit-connection": "^0.0.0-alpha.8", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", diff --git a/src/components/AddDataGridRow.tsx b/src/components/AddDataGridRow.tsx index 10d48ae4b..86b2e68af 100644 --- a/src/components/AddDataGridRow.tsx +++ b/src/components/AddDataGridRow.tsx @@ -1,20 +1,22 @@ +import { GridItem } from "@chakra-ui/react"; import { useCallback } from "react"; import { useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; import DataRecordingGridItem from "./DataRecordingGridItem"; import GestureNameGridItem from "./GestureNameGridItem"; -import { GridItem } from "@chakra-ui/react"; interface AddDataGridRowProps { gesture: GestureData; selected: boolean; onSelectRow: () => void; + startRecording: () => void; } const AddDataGridRow = ({ gesture, selected, onSelectRow, + startRecording, }: AddDataGridRowProps) => { const intl = useIntl(); const actions = useGestureActions(); @@ -45,6 +47,7 @@ const AddDataGridRow = ({ data={gesture} selected={selected} onSelectRow={onSelectRow} + startRecording={startRecording} /> ) : ( // Empty grid item to fill column space diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index 15b7c0815..5190107ee 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -1,9 +1,12 @@ -import { Grid, GridProps } from "@chakra-ui/react"; -import { useMemo, useState } from "react"; -import { useGestureData } from "../gestures-hooks"; +import { Grid, GridProps, useDisclosure } from "@chakra-ui/react"; +import { useEffect, useMemo, useState } from "react"; +import { GestureData, useGestureData } from "../gestures-hooks"; import AddDataGridRow from "./AddDataGridRow"; import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; import HeadingGrid from "./HeadingGrid"; +import RecordingDialog from "./RecordingDialog"; +import { useConnectActions } from "../connect-actions-hooks"; +import { TempButtonEvent } from "../connect-actions"; const gridCommonProps: Partial = { gridTemplateColumns: "200px 1fr", @@ -26,16 +29,41 @@ const headings = [ const AddDataGridView = () => { const [gestures] = useGestureData(); - const [selected, setSelected] = useState(0); + const [selectedGesture, setSelectedGesture] = useState( + gestures.data[0] + ); const showWalkThrough = useMemo( () => gestures.data.length === 0 || (gestures.data.length === 1 && gestures.data[0].recordings.length === 0), [gestures.data] ); + const { isOpen, onClose, onOpen } = useDisclosure(); + + const connection = useConnectActions(); + + useEffect(() => { + const listener = (e: TempButtonEvent) => { + if (!isOpen) { + if (e.state) { + onOpen(); + } + } + }; + connection.addButtonListener("B", listener); + return () => { + connection.removeButtonListener("B", listener); + }; + }, [connection, isOpen, onOpen]); return ( <> + { h={0} > {showWalkThrough ? ( - + ) : ( - gestures.data.map((g, idx) => ( + gestures.data.map((g) => ( setSelected(idx)} + selected={selectedGesture.ID === g.ID} + onSelectRow={() => setSelectedGesture(g)} + startRecording={onOpen} /> )) )} diff --git a/src/components/AddDataGridWalkThrough.tsx b/src/components/AddDataGridWalkThrough.tsx index d14e6f1c6..88a88749d 100644 --- a/src/components/AddDataGridWalkThrough.tsx +++ b/src/components/AddDataGridWalkThrough.tsx @@ -8,9 +8,13 @@ import { FormattedMessage } from "react-intl"; interface AddDataGridWalkThrough { gesture: GestureData; + startRecording: () => void; } -const AddDataGridWalkThrough = ({ gesture }: AddDataGridWalkThrough) => { +const AddDataGridWalkThrough = ({ + gesture, + startRecording, +}: AddDataGridWalkThrough) => { return ( <> { ) : ( <> - + {/* Empty grid item to fill first column of grid */} diff --git a/src/components/DataRecordingGridItem.tsx b/src/components/DataRecordingGridItem.tsx index 322a3057c..216a006f0 100644 --- a/src/components/DataRecordingGridItem.tsx +++ b/src/components/DataRecordingGridItem.tsx @@ -6,29 +6,28 @@ import { HStack, Icon, IconButton, - useDisclosure, } from "@chakra-ui/react"; +import { useCallback, useRef } from "react"; import { useIntl } from "react-intl"; +import { useConnectionStage } from "../connection-stage-hooks"; import { GestureData, useGestureActions } from "../gestures-hooks"; import RecordIcon from "../images/record-icon.svg?react"; import RecordingGraph from "./RecordingGraph"; -import RecordingDialog from "./RecordingDialog"; -import { useCallback, useRef } from "react"; -import { useConnectionStage } from "../connection-stage-hooks"; interface DataRecordingGridItemProps { data: GestureData; selected: boolean; onSelectRow?: () => void; + startRecording: () => void; } const DataRecordingGridItem = ({ data, selected, onSelectRow, + startRecording, }: DataRecordingGridItemProps) => { const intl = useIntl(); - const { isOpen, onClose, onOpen: onStartRecording } = useDisclosure(); const closeRecordingDialogFocusRef = useRef(null); const actions = useGestureActions(); const { isConnected } = useConnectionStage(); @@ -42,13 +41,6 @@ const DataRecordingGridItem = ({ return ( <> - void; actionName: string; gestureId: GestureData["ID"]; - finalFocusRef: React.MutableRefObject; } enum RecordingStatus { @@ -44,7 +43,6 @@ const RecordingDialog = ({ actionName, onClose, gestureId, - finalFocusRef, }: RecordingDialogProps) => { const intl = useIntl(); const actions = useGestureActions(); @@ -122,7 +120,6 @@ const RecordingDialog = ({ onClose={handleOnClose} size="lg" isCentered - finalFocusRef={finalFocusRef} > diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 8259b5264..72be94835 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -31,6 +31,10 @@ export enum ConnectResult { const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); +// Should remove this and use the connection library type +// in the next release. +export type TempButtonEvent = { state: number }; + export class ConnectActions { private usb: MicrobitWebUSBConnection; private bluetooth: MicrobitWebBluetoothConnection; @@ -134,13 +138,23 @@ export class ConnectActions { addAccelerometerListener = ( listener: (e: AccelerometerDataEvent) => void ) => { - if (this.bluetooth instanceof MicrobitWebBluetoothConnection) { - this.bluetooth?.addEventListener("accelerometerdatachanged", listener); - } else { - throw new Error( - "`getAccelerometerData` is not supported on `MicrobitWebUSBConnection`" - ); - } + this.bluetooth?.addEventListener("accelerometerdatachanged", listener); + }; + + addButtonListener = ( + button: "A" | "B", + listener: (e: TempButtonEvent) => void + ) => { + const type = button === "A" ? "buttonachanged" : "buttonbchanged"; + this.bluetooth?.addEventListener(type, listener); + }; + + removeButtonListener = ( + button: "A" | "B", + listener: (e: TempButtonEvent) => void + ) => { + const type = button === "A" ? "buttonachanged" : "buttonbchanged"; + this.bluetooth?.removeEventListener(type, listener); }; // TODO: Replace with real disconnect logic From f2ff5039db49eaf8fd77ee1372aaf2c288643bef Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 23 Jul 2024 10:35:29 +0100 Subject: [PATCH 095/172] Smoothen live graph --- src/components/LiveGraph.tsx | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 11a57ed98..02ef25058 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -2,8 +2,13 @@ import { HStack } from "@chakra-ui/react"; import { useSize } from "@chakra-ui/react-use-size"; import { useEffect, useMemo, useRef, useState } from "react"; import { SmoothieChart, TimeSeries } from "smoothie"; +import { useConnectActions } from "../connect-actions-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; +const dampenDataPoint = (curr: number, next: number) => { + return (next / 1000) * 0.25 + curr * 0.75; +}; + const LiveGraph = () => { const { isConnected } = useConnectionStage(); @@ -65,15 +70,26 @@ const LiveGraph = () => { } }, [chart, isConnected]); - const { connectActions } = useConnectionStage(); + const connectActions = useConnectActions(); + const dataRef = useRef<{ x: number; y: number; z: number }>({ + x: 0, + y: 0, + z: 0, + }); useEffect(() => { if (isConnected) { connectActions.addAccelerometerListener(({ data }) => { const t = new Date().getTime(); - lineX.append(t, data.x / 1000, false); - lineY.append(t, data.y / 1000, false); - lineZ.append(t, data.z / 1000, false); + dataRef.current = { + x: dampenDataPoint(dataRef.current.x, data.x), + y: dampenDataPoint(dataRef.current.y, data.y), + z: dampenDataPoint(dataRef.current.z, data.z), + }; + console.log(dataRef.current); + lineX.append(t, dataRef.current.x, false); + lineY.append(t, dataRef.current.y, false); + lineZ.append(t, dataRef.current.z, false); }); } }, [connectActions, isConnected, lineX, lineY, lineZ]); From 1e7a2389e240235972d986fc4403c505b9c88fa4 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 23 Jul 2024 10:36:58 +0100 Subject: [PATCH 096/172] Refactor handling connect failure --- src/components/LiveGraph.tsx | 11 +++--- src/connection-stage-actions.ts | 59 ++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 02ef25058..2797f4c59 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -5,7 +5,9 @@ import { SmoothieChart, TimeSeries } from "smoothie"; import { useConnectActions } from "../connect-actions-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; -const dampenDataPoint = (curr: number, next: number) => { +const smoothenDataPoint = (curr: number, next: number) => { + // TODO: Factor out so that recording graph can do the same + // Remove dividing by 1000 operation once it gets moved to connection lib return (next / 1000) * 0.25 + curr * 0.75; }; @@ -82,11 +84,10 @@ const LiveGraph = () => { connectActions.addAccelerometerListener(({ data }) => { const t = new Date().getTime(); dataRef.current = { - x: dampenDataPoint(dataRef.current.x, data.x), - y: dampenDataPoint(dataRef.current.y, data.y), - z: dampenDataPoint(dataRef.current.z, data.z), + x: smoothenDataPoint(dataRef.current.x, data.x), + y: smoothenDataPoint(dataRef.current.y, data.y), + z: smoothenDataPoint(dataRef.current.z, data.z), }; - console.log(dataRef.current); lineX.append(t, dataRef.current.x, false); lineY.append(t, dataRef.current.y, false); lineZ.append(t, dataRef.current.z, false); diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index dc7de0b6f..17beb78ba 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -170,22 +170,42 @@ export class ConnectionStageActions { if (result === ConnectResult.Success) { return this.onConnected(); } - const reconnectFailStreak = this.setDisconnectedAndRecordFailStreak(); - if (reconnectFailStreak === 0) { - return this.setFlowStep( - this.stage.flowType === ConnectionFlowType.Bluetooth + const newReconnectFailStreak = + this.stage.status === ConnectionStatus.Reconnecting + ? this.stage.reconnectFailStreak + 1 + : this.stage.reconnectFailStreak; + + const nextFlowStep = this.getReconnectFailFlowStep( + newReconnectFailStreak, + result + ); + this.setStage({ + ...this.stage, + reconnectFailStreak: newReconnectFailStreak, + status: ConnectionStatus.Disconnected, + flowStep: nextFlowStep, + }); + }; + + private getReconnectFailFlowStep = ( + failStreak: number, + result: ConnectResult + ) => { + switch (failStreak) { + case 0: { + return this.stage.flowType === ConnectionFlowType.Bluetooth ? ConnectionFlowStep.TryAgainBluetoothConnect - : ConnectionFlowStep.TryAgainReplugMicrobit - ); - } - if (reconnectFailStreak === 1) { - return this.setFlowStep( - result === ConnectResult.ManualConnectFailed + : ConnectionFlowStep.TryAgainReplugMicrobit; + } + case 1: { + return result === ConnectResult.ManualConnectFailed ? ConnectionFlowStep.ReconnectManualFail - : ConnectionFlowStep.ReconnectAutoFail - ); + : ConnectionFlowStep.ReconnectAutoFail; + } + default: { + return ConnectionFlowStep.ReconnectFailedTwice; + } } - return this.setFlowStep(ConnectionFlowStep.ReconnectFailedTwice); }; private onConnected = () => { @@ -198,19 +218,6 @@ export class ConnectionStageActions { this.navigate(createStepPageUrl("add-data")); }; - private setDisconnectedAndRecordFailStreak = () => { - const reconnectFailStreak = - this.stage.status === ConnectionStatus.Reconnecting - ? this.stage.reconnectFailStreak + 1 - : this.stage.reconnectFailStreak; - this.setStage({ - ...this.stage, - reconnectFailStreak, - status: ConnectionStatus.Disconnected, - }); - return reconnectFailStreak; - }; - disconnect = async () => { await this.actions.disconnect(); this.setStage({ From bf8e957663fd9660dea40c7fc8acb082dbec2e30 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 23 Jul 2024 16:47:17 +0100 Subject: [PATCH 097/172] Clean up accelerometer listener for live graph --- src/components/LiveGraph.tsx | 27 ++++++++++++++++----------- src/connect-actions.ts | 6 ++++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 2797f4c59..585f3e798 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -4,6 +4,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { SmoothieChart, TimeSeries } from "smoothie"; import { useConnectActions } from "../connect-actions-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; +import { AccelerometerDataEvent } from "@microbit/microbit-connection"; const smoothenDataPoint = (curr: number, next: number) => { // TODO: Factor out so that recording graph can do the same @@ -80,19 +81,23 @@ const LiveGraph = () => { z: 0, }); useEffect(() => { + const listener = ({ data }: AccelerometerDataEvent) => { + const t = new Date().getTime(); + dataRef.current = { + x: smoothenDataPoint(dataRef.current.x, data.x), + y: smoothenDataPoint(dataRef.current.y, data.y), + z: smoothenDataPoint(dataRef.current.z, data.z), + }; + lineX.append(t, dataRef.current.x, false); + lineY.append(t, dataRef.current.y, false); + lineZ.append(t, dataRef.current.z, false); + }; if (isConnected) { - connectActions.addAccelerometerListener(({ data }) => { - const t = new Date().getTime(); - dataRef.current = { - x: smoothenDataPoint(dataRef.current.x, data.x), - y: smoothenDataPoint(dataRef.current.y, data.y), - z: smoothenDataPoint(dataRef.current.z, data.z), - }; - lineX.append(t, dataRef.current.x, false); - lineY.append(t, dataRef.current.y, false); - lineZ.append(t, dataRef.current.z, false); - }); + connectActions.addAccelerometerListener(listener); } + return () => { + connectActions.removeAccelerometerListener(listener); + }; }, [connectActions, isConnected, lineX, lineY, lineZ]); // TODO Recording logic diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 72be94835..33fb558c3 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -141,6 +141,12 @@ export class ConnectActions { this.bluetooth?.addEventListener("accelerometerdatachanged", listener); }; + removeAccelerometerListener = ( + listener: (e: AccelerometerDataEvent) => void + ) => { + this.bluetooth?.removeEventListener("accelerometerdatachanged", listener); + }; + addButtonListener = ( button: "A" | "B", listener: (e: TempButtonEvent) => void From a3f1cad088d505cd0b9c663da2ee18f84226f806 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 23 Jul 2024 16:51:45 +0100 Subject: [PATCH 098/172] Revert "Clean up accelerometer listener for live graph" This reverts commit bf8e957663fd9660dea40c7fc8acb082dbec2e30. --- src/components/LiveGraph.tsx | 27 +++++++++++---------------- src/connect-actions.ts | 6 ------ 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 585f3e798..2797f4c59 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -4,7 +4,6 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { SmoothieChart, TimeSeries } from "smoothie"; import { useConnectActions } from "../connect-actions-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; -import { AccelerometerDataEvent } from "@microbit/microbit-connection"; const smoothenDataPoint = (curr: number, next: number) => { // TODO: Factor out so that recording graph can do the same @@ -81,23 +80,19 @@ const LiveGraph = () => { z: 0, }); useEffect(() => { - const listener = ({ data }: AccelerometerDataEvent) => { - const t = new Date().getTime(); - dataRef.current = { - x: smoothenDataPoint(dataRef.current.x, data.x), - y: smoothenDataPoint(dataRef.current.y, data.y), - z: smoothenDataPoint(dataRef.current.z, data.z), - }; - lineX.append(t, dataRef.current.x, false); - lineY.append(t, dataRef.current.y, false); - lineZ.append(t, dataRef.current.z, false); - }; if (isConnected) { - connectActions.addAccelerometerListener(listener); + connectActions.addAccelerometerListener(({ data }) => { + const t = new Date().getTime(); + dataRef.current = { + x: smoothenDataPoint(dataRef.current.x, data.x), + y: smoothenDataPoint(dataRef.current.y, data.y), + z: smoothenDataPoint(dataRef.current.z, data.z), + }; + lineX.append(t, dataRef.current.x, false); + lineY.append(t, dataRef.current.y, false); + lineZ.append(t, dataRef.current.z, false); + }); } - return () => { - connectActions.removeAccelerometerListener(listener); - }; }, [connectActions, isConnected, lineX, lineY, lineZ]); // TODO Recording logic diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 33fb558c3..72be94835 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -141,12 +141,6 @@ export class ConnectActions { this.bluetooth?.addEventListener("accelerometerdatachanged", listener); }; - removeAccelerometerListener = ( - listener: (e: AccelerometerDataEvent) => void - ) => { - this.bluetooth?.removeEventListener("accelerometerdatachanged", listener); - }; - addButtonListener = ( button: "A" | "B", listener: (e: TempButtonEvent) => void From 8bbc0be8c110194402b2f3dff560519407d6a367 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 23 Jul 2024 16:52:46 +0100 Subject: [PATCH 099/172] Revert "Revert "Clean up accelerometer listener for live graph"" This reverts commit a3f1cad088d505cd0b9c663da2ee18f84226f806. --- src/components/LiveGraph.tsx | 27 ++++++++++++++++----------- src/connect-actions.ts | 6 ++++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 2797f4c59..585f3e798 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -4,6 +4,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { SmoothieChart, TimeSeries } from "smoothie"; import { useConnectActions } from "../connect-actions-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; +import { AccelerometerDataEvent } from "@microbit/microbit-connection"; const smoothenDataPoint = (curr: number, next: number) => { // TODO: Factor out so that recording graph can do the same @@ -80,19 +81,23 @@ const LiveGraph = () => { z: 0, }); useEffect(() => { + const listener = ({ data }: AccelerometerDataEvent) => { + const t = new Date().getTime(); + dataRef.current = { + x: smoothenDataPoint(dataRef.current.x, data.x), + y: smoothenDataPoint(dataRef.current.y, data.y), + z: smoothenDataPoint(dataRef.current.z, data.z), + }; + lineX.append(t, dataRef.current.x, false); + lineY.append(t, dataRef.current.y, false); + lineZ.append(t, dataRef.current.z, false); + }; if (isConnected) { - connectActions.addAccelerometerListener(({ data }) => { - const t = new Date().getTime(); - dataRef.current = { - x: smoothenDataPoint(dataRef.current.x, data.x), - y: smoothenDataPoint(dataRef.current.y, data.y), - z: smoothenDataPoint(dataRef.current.z, data.z), - }; - lineX.append(t, dataRef.current.x, false); - lineY.append(t, dataRef.current.y, false); - lineZ.append(t, dataRef.current.z, false); - }); + connectActions.addAccelerometerListener(listener); } + return () => { + connectActions.removeAccelerometerListener(listener); + }; }, [connectActions, isConnected, lineX, lineY, lineZ]); // TODO Recording logic diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 72be94835..33fb558c3 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -141,6 +141,12 @@ export class ConnectActions { this.bluetooth?.addEventListener("accelerometerdatachanged", listener); }; + removeAccelerometerListener = ( + listener: (e: AccelerometerDataEvent) => void + ) => { + this.bluetooth?.removeEventListener("accelerometerdatachanged", listener); + }; + addButtonListener = ( button: "A" | "B", listener: (e: TempButtonEvent) => void From 41c903d5ab310588f80de73013eb84e87b9df115 Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 23 Jul 2024 17:38:58 +0100 Subject: [PATCH 100/172] Add start and end recording lines in live graph --- src/components/LiveGraph.tsx | 23 ++++++++++++++++++++++- src/components/RecordingDialog.tsx | 5 ++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 585f3e798..87d17d9b1 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -5,6 +5,8 @@ import { SmoothieChart, TimeSeries } from "smoothie"; import { useConnectActions } from "../connect-actions-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; import { AccelerometerDataEvent } from "@microbit/microbit-connection"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; +import { mlSettings } from "../ml"; const smoothenDataPoint = (curr: number, next: number) => { // TODO: Factor out so that recording graph can do the same @@ -14,6 +16,8 @@ const smoothenDataPoint = (curr: number, next: number) => { const LiveGraph = () => { const { isConnected } = useConnectionStage(); + const [{ stage }] = useMlStatus(); + const connectActions = useConnectActions(); const canvasRef = useRef(null); @@ -73,13 +77,30 @@ const LiveGraph = () => { } }, [chart, isConnected]); - const connectActions = useConnectActions(); + // Draw on graph to display that users are recording + const [isRecording, setIsRecording] = useState(false); + useEffect(() => { + if (stage === MlStage.RecordingData && !isRecording) { + // Set the start recording line + recordLines.append(new Date().getTime() - 1, -2, false); + recordLines.append(new Date().getTime(), 2.3, false); + setIsRecording(true); + + setTimeout(() => { + // Set the end recording line + recordLines.append(new Date().getTime() - 1, 2.3, false); + recordLines.append(new Date().getTime(), -2, false); + setIsRecording(false); + }, mlSettings.duration); + } + }, [isRecording, recordLines, stage]); const dataRef = useRef<{ x: number; y: number; z: number }>({ x: 0, y: 0, z: 0, }); + useEffect(() => { const listener = ({ data }: AccelerometerDataEvent) => { const t = new Date().getTime(); diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index 5f5a7457b..2be1f99af 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -16,8 +16,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; import gestureData from "../test-fixtures/gesture-data.json"; import { MlStage, useMlStatus } from "../ml-status-hooks"; - -const recordingDuration = 1800; +import { mlSettings } from "../ml"; interface CountdownConfig { value: string | number; @@ -108,7 +107,7 @@ const RecordingDialog = ({ ]); handleOnClose(); } - }, recordingDuration); + }, mlSettings.duration); } }, [actions, gestureId, handleOnClose, recordingStatus]); From 86d492961e458ce934b5492829d149d3b033bad7 Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:50:41 +0100 Subject: [PATCH 101/172] Initial radio bridge connection (#278) Upgrade connection library Use connection library to setup radio bridge connection Connect, disconnect and reconnect work. Error cases and reconnect handling have not been reviewed Use exported ButtonEvent --------- Co-authored-by: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> --- package-lock.json | 34 +++++++++--------- package.json | 2 +- src/components/AddDataGridView.tsx | 4 +-- src/connect-actions-hooks.tsx | 21 +++++++++--- src/connect-actions.ts | 55 +++++++++++++++--------------- 5 files changed, 63 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c05071d1..65fbb900d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit/microbit-connection": "^0.0.0-alpha.8", + "@microbit/microbit-connection": "^0.0.0-alpha.13", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", @@ -4356,9 +4356,9 @@ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, "node_modules/@microbit/microbit-connection": { - "version": "0.0.0-alpha.8", - "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.8.tgz", - "integrity": "sha512-KuQTmn/Q55lB9f95UZwcMLtpyVvhkFyUPifO6SNTVxeP6JoRYYh2txEOwM3/AlNhdkkqme0tFS+qpFlH7nRu/A==", + "version": "0.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.13.tgz", + "integrity": "sha512-TwlKERm+WmcA4wFK5ioV5S/ah71SEH4wFqNdrfgyMO5U1QZfmG+H3DEMWBuYiI67NLn4ZED71vadgSMzwuOaxg==", "dependencies": { "@microbit/microbit-universal-hex": "^0.2.2", "@types/web-bluetooth": "^0.0.20", @@ -4367,19 +4367,6 @@ "nrf-intel-hex": "^1.4.0" } }, - "node_modules/@microbit/microbit-connection/node_modules/dapjs": { - "version": "2.3.0-microbit.2", - "resolved": "git+ssh://git@github.com/microbit-matt-hillsdon/dapjs.git#4529bf7a8dcb124b0a7be2bd87a5655557796456", - "license": "MIT", - "dependencies": { - "@types/node-hid": "^1.2.0", - "@types/usb": "^1.5.1", - "@types/w3c-web-usb": "^1.0.10" - }, - "engines": { - "node": ">=8.14.0" - } - }, "node_modules/@microbit/microbit-universal-hex": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@microbit/microbit-universal-hex/-/microbit-universal-hex-0.2.2.tgz", @@ -7878,6 +7865,19 @@ "node": ">=12" } }, + "node_modules/dapjs": { + "version": "2.3.0-microbit.2", + "resolved": "git+ssh://git@github.com/microbit-matt-hillsdon/dapjs.git#4529bf7a8dcb124b0a7be2bd87a5655557796456", + "license": "MIT", + "dependencies": { + "@types/node-hid": "^1.2.0", + "@types/usb": "^1.5.1", + "@types/w3c-web-usb": "^1.0.10" + }, + "engines": { + "node": ">=8.14.0" + } + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", diff --git a/package.json b/package.json index 9b77a2b0d..7d0e5ab91 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit/microbit-connection": "^0.0.0-alpha.8", + "@microbit/microbit-connection": "^0.0.0-alpha.13", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index 5190107ee..4a0226f9d 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -6,7 +6,7 @@ import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; import HeadingGrid from "./HeadingGrid"; import RecordingDialog from "./RecordingDialog"; import { useConnectActions } from "../connect-actions-hooks"; -import { TempButtonEvent } from "../connect-actions"; +import { ButtonEvent } from "@microbit/microbit-connection"; const gridCommonProps: Partial = { gridTemplateColumns: "200px 1fr", @@ -43,7 +43,7 @@ const AddDataGridView = () => { const connection = useConnectActions(); useEffect(() => { - const listener = (e: TempButtonEvent) => { + const listener = (e: ButtonEvent) => { if (!isOpen) { if (e.state) { onOpen(); diff --git a/src/connect-actions-hooks.tsx b/src/connect-actions-hooks.tsx index 7d1098a32..53b66f010 100644 --- a/src/connect-actions-hooks.tsx +++ b/src/connect-actions-hooks.tsx @@ -1,4 +1,5 @@ import { + MicrobitRadioBridgeConnection, MicrobitWebBluetoothConnection, MicrobitWebUSBConnection, } from "@microbit/microbit-connection"; @@ -16,6 +17,7 @@ import { useLogging } from "./logging/logging-hooks"; interface ConnectContextValue { usb: MicrobitWebUSBConnection; bluetooth: MicrobitWebBluetoothConnection; + radioBridge: MicrobitRadioBridgeConnection; } const ConnectContext = createContext(null); @@ -27,21 +29,30 @@ interface ConnectProviderProps { export const ConnectProvider = ({ children }: ConnectProviderProps) => { const usb = useMemo(() => new MicrobitWebUSBConnection(), []); const bluetooth = useMemo(() => new MicrobitWebBluetoothConnection(), []); + const logging = useLogging(); + const radioBridge = useMemo( + () => + new MicrobitRadioBridgeConnection(usb, { + logging, + }), + [logging, usb] + ); const isInitialized = useRef(false); useEffect(() => { const initialize = async () => { await usb.initialize(); await bluetooth.initialize(); + await radioBridge.initialize(); }; if (!isInitialized.current) { void initialize(); isInitialized.current = true; } - }, [bluetooth, usb]); + }, [bluetooth, radioBridge, usb]); return ( - + {children} ); @@ -52,12 +63,12 @@ export const useConnectActions = (): ConnectActions => { if (!connectContextValue) { throw new Error("Missing provider"); } - const { usb, bluetooth } = connectContextValue; + const { usb, bluetooth, radioBridge } = connectContextValue; const logging = useLogging(); const connectActions = useMemo( - () => new ConnectActions(logging, usb, bluetooth), - [bluetooth, logging, usb] + () => new ConnectActions(logging, usb, bluetooth, radioBridge), + [bluetooth, logging, radioBridge, usb] ); return connectActions; diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 33fb558c3..9db6a9de0 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -1,7 +1,9 @@ import { AccelerometerDataEvent, + ButtonEvent, ConnectionStatus as DeviceConnectionStatus, DeviceError, + MicrobitRadioBridgeConnection, MicrobitWebBluetoothConnection, MicrobitWebUSBConnection, } from "@microbit/microbit-connection"; @@ -29,24 +31,13 @@ export enum ConnectResult { AutomaticConnectFailed, } -const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); - -// Should remove this and use the connection library type -// in the next release. -export type TempButtonEvent = { state: number }; - export class ConnectActions { - private usb: MicrobitWebUSBConnection; - private bluetooth: MicrobitWebBluetoothConnection; - constructor( private logging: Logging, - usb: MicrobitWebUSBConnection, - bluetooth: MicrobitWebBluetoothConnection - ) { - this.usb = usb; - this.bluetooth = bluetooth; - } + private usb: MicrobitWebUSBConnection, + private bluetooth: MicrobitWebBluetoothConnection, + private radioBridge: MicrobitRadioBridgeConnection + ) {} requestUSBConnectionAndFlash = async ( hexType: ConnectionFlowType, @@ -114,21 +105,25 @@ export class ConnectActions { return ConnectAndFlashResult.Failed; }; - // TODO: Replace with real connecting logic connectMicrobitsSerial = async (deviceId: number): Promise => { - await delay(5000); - - // TODO: Use deviceId to assign to connect microbits if (!deviceId) { return ConnectResult.ManualConnectFailed; } - return ConnectResult.Success; + this.radioBridge.setRemoteDeviceId(deviceId); + const status = await this.radioBridge.connect(); + if (status === DeviceConnectionStatus.CONNECTED) { + return ConnectResult.Success; + } + return ConnectResult.ManualConnectFailed; }; connectBluetooth = async ( name: string | undefined ): Promise => { - await this.bluetooth.connect({ name }); + if (name) { + this.bluetooth.setNameFilter(name); + } + await this.bluetooth.connect(); if (this.bluetooth.status === DeviceConnectionStatus.CONNECTED) { return ConnectResult.Success; } @@ -138,33 +133,37 @@ export class ConnectActions { addAccelerometerListener = ( listener: (e: AccelerometerDataEvent) => void ) => { - this.bluetooth?.addEventListener("accelerometerdatachanged", listener); + this.bluetooth.addEventListener("accelerometerdatachanged", listener); + this.radioBridge.addEventListener("accelerometerdatachanged", listener); }; removeAccelerometerListener = ( listener: (e: AccelerometerDataEvent) => void ) => { - this.bluetooth?.removeEventListener("accelerometerdatachanged", listener); + this.bluetooth.removeEventListener("accelerometerdatachanged", listener); + this.radioBridge.removeEventListener("accelerometerdatachanged", listener); }; addButtonListener = ( button: "A" | "B", - listener: (e: TempButtonEvent) => void + listener: (e: ButtonEvent) => void ) => { const type = button === "A" ? "buttonachanged" : "buttonbchanged"; - this.bluetooth?.addEventListener(type, listener); + this.bluetooth.addEventListener(type, listener); + this.radioBridge.addEventListener(type, listener); }; removeButtonListener = ( button: "A" | "B", - listener: (e: TempButtonEvent) => void + listener: (e: ButtonEvent) => void ) => { const type = button === "A" ? "buttonachanged" : "buttonbchanged"; - this.bluetooth?.removeEventListener(type, listener); + this.bluetooth.removeEventListener(type, listener); + this.radioBridge.removeEventListener(type, listener); }; - // TODO: Replace with real disconnect logic disconnect = async () => { await this.bluetooth.disconnect(); + await this.radioBridge.disconnect(); }; } From f0e44dd00d9f3315fa7cafce259e14798b8a7da2 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:05:20 +0100 Subject: [PATCH 102/172] Handle bluetooth connection error scenarios (#279) Only handles bluetooth connection error cases, not radio bridge --- package-lock.json | 8 +- package.json | 2 +- src/App.tsx | 17 +- src/components/ConnectCableDialog.tsx | 14 +- src/components/ConnectContainerDialog.tsx | 14 +- src/components/ConnectionFlowDialogs.tsx | 5 +- src/components/LiveGraphPanel.tsx | 44 ++-- src/components/ReconnectErrorDialog.tsx | 8 +- src/components/WhatYouWillNeedDialog.tsx | 29 ++- src/connect-actions.ts | 23 +- src/connect-status-hooks.tsx | 189 +++++++++++++++ src/connection-stage-actions.ts | 272 +++++++++++++--------- src/connection-stage-hooks.tsx | 37 ++- 13 files changed, 458 insertions(+), 204 deletions(-) create mode 100644 src/connect-status-hooks.tsx diff --git a/package-lock.json b/package-lock.json index 65fbb900d..00f6cb1b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit/microbit-connection": "^0.0.0-alpha.13", + "@microbit/microbit-connection": "^0.0.0-alpha.14", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", @@ -4356,9 +4356,9 @@ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, "node_modules/@microbit/microbit-connection": { - "version": "0.0.0-alpha.13", - "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.13.tgz", - "integrity": "sha512-TwlKERm+WmcA4wFK5ioV5S/ah71SEH4wFqNdrfgyMO5U1QZfmG+H3DEMWBuYiI67NLn4ZED71vadgSMzwuOaxg==", + "version": "0.0.0-alpha.14", + "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.14.tgz", + "integrity": "sha512-Z6PsxYTO359KD5hah0JOlxxehhlzmd7Q45NcrDYT6njTjbAT9FbSF4KnnQbgnbRgePmBzW4q/HAUl89Nvqevdw==", "dependencies": { "@microbit/microbit-universal-hex": "^0.2.2", "@types/web-bluetooth": "^0.0.20", diff --git a/package.json b/package.json index 7d0e5ab91..c3107931d 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit/microbit-connection": "^0.0.0-alpha.13", + "@microbit/microbit-connection": "^0.0.0-alpha.14", "@tensorflow/tfjs": "^4.4.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", diff --git a/src/App.tsx b/src/App.tsx index 2efcfe26a..0570011b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,6 +25,7 @@ import { GesturesProvider } from "./gestures-hooks"; import { MlStatusProvider } from "./ml-status-hooks"; import { ConnectionStageProvider } from "./connection-stage-hooks"; import { ConnectProvider } from "./connect-actions-hooks"; +import { ConnectStatusProvider } from "./connect-status-hooks"; export interface ProviderLayoutProps { children: ReactNode; @@ -43,13 +44,15 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - - - {children} - - - + + + + + {children} + + + + diff --git a/src/components/ConnectCableDialog.tsx b/src/components/ConnectCableDialog.tsx index 5e17e1d84..e641f5846 100644 --- a/src/components/ConnectCableDialog.tsx +++ b/src/components/ConnectCableDialog.tsx @@ -1,4 +1,4 @@ -import { Image, Text, VStack } from "@chakra-ui/react"; +import { Button, Image, Text, VStack } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; import connectCableImage from "../images/connect-cable.gif"; import ConnectContainerDialog, { @@ -51,7 +51,7 @@ const ConnectCableDialog = ({ onSwitch, ...props }: ConnectCableDialogProps) => { - const { subtitleId, onLink, ...typeProps } = configs[type]; + const { subtitleId, onLink, linkTextId, headingId } = configs[type]; const linkConfig = { [LinkType.None]: undefined, [LinkType.Skip]: onSkip, @@ -59,9 +59,15 @@ const ConnectCableDialog = ({ }; return ( + + + ) + } > diff --git a/src/components/ConnectContainerDialog.tsx b/src/components/ConnectContainerDialog.tsx index f0add80ce..9b4148564 100644 --- a/src/components/ConnectContainerDialog.tsx +++ b/src/components/ConnectContainerDialog.tsx @@ -19,8 +19,7 @@ export interface ConnectContainerDialogProps { isOpen: boolean; onClose: () => void; headingId: string; - onLinkClick?: () => void; - linkTextId?: string; + footerLeft?: ReactNode; onNextClick?: () => void; children: ReactNode; onBackClick?: () => void; @@ -30,8 +29,7 @@ const ConnectContainerDialog = ({ isOpen, onClose, headingId, - onLinkClick, - linkTextId, + footerLeft, onNextClick, onBackClick, children, @@ -57,14 +55,10 @@ const ConnectContainerDialog = ({ - {onLinkClick && linkTextId && ( - - )} + {footerLeft && footerLeft} {onBackClick && ( @@ -58,15 +56,15 @@ const LiveGraphPanel = () => { variant="primary" size="sm" isDisabled={ - stage.status === ConnectionStatus.Reconnecting || - stage.status === ConnectionStatus.Connecting + status === ConnectionStatus.Reconnecting || + status === ConnectionStatus.Connecting } onClick={connectBtnConfig.onClick} > )} - {stage.status === ConnectionStatus.Reconnecting && ( + {status === ConnectionStatus.Reconnecting && ( diff --git a/src/components/ReconnectErrorDialog.tsx b/src/components/ReconnectErrorDialog.tsx index 76567c50b..717aa4b83 100644 --- a/src/components/ReconnectErrorDialog.tsx +++ b/src/components/ReconnectErrorDialog.tsx @@ -26,8 +26,8 @@ interface ReconnectErrorDialogProps { onReconnect: () => void; flowType: ConnectionFlowType; errorStep: - | ConnectionFlowStep.ReconnectManualFail - | ConnectionFlowStep.ReconnectAutoFail; + | ConnectionFlowStep.ReconnectFailed + | ConnectionFlowStep.ConnectionLost; } const contentConfig = { @@ -55,8 +55,8 @@ const contentConfig = { }; const errorTextIdPrefixConfig = { - [ConnectionFlowStep.ReconnectAutoFail]: "disconnectedWarning", - [ConnectionFlowStep.ReconnectManualFail]: "reconnectFailed", + [ConnectionFlowStep.ConnectionLost]: "disconnectedWarning", + [ConnectionFlowStep.ReconnectFailed]: "reconnectFailed", }; const ReconnectErrorDialog = ({ diff --git a/src/components/WhatYouWillNeedDialog.tsx b/src/components/WhatYouWillNeedDialog.tsx index 0098db768..efc6455fe 100644 --- a/src/components/WhatYouWillNeedDialog.tsx +++ b/src/components/WhatYouWillNeedDialog.tsx @@ -1,4 +1,4 @@ -import { Grid, GridItem, Image, Text, VStack } from "@chakra-ui/react"; +import { Button, Grid, GridItem, Image, Text, VStack } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; import batteryPackImage from "../images/stylised-battery-pack.svg"; import microbitImage from "../images/stylised-microbit-black.svg"; @@ -9,6 +9,7 @@ import computerBluetoothImage from "../images/stylised_computer_w_bluetooth.svg" import ConnectContainerDialog, { ConnectContainerDialogProps, } from "./ConnectContainerDialog"; +import ExternalLink from "./ExternalLink"; const itemsConfig = { radio: [ @@ -60,24 +61,42 @@ export interface WhatYouWillNeedDialogProps > { reconnect: boolean; type: "radio" | "bluetooth"; + onLinkClick: (() => void) | undefined; } const WhatYouWillNeedDialog = ({ reconnect, type, + onLinkClick, ...props }: WhatYouWillNeedDialogProps) => { return ( + {onLinkClick && ( + + )} + {reconnect && ( + + )} + + } > {reconnect && ( diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 9db6a9de0..6516ed0b0 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -1,5 +1,6 @@ import { AccelerometerDataEvent, + ConnectionStatusEvent, ButtonEvent, ConnectionStatus as DeviceConnectionStatus, DeviceError, @@ -78,7 +79,7 @@ export class ConnectActions { try { await this.usb.flash(data, { partial: true, - progress: (v) => progress(v ?? 100), + progress: (v: number | undefined) => progress(v ?? 100), }); return ConnectAndFlashResult.Success; } catch (e) { @@ -118,16 +119,16 @@ export class ConnectActions { }; connectBluetooth = async ( - name: string | undefined - ): Promise => { + name: string | undefined, + clearDevice: boolean + ): Promise => { + if (clearDevice) { + await this.bluetooth.clearDevice(); + } if (name) { this.bluetooth.setNameFilter(name); } await this.bluetooth.connect(); - if (this.bluetooth.status === DeviceConnectionStatus.CONNECTED) { - return ConnectResult.Success; - } - return ConnectResult.ManualConnectFailed; }; addAccelerometerListener = ( @@ -166,4 +167,12 @@ export class ConnectActions { await this.bluetooth.disconnect(); await this.radioBridge.disconnect(); }; + + addStatusListener = (listener: (e: ConnectionStatusEvent) => void) => { + this.bluetooth.addEventListener("status", listener); + }; + + removeStatusListener = (listener: (e: ConnectionStatusEvent) => void) => { + this.bluetooth.removeEventListener("status", listener); + }; } diff --git a/src/connect-status-hooks.tsx b/src/connect-status-hooks.tsx new file mode 100644 index 000000000..4e7c7864c --- /dev/null +++ b/src/connect-status-hooks.tsx @@ -0,0 +1,189 @@ +import { + ConnectionStatusEvent, + ConnectionStatus as DeviceConnectionStatus, +} from "@microbit/microbit-connection"; +import { + MutableRefObject, + ReactNode, + createContext, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { useConnectActions } from "./connect-actions-hooks"; + +export enum ConnectionStatus { + /** + * Represents the initial connection status. + */ + NotConnected = "NotConnected", + /** + * Connecting occurs for the initial connection. + */ + Connecting = "Connecting", + /** + * Connected. + */ + Connected = "Connected", + /** + * Reconnecting occurs for the subsequent connections after the initial one. + */ + Reconnecting = "Reconnecting", + /** + * Disconnected. The disconnection is triggered by the user. + */ + Disconnected = "Disconnected", + /** + * Failure to establish initial connection triggered by the user. + */ + FailedToConnect = "FailedToConnect", + /** + * Failure to reconnect triggered by the user. + */ + FailedToReconnect = "FailedToReconnect", + /** + * Connection lost. Auto-reconnect was attempted, but failed. + */ + ConnectionLost = "ConnectionLost", + /** + * A subsequent failure to reconnect after a reconnection failure. + * The initial reconnection failure may have been triggered automatically + * or by the user (ConnectionLost or FailedToReconnect). + */ + FailedToReconnectTwice = "FailedToReconnectTwice", +} + +type ConnectStatusContextValue = [ + ConnectionStatus, + (status: ConnectionStatus) => void +]; + +const ConnectStatusContext = createContext( + null +); + +interface ConnectStatusProviderProps { + children: ReactNode; +} + +export const ConnectStatusProvider = ({ + children, +}: ConnectStatusProviderProps) => { + const connectStatusContextValue = useState( + ConnectionStatus.NotConnected + ); + return ( + + {children} + + ); +}; + +export const useSetConnectStatus = (): ((status: ConnectionStatus) => void) => { + const connectStatusContextValue = useContext(ConnectStatusContext); + if (!connectStatusContextValue) { + throw new Error("Missing provider"); + } + const [, setStatus] = connectStatusContextValue; + + return setStatus; +}; + +export const useConnectStatus = ( + handleStatus?: (status: ConnectionStatus) => void +): ConnectionStatus => { + const connectStatusContextValue = useContext(ConnectStatusContext); + if (!connectStatusContextValue) { + throw new Error("Missing provider"); + } + const [status, setStatus] = connectStatusContextValue; + const connectActions = useConnectActions(); + const prevDeviceStatus = useRef(null); + const hasAttempedReconnect = useRef(false); + + useEffect(() => { + const listener = ({ status: deviceStatus }: ConnectionStatusEvent) => { + const newStatus = getNextConnectionStatus( + status, + deviceStatus, + prevDeviceStatus.current, + hasAttempedReconnect + ); + prevDeviceStatus.current = deviceStatus; + if (newStatus) { + handleStatus && handleStatus(newStatus); + setStatus(newStatus); + } + }; + connectActions.addStatusListener(listener); + return () => { + connectActions.removeStatusListener(listener); + }; + }, [connectActions, handleStatus, setStatus, status]); + + return status; +}; + +const getNextConnectionStatus = ( + status: ConnectionStatus, + deviceStatus: DeviceConnectionStatus, + prevDeviceStatus: DeviceConnectionStatus | null, + hasAttempedReconnect: MutableRefObject +) => { + if ( + // Disconnection happens for newly started / restarted + // connection flows when clearing device + deviceStatus === DeviceConnectionStatus.DISCONNECTED && + status === ConnectionStatus.NotConnected + ) { + return ConnectionStatus.NotConnected; + } + if (deviceStatus === DeviceConnectionStatus.CONNECTED) { + hasAttempedReconnect.current = false; + return ConnectionStatus.Connected; + } + if ( + (status === ConnectionStatus.Connecting && + deviceStatus === DeviceConnectionStatus.DISCONNECTED) || + // If user does not select a device + (deviceStatus === DeviceConnectionStatus.NO_AUTHORIZED_DEVICE && + prevDeviceStatus === DeviceConnectionStatus.NO_AUTHORIZED_DEVICE) + ) { + return ConnectionStatus.FailedToConnect; + } + if ( + hasAttempedReconnect.current && + deviceStatus === DeviceConnectionStatus.DISCONNECTED && + prevDeviceStatus === DeviceConnectionStatus.CONNECTING + ) { + return ConnectionStatus.FailedToReconnectTwice; + } + if ( + deviceStatus === DeviceConnectionStatus.DISCONNECTED && + prevDeviceStatus === DeviceConnectionStatus.CONNECTING + ) { + hasAttempedReconnect.current = true; + return ConnectionStatus.FailedToReconnect; + } + if ( + deviceStatus === DeviceConnectionStatus.DISCONNECTED && + prevDeviceStatus === DeviceConnectionStatus.RECONNECTING + ) { + hasAttempedReconnect.current = true; + return ConnectionStatus.ConnectionLost; + } + if (deviceStatus === DeviceConnectionStatus.DISCONNECTED) { + return ConnectionStatus.Disconnected; + } + if ( + deviceStatus === DeviceConnectionStatus.RECONNECTING || + deviceStatus === DeviceConnectionStatus.CONNECTING + ) { + return status === ConnectionStatus.NotConnected || + status === ConnectionStatus.FailedToConnect + ? ConnectionStatus.Connecting + : ConnectionStatus.Reconnecting; + } + return undefined; +}; diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 17beb78ba..e7a59fc7c 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -10,9 +10,10 @@ import { ConnectionFlowStep, ConnectionFlowType, ConnectionStage, - ConnectionStatus, + ConnectionType, } from "./connection-stage-hooks"; import { createStepPageUrl } from "./urls"; +import { ConnectionStatus } from "./connect-status-hooks"; type FlowStage = Pick; @@ -21,12 +22,15 @@ export class ConnectionStageActions { private actions: ConnectActions, private navigate: NavigateFunction, private stage: ConnectionStage, - private setStage: (stage: ConnectionStage) => void + private setStage: (stage: ConnectionStage) => void, + private setStatus: (status: ConnectionStatus) => void ) {} - start = () => + start = () => { + this.setStatus(ConnectionStatus.NotConnected); this.setStage({ ...this.stage, + hasFailedToReconnectTwice: false, flowType: this.stage.flowType === ConnectionFlowType.RadioBridge ? ConnectionFlowType.RadioRemote @@ -36,6 +40,7 @@ export class ConnectionStageActions { ? ConnectionFlowStep.WebUsbBluetoothUnsupported : ConnectionFlowStep.Start, }); + }; setFlowStep = (step: ConnectionFlowStep) => { this.setStage({ ...this.stage, flowStep: step }); @@ -79,11 +84,8 @@ export class ConnectionStageActions { } case ConnectionFlowType.RadioBridge: { newStage = { - ...this.stage, - connType: "radio", - flowStep: ConnectionFlowStep.ConnectingMicrobits, + ...this.getConnectingStage("radio"), radioBridgeDeviceId: deviceId, - status: this.getConnectingOrReconnectingStatus(), }; break; } @@ -136,17 +138,12 @@ export class ConnectionStageActions { }); }; - connectBluetooth = async () => { - this.setStage({ - ...this.stage, - connType: "bluetooth", - flowStep: ConnectionFlowStep.ConnectingBluetooth, - status: this.getConnectingOrReconnectingStatus(), - }); - const result = await this.actions.connectBluetooth( - this.stage.bluetoothMicrobitName + connectBluetooth = async (clearDevice: boolean = true) => { + this.setStage(this.getConnectingStage("bluetooth")); + await this.actions.connectBluetooth( + this.stage.bluetoothMicrobitName, + clearDevice ); - this.handleConnectResult(result); }; connectMicrobits = async () => { @@ -160,76 +157,79 @@ export class ConnectionStageActions { } }; - private getConnectingOrReconnectingStatus = () => { - return this.stage.status === ConnectionStatus.None - ? ConnectionStatus.Connecting - : ConnectionStatus.Reconnecting; + private getConnectingStage = (connType: ConnectionType) => { + return { + ...this.stage, + connType, + flowStep: + connType === "bluetooth" + ? ConnectionFlowStep.ConnectingBluetooth + : ConnectionFlowStep.ConnectingMicrobits, + }; }; private handleConnectResult = (result: ConnectResult) => { if (result === ConnectResult.Success) { + // TODO: Remove forced set status and listen to status event + // from connection library instead for radio + if (this.stage.connType === "radio") { + this.setStatus(ConnectionStatus.Connected); + } return this.onConnected(); } - const newReconnectFailStreak = - this.stage.status === ConnectionStatus.Reconnecting - ? this.stage.reconnectFailStreak + 1 - : this.stage.reconnectFailStreak; - - const nextFlowStep = this.getReconnectFailFlowStep( - newReconnectFailStreak, - result - ); - this.setStage({ - ...this.stage, - reconnectFailStreak: newReconnectFailStreak, - status: ConnectionStatus.Disconnected, - flowStep: nextFlowStep, - }); + this.handleConnectFail(); }; - private getReconnectFailFlowStep = ( - failStreak: number, - result: ConnectResult - ) => { - switch (failStreak) { - case 0: { - return this.stage.flowType === ConnectionFlowType.Bluetooth - ? ConnectionFlowStep.TryAgainBluetoothConnect - : ConnectionFlowStep.TryAgainReplugMicrobit; - } - case 1: { - return result === ConnectResult.ManualConnectFailed - ? ConnectionFlowStep.ReconnectManualFail - : ConnectionFlowStep.ReconnectAutoFail; - } - default: { - return ConnectionFlowStep.ReconnectFailedTwice; - } - } + private handleConnectFail = () => { + this.setFlowStep( + this.stage.flowType === ConnectionFlowType.Bluetooth + ? ConnectionFlowStep.TryAgainBluetoothConnect + : ConnectionFlowStep.TryAgainReplugMicrobit + ); }; private onConnected = () => { - this.setStage({ - ...this.stage, - flowStep: ConnectionFlowStep.None, - status: ConnectionStatus.Connected, - reconnectFailStreak: 0, - }); + this.setFlowStep(ConnectionFlowStep.None); this.navigate(createStepPageUrl("add-data")); }; disconnect = async () => { await this.actions.disconnect(); - this.setStage({ - ...this.stage, - status: ConnectionStatus.Disconnected, - }); + }; + + handleConnectionStatus = (status: ConnectionStatus) => { + switch (status) { + case ConnectionStatus.Connected: { + return this.onConnected(); + } + case ConnectionStatus.FailedToConnect: { + return this.handleConnectFail(); + } + case ConnectionStatus.FailedToReconnectTwice: { + return this.setStage({ + ...this.stage, + hasFailedToReconnectTwice: true, + flowStep: ConnectionFlowStep.ReconnectFailedTwice, + }); + } + case ConnectionStatus.FailedToReconnect: { + return this.setFlowStep(ConnectionFlowStep.ReconnectFailed); + } + case ConnectionStatus.ConnectionLost: { + return this.setFlowStep(ConnectionFlowStep.ConnectionLost); + } + case ConnectionStatus.Reconnecting: { + return this.setStage(this.getConnectingStage("bluetooth")); + } + } + return; }; reconnect = async () => { if (this.stage.connType === "bluetooth") { - await this.connectBluetooth(); + await this.connectBluetooth(false); } else { + this.setStage(this.getConnectingStage("radio")); await this.connectMicrobits(); } }; @@ -252,12 +252,30 @@ export class ConnectionStageActions { }); }; + private getStagesOrder = () => { + if (this.stage.flowType === ConnectionFlowType.Bluetooth) { + return bluetoothFlow({ + isManualFlashing: + !this.stage.isWebUsbSupported || + this.stage.flowStep === ConnectionFlowStep.ManualFlashingTutorial, + isRestartAgain: this.stage.hasFailedToReconnectTwice, + }); + } + return radioFlow(); + }; + onNextClick = () => { - this.setStage({ ...this.stage, ...getNextStage(this.stage, 1) }); + this.setStage({ + ...this.stage, + ...getNextStage(this.stage, 1, this.getStagesOrder()), + }); }; onBackClick = () => { - this.setStage({ ...this.stage, ...getNextStage(this.stage, -1) }); + this.setStage({ + ...this.stage, + ...getNextStage(this.stage, -1, this.getStagesOrder()), + }); }; onTryAgain = () => { @@ -269,47 +287,70 @@ export class ConnectionStageActions { }; } -const getStagesOrder = (state: ConnectionStage): FlowStage[] => { - const { RadioRemote, RadioBridge, Bluetooth } = ConnectionFlowType; - if (state.flowType === ConnectionFlowType.Bluetooth) { - return [ - { flowStep: ConnectionFlowStep.Start, flowType: Bluetooth }, - { flowStep: ConnectionFlowStep.ConnectCable, flowType: Bluetooth }, - // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. - { - flowStep: - !state.isWebUsbSupported || - state.flowStep === ConnectionFlowStep.ManualFlashingTutorial - ? ConnectionFlowStep.ManualFlashingTutorial - : ConnectionFlowStep.WebUsbFlashingTutorial, - flowType: Bluetooth, - }, - { flowStep: ConnectionFlowStep.ConnectBattery, flowType: Bluetooth }, - { - flowStep: ConnectionFlowStep.EnterBluetoothPattern, - flowType: Bluetooth, - }, - { - flowStep: ConnectionFlowStep.ConnectBluetoothTutorial, - flowType: Bluetooth, - }, - ]; - } - return [ - { flowStep: ConnectionFlowStep.Start, flowType: RadioRemote }, - { flowStep: ConnectionFlowStep.ConnectCable, flowType: RadioRemote }, - { - flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, - flowType: RadioRemote, - }, - { flowStep: ConnectionFlowStep.ConnectBattery, flowType: RadioRemote }, - { flowStep: ConnectionFlowStep.ConnectCable, flowType: RadioBridge }, - { - flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, - flowType: RadioBridge, - }, - ]; -}; +const bluetoothFlow = ({ + isManualFlashing, + isRestartAgain, +}: { + isManualFlashing: boolean; + isRestartAgain: boolean; +}) => [ + { + flowStep: isRestartAgain + ? ConnectionFlowStep.ReconnectFailedTwice + : ConnectionFlowStep.Start, + flowType: ConnectionFlowType.Bluetooth, + }, + { + flowStep: ConnectionFlowStep.ConnectCable, + flowType: ConnectionFlowType.Bluetooth, + }, + // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. + { + flowStep: isManualFlashing + ? ConnectionFlowStep.ManualFlashingTutorial + : ConnectionFlowStep.WebUsbFlashingTutorial, + flowType: ConnectionFlowType.Bluetooth, + }, + { + flowStep: ConnectionFlowStep.ConnectBattery, + flowType: ConnectionFlowType.Bluetooth, + }, + { + flowStep: ConnectionFlowStep.EnterBluetoothPattern, + flowType: ConnectionFlowType.Bluetooth, + }, + { + flowStep: ConnectionFlowStep.ConnectBluetoothTutorial, + flowType: ConnectionFlowType.Bluetooth, + }, +]; + +const radioFlow = () => [ + { + flowStep: ConnectionFlowStep.Start, + flowType: ConnectionFlowType.RadioRemote, + }, + { + flowStep: ConnectionFlowStep.ConnectCable, + flowType: ConnectionFlowType.RadioRemote, + }, + { + flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, + flowType: ConnectionFlowType.RadioRemote, + }, + { + flowStep: ConnectionFlowStep.ConnectBattery, + flowType: ConnectionFlowType.RadioRemote, + }, + { + flowStep: ConnectionFlowStep.ConnectCable, + flowType: ConnectionFlowType.RadioBridge, + }, + { + flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, + flowType: ConnectionFlowType.RadioBridge, + }, +]; const getFlowStageIdx = ( { flowStep, flowType }: FlowStage, @@ -324,12 +365,15 @@ const getFlowStageIdx = ( throw new Error("Should be able to match stage and type again order"); }; -const getNextStage = (stage: ConnectionStage, increment: number): FlowStage => { - const order = getStagesOrder(stage); - const currIdx = getFlowStageIdx(stage, order); +const getNextStage = ( + stage: ConnectionStage, + increment: number, + stagesOrder: FlowStage[] +): FlowStage => { + const currIdx = getFlowStageIdx(stage, stagesOrder); const newIdx = currIdx + increment; - if (newIdx === order.length || newIdx < 0) { + if (newIdx === stagesOrder.length || newIdx < 0) { throw new Error("Impossible step stage"); } - return order[newIdx]; + return stagesOrder[newIdx]; }; diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index c455ca8d8..644521ce9 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -11,6 +11,11 @@ import { ConnectActions } from "./connect-actions"; import { useConnectActions } from "./connect-actions-hooks"; import { ConnectionStageActions } from "./connection-stage-actions"; import { useStorage } from "./hooks/use-storage"; +import { + ConnectionStatus, + useConnectStatus, + useSetConnectStatus, +} from "./connect-status-hooks"; export enum ConnectionFlowType { Bluetooth = "bluetooth", @@ -18,14 +23,6 @@ export enum ConnectionFlowType { RadioRemote = "remote", } -export enum ConnectionStatus { - None = "None", // Have not been connected before - Connecting = "Connecting", - Connected = "Connected", - Disconnected = "Disconnected", - Reconnecting = "Reconnecting", -} - export type ConnectionType = "bluetooth" | "radio"; export enum ConnectionFlowStep { @@ -54,8 +51,8 @@ export enum ConnectionFlowStep { MicrobitUnsupported = "MicrobitUnsupported", WebUsbBluetoothUnsupported = "WebUsbBluetoothUnsupported", - ReconnectAutoFail = "ReconnectAutoFail", - ReconnectManualFail = "ReconnectManualFail", + ConnectionLost = "ConnectionLoss", + ReconnectFailed = "ReconnectFailed", ReconnectFailedTwice = "ReconnectFailedTwice", } @@ -63,21 +60,18 @@ export interface ConnectionStage { // For connection flow flowStep: ConnectionFlowStep; flowType: ConnectionFlowType; - // Number of times there have been consecutive reconnect fails - // for determining which reconnection dialog to show - reconnectFailStreak: number; // Compatibility isWebBluetoothSupported: boolean; isWebUsbSupported: boolean; // Connection state - status: ConnectionStatus; connType: "bluetooth" | "radio"; bluetoothDeviceId?: number; bluetoothMicrobitName?: string; radioBridgeDeviceId?: number; radioRemoteDeviceId?: number; + hasFailedToReconnectTwice: boolean; } type ConnectionStageContextValue = [ @@ -97,12 +91,11 @@ const getInitialConnectionStageValue = ( ): ConnectionStage => ({ flowStep: ConnectionFlowStep.None, flowType: ConnectionFlowType.Bluetooth, - reconnectFailStreak: 0, - status: ConnectionStatus.None, bluetoothMicrobitName: microbitName, connType: "bluetooth", isWebBluetoothSupported: true, isWebUsbSupported: true, + hasFailedToReconnectTwice: false, }); export const ConnectionStageProvider = ({ @@ -144,20 +137,20 @@ export const useConnectionStage = (): { const [stage, setStage] = connectionStageContextValue; const navigate = useNavigate(); const connectActions = useConnectActions(); + const setStatus = useSetConnectStatus(); const actions = useMemo(() => { return new ConnectionStageActions( connectActions, navigate, stage, - setStage + setStage, + setStatus ); - }, [connectActions, navigate, stage, setStage]); + }, [connectActions, navigate, stage, setStage, setStatus]); - const isConnected = useMemo( - () => stage.status === ConnectionStatus.Connected, - [stage.status] - ); + const status = useConnectStatus(actions.handleConnectionStatus); + const isConnected = status === ConnectionStatus.Connected; return { stage, From 4f4197237f5dceff2151d24c3f2fd87a9f8ff126 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:39:43 +0100 Subject: [PATCH 103/172] Recording (#280) Use time based recording for now. We drop ~10-11 samples on my machine by using min sample count which makes the recording process feel a lot quicker. --- src/components/RecordingDialog.tsx | 199 +++++++++++++++++++++-------- 1 file changed, 146 insertions(+), 53 deletions(-) diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index 2be1f99af..507d7b2e5 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -6,19 +6,20 @@ import { ModalCloseButton, ModalContent, ModalOverlay, + Progress, Text, + useToast, VStack, - Box, } from "@chakra-ui/react"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { motion } from "framer-motion"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { GestureData, useGestureActions } from "../gestures-hooks"; -import gestureData from "../test-fixtures/gesture-data.json"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; +import { useConnectActions } from "../connect-actions-hooks"; +import { GestureData, useGestureActions, XYZData } from "../gestures-hooks"; import { mlSettings } from "../ml"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; +import { AccelerometerDataEvent } from "@microbit/microbit-connection"; -interface CountdownConfig { +interface CountdownStage { value: string | number; duration: number; fontSize?: string; @@ -44,14 +45,14 @@ const RecordingDialog = ({ gestureId, }: RecordingDialogProps) => { const intl = useIntl(); + const toast = useToast(); const actions = useGestureActions(); + const recordingDataSource = useRecordingDataSource(); const [, setStatus] = useMlStatus(); const [recordingStatus, setRecordingStatus] = useState( RecordingStatus.None ); - const [countdownIdx, setIsCountdownIdx] = useState(0); - - const countdownConfigs: CountdownConfig[] = useMemo( + const countdownStages: CountdownStage[] = useMemo( () => [ { value: 3, duration: 500, fontSize: "8xl" }, { value: 2, duration: 500, fontSize: "8xl" }, @@ -64,52 +65,83 @@ const RecordingDialog = ({ ], [intl] ); + const [countdownStageIndex, setCountdownStageIndex] = useState(0); - const handleOnClose = useCallback(() => { + const handleCleanup = useCallback(() => { setRecordingStatus(RecordingStatus.None); setStatus({ stage: MlStage.NotTrained }); - setIsCountdownIdx(0); + setCountdownStageIndex(0); + setProgress(0); onClose(); }, [onClose, setStatus]); + const handleOnClose = useCallback(() => { + recordingDataSource.cancelRecording(); + handleCleanup(); + }, [handleCleanup, recordingDataSource]); + useEffect(() => { if (isOpen) { // When dialog is opened, restart countdown setRecordingStatus(RecordingStatus.Countdown); - setIsCountdownIdx(0); + setCountdownStageIndex(0); } }, [isOpen]); + const [progress, setProgress] = useState(0); useEffect(() => { if (recordingStatus === RecordingStatus.Countdown) { - const config = countdownConfigs[countdownIdx]; + const config = countdownStages[countdownStageIndex]; - setTimeout(() => { - if (countdownIdx < countdownConfigs.length - 1) { - setIsCountdownIdx(countdownIdx + 1); + const countdownTimeout = setTimeout(() => { + if (countdownStageIndex < countdownStages.length - 1) { + setCountdownStageIndex(countdownStageIndex + 1); return; } else { setRecordingStatus(RecordingStatus.Recording); setStatus({ stage: MlStage.RecordingData }); - } - }, config.duration); - } - }, [countdownConfigs, isOpen, recordingStatus, countdownIdx, setStatus]); + recordingDataSource.startRecording({ + onDone(data) { + actions.addGestureRecordings(gestureId, [ + { ID: Date.now(), data }, + ]); + handleCleanup(); + }, + onError() { + handleCleanup(); - useEffect(() => { - if (recordingStatus === RecordingStatus.Recording) { - setTimeout(() => { - if (recordingStatus === RecordingStatus.Recording) { - // TODO: Record samples - // Stubbing of recording of gesture - actions.addGestureRecordings(gestureId, [ - (gestureData as GestureData[])[0].recordings[0], - ]); - handleOnClose(); + toast({ + position: "top", + duration: 5_000, + title: intl.formatMessage({ + id: "alert.recording.disconnectedDuringRecording", + }), + variant: "subtle", + status: "error", + }); + }, + onProgress: setProgress, + }); } - }, mlSettings.duration); + }, config.duration); + return () => { + clearTimeout(countdownTimeout); + }; } - }, [actions, gestureId, handleOnClose, recordingStatus]); + }, [ + countdownStages, + isOpen, + recordingStatus, + countdownStageIndex, + setStatus, + recordingDataSource, + actions, + gestureId, + handleOnClose, + handleCleanup, + toast, + intl, + ]); return ( ) : ( - {countdownConfigs[countdownIdx].value} + {countdownStages[countdownStageIndex].value} )} - - - + colorScheme="red" + borderRadius="xl" + value={progress} + /> - diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index d50464544..6004a4f02 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -13,7 +13,7 @@ import DownloadingDialog from "./DownloadingDialog"; import EnterBluetoothPatternDialog from "./EnterBluetoothPatternDialog"; import LoadingDialog from "./LoadingDialog"; import ManualFlashingDialog from "./ManualFlashingDialog"; -import ReconnectErrorDialog from "./ReconnectErrorDialog"; +import ConnectErrorDialog from "./ConnectErrorDialog"; import SelectMicrobitBluetoothDialog from "./SelectMicrobitBluetoothDialog"; import SelectMicrobitUsbDialog from "./SelectMicrobitUsbDialog"; import TryAgainDialog from "./TryAgainDialog"; @@ -60,19 +60,13 @@ const ConnectionDialogs = () => { [actions] ); - const onFlashSuccess = useCallback( - async (newStage: ConnectionStage) => { - // Inferring microbit name saves the user from entering the pattern - // for bluetooth connection flow - if (newStage.bluetoothMicrobitName) { - setMicrobitName(newStage.bluetoothMicrobitName); - } - if (newStage.flowType === ConnectionFlowType.RadioBridge) { - await actions.connectMicrobits(); - } - }, - [actions] - ); + const onFlashSuccess = useCallback((newStage: ConnectionStage) => { + // Inferring microbit name saves the user from entering the pattern + // for bluetooth connection flow + if (newStage.bluetoothMicrobitName) { + setMicrobitName(newStage.bluetoothMicrobitName); + } + }, []); async function connectAndFlash(): Promise { await actions.connectAndflashMicrobit(progressCallback, onFlashSuccess); @@ -202,9 +196,9 @@ const ConnectionDialogs = () => { ); } - case ConnectionFlowStep.TryAgainBluetoothConnect: + case ConnectionFlowStep.TryAgainBluetoothSelectMicrobit: case ConnectionFlowStep.TryAgainReplugMicrobit: - case ConnectionFlowStep.TryAgainSelectMicrobit: + case ConnectionFlowStep.TryAgainWebUsbSelectMicrobit: case ConnectionFlowStep.TryAgainCloseTabs: { return ( { case ConnectionFlowStep.WebUsbBluetoothUnsupported: { return ; } + case ConnectionFlowStep.ConnectFailed: case ConnectionFlowStep.ReconnectFailed: case ConnectionFlowStep.ConnectionLost: { return ( - diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 09f545393..bcee7910d 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -7,6 +7,7 @@ import { useConnectionStage } from "../connection-stage-hooks"; import { AccelerometerDataEvent } from "@microbit/microbit-connection"; import { MlStage, useMlStatus } from "../ml-status-hooks"; import { mlSettings } from "../ml"; +import { ConnectionStatus } from "../connect-status-hooks"; const smoothenDataPoint = (curr: number, next: number) => { // TODO: Factor out so that recording graph can do the same @@ -15,7 +16,7 @@ const smoothenDataPoint = (curr: number, next: number) => { }; const LiveGraph = () => { - const { isConnected } = useConnectionStage(); + const { isConnected, status } = useConnectionStage(); const [{ stage }] = useMlStatus(); const connectActions = useConnectActions(); @@ -70,12 +71,12 @@ const LiveGraph = () => { }, [lineX, lineY, lineZ, recordLines]); useEffect(() => { - if (isConnected) { + if (isConnected || status === ConnectionStatus.ReconnectingAutomatically) { chart?.start(); } else { chart?.stop(); } - }, [chart, isConnected]); + }, [chart, isConnected, status]); // Draw on graph to display that users are recording const [isRecording, setIsRecording] = useState(false); diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 7e6f1ff1a..2860b8dc7 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -5,13 +5,14 @@ import { FormattedMessage } from "react-intl"; import { useConnectionStage } from "../connection-stage-hooks"; import InfoToolTip from "./InfoToolTip"; import LiveGraph from "./LiveGraph"; -import { ConnectionStatus, useConnectStatus } from "../connect-status-hooks"; +import { ConnectionStatus } from "../connect-status-hooks"; const LiveGraphPanel = () => { - const { actions } = useConnectionStage(); - const status = useConnectStatus(); + const { actions, status } = useConnectionStage(); const parentPortalRef = useRef(null); - + const isReconnecting = + status === ConnectionStatus.ReconnectingAutomatically || + status === ConnectionStatus.ReconnectingExplicitly; const connectBtnConfig = useMemo(() => { return status === ConnectionStatus.NotConnected || status === ConnectionStatus.Connecting || @@ -56,15 +57,14 @@ const LiveGraphPanel = () => { variant="primary" size="sm" isDisabled={ - status === ConnectionStatus.Reconnecting || - status === ConnectionStatus.Connecting + isReconnecting || status === ConnectionStatus.Connecting } onClick={connectBtnConfig.onClick} > )} - {status === ConnectionStatus.Reconnecting && ( + {isReconnecting && ( diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index 078114038..758cd14b6 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -22,13 +22,20 @@ const StartResumeActions = ({ ...props }: Partial) => { }, [navigate]); const handleStartNewSession = useCallback(() => { + startOverWarningDialogDisclosure.onClose(); gestureActions.deleteAllGestures(); if (isConnected) { handleNavigateToAddData(); } else { connStageActions.start(); } - }, [gestureActions, connStageActions, handleNavigateToAddData, isConnected]); + }, [ + startOverWarningDialogDisclosure, + gestureActions, + isConnected, + handleNavigateToAddData, + connStageActions, + ]); const onClickStartNewSession = useCallback(() => { if (hasExistingSession) { diff --git a/src/components/TryAgainDialog.tsx b/src/components/TryAgainDialog.tsx index 65ece4089..2acb5c011 100644 --- a/src/components/TryAgainDialog.tsx +++ b/src/components/TryAgainDialog.tsx @@ -68,11 +68,11 @@ const configs = { headingId: "connectMB.usbTryAgain.heading", children: , }, - [ConnectionFlowStep.TryAgainSelectMicrobit]: { + [ConnectionFlowStep.TryAgainWebUsbSelectMicrobit]: { headingId: "connectMB.usbTryAgain.heading", children: , }, - [ConnectionFlowStep.TryAgainBluetoothConnect]: { + [ConnectionFlowStep.TryAgainBluetoothSelectMicrobit]: { headingId: "connectMB.bluetooth.heading", children: ( @@ -80,23 +80,23 @@ const configs = { }, }; -interface TryAgainWebUsbDialogProps { +interface TryAgainDialogProps { isOpen: boolean; onClose: () => void; onTryAgain: () => void; type: | ConnectionFlowStep.TryAgainReplugMicrobit | ConnectionFlowStep.TryAgainCloseTabs - | ConnectionFlowStep.TryAgainSelectMicrobit - | ConnectionFlowStep.TryAgainBluetoothConnect; + | ConnectionFlowStep.TryAgainWebUsbSelectMicrobit + | ConnectionFlowStep.TryAgainBluetoothSelectMicrobit; } -const TryAgainWebUsbDialog = ({ +const TryAgainDialog = ({ type, isOpen, onClose, onTryAgain, -}: TryAgainWebUsbDialogProps) => { +}: TryAgainDialogProps) => { const config = configs[type]; return ( void; + radioBridge: (e: ConnectionStatusEvent) => void; + usb: (e: ConnectionStatusEvent) => void; +} + +export type StatusListenerType = "bluetooth" | "radioRemote" | "usb"; + +export type StatusListener = (e: { + status: DeviceConnectionStatus; + type: StatusListenerType; +}) => void; + export class ConnectActions { + private statusListeners: StatusListeners = { + bluetooth: () => {}, + radioBridge: () => {}, + usb: () => {}, + }; constructor( private logging: Logging, private usb: MicrobitWebUSBConnection, @@ -106,16 +124,9 @@ export class ConnectActions { return ConnectAndFlashResult.Failed; }; - connectMicrobitsSerial = async (deviceId: number): Promise => { - if (!deviceId) { - return ConnectResult.ManualConnectFailed; - } + connectMicrobitsSerial = async (deviceId: number): Promise => { this.radioBridge.setRemoteDeviceId(deviceId); - const status = await this.radioBridge.connect(); - if (status === DeviceConnectionStatus.CONNECTED) { - return ConnectResult.Success; - } - return ConnectResult.ManualConnectFailed; + await this.radioBridge.connect(); }; connectBluetooth = async ( @@ -168,11 +179,27 @@ export class ConnectActions { await this.radioBridge.disconnect(); }; - addStatusListener = (listener: (e: ConnectionStatusEvent) => void) => { - this.bluetooth.addEventListener("status", listener); + private prepareStatusListeners = (listener: StatusListener) => { + const listeners: StatusListeners = { + bluetooth: (e) => listener({ status: e.status, type: "bluetooth" }), + radioBridge: (e) => listener({ status: e.status, type: "radioRemote" }), + usb: (e) => listener({ status: e.status, type: "usb" }), + }; + this.statusListeners = listeners; + return listeners; + }; + + addStatusListener = (listener: StatusListener) => { + const listeners = this.prepareStatusListeners(listener); + this.bluetooth.addEventListener("status", listeners.bluetooth); + this.radioBridge.addEventListener("status", listeners.radioBridge); + this.usb.addEventListener("status", listeners.usb); }; - removeStatusListener = (listener: (e: ConnectionStatusEvent) => void) => { - this.bluetooth.removeEventListener("status", listener); + removeStatusListener = () => { + const listeners = this.statusListeners; + this.bluetooth.removeEventListener("status", listeners.bluetooth); + this.radioBridge.removeEventListener("status", listeners.radioBridge); + this.usb.removeEventListener("status", listeners.usb); }; } diff --git a/src/connect-status-hooks.tsx b/src/connect-status-hooks.tsx index 4e7c7864c..7702ac31d 100644 --- a/src/connect-status-hooks.tsx +++ b/src/connect-status-hooks.tsx @@ -1,9 +1,5 @@ +import { ConnectionStatus as DeviceConnectionStatus } from "@microbit/microbit-connection"; import { - ConnectionStatusEvent, - ConnectionStatus as DeviceConnectionStatus, -} from "@microbit/microbit-connection"; -import { - MutableRefObject, ReactNode, createContext, useContext, @@ -11,7 +7,10 @@ import { useRef, useState, } from "react"; +import { StatusListener } from "./connect-actions"; import { useConnectActions } from "./connect-actions-hooks"; +import { ConnectionFlowType, ConnectionType } from "./connection-stage-hooks"; +import { getNextConnectionState } from "./get-next-connection-state"; export enum ConnectionStatus { /** @@ -27,13 +26,22 @@ export enum ConnectionStatus { */ Connected = "Connected", /** - * Reconnecting occurs for the subsequent connections after the initial one. + * Reconnecting explicitly occurs when a reconnection is triggered by the user. + */ + ReconnectingExplicitly = "ReconnectingExplicitly", + /** + * Reconnecting automatically occurs when a reconnection is triggered automatically. + * This happens before a connection lost is declared. */ - Reconnecting = "Reconnecting", + ReconnectingAutomatically = "ReconnectingAutomatically", /** * Disconnected. The disconnection is triggered by the user. */ Disconnected = "Disconnected", + /** + * Failure to select a device by the user. + */ + FailedToSelectBluetoothDevice = "FailedToSelectDevice", /** * Failure to establish initial connection triggered by the user. */ @@ -80,110 +88,61 @@ export const ConnectStatusProvider = ({ ); }; -export const useSetConnectStatus = (): ((status: ConnectionStatus) => void) => { +export const useConnectStatus = (): [ + ConnectionStatus, + (status: ConnectionStatus) => void +] => { const connectStatusContextValue = useContext(ConnectStatusContext); if (!connectStatusContextValue) { throw new Error("Missing provider"); } - const [, setStatus] = connectStatusContextValue; - - return setStatus; + return connectStatusContextValue; }; -export const useConnectStatus = ( - handleStatus?: (status: ConnectionStatus) => void +export const useConnectStatusUpdater = ( + currConnType: ConnectionType, + handleStatus: (status: ConnectionStatus, type: ConnectionFlowType) => void ): ConnectionStatus => { - const connectStatusContextValue = useContext(ConnectStatusContext); - if (!connectStatusContextValue) { - throw new Error("Missing provider"); - } - const [status, setStatus] = connectStatusContextValue; + const [connectionStatus, setConnectionStatus] = useConnectStatus(); const connectActions = useConnectActions(); const prevDeviceStatus = useRef(null); - const hasAttempedReconnect = useRef(false); + const [hasAttempedReconnect, setHasAttemptedReconnect] = + useState(false); + const [onFirstConnectAttempt, setOnFirstConnectAttempt] = + useState(true); useEffect(() => { - const listener = ({ status: deviceStatus }: ConnectionStatusEvent) => { - const newStatus = getNextConnectionStatus( - status, + const listener: StatusListener = ({ status: deviceStatus, type }) => { + const nextState = getNextConnectionState({ + currConnType, + currStatus: connectionStatus, deviceStatus, - prevDeviceStatus.current, - hasAttempedReconnect - ); + prevDeviceStatus: prevDeviceStatus.current, + type, + hasAttempedReconnect, + setHasAttemptedReconnect, + onFirstConnectAttempt, + setOnFirstConnectAttempt, + }); prevDeviceStatus.current = deviceStatus; - if (newStatus) { - handleStatus && handleStatus(newStatus); - setStatus(newStatus); + if (nextState) { + handleStatus && handleStatus(nextState.status, nextState.flowType); + setConnectionStatus(nextState.status); } }; connectActions.addStatusListener(listener); return () => { - connectActions.removeStatusListener(listener); + connectActions.removeStatusListener(); }; - }, [connectActions, handleStatus, setStatus, status]); - - return status; -}; + }, [ + connectActions, + connectionStatus, + currConnType, + handleStatus, + hasAttempedReconnect, + onFirstConnectAttempt, + setConnectionStatus, + ]); -const getNextConnectionStatus = ( - status: ConnectionStatus, - deviceStatus: DeviceConnectionStatus, - prevDeviceStatus: DeviceConnectionStatus | null, - hasAttempedReconnect: MutableRefObject -) => { - if ( - // Disconnection happens for newly started / restarted - // connection flows when clearing device - deviceStatus === DeviceConnectionStatus.DISCONNECTED && - status === ConnectionStatus.NotConnected - ) { - return ConnectionStatus.NotConnected; - } - if (deviceStatus === DeviceConnectionStatus.CONNECTED) { - hasAttempedReconnect.current = false; - return ConnectionStatus.Connected; - } - if ( - (status === ConnectionStatus.Connecting && - deviceStatus === DeviceConnectionStatus.DISCONNECTED) || - // If user does not select a device - (deviceStatus === DeviceConnectionStatus.NO_AUTHORIZED_DEVICE && - prevDeviceStatus === DeviceConnectionStatus.NO_AUTHORIZED_DEVICE) - ) { - return ConnectionStatus.FailedToConnect; - } - if ( - hasAttempedReconnect.current && - deviceStatus === DeviceConnectionStatus.DISCONNECTED && - prevDeviceStatus === DeviceConnectionStatus.CONNECTING - ) { - return ConnectionStatus.FailedToReconnectTwice; - } - if ( - deviceStatus === DeviceConnectionStatus.DISCONNECTED && - prevDeviceStatus === DeviceConnectionStatus.CONNECTING - ) { - hasAttempedReconnect.current = true; - return ConnectionStatus.FailedToReconnect; - } - if ( - deviceStatus === DeviceConnectionStatus.DISCONNECTED && - prevDeviceStatus === DeviceConnectionStatus.RECONNECTING - ) { - hasAttempedReconnect.current = true; - return ConnectionStatus.ConnectionLost; - } - if (deviceStatus === DeviceConnectionStatus.DISCONNECTED) { - return ConnectionStatus.Disconnected; - } - if ( - deviceStatus === DeviceConnectionStatus.RECONNECTING || - deviceStatus === DeviceConnectionStatus.CONNECTING - ) { - return status === ConnectionStatus.NotConnected || - status === ConnectionStatus.FailedToConnect - ? ConnectionStatus.Connecting - : ConnectionStatus.Reconnecting; - } - return undefined; + return connectionStatus; }; diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index e7a59fc7c..7f2afe0a3 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -4,7 +4,6 @@ import { ConnectActions, ConnectAndFlashFailResult, ConnectAndFlashResult, - ConnectResult, } from "./connect-actions"; import { ConnectionFlowStep, @@ -60,10 +59,10 @@ export class ConnectionStageActions { return this.handleConnectAndFlashFail(result); } - this.onFlashSuccess(deviceId, onSuccess); + await this.onFlashSuccess(deviceId, onSuccess); }; - private onFlashSuccess = ( + private onFlashSuccess = async ( deviceId: number, onSuccess: (stage: ConnectionStage) => void ) => { @@ -83,11 +82,10 @@ export class ConnectionStageActions { break; } case ConnectionFlowType.RadioBridge: { - newStage = { - ...this.getConnectingStage("radio"), + await this.connectMicrobits({ radioBridgeDeviceId: deviceId, - }; - break; + }); + return; } case ConnectionFlowType.RadioRemote: { newStage = { @@ -116,7 +114,9 @@ export class ConnectionStageActions { case ConnectAndFlashResult.ErrorBadFirmware: return this.setFlowStep(ConnectionFlowStep.BadFirmware); case ConnectAndFlashResult.ErrorNoDeviceSelected: - return this.setFlowStep(ConnectionFlowStep.TryAgainSelectMicrobit); + return this.setFlowStep( + ConnectionFlowStep.TryAgainWebUsbSelectMicrobit + ); case ConnectAndFlashResult.ErrorUnableToClaimInterface: return this.setFlowStep(ConnectionFlowStep.TryAgainCloseTabs); default: @@ -146,15 +146,16 @@ export class ConnectionStageActions { ); }; - connectMicrobits = async () => { - if (this.stage.connType === "radio" && this.stage.radioRemoteDeviceId) { - const result = await this.actions.connectMicrobitsSerial( - this.stage.radioRemoteDeviceId - ); - this.handleConnectResult(result); - } else { - this.setFlowStep(ConnectionFlowStep.TryAgainReplugMicrobit); + connectMicrobits = async (partialStage?: Partial) => { + const newStage = { + ...this.getConnectingStage("radio"), + ...(partialStage || {}), + }; + this.setStage(newStage); + if (!newStage.radioRemoteDeviceId) { + throw new Error("Radio bridge device id not set"); } + await this.actions.connectMicrobitsSerial(newStage.radioRemoteDeviceId); }; private getConnectingStage = (connType: ConnectionType) => { @@ -168,24 +169,8 @@ export class ConnectionStageActions { }; }; - private handleConnectResult = (result: ConnectResult) => { - if (result === ConnectResult.Success) { - // TODO: Remove forced set status and listen to status event - // from connection library instead for radio - if (this.stage.connType === "radio") { - this.setStatus(ConnectionStatus.Connected); - } - return this.onConnected(); - } - this.handleConnectFail(); - }; - private handleConnectFail = () => { - this.setFlowStep( - this.stage.flowType === ConnectionFlowType.Bluetooth - ? ConnectionFlowStep.TryAgainBluetoothConnect - : ConnectionFlowStep.TryAgainReplugMicrobit - ); + this.setFlowStep(ConnectionFlowStep.ConnectFailed); }; private onConnected = () => { @@ -197,29 +182,41 @@ export class ConnectionStageActions { await this.actions.disconnect(); }; - handleConnectionStatus = (status: ConnectionStatus) => { + handleConnectionStatus = ( + status: ConnectionStatus, + flowType: ConnectionFlowType + ) => { switch (status) { case ConnectionStatus.Connected: { return this.onConnected(); } + case ConnectionStatus.FailedToSelectBluetoothDevice: { + return this.setFlowStep( + ConnectionFlowStep.TryAgainBluetoothSelectMicrobit + ); + } case ConnectionStatus.FailedToConnect: { return this.handleConnectFail(); } case ConnectionStatus.FailedToReconnectTwice: { return this.setStage({ ...this.stage, + flowType, hasFailedToReconnectTwice: true, flowStep: ConnectionFlowStep.ReconnectFailedTwice, }); } case ConnectionStatus.FailedToReconnect: { - return this.setFlowStep(ConnectionFlowStep.ReconnectFailed); + return this.setFlowStage({ + flowStep: ConnectionFlowStep.ReconnectFailed, + flowType, + }); } case ConnectionStatus.ConnectionLost: { - return this.setFlowStep(ConnectionFlowStep.ConnectionLost); - } - case ConnectionStatus.Reconnecting: { - return this.setStage(this.getConnectingStage("bluetooth")); + return this.setFlowStage({ + flowStep: ConnectionFlowStep.ConnectionLost, + flowType, + }); } } return; @@ -229,7 +226,6 @@ export class ConnectionStageActions { if (this.stage.connType === "bluetooth") { await this.connectBluetooth(false); } else { - this.setStage(this.getConnectingStage("radio")); await this.connectMicrobits(); } }; @@ -253,34 +249,30 @@ export class ConnectionStageActions { }; private getStagesOrder = () => { - if (this.stage.flowType === ConnectionFlowType.Bluetooth) { - return bluetoothFlow({ - isManualFlashing: - !this.stage.isWebUsbSupported || - this.stage.flowStep === ConnectionFlowStep.ManualFlashingTutorial, - isRestartAgain: this.stage.hasFailedToReconnectTwice, - }); - } - return radioFlow(); + const isRestartAgain = this.stage.hasFailedToReconnectTwice; + const isManualFlashing = + !this.stage.isWebUsbSupported || + this.stage.flowStep === ConnectionFlowStep.ManualFlashingTutorial; + return this.stage.flowType === ConnectionFlowType.Bluetooth + ? bluetoothFlow({ isManualFlashing, isRestartAgain }) + : radioFlow({ isRestartAgain }); + }; + + private setFlowStage = (flowStage: FlowStage) => { + this.setStage({ ...this.stage, ...flowStage }); }; onNextClick = () => { - this.setStage({ - ...this.stage, - ...getNextStage(this.stage, 1, this.getStagesOrder()), - }); + this.setFlowStage(getNextStage(this.stage, 1, this.getStagesOrder())); }; onBackClick = () => { - this.setStage({ - ...this.stage, - ...getNextStage(this.stage, -1, this.getStagesOrder()), - }); + this.setFlowStage(getNextStage(this.stage, -1, this.getStagesOrder())); }; onTryAgain = () => { this.setFlowStep( - this.stage.flowStep === ConnectionFlowStep.TryAgainBluetoothConnect + this.stage.flowStep === ConnectionFlowStep.TryAgainBluetoothSelectMicrobit ? ConnectionFlowStep.EnterBluetoothPattern : ConnectionFlowStep.ConnectCable ); @@ -325,9 +317,11 @@ const bluetoothFlow = ({ }, ]; -const radioFlow = () => [ +const radioFlow = ({ isRestartAgain }: { isRestartAgain: boolean }) => [ { - flowStep: ConnectionFlowStep.Start, + flowStep: isRestartAgain + ? ConnectionFlowStep.ReconnectFailedTwice + : ConnectionFlowStep.Start, flowType: ConnectionFlowType.RadioRemote, }, { diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 644521ce9..802203f33 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -14,7 +14,7 @@ import { useStorage } from "./hooks/use-storage"; import { ConnectionStatus, useConnectStatus, - useSetConnectStatus, + useConnectStatusUpdater, } from "./connect-status-hooks"; export enum ConnectionFlowType { @@ -45,8 +45,9 @@ export enum ConnectionFlowStep { // Failure stages TryAgainReplugMicrobit = "TryAgainReplugMicrobit", TryAgainCloseTabs = "TryAgainCloseTabs", - TryAgainSelectMicrobit = "TryAgainSelectMicrobit", - TryAgainBluetoothConnect = "TryAgainBluetoothConnect", + TryAgainWebUsbSelectMicrobit = "TryAgainWebUsbSelectMicrobit", + TryAgainBluetoothSelectMicrobit = "TryAgainBluetoothSelectMicrobit", + ConnectFailed = "ConnectFailed", BadFirmware = "BadFirmware", MicrobitUnsupported = "MicrobitUnsupported", WebUsbBluetoothUnsupported = "WebUsbBluetoothUnsupported", @@ -86,12 +87,18 @@ interface ConnectionStageProviderProps { children: ReactNode; } +interface StoredConnectionConfig { + bluetoothMicrobitName?: string; + radioRemoteDeviceId?: number; +} + const getInitialConnectionStageValue = ( - microbitName: string | undefined + config: StoredConnectionConfig ): ConnectionStage => ({ flowStep: ConnectionFlowStep.None, flowType: ConnectionFlowType.Bluetooth, - bluetoothMicrobitName: microbitName, + bluetoothMicrobitName: config.bluetoothMicrobitName, + radioRemoteDeviceId: config.radioRemoteDeviceId, connType: "bluetooth", isWebBluetoothSupported: true, isWebUsbSupported: true, @@ -101,18 +108,23 @@ const getInitialConnectionStageValue = ( export const ConnectionStageProvider = ({ children, }: ConnectionStageProviderProps) => { - const [val, setMicrobitName] = useStorage<{ - value?: string; - }>("local", "microbitName", { value: undefined }); + const [config, setConfig] = useStorage( + "local", + "connectionConfig", + { bluetoothMicrobitName: undefined, radioRemoteDeviceId: undefined } + ); const [connectionStage, setConnStage] = useState( - getInitialConnectionStageValue(val.value) + getInitialConnectionStageValue(config) ); const setConnectionStage = useCallback( (connStage: ConnectionStage) => { - setMicrobitName({ value: connStage.bluetoothMicrobitName }); + setConfig({ + bluetoothMicrobitName: connStage.bluetoothMicrobitName, + radioRemoteDeviceId: connStage.radioRemoteDeviceId, + }); setConnStage(connStage); }, - [setMicrobitName] + [setConfig] ); return ( @@ -125,6 +137,7 @@ export const ConnectionStageProvider = ({ }; export const useConnectionStage = (): { + status: ConnectionStatus; stage: ConnectionStage; actions: ConnectionStageActions; isConnected: boolean; @@ -137,7 +150,7 @@ export const useConnectionStage = (): { const [stage, setStage] = connectionStageContextValue; const navigate = useNavigate(); const connectActions = useConnectActions(); - const setStatus = useSetConnectStatus(); + const [, setStatus] = useConnectStatus(); const actions = useMemo(() => { return new ConnectionStageActions( @@ -149,10 +162,14 @@ export const useConnectionStage = (): { ); }, [connectActions, navigate, stage, setStage, setStatus]); - const status = useConnectStatus(actions.handleConnectionStatus); + const status = useConnectStatusUpdater( + stage.connType, + actions.handleConnectionStatus + ); const isConnected = status === ConnectionStatus.Connected; return { + status, stage, actions, isConnected, diff --git a/src/get-next-connection-state.test.ts b/src/get-next-connection-state.test.ts new file mode 100644 index 000000000..f37f4e7ad --- /dev/null +++ b/src/get-next-connection-state.test.ts @@ -0,0 +1,533 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { ConnectionStatus } from "./connect-status-hooks"; +import { + GetNextConnectionStateInput, + NextConnectionState, + getNextConnectionState, +} from "./get-next-connection-state"; +import { ConnectionStatus as DeviceConnectionStatus } from "@microbit/microbit-connection"; +import { ConnectionFlowType } from "./connection-stage-hooks"; + +type Input = Pick< + GetNextConnectionStateInput, + "currConnType" | "currStatus" | "deviceStatus" | "prevDeviceStatus" | "type" +>; + +const testGetNextConnectionState = ({ + input, + initialHasAttemptedReconnect, + initialOnFirstConnectAttempt, + expectedNextConnectionState, + expectedHasAttemptedReconnect, + expectedOnFirstConnectAttempt, +}: { + input: Input; + initialHasAttemptedReconnect: boolean; + expectedNextConnectionState: NextConnectionState; + expectedHasAttemptedReconnect: boolean; + initialOnFirstConnectAttempt: boolean; + expectedOnFirstConnectAttempt: boolean; +}) => { + let hasAttempedReconnect = initialHasAttemptedReconnect; + let onFirstConnectAttempt = initialOnFirstConnectAttempt; + const result = getNextConnectionState({ + ...input, + hasAttempedReconnect, + onFirstConnectAttempt, + setOnFirstConnectAttempt: (val: boolean) => { + onFirstConnectAttempt = val; + }, + setHasAttemptedReconnect: (val: boolean) => { + hasAttempedReconnect = val; + }, + }); + expect(result).toEqual(expectedNextConnectionState); + expect(hasAttempedReconnect).toEqual(expectedHasAttemptedReconnect); + expect(onFirstConnectAttempt).toEqual(expectedOnFirstConnectAttempt); +}; + +describe("getNextConnectionState for radio connection", () => { + test("radio usb flashing", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.NotConnected, + deviceStatus: DeviceConnectionStatus.CONNECTING, + prevDeviceStatus: DeviceConnectionStatus.NO_AUTHORIZED_DEVICE, + type: "usb", + }, + initialOnFirstConnectAttempt: true, + expectedOnFirstConnectAttempt: true, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: undefined, + }); + }); + test("radio connecting", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.NotConnected, + deviceStatus: DeviceConnectionStatus.CONNECTING, + prevDeviceStatus: DeviceConnectionStatus.NO_AUTHORIZED_DEVICE, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: true, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.Connecting, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio connected for the first time", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.NotConnected, + deviceStatus: DeviceConnectionStatus.CONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: true, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.Connected, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio connected subsequent times", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.Disconnected, + deviceStatus: DeviceConnectionStatus.CONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: true, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.Connected, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio disconnect", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.Disconnected, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTED, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.Disconnected, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio connect fail", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.Connecting, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: true, + expectedOnFirstConnectAttempt: true, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToConnect, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio reconnecting explicitly", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.Disconnected, + deviceStatus: DeviceConnectionStatus.CONNECTING, + prevDeviceStatus: DeviceConnectionStatus.DISCONNECTED, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.ReconnectingExplicitly, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio reconnecting automatically", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.Connected, + deviceStatus: DeviceConnectionStatus.RECONNECTING, + prevDeviceStatus: DeviceConnectionStatus.CONNECTED, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.ReconnectingAutomatically, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio bridge device connection lost by unplugging device", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.Connected, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.NO_AUTHORIZED_DEVICE, + type: "usb", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.ConnectionLost, + flowType: ConnectionFlowType.RadioBridge, + }, + }); + }); + test("radio bridge device reconnect fail", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.Connecting, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "usb", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToReconnect, + flowType: ConnectionFlowType.RadioBridge, + }, + }); + }); + test("radio bridge device reconnect fail twice from connection loss", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.Connected, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.DISCONNECTED, + type: "usb", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: true, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToReconnectTwice, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio remote connection lost by unplugging device", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.ReconnectingAutomatically, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.RECONNECTING, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.ConnectionLost, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio remote reconnect fail", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.ReconnectingExplicitly, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToReconnect, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); + test("radio remote reconnect fail twice", () => { + testGetNextConnectionState({ + input: { + currConnType: "radio", + currStatus: ConnectionStatus.ReconnectingExplicitly, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "radioRemote", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: true, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToReconnectTwice, + flowType: ConnectionFlowType.RadioRemote, + }, + }); + }); +}); + +describe("getNextConnectionState for bluetooth connection", () => { + test("bluetooth connecting", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.NotConnected, + deviceStatus: DeviceConnectionStatus.CONNECTING, + prevDeviceStatus: DeviceConnectionStatus.NO_AUTHORIZED_DEVICE, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: true, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.Connecting, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth connected for the first time", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.NotConnected, + deviceStatus: DeviceConnectionStatus.CONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: true, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.Connected, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth connected subsequent times", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.Disconnected, + deviceStatus: DeviceConnectionStatus.CONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.Connected, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth disconnect", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.Disconnected, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTED, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.Disconnected, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth did not select device", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.NotConnected, + deviceStatus: DeviceConnectionStatus.NO_AUTHORIZED_DEVICE, + prevDeviceStatus: DeviceConnectionStatus.NO_AUTHORIZED_DEVICE, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToSelectBluetoothDevice, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth connect fail", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.Connecting, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: true, + expectedOnFirstConnectAttempt: true, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToConnect, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth reconnecting explicitly", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.Disconnected, + deviceStatus: DeviceConnectionStatus.CONNECTING, + prevDeviceStatus: DeviceConnectionStatus.DISCONNECTED, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.ReconnectingExplicitly, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth reconnecting automatically", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.Connected, + deviceStatus: DeviceConnectionStatus.RECONNECTING, + prevDeviceStatus: DeviceConnectionStatus.CONNECTED, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: false, + expectedNextConnectionState: { + status: ConnectionStatus.ReconnectingAutomatically, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth connection lost by unplugging device", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.ReconnectingAutomatically, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.RECONNECTING, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.ConnectionLost, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth reconnect fail", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.ReconnectingExplicitly, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: false, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToReconnect, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); + test("bluetooth reconnect fail twice", () => { + testGetNextConnectionState({ + input: { + currConnType: "bluetooth", + currStatus: ConnectionStatus.ReconnectingExplicitly, + deviceStatus: DeviceConnectionStatus.DISCONNECTED, + prevDeviceStatus: DeviceConnectionStatus.CONNECTING, + type: "bluetooth", + }, + initialOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: false, + initialHasAttemptedReconnect: true, + expectedHasAttemptedReconnect: true, + expectedNextConnectionState: { + status: ConnectionStatus.FailedToReconnectTwice, + flowType: ConnectionFlowType.Bluetooth, + }, + }); + }); +}); diff --git a/src/get-next-connection-state.ts b/src/get-next-connection-state.ts new file mode 100644 index 000000000..0bd6bd903 --- /dev/null +++ b/src/get-next-connection-state.ts @@ -0,0 +1,163 @@ +import { StatusListenerType } from "./connect-actions"; +import { ConnectionStatus } from "./connect-status-hooks"; +import { ConnectionFlowType, ConnectionType } from "./connection-stage-hooks"; +import { ConnectionStatus as DeviceConnectionStatus } from "@microbit/microbit-connection"; + +export interface GetNextConnectionStateInput { + currConnType: ConnectionType; + currStatus: ConnectionStatus; + deviceStatus: DeviceConnectionStatus; + prevDeviceStatus: DeviceConnectionStatus | null; + type: StatusListenerType; + hasAttempedReconnect: boolean; + setHasAttemptedReconnect: (val: boolean) => void; + onFirstConnectAttempt: boolean; + setOnFirstConnectAttempt: (val: boolean) => void; +} + +export type NextConnectionState = + | { status: ConnectionStatus; flowType: ConnectionFlowType } + | undefined; + +export const getNextConnectionState = ({ + currConnType, + currStatus, + deviceStatus, + prevDeviceStatus, + type, + hasAttempedReconnect, + setHasAttemptedReconnect, + onFirstConnectAttempt, + setOnFirstConnectAttempt, +}: GetNextConnectionStateInput): NextConnectionState => { + const flowType = + type === "usb" + ? ConnectionFlowType.RadioBridge + : type === "radioRemote" + ? ConnectionFlowType.RadioRemote + : ConnectionFlowType.Bluetooth; + + // We use usb status to infer the radio bridge device status for handling error. + if (type === "usb") { + if ( + // Ignore USB status updates when radio connection is not established, + // is disconnected, or is reconnecting. The connection gets intentionally + // disconnected before reconnecting. + currConnType !== "radio" || + onFirstConnectAttempt || + deviceStatus !== DeviceConnectionStatus.DISCONNECTED || + currStatus === ConnectionStatus.Disconnected || + // Ignore usb status when reconnecting. + // Serial connection gets intentionally disconnected before reconnect. + currStatus === ConnectionStatus.ReconnectingAutomatically || + currStatus === ConnectionStatus.ReconnectingExplicitly + ) { + return undefined; + } + if ( + // If bridge micro:bit causes radio bridge reconnect to fail twice + hasAttempedReconnect + ) { + return { + status: ConnectionStatus.FailedToReconnectTwice, + flowType: ConnectionFlowType.RadioRemote, + }; + } + // If bridge micro:bit causes radio bridge reconnect to fail + setHasAttemptedReconnect(true); + const status = + currStatus === ConnectionStatus.Connected + ? ConnectionStatus.ConnectionLost + : ConnectionStatus.FailedToReconnect; + return { status, flowType }; + } + + if ( + // If user starts or restarts connection flow. + // Disconnection happens for newly started / restarted + // bluetooth connection flows when clearing device + deviceStatus === DeviceConnectionStatus.DISCONNECTED && + currStatus === ConnectionStatus.NotConnected + ) { + return { status: ConnectionStatus.NotConnected, flowType }; + } + if ( + // If connected. + deviceStatus === DeviceConnectionStatus.CONNECTED + ) { + setOnFirstConnectAttempt(false); + setHasAttemptedReconnect(false); + return { status: ConnectionStatus.Connected, flowType }; + } + if ( + // If user fail to connect. + onFirstConnectAttempt && + currStatus === ConnectionStatus.Connecting && + deviceStatus === DeviceConnectionStatus.DISCONNECTED + ) { + return { status: ConnectionStatus.FailedToConnect, flowType }; + } + if ( + // If user does not select a device for bluetooth connection. + type === "bluetooth" && + deviceStatus === DeviceConnectionStatus.NO_AUTHORIZED_DEVICE && + prevDeviceStatus === DeviceConnectionStatus.NO_AUTHORIZED_DEVICE + ) { + return { status: ConnectionStatus.FailedToSelectBluetoothDevice, flowType }; + } + if ( + // If fails to reconnect twice. + hasAttempedReconnect && + deviceStatus === DeviceConnectionStatus.DISCONNECTED && + prevDeviceStatus === DeviceConnectionStatus.CONNECTING + ) { + return { status: ConnectionStatus.FailedToReconnectTwice, flowType }; + } + if ( + // If fails to reconnect by user. + deviceStatus === DeviceConnectionStatus.DISCONNECTED && + (prevDeviceStatus === DeviceConnectionStatus.CONNECTING || + prevDeviceStatus === DeviceConnectionStatus.NO_AUTHORIZED_DEVICE) + ) { + setHasAttemptedReconnect(true); + return { status: ConnectionStatus.FailedToReconnect, flowType }; + } + if ( + // If fails to reconnect automatically. + deviceStatus === DeviceConnectionStatus.DISCONNECTED && + currStatus === ConnectionStatus.ReconnectingAutomatically + ) { + setHasAttemptedReconnect(true); + return { status: ConnectionStatus.ConnectionLost, flowType }; + } + if ( + // If disconnected by user. + deviceStatus === DeviceConnectionStatus.DISCONNECTED + ) { + return { status: ConnectionStatus.Disconnected, flowType }; + } + const hasStartedOver = + currStatus === ConnectionStatus.NotConnected || + currStatus === ConnectionStatus.FailedToConnect; + if ( + // If connecting. + deviceStatus === DeviceConnectionStatus.CONNECTING && + hasStartedOver + ) { + setOnFirstConnectAttempt(true); + return { status: ConnectionStatus.Connecting, flowType }; + } + if ( + // If reconnecting explicitly by user. + deviceStatus === DeviceConnectionStatus.CONNECTING + ) { + return { status: ConnectionStatus.ReconnectingExplicitly, flowType }; + } + if ( + // If reconnecting automatically. + deviceStatus === DeviceConnectionStatus.RECONNECTING + ) { + return { status: ConnectionStatus.ReconnectingAutomatically, flowType }; + } + return undefined; +}; diff --git a/src/ml-hooks.tsx b/src/ml-hooks.tsx index 83da96f48..a7e9e9731 100644 --- a/src/ml-hooks.tsx +++ b/src/ml-hooks.tsx @@ -24,7 +24,7 @@ export const usePrediction = () => { const buffer = useBufferedData(); const logging = useLogging(); const [status] = useMlStatus(); - const connectStatus = useConnectStatus(); + const [connectStatus] = useConnectStatus(); const connection = useConnectActions(); const [confidences, setConfidences] = useState(); const [gestureData] = useGestureData(); diff --git a/src/pages/AddDataPage.tsx b/src/pages/AddDataPage.tsx index 28fdcb468..af0afd148 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/AddDataPage.tsx @@ -29,13 +29,14 @@ import { import { addDataConfig } from "../pages-config"; import { createStepPageUrl } from "../urls"; import { useConnectionStage } from "../connection-stage-hooks"; +import { ConnectionStatus } from "../connect-status-hooks"; const AddDataPage = () => { const intl = useIntl(); const navigate = useNavigate(); const [gestures] = useGestureData(); const actions = useGestureActions(); - const { isConnected } = useConnectionStage(); + const { isConnected, status } = useConnectionStage(); const hasSufficientData = useMemo( () => hasSufficientDataForTraining(gestures.data), @@ -44,7 +45,7 @@ const AddDataPage = () => { const noStoredData = useMemo(() => { const gestureData = gestures.data; - return ( + return !( gestureData.length !== 0 && gestureData.some((g) => g.recordings.length > 0) ); @@ -65,7 +66,9 @@ const AddDataPage = () => { return ( - {noStoredData && !isConnected ? ( + {noStoredData && + !isConnected && + status !== ConnectionStatus.ReconnectingAutomatically ? ( ) : ( From ab2f68bbafeb60493ca53d7162571565e11f9169 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:39:50 +0100 Subject: [PATCH 107/172] Fix missing space between strings (#286) --- src/components/ManualFlashingDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ManualFlashingDialog.tsx b/src/components/ManualFlashingDialog.tsx index 912edd2d8..0864b7bd4 100644 --- a/src/components/ManualFlashingDialog.tsx +++ b/src/components/ManualFlashingDialog.tsx @@ -67,7 +67,7 @@ const ManualFlashingDialog = ({ ...props }: ManualFlashingDialogProps) => { > - + {" "} Date: Wed, 7 Aug 2024 16:50:26 +0100 Subject: [PATCH 108/172] Handle connection status when user hides browser tab (#285) Add and remove connection status listener depending on whether connection is bluetooth or radio. If user switches tab in radio, set connection status as reconnecting automatically. Other changes include: 1. Reset connection states when user failed twice (so that an error after the twice fail dialog is reset to the initial fail dialogs), 2. Add logging to bluetooth connection instance --- src/connect-actions-hooks.tsx | 5 ++++- src/connect-actions.ts | 24 +++++++++++++-------- src/connect-status-hooks.tsx | 25 +++++++++++++++++++++- src/connection-stage-actions.ts | 6 ++++++ src/get-next-connection-state.test.ts | 30 ++++++++------------------- src/get-next-connection-state.ts | 23 +++++++------------- 6 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/connect-actions-hooks.tsx b/src/connect-actions-hooks.tsx index 53b66f010..cf4eef12f 100644 --- a/src/connect-actions-hooks.tsx +++ b/src/connect-actions-hooks.tsx @@ -28,8 +28,11 @@ interface ConnectProviderProps { export const ConnectProvider = ({ children }: ConnectProviderProps) => { const usb = useMemo(() => new MicrobitWebUSBConnection(), []); - const bluetooth = useMemo(() => new MicrobitWebBluetoothConnection(), []); const logging = useLogging(); + const bluetooth = useMemo( + () => new MicrobitWebBluetoothConnection({ logging }), + [logging] + ); const radioBridge = useMemo( () => new MicrobitRadioBridgeConnection(usb, { diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 04778cf4c..505b739ab 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -8,7 +8,7 @@ import { MicrobitWebUSBConnection, ConnectionStatus as DeviceConnectionStatus, } from "@microbit/microbit-connection"; -import { ConnectionFlowType } from "./connection-stage-hooks"; +import { ConnectionFlowType, ConnectionType } from "./connection-stage-hooks"; import { getFlashDataSource } from "./device/get-hex-file"; import { Logging } from "./logging/logging"; @@ -179,21 +179,27 @@ export class ConnectActions { await this.radioBridge.disconnect(); }; - private prepareStatusListeners = (listener: StatusListener) => { - const listeners: StatusListeners = { + private prepareStatusListeners = ( + listener: StatusListener + ): StatusListeners => { + return { bluetooth: (e) => listener({ status: e.status, type: "bluetooth" }), radioBridge: (e) => listener({ status: e.status, type: "radioRemote" }), usb: (e) => listener({ status: e.status, type: "usb" }), }; - this.statusListeners = listeners; - return listeners; }; - addStatusListener = (listener: StatusListener) => { + addStatusListener = (listener: StatusListener, connType: ConnectionType) => { const listeners = this.prepareStatusListeners(listener); - this.bluetooth.addEventListener("status", listeners.bluetooth); - this.radioBridge.addEventListener("status", listeners.radioBridge); - this.usb.addEventListener("status", listeners.usb); + if (connType === "bluetooth") { + this.bluetooth.addEventListener("status", listeners.bluetooth); + this.statusListeners.bluetooth = listeners.bluetooth; + } else { + this.radioBridge.addEventListener("status", listeners.radioBridge); + this.statusListeners.radioBridge = listeners.radioBridge; + this.usb.addEventListener("status", listeners.usb); + this.statusListeners.usb = listeners.usb; + } }; removeStatusListener = () => { diff --git a/src/connect-status-hooks.tsx b/src/connect-status-hooks.tsx index 7702ac31d..24c3d42e0 100644 --- a/src/connect-status-hooks.tsx +++ b/src/connect-status-hooks.tsx @@ -105,6 +105,7 @@ export const useConnectStatusUpdater = ( ): ConnectionStatus => { const [connectionStatus, setConnectionStatus] = useConnectStatus(); const connectActions = useConnectActions(); + const isBrowserTabVisible = useBrowserTabVisibility(); const prevDeviceStatus = useRef(null); const [hasAttempedReconnect, setHasAttemptedReconnect] = useState(false); @@ -112,6 +113,11 @@ export const useConnectStatusUpdater = ( useState(true); useEffect(() => { + if (!isBrowserTabVisible && currConnType === "radio") { + // Show reconnecting when user hides browser tab for radio bridge connection + setConnectionStatus(ConnectionStatus.ReconnectingAutomatically); + return; + } const listener: StatusListener = ({ status: deviceStatus, type }) => { const nextState = getNextConnectionState({ currConnType, @@ -130,7 +136,9 @@ export const useConnectStatusUpdater = ( setConnectionStatus(nextState.status); } }; - connectActions.addStatusListener(listener); + // Only add relevant connection type status listener + connectActions.removeStatusListener(); + connectActions.addStatusListener(listener, currConnType); return () => { connectActions.removeStatusListener(); }; @@ -140,9 +148,24 @@ export const useConnectStatusUpdater = ( currConnType, handleStatus, hasAttempedReconnect, + isBrowserTabVisible, onFirstConnectAttempt, setConnectionStatus, ]); return connectionStatus; }; + +const useBrowserTabVisibility = (): boolean => { + const [visible, setVisible] = useState(true); + + useEffect(() => { + const listener = () => setVisible(!document.hidden); + document.addEventListener("visibilitychange", listener, false); + return () => { + document.removeEventListener("visibilitychange", listener); + }; + }, []); + + return visible; +}; diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 7f2afe0a3..f421940b1 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -179,6 +179,7 @@ export class ConnectionStageActions { }; disconnect = async () => { + this.setStatus(ConnectionStatus.Disconnected); await this.actions.disconnect(); }; @@ -218,11 +219,16 @@ export class ConnectionStageActions { flowType, }); } + case ConnectionStatus.ReconnectingAutomatically: { + // Don't show dialogs when reconnecting automatically + return this.setFlowStep(ConnectionFlowStep.None); + } } return; }; reconnect = async () => { + this.setStatus(ConnectionStatus.ReconnectingExplicitly); if (this.stage.connType === "bluetooth") { await this.connectBluetooth(false); } else { diff --git a/src/get-next-connection-state.test.ts b/src/get-next-connection-state.test.ts index f37f4e7ad..3c32056ee 100644 --- a/src/get-next-connection-state.test.ts +++ b/src/get-next-connection-state.test.ts @@ -113,7 +113,7 @@ describe("getNextConnectionState for radio connection", () => { testGetNextConnectionState({ input: { currConnType: "radio", - currStatus: ConnectionStatus.Disconnected, + currStatus: ConnectionStatus.Connecting, deviceStatus: DeviceConnectionStatus.CONNECTED, prevDeviceStatus: DeviceConnectionStatus.CONNECTING, type: "radioRemote", @@ -141,10 +141,7 @@ describe("getNextConnectionState for radio connection", () => { expectedOnFirstConnectAttempt: false, initialHasAttemptedReconnect: false, expectedHasAttemptedReconnect: false, - expectedNextConnectionState: { - status: ConnectionStatus.Disconnected, - flowType: ConnectionFlowType.RadioRemote, - }, + expectedNextConnectionState: undefined, }); }); test("radio connect fail", () => { @@ -179,10 +176,7 @@ describe("getNextConnectionState for radio connection", () => { expectedOnFirstConnectAttempt: false, initialHasAttemptedReconnect: false, expectedHasAttemptedReconnect: false, - expectedNextConnectionState: { - status: ConnectionStatus.ReconnectingExplicitly, - flowType: ConnectionFlowType.RadioRemote, - }, + expectedNextConnectionState: undefined, }); }); test("radio reconnecting automatically", () => { @@ -254,7 +248,7 @@ describe("getNextConnectionState for radio connection", () => { initialOnFirstConnectAttempt: false, expectedOnFirstConnectAttempt: false, initialHasAttemptedReconnect: true, - expectedHasAttemptedReconnect: true, + expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnectTwice, flowType: ConnectionFlowType.RadioRemote, @@ -311,7 +305,7 @@ describe("getNextConnectionState for radio connection", () => { initialOnFirstConnectAttempt: false, expectedOnFirstConnectAttempt: false, initialHasAttemptedReconnect: true, - expectedHasAttemptedReconnect: true, + expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnectTwice, flowType: ConnectionFlowType.RadioRemote, @@ -363,7 +357,7 @@ describe("getNextConnectionState for bluetooth connection", () => { testGetNextConnectionState({ input: { currConnType: "bluetooth", - currStatus: ConnectionStatus.Disconnected, + currStatus: ConnectionStatus.Connecting, deviceStatus: DeviceConnectionStatus.CONNECTED, prevDeviceStatus: DeviceConnectionStatus.CONNECTING, type: "bluetooth", @@ -391,10 +385,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedOnFirstConnectAttempt: false, initialHasAttemptedReconnect: false, expectedHasAttemptedReconnect: false, - expectedNextConnectionState: { - status: ConnectionStatus.Disconnected, - flowType: ConnectionFlowType.Bluetooth, - }, + expectedNextConnectionState: undefined, }); }); test("bluetooth did not select device", () => { @@ -448,10 +439,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedOnFirstConnectAttempt: false, initialHasAttemptedReconnect: false, expectedHasAttemptedReconnect: false, - expectedNextConnectionState: { - status: ConnectionStatus.ReconnectingExplicitly, - flowType: ConnectionFlowType.Bluetooth, - }, + expectedNextConnectionState: undefined, }); }); test("bluetooth reconnecting automatically", () => { @@ -523,7 +511,7 @@ describe("getNextConnectionState for bluetooth connection", () => { initialOnFirstConnectAttempt: false, expectedOnFirstConnectAttempt: false, initialHasAttemptedReconnect: true, - expectedHasAttemptedReconnect: true, + expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnectTwice, flowType: ConnectionFlowType.Bluetooth, diff --git a/src/get-next-connection-state.ts b/src/get-next-connection-state.ts index 0bd6bd903..6af470da5 100644 --- a/src/get-next-connection-state.ts +++ b/src/get-next-connection-state.ts @@ -30,6 +30,11 @@ export const getNextConnectionState = ({ onFirstConnectAttempt, setOnFirstConnectAttempt, }: GetNextConnectionStateInput): NextConnectionState => { + if (currStatus === ConnectionStatus.Disconnected) { + // Do not update connection status when user explicitly disconnected connection + // until user reconnects explicitly + return undefined; + } const flowType = type === "usb" ? ConnectionFlowType.RadioBridge @@ -46,7 +51,6 @@ export const getNextConnectionState = ({ currConnType !== "radio" || onFirstConnectAttempt || deviceStatus !== DeviceConnectionStatus.DISCONNECTED || - currStatus === ConnectionStatus.Disconnected || // Ignore usb status when reconnecting. // Serial connection gets intentionally disconnected before reconnect. currStatus === ConnectionStatus.ReconnectingAutomatically || @@ -58,6 +62,7 @@ export const getNextConnectionState = ({ // If bridge micro:bit causes radio bridge reconnect to fail twice hasAttempedReconnect ) { + setHasAttemptedReconnect(false); return { status: ConnectionStatus.FailedToReconnectTwice, flowType: ConnectionFlowType.RadioRemote, @@ -108,9 +113,9 @@ export const getNextConnectionState = ({ if ( // If fails to reconnect twice. hasAttempedReconnect && - deviceStatus === DeviceConnectionStatus.DISCONNECTED && - prevDeviceStatus === DeviceConnectionStatus.CONNECTING + deviceStatus === DeviceConnectionStatus.DISCONNECTED ) { + setHasAttemptedReconnect(false); return { status: ConnectionStatus.FailedToReconnectTwice, flowType }; } if ( @@ -130,12 +135,6 @@ export const getNextConnectionState = ({ setHasAttemptedReconnect(true); return { status: ConnectionStatus.ConnectionLost, flowType }; } - if ( - // If disconnected by user. - deviceStatus === DeviceConnectionStatus.DISCONNECTED - ) { - return { status: ConnectionStatus.Disconnected, flowType }; - } const hasStartedOver = currStatus === ConnectionStatus.NotConnected || currStatus === ConnectionStatus.FailedToConnect; @@ -147,12 +146,6 @@ export const getNextConnectionState = ({ setOnFirstConnectAttempt(true); return { status: ConnectionStatus.Connecting, flowType }; } - if ( - // If reconnecting explicitly by user. - deviceStatus === DeviceConnectionStatus.CONNECTING - ) { - return { status: ConnectionStatus.ReconnectingExplicitly, flowType }; - } if ( // If reconnecting automatically. deviceStatus === DeviceConnectionStatus.RECONNECTING From 753742292d915e25b14c5844c8896e5d69093475 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:20:49 +0100 Subject: [PATCH 109/172] Fix connect btn in live graph panel when user has failed to select bluetooth device (#287) --- src/components/LiveGraphPanel.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 2860b8dc7..525a92174 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -17,7 +17,8 @@ const LiveGraphPanel = () => { return status === ConnectionStatus.NotConnected || status === ConnectionStatus.Connecting || status === ConnectionStatus.FailedToConnect || - status === ConnectionStatus.FailedToReconnectTwice + status === ConnectionStatus.FailedToReconnectTwice || + status === ConnectionStatus.FailedToSelectBluetoothDevice ? { textId: "footer.connectButton", onClick: actions.start, From eb99a10d69bee3fe125d23b95feafc973d45a86d Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:49:22 +0100 Subject: [PATCH 110/172] Fix homepage long resource cards in Safari (#289) --- src/components/ResourceCard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ResourceCard.tsx b/src/components/ResourceCard.tsx index 6fca1c820..a3caec13e 100644 --- a/src/components/ResourceCard.tsx +++ b/src/components/ResourceCard.tsx @@ -26,7 +26,6 @@ const ResourceCard = ({ id, imgSrc }: ResourceCardProps) => { borderRadius="10px" overflow="hidden" maxW="22rem" - h="100%" boxShadow="0 0 12px 0 rgba(0,0,0,.1)" > From da2525429b3434b9fa3a1bdf0dcddd06caa86247 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:33:07 +0100 Subject: [PATCH 111/172] Handle unsupported WebUsb/Bluetooth connection flows (#288) * Fix manual flashing dialog download handling --- src/components/ConnectCableDialog.tsx | 88 +++++++++++++----------- src/components/ConnectionFlowDialogs.tsx | 19 ++--- src/components/ManualFlashingDialog.tsx | 10 ++- src/connect-actions-hooks.tsx | 32 ++++----- src/connect-actions.ts | 9 ++- src/connection-stage-actions.ts | 10 +-- src/connection-stage-hooks.tsx | 21 ++++-- 7 files changed, 104 insertions(+), 85 deletions(-) diff --git a/src/components/ConnectCableDialog.tsx b/src/components/ConnectCableDialog.tsx index 3cee477a1..2a6909d81 100644 --- a/src/components/ConnectCableDialog.tsx +++ b/src/components/ConnectCableDialog.tsx @@ -7,71 +7,77 @@ import ConnectContainerDialog, { import { ConnectionFlowType } from "../connection-stage-hooks"; import { stage } from "../environment"; -enum LinkType { - Switch, - Skip, - None, -} +type LinkType = "switch" | "skip" | "none"; interface Config { headingId: string; subtitleId: string; linkTextId?: string; - onLink: LinkType; + linkType?: LinkType; } -const configs: Record = { - [ConnectionFlowType.Bluetooth]: { - headingId: "connectMB.connectCable.heading", - subtitleId: "connectMB.connectCable.subtitle", - linkTextId: "connectMB.connectCable.skip", - onLink: LinkType.Skip, - }, - [ConnectionFlowType.RadioRemote]: { - headingId: "connectMB.connectCableMB1.heading", - subtitleId: "connectMB.connectCableMB1.subtitle", - ...(stage === "local" - ? { - linkTextId: "connectMB.connectCable.skip", - onLink: LinkType.Skip, - } - : { - onLink: LinkType.None, - }), - }, - [ConnectionFlowType.RadioBridge]: { - headingId: "connectMB.connectCableMB2.heading", - subtitleId: "connectMB.connectCableMB2.subtitle", - linkTextId: "connectMB.radioStart.switchBluetooth", - onLink: LinkType.Switch, - }, +export const getConnectionCableDialogConfig = ( + flowType: ConnectionFlowType, + isWebBluetoothSupported: boolean +): Config => { + switch (flowType) { + case ConnectionFlowType.Bluetooth: + return { + headingId: "connectMB.connectCable.heading", + subtitleId: "connectMB.connectCable.subtitle", + linkTextId: "connectMB.connectCable.skip", + linkType: "skip", + }; + case ConnectionFlowType.RadioRemote: + return { + headingId: "connectMB.connectCableMB1.heading", + subtitleId: "connectMB.connectCableMB1.subtitle", + ...(stage === "local" + ? { + linkTextId: "connectMB.connectCable.skip", + linkType: "skip", + } + : {}), + }; + case ConnectionFlowType.RadioBridge: + return { + headingId: "connectMB.connectCableMB2.heading", + subtitleId: "connectMB.connectCableMB2.subtitle", + ...(isWebBluetoothSupported + ? { + linkTextId: "connectMB.radioStart.switchBluetooth", + linkType: "switch", + } + : {}), + }; + } }; export interface ConnectCableDialogProps extends Omit { - type: ConnectionFlowType; + config: Config; onSkip: () => void; onSwitch: () => void; } const ConnectCableDialog = ({ - type, + config, onSkip, onSwitch, ...props }: ConnectCableDialogProps) => { - const { subtitleId, onLink, linkTextId, headingId } = configs[type]; - const linkConfig = { - [LinkType.None]: undefined, - [LinkType.Skip]: onSkip, - [LinkType.Switch]: onSwitch, - }; + const { subtitleId, linkType, linkTextId, headingId } = config; return ( + linkTextId && + linkType && ( + ) diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index 6004a4f02..4d5575801 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -8,7 +8,9 @@ import { } from "../connection-stage-hooks"; import BrokenFirmwareDialog from "./BrokenFirmwareDialog"; import ConnectBatteryDialog from "./ConnectBatteryDialog"; -import ConnectCableDialog from "./ConnectCableDialog"; +import ConnectCableDialog, { + getConnectionCableDialogConfig, +} from "./ConnectCableDialog"; import DownloadingDialog from "./DownloadingDialog"; import EnterBluetoothPatternDialog from "./EnterBluetoothPatternDialog"; import LoadingDialog from "./LoadingDialog"; @@ -104,15 +106,16 @@ const ConnectionDialogs = () => { ); } case ConnectionFlowStep.ConnectCable: { - const commonProps = { - onBackClick: actions.onBackClick, - onNextClick: actions.onNextClick, - ...dialogCommonProps, - }; + const config = getConnectionCableDialogConfig( + stage.flowType, + stage.isWebBluetoothSupported + ); return ( diff --git a/src/components/ManualFlashingDialog.tsx b/src/components/ManualFlashingDialog.tsx index 0864b7bd4..f4a8d9729 100644 --- a/src/components/ManualFlashingDialog.tsx +++ b/src/components/ManualFlashingDialog.tsx @@ -33,10 +33,10 @@ const getImageProps = (os: string): ImageProps => { export interface ManualFlashingDialogProps extends Omit {} -const download = (data: string, filename: string) => { +const download = (fileUrl: string, filename: string) => { const a = document.createElement("a"); a.download = filename; - a.href = URL.createObjectURL(new Blob([data], { type: "text/csv" })); + a.href = fileUrl; a.click(); a.remove(); }; @@ -50,10 +50,8 @@ const ManualFlashingDialog = ({ ...props }: ManualFlashingDialogProps) => { const imageProps = getImageProps(osName); const handleDownload = useCallback(() => { - download( - getHexFileUrl("universal", ConnectionFlowType.Bluetooth)!, - "machine-learning-tool-program.hex" - ); + const hexFileUrl = getHexFileUrl("universal", ConnectionFlowType.Bluetooth); + download(hexFileUrl!, "machine-learning-tool-program.hex"); }, []); useEffect(() => { diff --git a/src/connect-actions-hooks.tsx b/src/connect-actions-hooks.tsx index cf4eef12f..dfd616106 100644 --- a/src/connect-actions-hooks.tsx +++ b/src/connect-actions-hooks.tsx @@ -10,6 +10,7 @@ import { useEffect, useMemo, useRef, + useState, } from "react"; import { ConnectActions } from "./connect-actions"; import { useLogging } from "./logging/logging-hooks"; @@ -27,36 +28,31 @@ interface ConnectProviderProps { } export const ConnectProvider = ({ children }: ConnectProviderProps) => { - const usb = useMemo(() => new MicrobitWebUSBConnection(), []); - const logging = useLogging(); - const bluetooth = useMemo( - () => new MicrobitWebBluetoothConnection({ logging }), - [logging] - ); - const radioBridge = useMemo( - () => - new MicrobitRadioBridgeConnection(usb, { - logging, - }), - [logging, usb] - ); - const isInitialized = useRef(false); + const usb = useRef(new MicrobitWebUSBConnection()).current; + const logging = useRef(useLogging()).current; + const bluetooth = useRef( + new MicrobitWebBluetoothConnection({ logging }) + ).current; + const radioBridge = useRef( + new MicrobitRadioBridgeConnection(usb, { logging }) + ).current; + const [isInitialized, setIsInitialized] = useState(false); useEffect(() => { const initialize = async () => { await usb.initialize(); await bluetooth.initialize(); await radioBridge.initialize(); + setIsInitialized(true); }; - if (!isInitialized.current) { + if (!isInitialized) { void initialize(); - isInitialized.current = true; } - }, [bluetooth, radioBridge, usb]); + }, [bluetooth, isInitialized, radioBridge, usb]); return ( - {children} + {isInitialized ? children : <>} ); }; diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 505b739ab..58f4595d9 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -51,12 +51,19 @@ export class ConnectActions { radioBridge: () => {}, usb: () => {}, }; + isWebBluetoothSupported: boolean; + isWebUsbSupported: boolean; constructor( private logging: Logging, private usb: MicrobitWebUSBConnection, private bluetooth: MicrobitWebBluetoothConnection, private radioBridge: MicrobitRadioBridgeConnection - ) {} + ) { + this.isWebBluetoothSupported = + bluetooth.status !== DeviceConnectionStatus.NOT_SUPPORTED; + this.isWebUsbSupported = + usb.status !== DeviceConnectionStatus.NOT_SUPPORTED; + } requestUSBConnectionAndFlash = async ( hexType: ConnectionFlowType, diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index f421940b1..ca335ccbd 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -27,15 +27,15 @@ export class ConnectionStageActions { start = () => { this.setStatus(ConnectionStatus.NotConnected); + const { isWebBluetoothSupported, isWebUsbSupported } = this.stage; this.setStage({ ...this.stage, hasFailedToReconnectTwice: false, - flowType: - this.stage.flowType === ConnectionFlowType.RadioBridge - ? ConnectionFlowType.RadioRemote - : ConnectionFlowType.Bluetooth, + flowType: !isWebBluetoothSupported + ? ConnectionFlowType.RadioRemote + : ConnectionFlowType.Bluetooth, flowStep: - !this.stage.isWebBluetoothSupported && !this.stage.isWebUsbSupported + !isWebBluetoothSupported && !isWebUsbSupported ? ConnectionFlowStep.WebUsbBluetoothUnsupported : ConnectionFlowStep.Start, }); diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 802203f33..64e927064 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -93,15 +93,19 @@ interface StoredConnectionConfig { } const getInitialConnectionStageValue = ( - config: StoredConnectionConfig + config: StoredConnectionConfig, + isWebBluetoothSupported: boolean, + isWebUsbSupported: boolean ): ConnectionStage => ({ flowStep: ConnectionFlowStep.None, - flowType: ConnectionFlowType.Bluetooth, + flowType: isWebBluetoothSupported + ? ConnectionFlowType.Bluetooth + : ConnectionFlowType.RadioRemote, bluetoothMicrobitName: config.bluetoothMicrobitName, radioRemoteDeviceId: config.radioRemoteDeviceId, - connType: "bluetooth", - isWebBluetoothSupported: true, - isWebUsbSupported: true, + connType: isWebBluetoothSupported ? "bluetooth" : "radio", + isWebBluetoothSupported, + isWebUsbSupported, hasFailedToReconnectTwice: false, }); @@ -113,8 +117,13 @@ export const ConnectionStageProvider = ({ "connectionConfig", { bluetoothMicrobitName: undefined, radioRemoteDeviceId: undefined } ); + const connectActions = useConnectActions(); const [connectionStage, setConnStage] = useState( - getInitialConnectionStageValue(config) + getInitialConnectionStageValue( + config, + connectActions.isWebBluetoothSupported, + connectActions.isWebUsbSupported + ) ); const setConnectionStage = useCallback( (connStage: ConnectionStage) => { From 37c4b2de68d12dd6b4c14f4115962ec3d19bac32 Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:23:25 +0100 Subject: [PATCH 112/172] Add unique icons for each ML class (#291) --------- Co-authored-by: Grace <145345672+microbit-grace@users.noreply.github.com> --- src/components/AddDataGridRow.tsx | 1 + src/components/AddDataGridView.tsx | 2 +- src/components/AddDataGridWalkThrough.tsx | 1 + src/components/CertaintyThresholdGridItem.tsx | 4 +- src/components/GestureNameGridItem.tsx | 89 +++++++++----- src/components/LedIcon.tsx | 109 ++++++++++++++++ src/components/LedIconPicker.tsx | 67 ++++++++++ src/components/LiveGraphPanel.tsx | 30 ++++- src/components/PercentageMeter.tsx | 4 +- src/components/TestModelGridView.tsx | 58 +++------ src/components/UploadDataSamplesMenuItem.tsx | 2 +- src/gestures-hooks.tsx | 116 +++++++++++++----- src/ml-hooks.tsx | 28 ++++- src/ml.test.ts | 15 ++- src/pages/TestModelPage.tsx | 2 +- src/utils/icons.ts | 66 ++++++++++ 16 files changed, 472 insertions(+), 122 deletions(-) create mode 100644 src/components/LedIcon.tsx create mode 100644 src/components/LedIconPicker.tsx create mode 100644 src/utils/icons.ts diff --git a/src/components/AddDataGridRow.tsx b/src/components/AddDataGridRow.tsx index 86b2e68af..07420ac35 100644 --- a/src/components/AddDataGridRow.tsx +++ b/src/components/AddDataGridRow.tsx @@ -37,6 +37,7 @@ const AddDataGridRow = ({ = { - gridTemplateColumns: "200px 1fr", + gridTemplateColumns: "290px 1fr", gap: 3, px: 10, py: 2, diff --git a/src/components/AddDataGridWalkThrough.tsx b/src/components/AddDataGridWalkThrough.tsx index 88a88749d..57b58078c 100644 --- a/src/components/AddDataGridWalkThrough.tsx +++ b/src/components/AddDataGridWalkThrough.tsx @@ -20,6 +20,7 @@ const AddDataGridWalkThrough = ({ diff --git a/src/components/CertaintyThresholdGridItem.tsx b/src/components/CertaintyThresholdGridItem.tsx index 1d20b92b8..e3e9b967a 100644 --- a/src/components/CertaintyThresholdGridItem.tsx +++ b/src/components/CertaintyThresholdGridItem.tsx @@ -10,10 +10,10 @@ import { Text, VStack, } from "@chakra-ui/react"; +import { useCallback, useMemo } from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import PercentageMeter from "./PercentageMeter"; import PercentageDisplay from "./PercentageDisplay"; -import { useCallback, useMemo } from "react"; +import PercentageMeter from "./PercentageMeter"; interface CertaintyThresholdGridItemProps { requiredConfidence?: number; diff --git a/src/components/GestureNameGridItem.tsx b/src/components/GestureNameGridItem.tsx index 16fd61933..b2325815e 100644 --- a/src/components/GestureNameGridItem.tsx +++ b/src/components/GestureNameGridItem.tsx @@ -1,34 +1,41 @@ import { Card, CardBody, - CardHeader, CloseButton, GridItem, + HStack, Input, useToast, } from "@chakra-ui/react"; import { useCallback } from "react"; import { useIntl } from "react-intl"; import { useGestureActions } from "../gestures-hooks"; +import { MakeCodeIcon } from "../utils/icons"; +import LedIcon from "./LedIcon"; +import LedIconPicker from "./LedIconPicker"; interface GestureNameGridItemProps { name: string; + icon: MakeCodeIcon; onCloseClick?: () => void; onSelectRow?: () => void; id: number; selected?: boolean; readOnly: boolean; + isTriggered?: boolean; } const gestureNameMaxLength = 18; const GestureNameGridItem = ({ name, + icon, onCloseClick, onSelectRow, id, selected = false, readOnly = false, + isTriggered = false, }: GestureNameGridItemProps) => { const intl = useIntl(); const toast = useToast(); @@ -58,6 +65,13 @@ const GestureNameGridItem = ({ [actions, id, intl, toast] ); + const handleIconSelected = useCallback( + (icon: MakeCodeIcon) => { + actions.setGestureIcon(id, icon); + }, + [actions, id] + ); + return ( - {!readOnly && ( - - {onCloseClick && ( - + {!readOnly && onCloseClick && ( + - )} - - + )} + + + + + ; + {!readOnly && ( + + )} + + + diff --git a/src/components/LedIcon.tsx b/src/components/LedIcon.tsx new file mode 100644 index 000000000..9036f1e23 --- /dev/null +++ b/src/components/LedIcon.tsx @@ -0,0 +1,109 @@ +import { AspectRatio, Box, HStack, keyframes, VStack } from "@chakra-ui/react"; +import { memo, useCallback } from "react"; +import { icons, LedIconType } from "../utils/icons"; + +interface LedIconProps { + icon: LedIconType; + isTestModelPage?: boolean; + isTriggered?: boolean; + size?: string | number; +} + +const LedIcon = ({ + icon, + isTestModelPage, + isTriggered, + size = 20, +}: LedIconProps) => { + const iconData = icons[icon]; + return ( + + + {Array.from(Array(5)).map((_, idx) => { + const start = idx * 5; + return ( + + ); + })} + + + ); +}; + +const turnOn = keyframes` + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.15); + } + 100% { + transform: scale(1); + } +`; + +const turnOff = keyframes` + 0% { + transform: scale(1); + } + 50% { + transform: scale(0.8); + } + 100% { + transform: scale(1); + } +`; + +interface LedIconRowProps { + data: string; + isTestModelPage: boolean; + isTriggered: boolean; +} + +const LedIconRow = ({ + data, + isTestModelPage, + isTriggered, +}: LedIconRowProps) => { + const turnOnAnimation = `${turnOn} 200ms ease`; + const turnOffAnimation = `${turnOff} 200ms ease`; + const getBgColor = useCallback( + (isOn: boolean) => { + if (!isOn) { + return "gray.200"; + } + if (isTestModelPage && isTriggered) { + return "green.500"; + } + if (isTestModelPage && !isTriggered) { + return "gray.600"; + } + return "brand.500"; + }, + [isTriggered, isTestModelPage] + ); + return ( + + {Array.from(Array(5)).map((_, idx) => ( + + ))} + + ); +}; + +export default memo(LedIcon); diff --git a/src/components/LedIconPicker.tsx b/src/components/LedIconPicker.tsx new file mode 100644 index 000000000..b8464bd51 --- /dev/null +++ b/src/components/LedIconPicker.tsx @@ -0,0 +1,67 @@ +import { + Grid, + IconButton, + Popover, + PopoverArrow, + PopoverBody, + PopoverContent, + PopoverTrigger, +} from "@chakra-ui/react"; +import { memo, useCallback } from "react"; +import { RiArrowDropDownFill } from "react-icons/ri"; +import { MakeCodeIcon, makecodeIcons } from "../utils/icons"; +import LedIcon from "./LedIcon"; + +interface LedIconPicker { + onIconSelected: (icon: MakeCodeIcon) => void; +} + +const LedIconPicker = ({ onIconSelected }: LedIconPicker) => { + const handleClick = useCallback( + (icon: MakeCodeIcon, callback: () => void) => { + onIconSelected(icon); + callback(); + }, + [onIconSelected] + ); + + return ( + + {({ onClose }) => ( + <> + + + + + + + + + + {Object.keys(makecodeIcons).map((icon, idx) => ( + handleClick(icon as MakeCodeIcon, onClose)} + variant="unstyled" + h={20} + w={20} + > + + + ))} + + + + + )} + + ); +}; + +export default memo(LedIconPicker); diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 525a92174..93ab82b3e 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -1,13 +1,20 @@ -import { Button, HStack, Portal, Text } from "@chakra-ui/react"; +import { Box, Button, HStack, Portal, Text } from "@chakra-ui/react"; import { useMemo, useRef } from "react"; import { MdBolt } from "react-icons/md"; import { FormattedMessage } from "react-intl"; +import { ConnectionStatus } from "../connect-status-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; +import { getPredictedGesture, usePrediction } from "../ml-hooks"; import InfoToolTip from "./InfoToolTip"; +import LedIcon from "./LedIcon"; import LiveGraph from "./LiveGraph"; -import { ConnectionStatus } from "../connect-status-hooks"; +import { useGestureData } from "../gestures-hooks"; -const LiveGraphPanel = () => { +interface LiveGraphPanelProps { + isTestModelPage?: boolean; +} + +const LiveGraphPanel = ({ isTestModelPage = false }: LiveGraphPanelProps) => { const { actions, status } = useConnectionStage(); const parentPortalRef = useRef(null); const isReconnecting = @@ -29,6 +36,10 @@ const LiveGraphPanel = () => { }; }, [actions.reconnect, actions.start, status]); + const confidences = usePrediction(); + const [gestures] = useGestureData(); + const predictedGesture = getPredictedGesture(gestures, confidences); + return ( { right={0} px={7} py={4} + w={`calc(100% - ${isTestModelPage ? "160px" : "0"})`} > @@ -77,8 +89,18 @@ const LiveGraphPanel = () => { /> - + + {isTestModelPage && ( + + + + )} ); diff --git a/src/components/PercentageMeter.tsx b/src/components/PercentageMeter.tsx index 4147f78e3..3f05d38f4 100644 --- a/src/components/PercentageMeter.tsx +++ b/src/components/PercentageMeter.tsx @@ -23,7 +23,9 @@ const PercentageMeter = ({ position="relative" > { const intl = useIntl(); const [gestures] = useGestureData(); const { setRequiredConfidence } = useGestureActions(); - const confidences = usePrediction(); - const prediction = applyThresholds(gestures, confidences); + const predictedGesture = getPredictedGesture(gestures, confidences); const predicationLabel = - prediction?.name ?? + predictedGesture?.name ?? intl.formatMessage({ id: "content.model.output.estimatedGesture.none", }); @@ -77,9 +71,9 @@ const TestModelGridView = () => { {predicationLabel} - {prediction && confidences && ( + {predictedGesture && confidences && ( )} @@ -105,17 +99,23 @@ const TestModelGridView = () => { h={0} > {gestures.data.map( - ({ ID, name, requiredConfidence: threshold }, idx) => { + ({ ID, name, icon, requiredConfidence: threshold }, idx) => { return ( - + setRequiredConfidence(ID, val)} currentConfidence={confidences?.[ID]} requiredConfidence={ threshold ?? mlSettings.defaultRequiredConfidence } - isTriggered={prediction?.ID === ID} + isTriggered={predictedGesture?.ID === ID} /> ); @@ -126,30 +126,4 @@ const TestModelGridView = () => { ); }; -const applyThresholds = ( - gestureData: GestureContextState, - confidences: Confidences | undefined -): Gesture | undefined => { - if (!confidences) { - return undefined; - } - - // If more than one meet the threshold pick the highest - const thresholded = gestureData.data - .map((gesture) => ({ - gesture, - thresholdDelta: - confidences[gesture.ID] - - (gesture.requiredConfidence ?? mlSettings.defaultRequiredConfidence), - })) - .sort((left, right) => { - const a = left.thresholdDelta; - const b = right.thresholdDelta; - return a < b ? -1 : a > b ? 1 : 0; - }); - - const prediction = thresholded[thresholded.length - 1]; - return prediction.thresholdDelta >= 0 ? prediction.gesture : undefined; -}; - export default TestModelGridView; diff --git a/src/components/UploadDataSamplesMenuItem.tsx b/src/components/UploadDataSamplesMenuItem.tsx index e708e4a60..3e1dbdd84 100644 --- a/src/components/UploadDataSamplesMenuItem.tsx +++ b/src/components/UploadDataSamplesMenuItem.tsx @@ -38,7 +38,7 @@ const UploadDataSamplesMenuItem = () => { throw new Error("Expected to be called with at least one file"); } const gestureData = await readFileAsText(files[0]); - actions.setGestures(JSON.parse(gestureData) as GestureData[]); + actions.importGestures(JSON.parse(gestureData) as Partial[]); }, [actions] ); diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 6f4743f51..336904525 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -1,7 +1,8 @@ -import { ReactNode, createContext, useContext, useMemo, useState } from "react"; +import { createContext, ReactNode, useContext, useMemo } from "react"; import { useStorage } from "./hooks/use-storage"; import { MlStage, MlStatus, useMlStatus } from "./ml-status-hooks"; import { isArray } from "./utils"; +import { defaultIcons, MakeCodeIcon } from "./utils/icons"; export interface XYZData { x: number[]; y: number[]; @@ -16,6 +17,7 @@ interface RecordingData { export interface Gesture { name: string; ID: number; + icon: MakeCodeIcon; requiredConfidence?: number; } @@ -96,39 +98,15 @@ export const useGestureData = (): GestureContextValue => { return gestureData; }; -const generateNewGesture = (): GestureData => ({ - name: "", - recordings: [], - ID: Date.now(), -}); - -const initialGestureContextState: GestureContextState = { - data: [generateNewGesture()], -}; - export const GesturesProvider = ({ children }: { children: ReactNode }) => { - const [storedState, setStoredState] = useStorage( + const [state, setState] = useStorage( "local", "gestures", - initialGestureContextState, + { data: [] }, isValidStoredGestureData ); - const [state, setState] = useState({ - data: storedState.data, - }); - const setStates = (newState: GestureContextState) => { - setStoredState({ - ...newState, - data: newState.data.map(({ name, recordings, ID }) => ({ - name, - recordings, - ID, - })), - }); - setState(newState); - }; return ( - + {children} ); @@ -151,7 +129,47 @@ class GestureActions { private setGestureState: (gestureData: GestureContextState) => void, private status: MlStatus, private setStatus: (status: MlStatus) => void - ) {} + ) { + // Initialize with at least one gesture for walkthrough. + if (!this.gestureState.data.length) { + this.setGestureState({ data: [this.generateNewGesture(true)] }); + } + } + + private getDefaultIcon = ({ + isFirstGesture, + iconsInUse, + }: { + isFirstGesture?: boolean; + iconsInUse?: MakeCodeIcon[]; + }): MakeCodeIcon => { + if (isFirstGesture) { + return defaultIcons[0]; + } + if (!iconsInUse) { + iconsInUse = this.gestureState.data.map((g) => g.icon); + } + const useableIcons: MakeCodeIcon[] = []; + for (const icon of defaultIcons) { + if (!iconsInUse.includes(icon)) { + useableIcons.push(icon); + } + } + if (!useableIcons.length) { + // Better than throwing an error. + return "Heart"; + } + return useableIcons[0]; + }; + + private generateNewGesture = ( + isFirstGesture: boolean = false + ): GestureData => ({ + name: "", + recordings: [], + ID: Date.now(), + icon: this.getDefaultIcon({ isFirstGesture }), + }); hasGestures = (): boolean => { return ( @@ -161,11 +179,32 @@ class GestureActions { ); }; + importGestures = (gestures: Partial[]) => { + const validGestures: GestureData[] = []; + const importedGestureIcons: MakeCodeIcon[] = gestures + .map((g) => g.icon as MakeCodeIcon) + .filter(Boolean); + gestures.forEach((g) => { + if (g.ID && g.name !== undefined && Array.isArray(g.recordings)) { + if (!g.icon) { + g.icon = this.getDefaultIcon({ + iconsInUse: [ + ...validGestures.map((g) => g.icon), + ...importedGestureIcons, + ], + }); + } + validGestures.push(g as GestureData); + } + }); + this.setGestures(validGestures); + }; + setGestures = (gestures: GestureData[], isRetrainNeeded: boolean = true) => { const data = // Always have at least one gesture for walk through - gestures.length === 0 ? initialGestureContextState.data : gestures; - this.setGestureState({ ...this.gestureState, data }); + gestures.length === 0 ? [this.generateNewGesture(true)] : gestures; + this.setGestureState({ data }); // Update training status const newTrainingStatus = !hasSufficientDataForTraining(data) @@ -179,7 +218,7 @@ class GestureActions { }; addNewGesture = () => { - this.setGestures([...this.gestureState.data, generateNewGesture()]); + this.setGestures([...this.gestureState.data, this.generateNewGesture()]); }; addGestureRecordings = (id: GestureData["ID"], recs: RecordingData[]) => { @@ -200,6 +239,19 @@ class GestureActions { this.setGestures(newGestures, false); }; + setGestureIcon = (id: GestureData["ID"], icon: MakeCodeIcon) => { + const currentIcon = this.gestureState.data.find((g) => g.ID === id)?.icon; + const newGestures = this.gestureState.data.map((g) => { + if (g.ID === id) { + g.icon = icon; + } else if (g.ID !== id && g.icon === icon && currentIcon) { + g.icon = currentIcon; + } + return g; + }); + this.setGestures(newGestures, false); + }; + setRequiredConfidence = (id: GestureData["ID"], value: number) => { const newGestures = this.gestureState.data.map((g) => { return id !== g.ID ? g : { ...g, requiredConfidence: value }; diff --git a/src/ml-hooks.tsx b/src/ml-hooks.tsx index a7e9e9731..32a4da4f7 100644 --- a/src/ml-hooks.tsx +++ b/src/ml-hooks.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react"; import { useBufferedData } from "./buffered-data-hooks"; import { useConnectActions } from "./connect-actions-hooks"; import { ConnectionStatus, useConnectStatus } from "./connect-status-hooks"; -import { useGestureData } from "./gestures-hooks"; +import { Gesture, GestureContextState, useGestureData } from "./gestures-hooks"; import { useLogging } from "./logging/logging-hooks"; import { Confidences, mlSettings, predict } from "./ml"; import { MlActions } from "./ml-actions"; @@ -73,3 +73,29 @@ export const usePrediction = () => { return confidences; }; + +export const getPredictedGesture = ( + gestureData: GestureContextState, + confidences: Confidences | undefined +): Gesture | undefined => { + if (!confidences) { + return undefined; + } + + // If more than one meet the threshold pick the highest + const thresholded = gestureData.data + .map((gesture) => ({ + gesture, + thresholdDelta: + confidences[gesture.ID] - + (gesture.requiredConfidence ?? mlSettings.defaultRequiredConfidence), + })) + .sort((left, right) => { + const a = left.thresholdDelta; + const b = right.thresholdDelta; + return a < b ? -1 : a > b ? 1 : 0; + }); + + const prediction = thresholded[thresholded.length - 1]; + return prediction.thresholdDelta >= 0 ? prediction.gesture : undefined; +}; diff --git a/src/ml.test.ts b/src/ml.test.ts index 5b7e6dcb2..46ae802f7 100644 --- a/src/ml.test.ts +++ b/src/ml.test.ts @@ -15,6 +15,11 @@ import gestureDataBadLabels from "./test-fixtures/gesture-data-bad-labels.json"; import gestureData from "./test-fixtures/gesture-data.json"; import testdataShakeStill from "./test-fixtures/test-data-shake-still.json"; +const fixUpTestData = (data: Partial[]): GestureData[] => { + data.forEach((action) => (action.icon = "Heart")); + return data as GestureData[]; +}; + let trainingResult: TrainingResult; beforeAll(async () => { // No webgl in tests running in node. @@ -24,7 +29,7 @@ beforeAll(async () => { const randomSpy = vi.spyOn(Math, "random"); randomSpy.mockImplementation(() => 0.5); - trainingResult = await trainModel({ data: gestureData }); + trainingResult = await trainModel({ data: fixUpTestData(gestureData) }); }); const getModelResults = (data: GestureData[]) => { @@ -54,7 +59,7 @@ const getModelResults = (data: GestureData[]) => { describe("Model tests", () => { test("returns acceptable results on training data", () => { const { tensorFlowResultAccuracy, tensorflowPredictionResult, labels } = - getModelResults(gestureData); + getModelResults(fixUpTestData(gestureData)); const d = labels[0].length; // dimensions for (let i = 0, j = 0; i < tensorflowPredictionResult.length; i += d, j++) { const result = tensorflowPredictionResult.slice(i, i + d); @@ -69,7 +74,7 @@ describe("Model tests", () => { // Training data is shake, still, circle. This data is still, circle, shake. test("returns incorrect results on wrongly labelled training data", () => { const { tensorFlowResultAccuracy, tensorflowPredictionResult, labels } = - getModelResults(gestureDataBadLabels); + getModelResults(fixUpTestData(gestureDataBadLabels)); const d = labels[0].length; // dimensions for (let i = 0, j = 0; i < tensorflowPredictionResult.length; i += d, j++) { const result = tensorflowPredictionResult.slice(i, i + d); @@ -81,7 +86,9 @@ describe("Model tests", () => { }); test("returns correct results on testing data", () => { - const { tensorFlowResultAccuracy } = getModelResults(testdataShakeStill); + const { tensorFlowResultAccuracy } = getModelResults( + fixUpTestData(testdataShakeStill) + ); // The model thinks two samples of still are circle. // 14 samples; 1.0 / 14 = 0.0714; 0.0714 * 12 correct inferences = 0.8571 expect(parseFloat(tensorFlowResultAccuracy)).toBeGreaterThan(0.85); diff --git a/src/pages/TestModelPage.tsx b/src/pages/TestModelPage.tsx index c25a563ce..756e32852 100644 --- a/src/pages/TestModelPage.tsx +++ b/src/pages/TestModelPage.tsx @@ -15,7 +15,7 @@ const TestModelPage = () => { {stage === MlStage.TrainingComplete ? ( <> - + ) : ( diff --git a/src/utils/icons.ts b/src/utils/icons.ts new file mode 100644 index 000000000..b14f57d6d --- /dev/null +++ b/src/utils/icons.ts @@ -0,0 +1,66 @@ +export const makecodeIcons = { + Heart: "0101011111111110111000100", + SmallHeart: "0000001010011100010000000", + Happy: "0000001010000001000101110", + Sad: "0000001010000000111010001", + Confused: "0000001010000000101010101", + Angry: "1000101010000001111110101", + Asleep: "0000011011000000111000000", + Surprised: "0101000000001000101000100", + Silly: "1000100000111110001100011", + Fabulous: "1111111011000000101001110", + Meh: "1101100000000100010001000", + Yes: "0000000001000101010001000", + No: "1000101010001000101010001", + Triangle: "0000000100010101111100000", + LeftTriangle: "1000011000101001001011111", + Chessboard: "0101010101010101010101010", + Diamond: "0010001010100010101000100", + SmallDiamond: "0000000100010100010000000", + Square: "1111110001100011000111111", + SmallSquare: "0000001110010100111000000", + Scissors: "1100111010001001101011001", + TShirt: "1101111111011100111001110", + Rollerskate: "0001100011111111111101010", + Duck: "0110011100011110111000000", + House: "0010001110111110111001010", + Tortoise: "0000001110111110101000000", + Butterfly: "1101111111001001111111011", + StickFigure: "0010011111001000101010001", + Ghost: "0111010101111111111110101", + Sword: "0010000100001000111000100", + Giraffe: "1100001000010000111001010", + Skull: "0111010101111110111001110", + Umbrella: "0111011111001001010011100", + Snake: "1100011011010100111000000", + Rabbit: "1010010100111101101011110", + Cow: "1000110001111110111000100", + QuarterNote: "0010000100001001110011100", + EighthNote: "0010000110001011110011100", + Pitchfork: "1010110101111110010000100", + Target: "0010001110110110111000100", +}; + +export const Off = "0000000000000000000000000"; + +export type MakeCodeIcon = keyof typeof makecodeIcons; + +export const icons = { + ...makecodeIcons, + off: Off, +}; + +export type LedIconType = keyof typeof icons; + +export const defaultIcons: MakeCodeIcon[] = [ + "House", + "Duck", + "Tortoise", + "StickFigure", + "Ghost", + "Giraffe", + "Umbrella", + "Cow", + "EighthNote", + "Pitchfork", +]; From 638018e005ae2b95102dbf78d484d6f3ccdded6a Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:09:15 +0100 Subject: [PATCH 113/172] Add live graph XYZ labels (#292) --- src/components/LiveGraph.tsx | 52 +++++++++++++++++++++++++++-- src/live-graph-label-config.ts | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/live-graph-label-config.ts diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index bcee7910d..543078ab4 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -1,4 +1,4 @@ -import { HStack } from "@chakra-ui/react"; +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 { SmoothieChart, TimeSeries } from "smoothie"; @@ -8,6 +8,15 @@ import { AccelerometerDataEvent } from "@microbit/microbit-connection"; import { MlStage, useMlStatus } from "../ml-status-hooks"; import { mlSettings } from "../ml"; import { ConnectionStatus } from "../connect-status-hooks"; +import { RiArrowDropLeftFill } from "react-icons/ri"; +import React from "react"; +import { LabelConfig, getUpdatedLabelConfig } from "../live-graph-label-config"; + +const initialLabelConfigs: LabelConfig[] = [ + { label: "x", arrowHeight: 0, labelHeight: 0, color: "#f9808e", id: 0 }, + { label: "y", arrowHeight: 0, labelHeight: 0, color: "#80f98e", id: 1 }, + { label: "z", arrowHeight: 0, labelHeight: 0, color: "#808ef9", id: 2 }, +]; const smoothenDataPoint = (curr: number, next: number) => { // TODO: Factor out so that recording graph can do the same @@ -100,6 +109,9 @@ const LiveGraph = () => { } }, [isRecording, recordLines, stage]); + const [labelConfigs, setLabelConfigs] = + useState(initialLabelConfigs); + const dataRef = useRef<{ x: number; y: number; z: number }>({ x: 0, y: 0, @@ -117,6 +129,8 @@ const LiveGraph = () => { lineX.append(t, dataRef.current.x, false); lineY.append(t, dataRef.current.y, false); lineZ.append(t, dataRef.current.z, false); + + setLabelConfigs(getUpdatedLabelConfig(labelConfigs, dataRef.current)); }; if (isConnected) { connectActions.addAccelerometerListener(listener); @@ -124,9 +138,10 @@ const LiveGraph = () => { return () => { connectActions.removeAccelerometerListener(listener); }; - }, [connectActions, isConnected, lineX, lineY, lineZ]); + }, [connectActions, isConnected, labelConfigs, lineX, lineY, lineZ]); + + const arrowHeightTransformAdjustValue = 1; - // TODO Recording logic return ( { id="smoothie-chart" width={width - 30} /> + {isConnected && ( + + {labelConfigs.map((config, idx) => ( + + + + + + {config.label} + + + ))} + + )} ); }; diff --git a/src/live-graph-label-config.ts b/src/live-graph-label-config.ts new file mode 100644 index 000000000..e455bf471 --- /dev/null +++ b/src/live-graph-label-config.ts @@ -0,0 +1,60 @@ +const maxDistance = 1.1; + +type Dimension = "x" | "y" | "z"; +export interface LabelConfig { + label: Dimension; + arrowHeight: number; + labelHeight: number; + color: string; + id: number; +} + +export const getUpdatedLabelConfig = ( + labelConfigs: LabelConfig[], + dataPoint: { x: number; y: number; z: number } +) => { + const newLabelConfigs = labelConfigs.map((config) => ({ + ...config, + arrowHeight: getArrowHeight(dataPoint[config.label]), + })); + return fixOverlappingLabels(newLabelConfigs); +}; + +const getArrowHeight = (pos: number) => (2.1 - pos) * 2.32; + +const fixOverlappingLabels = (labels: LabelConfig[]): LabelConfig[] => { + labels.sort((a, b) => a.arrowHeight - b.arrowHeight); + + const height0 = labels[0].arrowHeight; + const height1 = labels[1].arrowHeight; + const height2 = labels[2].arrowHeight; + + const currMaxDistanceBetweenAll = height2 - height0; + + // If all the labels are too close, we find the middle and position the labels around it. + if (currMaxDistanceBetweenAll < maxDistance * 2) { + const midArrowHeight = currMaxDistanceBetweenAll / 2 + height0; + labels[0].labelHeight = midArrowHeight - maxDistance; + labels[1].labelHeight = midArrowHeight; + labels[2].labelHeight = midArrowHeight + maxDistance; + return labels; + } + + labels[0].labelHeight = height0; + labels[1].labelHeight = height1; + labels[2].labelHeight = height2; + + // If a pair of labels are too close, we find the middle and position both labels around it. + for (let i = 0; i < 2; i++) { + const diff = labels[i + 1].labelHeight - labels[i].labelHeight; + if (diff > maxDistance) continue; + + const midArrowHeight = diff / 2 + labels[i].labelHeight; + labels[i + 1].labelHeight = midArrowHeight + maxDistance / 2; + labels[i].labelHeight = midArrowHeight - maxDistance / 2; + + // Only one of the labels will be close to the other, otherwise all are too close. + break; + } + return labels; +}; From cc622863a73e84e3d28313090534aac7c8533e6a Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:13:28 +0100 Subject: [PATCH 114/172] Makecode embed (#290) - Test model page preview ML MakeCode blocks. - Reset to default button resets blocks to default blocks. - Edit in MakeCode button opens embedded MakeCode. - User project is saved in local storage. Going back to test model page should preview project blocks. - Download MakeCode user project basic flash flow with fallback on drag and drop. Other fixes: - Only navigate to add data page when connection is done through homepage - Add max length restriction (18 char) for gesture name - Allow spaces to be added for gesture name - Fix console errors regarding svg --- .github/workflows/build.yml | 2 + lang/ui.en.json | 16 + package-lock.json | 2282 ++++++++++++++++++-- package.json | 10 +- src/App.tsx | 29 +- src/components/ActionBar.tsx | 41 +- src/components/CodeViewCard.tsx | 37 + src/components/CodeViewGridItem.tsx | 49 + src/components/ConnectCableDialog.tsx | 11 +- src/components/ConnectErrorDialog.tsx | 22 +- src/components/ConnectFirstView.tsx | 2 +- src/components/ConnectionFlowDialogs.tsx | 11 +- src/components/DownloadingDialog.tsx | 13 + src/components/EditCodeDialog.tsx | 65 + src/components/Editor.tsx | 42 + src/components/GestureNameGridItem.tsx | 3 +- src/components/HeadingGrid.tsx | 41 +- src/components/LedIconPicker.tsx | 2 +- src/components/LiveGraphPanel.tsx | 4 +- src/components/ManualFlashingDialog.tsx | 9 +- src/components/StartResumeActions.tsx | 20 +- src/components/TestModelGridView.tsx | 244 ++- src/connect-actions.ts | 19 +- src/connect-status-hooks.tsx | 2 +- src/connection-stage-actions.ts | 116 +- src/connection-stage-hooks.tsx | 24 +- src/device/get-hex-file.ts | 22 +- src/get-next-connection-state.test.ts | 42 +- src/get-next-connection-state.ts | 24 +- src/makecode/generate-custom-scripts.ts | 96 + src/makecode/generate-main-scripts.test.ts | 34 + src/makecode/generate-main-scripts.ts | 87 + src/makecode/utils.test.ts | 126 ++ src/makecode/utils.ts | 43 + src/messages/ui.en.json | 24 + src/ml-status-hooks.tsx | 10 +- src/settings.tsx | 6 + src/user-projects-hooks.tsx | 116 + 38 files changed, 3278 insertions(+), 468 deletions(-) create mode 100644 src/components/CodeViewCard.tsx create mode 100644 src/components/CodeViewGridItem.tsx create mode 100644 src/components/EditCodeDialog.tsx create mode 100644 src/components/Editor.tsx create mode 100644 src/makecode/generate-custom-scripts.ts create mode 100644 src/makecode/generate-main-scripts.test.ts create mode 100644 src/makecode/generate-main-scripts.ts create mode 100644 src/makecode/utils.test.ts create mode 100644 src/makecode/utils.ts create mode 100644 src/user-projects-hooks.tsx diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3571fe8bf..80f3b786e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,8 @@ jobs: registry-url: 'https://npm.pkg.github.com' - uses: microbit-foundation/npm-package-versioner-action@v1 - run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm install --no-save @microbit-foundation/website-deploy-aws@0.6 @microbit-foundation/website-deploy-aws-config@0.9 if: github.repository_owner == 'microbit-foundation' env: diff --git a/lang/ui.en.json b/lang/ui.en.json index 6bd5ca18d..32ae26b2c 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -599,6 +599,14 @@ "defaultMessage": "None", "description": "" }, + "content.model.output.output.descriptionBody": { + "defaultMessage": "What the micro:bit will do when each action is detected.", + "description": "" + }, + "content.model.output.output.descriptionTitle": { + "defaultMessage": "Output", + "description": "" + }, "content.model.output.recognitionPoint": { "defaultMessage": "Recognition Point:", "description": "" @@ -695,6 +703,10 @@ "defaultMessage": "micro:bit 1 connection lost", "description": "" }, + "edit-in-makecode-action": { + "defaultMessage": "Edit in MakeCode", + "description": "Edit in MakeCode button text" + }, "footer.connectButton": { "defaultMessage": "Connect", "description": "" @@ -943,6 +955,10 @@ "defaultMessage": "Click to reload the page", "description": "Reload page button text" }, + "reset-to-default-action": { + "defaultMessage": "Reset to default", + "description": "Reset to default button text" + }, "resources": { "defaultMessage": "Resources", "description": "Title of resources section" diff --git a/package-lock.json b/package-lock.json index 3af975bcd..1a4b6d5fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,11 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@microbit-foundation/ml-header-generator": "^0.3.8", + "@microbit-foundation/react-code-view": "^5.0.2", + "@microbit-foundation/react-editor-embed": "^1.0.0-controller.mode.47", "@microbit/microbit-connection": "^0.0.0-alpha.18", - "@tensorflow/tfjs": "^4.4.0", + "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", "@vitejs/plugin-react": "^4.3.1", @@ -25,6 +28,9 @@ "d3": "^7.8.5", "framer-motion": "^10.2.4", "js-cookie": "^3.0.4", + "lodash.camelcase": "^4.3.0", + "lodash.upperfirst": "^4.3.1", + "ml4f": "git://github.com/microsoft/ml4f#v1.10.1", "postcss": "^8.4.23", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -47,6 +53,8 @@ "@types/d3": "^7.4.1", "@types/ejs": "^3.1.5", "@types/file-saver": "^2.0.3", + "@types/lodash.camelcase": "^4.3.9", + "@types/lodash.upperfirst": "^4.3.9", "@types/node": "^18.16.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", @@ -4355,6 +4363,76 @@ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, + "node_modules/@microbit-foundation/ml-header-generator": { + "version": "0.3.8", + "resolved": "https://npm.pkg.github.com/download/@microbit-foundation/ml-header-generator/0.3.8/c7faf62d2b931670b8aec55ae3333681b6318ede", + "integrity": "sha512-m6iq2Q/aqEwQ6fPsab9cICse45yiBJQd3/sbD1tgLgMAIfhY3AbjES/VQaFKsljUwqaRwzpizdvqTYN7wIT/rg==", + "license": "MIT" + }, + "node_modules/@microbit-foundation/react-code-view": { + "version": "5.0.2", + "resolved": "https://npm.pkg.github.com/download/@microbit-foundation/react-code-view/5.0.2/679a6240305b1ffdab0fa8965ccb4326a8a2ce9d", + "integrity": "sha512-CRgQMgHM/dIAnUhj4ynzRPjiltW45vLvnQfoDjD1IYgbBGl68N9aa9JNmKFGCpLGJYami/KWJD8L2LE2aob3Fg==", + "license": "MIT", + "dependencies": { + "react": "18.2.0", + "react-dom": "18.2.0", + "react-syntax-highlighter": "15.5.0", + "sass": "^1.72.0" + }, + "engines": { + "node": ">=20", + "npm": ">=10" + }, + "peerDependencies": { + "react": "17.x-18.x", + "react-dom": "17.x-18.x", + "tslib": ">=2.0.0" + } + }, + "node_modules/@microbit-foundation/react-code-view/node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@microbit-foundation/react-code-view/node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/@microbit-foundation/react-editor-embed": { + "version": "1.0.0-controller.mode.47", + "resolved": "https://npm.pkg.github.com/download/@microbit-foundation/react-editor-embed/1.0.0-controller.mode.47/1b7a0b15ab1120600436c1348820f0d9b515a73b", + "integrity": "sha512-wK+JC/uu9WyPjcCqk6j7qOXKW4/lL9GJNVKlA2SkLnUtC5mM3EZHlRENb42RAn1t4gGj6hr3haRiZfIMa6mDbw==", + "license": "MIT", + "dependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0", + "rxjs": ">=7.0.0", + "tslib": ">=2.0.0" + }, + "engines": { + "node": ">=20", + "npm": ">=10" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "node_modules/@microbit/microbit-connection": { "version": "0.0.0-alpha.18", "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.18.tgz", @@ -4991,16 +5069,16 @@ } }, "node_modules/@tensorflow/tfjs": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.15.0.tgz", - "integrity": "sha512-SdhKYAx/UiMJuKYxf3aXMOuK4j+rwEGRNlwAMIYPYJAFMySdqZ7hC4ZV6mB8D4LAjkgk35y3zOJ/3MWamstKdg==", - "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.15.0", - "@tensorflow/tfjs-backend-webgl": "4.15.0", - "@tensorflow/tfjs-converter": "4.15.0", - "@tensorflow/tfjs-core": "4.15.0", - "@tensorflow/tfjs-data": "4.15.0", - "@tensorflow/tfjs-layers": "4.15.0", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.20.0.tgz", + "integrity": "sha512-+ZLfJq2jyIOE2/+yKPoyD/gfy3RZypbfMrlzvBDgodTK5jnexprihhX38hxilh9HPWvWQXJqiUjKJP5ECCikrw==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.20.0", + "@tensorflow/tfjs-backend-webgl": "4.20.0", + "@tensorflow/tfjs-converter": "4.20.0", + "@tensorflow/tfjs-core": "4.20.0", + "@tensorflow/tfjs-data": "4.20.0", + "@tensorflow/tfjs-layers": "4.20.0", "argparse": "^1.0.10", "chalk": "^4.1.0", "core-js": "3.29.1", @@ -5012,9 +5090,9 @@ } }, "node_modules/@tensorflow/tfjs-backend-cpu": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.15.0.tgz", - "integrity": "sha512-w3ecwXOdp+usf0s8lkLVl7+hCkKJY5Fm4LxqJ2Oy5MeeMNbwka8fDt8xyW5gOf0/gAaeG3qMKkh0lo6rr9fRlw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.20.0.tgz", + "integrity": "sha512-1QRQ6AqAa/VB8JOArf5nY3Dc/QQHXbfuxgdIdQhKrABEHgvlaWt2Vv696UhIlVl75YoNY+vWlCwBdGQIKYfFGw==", "dependencies": { "@types/seedrandom": "^2.4.28", "seedrandom": "^3.0.5" @@ -5023,15 +5101,15 @@ "yarn": ">= 1.3.2" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0" + "@tensorflow/tfjs-core": "4.20.0" } }, "node_modules/@tensorflow/tfjs-backend-webgl": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.15.0.tgz", - "integrity": "sha512-FoOva3KjKvWVHAXWAW5Ojboz1IbM1K9i8ggNG7czJgE0La4JHMo814UHSoE6Rc0hkRoOpvDUa+FxsqYOBEhuzQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.20.0.tgz", + "integrity": "sha512-M03fJonJGxm2u3SCzRNA2JLh0gxaAye64SEmGAXOehizowxy42l+lMsPWU8xU7r7mN6PEilBNkuKAf5YJ7Xumg==", "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.15.0", + "@tensorflow/tfjs-backend-cpu": "4.20.0", "@types/offscreencanvas": "~2019.3.0", "@types/seedrandom": "^2.4.28", "seedrandom": "^3.0.5" @@ -5040,21 +5118,21 @@ "yarn": ">= 1.3.2" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0" + "@tensorflow/tfjs-core": "4.20.0" } }, "node_modules/@tensorflow/tfjs-converter": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.15.0.tgz", - "integrity": "sha512-694igyzRcJYf/l6F12l3Sz6hw0xPjihlxXEpIpypYieoq/8WBIoDgigMA/gv0NylkJSfPwwuYs7GF2/zKH2Tmg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.20.0.tgz", + "integrity": "sha512-UJ2ntQ1TNtVHB5qGMwB0j306bs3KH1E1HKJ9Dxvrc6PUaivOV+CPKqmbidOFG5LylXeRC36JBdhe+gVT2nFHNw==", "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0" + "@tensorflow/tfjs-core": "4.20.0" } }, "node_modules/@tensorflow/tfjs-core": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.15.0.tgz", - "integrity": "sha512-0D9olf5cdMNvJKpmY4yiN0Br1pabOyYDwRyBpl02/Hf6MxiOAi+pXqs/Xa1342g9H2CzqeL1oNxz7nRKa71GyA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.20.0.tgz", + "integrity": "sha512-m/cc9qDc63al9UhdbXRUYTLGfJJlhuN5tylAX/2pJMLj32c8a6ThGDJYoKzpf32n5g3MQGYLchjClDxeGdXMPQ==", "dependencies": { "@types/long": "^4.0.1", "@types/offscreencanvas": "~2019.7.0", @@ -5074,27 +5152,54 @@ "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==" }, "node_modules/@tensorflow/tfjs-data": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.15.0.tgz", - "integrity": "sha512-9iDWloyW/tfw11UlVhsAan+ekfGDoPYg2yS5f+43ixdwbfe0jWc/azDhIXoJALMfe7TTLmbMsx3A64e43RoeOw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.20.0.tgz", + "integrity": "sha512-k6S8joXhoXkatcoT6mYCxBzRCsnrLfnl6xjLe46SnXO0oEEy4Vuzbmp5Ydl1uU2hHr73zL91EdAC1k8Hng/+oA==", "dependencies": { "@types/node-fetch": "^2.1.2", "node-fetch": "~2.6.1", "string_decoder": "^1.3.0" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0", + "@tensorflow/tfjs-core": "4.20.0", "seedrandom": "^3.0.5" } }, "node_modules/@tensorflow/tfjs-layers": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.15.0.tgz", - "integrity": "sha512-PwIXB7UtGyKVmIcbonqdtArcBgva8DOxeZqklyvb/zfg17GpWeh2++eUHKSpAl5K0mdO5Y2pL4ssD9p6AQqk9w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.20.0.tgz", + "integrity": "sha512-SCHZH29Vyw+Y9eoaJHiaNo6yqM9vD3XCKncoczonRRywejm3FFqddg1AuWAfSE9XoNPE21o9PsknvKLl/Uh+Cg==", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.20.0" + } + }, + "node_modules/@tensorflow/tfjs-vis": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-vis/-/tfjs-vis-1.5.1.tgz", + "integrity": "sha512-oNithKiR7VZaE+xUvz6Leww4TYEPhKi8j5xnEYvT3j7brK2Njdvril7UgFtZ8EYZBdeX6XNim5Eu3/23gTQ1dA==", + "dependencies": { + "d3-format": "~1.3.0", + "d3-selection": "~1.3.0", + "glamor": "~2.20.40", + "preact": "~8.2.9", + "vega": "5.20.0", + "vega-embed": "6.17.0", + "vega-lite": "4.13.1" + }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.15.0" + "@tensorflow/tfjs-core": ">= 1.0.0" } }, + "node_modules/@tensorflow/tfjs-vis/node_modules/d3-format": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", + "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==" + }, + "node_modules/@tensorflow/tfjs-vis/node_modules/d3-selection": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.2.tgz", + "integrity": "sha512-OoXdv1nZ7h2aKMVg3kaUFbLLK5jXUFAMLD/Tu5JA96mjf8f2a9ZUESGY+C36t8R1WFeWk/e55hy54Ml2I62CRQ==" + }, "node_modules/@tensorflow/tfjs/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -5554,6 +5659,11 @@ "integrity": "sha512-F6UrLn++11o967g8+G4c0mILIuSuyhpE9N989T4Rr+sgHqYpdrAbPgp3KOPPf4AA2A09dQDUB8/3K1GBG9Daeg==", "dev": true }, + "node_modules/@types/clone": { + "version": "0.1.30", + "resolved": "https://registry.npmjs.org/@types/clone/-/clone-0.1.30.tgz", + "integrity": "sha512-vcxBr+ybljeSiasmdke1cQ9ICxoEwaBgM1OQ/P5h4MPj/kRyLcDl5L8PrftlbyV1kBbJIs3M3x1A1+rcWd4mEA==" + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -5816,8 +5926,16 @@ "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-IyNhGHu71jH1jCXTHmafuoAAdsbBON3kDh7u/UUhLmjYgN5TYB54e1R8ckTCiIevl2UuZaCsi9XRxineY5yUjw==", + "deprecated": "This is a stub types definition. fast-json-stable-stringify provides its own type definitions, so you do not need this installed.", + "dependencies": { + "fast-json-stable-stringify": "*" + } }, "node_modules/@types/file-saver": { "version": "2.0.7", @@ -5831,6 +5949,14 @@ "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", "dev": true }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", @@ -5911,6 +6037,15 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==" }, + "node_modules/@types/lodash.camelcase": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@types/lodash.camelcase/-/lodash.camelcase-4.3.9.tgz", + "integrity": "sha512-ys9/hGBfsKxzmFI8hckII40V0ASQ83UM2pxfQRghHAwekhH4/jWtjz/3/9YDy7ZpUd/H0k2STSqmPR28dnj7Zg==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.mergewith": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", @@ -5919,6 +6054,15 @@ "@types/lodash": "*" } }, + "node_modules/@types/lodash.upperfirst": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@types/lodash.upperfirst/-/lodash.upperfirst-4.3.9.tgz", + "integrity": "sha512-bYhT1QEsk9/ggrFjK86Pb5bnKJgTBbpVA77Ygbb1aW1oiWJNGRbVjSlQ9We/ihB9vVpX5WqDJvbISXlukGR+dQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", @@ -6033,6 +6177,11 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "dev": true }, + "node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/@types/usb": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/usb/-/usb-1.5.4.tgz", @@ -6575,7 +6724,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -6628,6 +6776,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-flat-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz", + "integrity": "sha512-hfJmKupmQN0lwi0xG6FQ5U8Rd97RnIERplymOv/qpq8AoNKPPAnxJadjFA23FNWm88wykh9HmpLJUUwUtNU/iw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/array-includes": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", @@ -6763,6 +6919,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -6870,7 +7031,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "engines": { "node": ">=8" }, @@ -6896,7 +7056,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -7058,6 +7217,33 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chart.js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", @@ -7085,7 +7271,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -7309,6 +7494,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -7338,6 +7531,15 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -7472,6 +7674,15 @@ "tiny-invariant": "^1.0.6" } }, + "node_modules/css-in-js-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", + "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", + "dependencies": { + "hyphenate-style-name": "^1.0.2", + "isobject": "^3.0.1" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -7705,6 +7916,50 @@ "node": ">=12" } }, + "node_modules/d3-geo-projection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-3.0.0.tgz", + "integrity": "sha512-1JE+filVbkEX2bT25dJdQ05iA4QHvUwev6o0nIQHOSrNlHCAKfVss/U10vEM3pA4j5v7uQoFdQ4KLbx9BlEbWA==", + "dependencies": { + "commander": "2", + "d3-array": "1 - 2", + "d3-geo": "1.12.0 - 2", + "resolve": "^1.1.10" + }, + "bin": { + "geo2svg": "bin/geo2svg", + "geograticule": "bin/geograticule", + "geoproject": "bin/geoproject", + "geoquantize": "bin/geoquantize", + "geostitch": "bin/geostitch" + } + }, + "node_modules/d3-geo-projection/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/d3-geo-projection/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-geo-projection/node_modules/d3-geo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.2.tgz", + "integrity": "sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==", + "dependencies": { + "d3-array": "^2.5.0" + } + }, + "node_modules/d3-geo-projection/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "node_modules/d3-hierarchy": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", @@ -7772,9 +8027,9 @@ } }, "node_modules/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" @@ -7958,6 +8213,14 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -8162,6 +8425,14 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -8851,8 +9122,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -8870,11 +9140,15 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -8891,6 +9165,38 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/fbjs": { + "version": "0.8.18", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.18.tgz", + "integrity": "sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA==", + "dependencies": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.30" + } + }, + "node_modules/fbjs/node_modules/core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js." + }, "node_modules/fflate": { "version": "0.6.10", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", @@ -8934,7 +9240,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -9016,6 +9321,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/framer-motion": { "version": "10.18.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz", @@ -9101,7 +9414,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -9234,6 +9546,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glamor": { + "version": "2.20.40", + "resolved": "https://registry.npmjs.org/glamor/-/glamor-2.20.40.tgz", + "integrity": "sha512-DNXCd+c14N9QF8aAKrfl4xakPk5FdcFwmH7sD0qnC0Pr7xoZ5W9yovhUrY/dJc3psfGGXC58vqQyRtuskyUJxA==", + "dependencies": { + "fbjs": "^0.8.12", + "inline-style-prefixer": "^3.0.6", + "object-assign": "^4.1.1", + "prop-types": "^15.5.10", + "through": "^2.3.8" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9259,7 +9583,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -9436,6 +9759,39 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -9491,6 +9847,11 @@ "node": ">=16.17.0" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==" + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -9517,6 +9878,11 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -9570,6 +9936,20 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/inline-style-prefixer": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz", + "integrity": "sha512-ne8XIyyqkRaNJ1JfL1NYzNdCNxq+MCBQhC8NgOQlzNm2vv3XxlP0VSLQUbSRCF6KPEoveCVEpayHoHzcMyZsMQ==", + "dependencies": { + "bowser": "^1.7.3", + "css-in-js-utils": "^2.0.0" + } + }, + "node_modules/inline-style-prefixer/node_modules/bowser": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", + "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -9611,6 +9991,28 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -9679,7 +10081,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -9771,11 +10172,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9819,7 +10228,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -9827,6 +10235,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -9861,7 +10278,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -10066,6 +10482,40 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", + "dependencies": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch/node_modules/node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dependencies": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, "node_modules/iterator.prototype": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", @@ -10758,6 +11208,11 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -10906,6 +11361,11 @@ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "dev": true }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -10929,6 +11389,11 @@ "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "dev": true }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==" + }, "node_modules/log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -10975,6 +11440,19 @@ "tslib": "^2.0.3" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -11085,6 +11563,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ml4f": { + "version": "1.10.1", + "resolved": "git+ssh://git@github.com/microsoft/ml4f.git#ec901a2378827139c4e422ecc196fc4759d397ca", + "license": "MIT", + "dependencies": { + "@tensorflow/tfjs": "^4.10.0", + "@tensorflow/tfjs-vis": "^1.5.1", + "commander": "^6.1.0" + }, + "bin": { + "ml4f": "ml4f" + } + }, + "node_modules/ml4f/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "engines": { + "node": ">= 6" + } + }, "node_modules/mlly": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", @@ -11183,7 +11682,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11429,6 +11927,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11440,6 +11946,23 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -11473,7 +11996,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -11533,7 +12055,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -11632,6 +12153,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/preact/-/preact-8.2.9.tgz", + "integrity": "sha512-ThuGXBmJS3VsT+jIP+eQufD3L8pRw/PY3FoCys6O9Pu6aF12Pn9zAJDX99TfwRAFOCEKm/P0lwiPTbqKMJp0fA==", + "hasInstallScript": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11697,6 +12224,22 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -11707,6 +12250,18 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -11963,11 +12518,25 @@ } } }, + "node_modules/react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -12027,6 +12596,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "engines": { + "node": ">=6" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -12132,6 +12723,11 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -12262,6 +12858,14 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -12321,6 +12925,22 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sass": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -12364,6 +12984,11 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -12396,6 +13021,11 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12525,6 +13155,15 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12872,6 +13511,11 @@ "resolved": "https://registry.npmjs.org/three/-/three-0.152.2.tgz", "integrity": "sha512-Ff9zIpSfkkqcBcpdiFo2f35vA9ZucO+N8TNacJOqaEE6DrB0eufItVMib8bK8Pcju/ZNT6a7blE1GhTpkdsILw==" }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -12913,7 +13557,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -12926,6 +13569,24 @@ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -13089,6 +13750,28 @@ "node": ">=14.17" } }, + "node_modules/ua-parser-js": { + "version": "0.7.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", + "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, "node_modules/ufo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", @@ -13281,181 +13964,1326 @@ "resolved": "https://registry.npmjs.org/uuid4/-/uuid4-2.0.3.tgz", "integrity": "sha512-CTpAkEVXMNJl2ojgtpLXHgz23dh8z81u6/HEPiQFOvBc/c2pde6TVHmH4uwY0d/GLF3tb7+VDAj4+2eJaQSdZQ==" }, - "node_modules/vite": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", - "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==", - "dev": true, + "node_modules/vega": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/vega/-/vega-5.20.0.tgz", + "integrity": "sha512-L2hDaTH2gz9DFbu7l1B8fR637HzctViuosFCo/Db5aBe93fCJ/w/oJu+vQNfQELzfm9sntkS/+A4u+39xrDCNA==", + "dependencies": { + "vega-crossfilter": "~4.0.5", + "vega-dataflow": "~5.7.3", + "vega-encode": "~4.8.3", + "vega-event-selector": "~2.0.6", + "vega-expression": "~4.0.1", + "vega-force": "~4.0.7", + "vega-format": "~1.0.4", + "vega-functions": "~5.12.0", + "vega-geo": "~4.3.8", + "vega-hierarchy": "~4.0.9", + "vega-label": "~1.0.0", + "vega-loader": "~4.4.0", + "vega-parser": "~6.1.3", + "vega-projection": "~1.4.5", + "vega-regression": "~1.0.9", + "vega-runtime": "~6.1.3", + "vega-scale": "~7.1.1", + "vega-scenegraph": "~4.9.4", + "vega-statistics": "~1.7.9", + "vega-time": "~2.0.4", + "vega-transforms": "~4.9.3", + "vega-typings": "~0.20.0", + "vega-util": "~1.16.1", + "vega-view": "~5.10.0", + "vega-view-transforms": "~4.5.8", + "vega-voronoi": "~4.1.5", + "vega-wordcloud": "~4.1.3" + } + }, + "node_modules/vega-canvas": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-1.2.7.tgz", + "integrity": "sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q==" + }, + "node_modules/vega-crossfilter": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-4.0.5.tgz", + "integrity": "sha512-yF+iyGP+ZxU7Tcj5yBsMfoUHTCebTALTXIkBNA99RKdaIHp1E690UaGVLZe6xde2n5WaYpho6I/I6wdAW3NXcg==", "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } + "d3-array": "^2.7.1", + "vega-dataflow": "^5.7.3", + "vega-util": "^1.15.2" } }, - "node_modules/vite-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", - "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", - "dev": true, + "node_modules/vega-crossfilter/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "internmap": "^1.0.0" } }, - "node_modules/vite-plugin-pwa": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.19.8.tgz", - "integrity": "sha512-e1oK0dfhzhDhY3VBuML6c0h8Xfx6EkOVYqolj7g+u8eRfdauZe5RLteCIA/c5gH0CBQ0CNFAuv/AFTx4Z7IXTw==", - "dev": true, + "node_modules/vega-crossfilter/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-dataflow": { + "version": "5.7.6", + "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-5.7.6.tgz", + "integrity": "sha512-9Md8+5iUC1MVKPKDyZ7pCEHk6I9am+DgaMzZqo/27O/KI4f23/WQXPyuI8jbNmc/mkm340P0TKREmzL5M7+2Dg==", "dependencies": { - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "pretty-bytes": "^6.1.1", - "workbox-build": "^7.0.0", - "workbox-window": "^7.0.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vite-pwa/assets-generator": "^0.2.4", - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", - "workbox-build": "^7.0.0", - "workbox-window": "^7.0.0" - }, - "peerDependenciesMeta": { - "@vite-pwa/assets-generator": { - "optional": true - } + "vega-format": "^1.1.2", + "vega-loader": "^4.5.2", + "vega-util": "^1.17.2" } }, - "node_modules/vite-plugin-svgr": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", - "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", - "dev": true, + "node_modules/vega-dataflow/node_modules/vega-format": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-1.1.2.tgz", + "integrity": "sha512-0kUfAj0dg0U6GcEY0Kp6LiSTCZ8l8jl1qVdQyToMyKmtZg/q56qsiJQZy3WWRr1MtWkTIZL71xSJXgjwjeUaAw==", "dependencies": { - "@rollup/pluginutils": "^5.0.5", - "@svgr/core": "^8.1.0", - "@svgr/plugin-jsx": "^8.1.0" - }, - "peerDependencies": { - "vite": "^2.6.0 || 3 || 4 || 5" + "d3-array": "^3.2.2", + "d3-format": "^3.1.0", + "d3-time-format": "^4.1.0", + "vega-time": "^2.1.2", + "vega-util": "^1.17.2" } }, - "node_modules/vitest": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", - "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", - "dev": true, + "node_modules/vega-dataflow/node_modules/vega-loader": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.5.2.tgz", + "integrity": "sha512-ktIdGz3DRIS3XfTP9lJ6oMT5cKwC86nQkjUbXZbOtwXQFVNE2xVWBuH13GP6FKUZxg5hJCMtb5v/e/fwTvhKsQ==", "dependencies": { - "@vitest/expect": "1.6.0", - "@vitest/runner": "1.6.0", - "@vitest/snapshot": "1.6.0", - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.0", - "why-is-node-running": "^2.2.2" + "d3-dsv": "^3.0.1", + "node-fetch": "^2.6.7", + "topojson-client": "^3.1.0", + "vega-format": "^1.1.2", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-dataflow/node_modules/vega-time": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-2.1.2.tgz", + "integrity": "sha512-6rXc6JdDt8MnCRy6UzUCsa6EeFycPDmvioMddLfKw38OYCV8pRQC5nw44gyddOwXgUTJLiCtn/sp53P0iA542A==", + "dependencies": { + "d3-array": "^3.2.2", + "d3-time": "^3.1.0", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-dataflow/node_modules/vega-util": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz", + "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw==" + }, + "node_modules/vega-embed": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/vega-embed/-/vega-embed-6.17.0.tgz", + "integrity": "sha512-9eiVZCrLDb/EiVCMbMYouWB/q9dOeVkL5Bh0vU6wsUpIV/bbEvS47uljuo3YSxFqkfNpJ+Qt8xvLRiYSnN4lqw==", + "dependencies": { + "fast-json-patch": "^3.0.0-1", + "json-stringify-pretty-compact": "^3.0.0", + "semver": "^7.3.5", + "vega-schema-url-parser": "^2.1.0", + "vega-themes": "^2.10.0", + "vega-tooltip": "^0.25.1" }, + "peerDependencies": { + "vega": "^5.13.0", + "vega-lite": "*" + } + }, + "node_modules/vega-embed/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { - "vitest": "vitest.mjs" + "semver": "bin/semver.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.0", - "@vitest/ui": "1.6.0", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, + "node": ">=10" + } + }, + "node_modules/vega-encode": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-4.8.3.tgz", + "integrity": "sha512-JoRYtaV2Hs8spWLzTu/IjR7J9jqRmuIOEicAaWj6T9NSZrNWQzu2zF3IVsX85WnrIDIRUDaehXaFZvy9uv9RQg==", + "dependencies": { + "d3-array": "^2.7.1", + "d3-interpolate": "^2.0.1", + "vega-dataflow": "^5.7.3", + "vega-scale": "^7.0.3", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-encode/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-encode/node_modules/d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "node_modules/vega-encode/node_modules/d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "dependencies": { + "d3-color": "1 - 2" + } + }, + "node_modules/vega-encode/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-event-selector": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-2.0.6.tgz", + "integrity": "sha512-UwCu50Sqd8kNZ1X/XgiAY+QAyQUmGFAwyDu7y0T5fs6/TPQnDo/Bo346NgSgINBEhEKOAMY1Nd/rPOk4UEm/ew==" + }, + "node_modules/vega-expression": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-4.0.1.tgz", + "integrity": "sha512-ZrDj0hP8NmrCpdLFf7Rd/xMUHGoSYsAOTaYp7uXZ2dkEH5x0uPy5laECMc8TiQvL8W+8IrN2HAWCMRthTSRe2Q==", + "dependencies": { + "vega-util": "^1.16.0" + } + }, + "node_modules/vega-force": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-4.0.7.tgz", + "integrity": "sha512-pyLKdwXSZ9C1dVIqdJOobvBY29rLvZjvRRTla9BU/nMwAiAGlGi6WKUFdRGdneyGe3zo2nSZDTZlZM/Z5VaQNA==", + "dependencies": { + "d3-force": "^2.1.1", + "vega-dataflow": "^5.7.3", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-force/node_modules/d3-dispatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", + "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==" + }, + "node_modules/vega-force/node_modules/d3-force": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz", + "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==", + "dependencies": { + "d3-dispatch": "1 - 2", + "d3-quadtree": "1 - 2", + "d3-timer": "1 - 2" + } + }, + "node_modules/vega-force/node_modules/d3-quadtree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz", + "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==" + }, + "node_modules/vega-force/node_modules/d3-timer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz", + "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==" + }, + "node_modules/vega-format": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-1.0.4.tgz", + "integrity": "sha512-oTAeub3KWm6nKhXoYCx1q9G3K43R6/pDMXvqDlTSUtjoY7b/Gixm8iLcir5S9bPjvH40n4AcbZsPmNfL/Up77A==", + "dependencies": { + "d3-array": "^2.7.1", + "d3-format": "^2.0.0", + "d3-time-format": "^3.0.0", + "vega-time": "^2.0.3", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-format/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-format/node_modules/d3-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", + "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" + }, + "node_modules/vega-format/node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "dependencies": { + "d3-array": "2" + } + }, + "node_modules/vega-format/node_modules/d3-time-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "dependencies": { + "d3-time": "1 - 2" + } + }, + "node_modules/vega-format/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-functions": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-5.12.1.tgz", + "integrity": "sha512-7cHfcjXOj27qEbh2FTzWDl7FJK4xGcMFF7+oiyqa0fp7BU/wNT5YdNV0t5kCX9WjV7mfJWACKV74usLJbyM6GA==", + "dependencies": { + "d3-array": "^2.7.1", + "d3-color": "^2.0.0", + "d3-geo": "^2.0.1", + "vega-dataflow": "^5.7.3", + "vega-expression": "^5.0.0", + "vega-scale": "^7.1.1", + "vega-scenegraph": "^4.9.3", + "vega-selections": "^5.3.1", + "vega-statistics": "^1.7.9", + "vega-time": "^2.0.4", + "vega-util": "^1.16.0" + } + }, + "node_modules/vega-functions/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-functions/node_modules/d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "node_modules/vega-functions/node_modules/d3-geo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.2.tgz", + "integrity": "sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==", + "dependencies": { + "d3-array": "^2.5.0" + } + }, + "node_modules/vega-functions/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-functions/node_modules/vega-expression": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-5.1.1.tgz", + "integrity": "sha512-zv9L1Hm0KHE9M7mldHyz8sXbGu3KmC0Cdk7qfHkcTNS75Jpsem6jkbu6ZAwx5cNUeW91AxUQOu77r4mygq2wUQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-functions/node_modules/vega-util": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz", + "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw==" + }, + "node_modules/vega-geo": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-4.3.8.tgz", + "integrity": "sha512-fsGxV96Q/QRgPqOPtMBZdI+DneIiROKTG3YDZvGn0EdV16OG5LzFhbNgLT5GPzI+kTwgLpAsucBHklexlB4kfg==", + "dependencies": { + "d3-array": "^2.7.1", + "d3-color": "^2.0.0", + "d3-geo": "^2.0.1", + "vega-canvas": "^1.2.5", + "vega-dataflow": "^5.7.3", + "vega-projection": "^1.4.5", + "vega-statistics": "^1.7.9", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-geo/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-geo/node_modules/d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "node_modules/vega-geo/node_modules/d3-geo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.2.tgz", + "integrity": "sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==", + "dependencies": { + "d3-array": "^2.5.0" + } + }, + "node_modules/vega-geo/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-hierarchy": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-4.0.9.tgz", + "integrity": "sha512-4XaWK6V38/QOZ+vllKKTafiwL25m8Kd+ebHmDV+Q236ONHmqc/gv82wwn9nBeXPEfPv4FyJw2SRoqa2Jol6fug==", + "dependencies": { + "d3-hierarchy": "^2.0.0", + "vega-dataflow": "^5.7.3", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-hierarchy/node_modules/d3-hierarchy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz", + "integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw==" + }, + "node_modules/vega-label": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vega-label/-/vega-label-1.0.0.tgz", + "integrity": "sha512-hCdm2pcHgkKgxnzW9GvX5JmYNiUMlOXOibtMmBzvFBQHX3NiV9giQ5nsPiQiFbV08VxEPtM+VYXr2HyrIcq5zQ==", + "dependencies": { + "vega-canvas": "^1.2.5", + "vega-dataflow": "^5.7.3", + "vega-scenegraph": "^4.9.2", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-lite": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-4.13.1.tgz", + "integrity": "sha512-OHZSSqVLuikoZ3idz3jIRk0UCKtVU2Lq5gaD6cLNTnJjNetoHKKdfZ023LVj4+Y9yWPz5meb+EJUsfBAGfF4Vw==", + "dependencies": { + "@types/clone": "~0.1.30", + "@types/fast-json-stable-stringify": "^2.0.0", + "array-flat-polyfill": "^1.0.1", + "clone": "~2.1.2", + "fast-deep-equal": "~3.1.1", + "fast-json-stable-stringify": "~2.1.0", + "json-stringify-pretty-compact": "~2.0.0", + "tslib": "~2.0.0", + "vega-event-selector": "~2.0.3", + "vega-expression": "~2.6.5", + "vega-util": "~1.14.0", + "yargs": "~15.3.1" + }, + "bin": { + "vl2pdf": "bin/vl2pdf", + "vl2png": "bin/vl2png", + "vl2svg": "bin/vl2svg", + "vl2vg": "bin/vl2vg" + }, + "peerDependencies": { + "vega": "^5.12.1" + } + }, + "node_modules/vega-lite/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vega-lite/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/vega-lite/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/vega-lite/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/vega-lite/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/vega-lite/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vega-lite/node_modules/json-stringify-pretty-compact": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz", + "integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==" + }, + "node_modules/vega-lite/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vega-lite/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vega-lite/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vega-lite/node_modules/tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + }, + "node_modules/vega-lite/node_modules/vega-expression": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-2.6.6.tgz", + "integrity": "sha512-zxPzXO33FawU3WQHRmHJaRreyJlyMaNMn1uuCFSouJttPkBBWB5gCrha2f5+pF3t4NMFWTnSrgCkR6mcaubnng==", + "dependencies": { + "vega-util": "^1.15.0" + } + }, + "node_modules/vega-lite/node_modules/vega-expression/node_modules/vega-util": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz", + "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw==" + }, + "node_modules/vega-lite/node_modules/vega-util": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.14.1.tgz", + "integrity": "sha512-pSKJ8OCkgfgHZDTljyj+gmGltgulceWbk1BV6LWrXqp6P3J8qPA/oZA8+a93YNApYxXZ3yzIVUDOo5O27xk0jw==" + }, + "node_modules/vega-lite/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vega-lite/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/vega-lite/node_modules/yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vega-lite/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/vega-loader": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.4.1.tgz", + "integrity": "sha512-dj65i4qlNhK0mOmjuchHgUrF5YUaWrYpx0A8kXA68lBk5Hkx8FNRztkcl07CZJ1+8V81ymEyJii9jzGbhEX0ag==", + "dependencies": { + "d3-dsv": "^2.0.0", + "node-fetch": "^2.6.1", + "topojson-client": "^3.1.0", + "vega-format": "^1.0.4", + "vega-util": "^1.16.0" + } + }, + "node_modules/vega-loader/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/vega-loader/node_modules/d3-dsv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz", + "integrity": "sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==", + "dependencies": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json", + "csv2tsv": "bin/dsv2dsv", + "dsv2dsv": "bin/dsv2dsv", + "dsv2json": "bin/dsv2json", + "json2csv": "bin/json2dsv", + "json2dsv": "bin/json2dsv", + "json2tsv": "bin/json2dsv", + "tsv2csv": "bin/dsv2dsv", + "tsv2json": "bin/dsv2json" + } + }, + "node_modules/vega-loader/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vega-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-6.1.4.tgz", + "integrity": "sha512-tORdpWXiH/kkXcpNdbSVEvtaxBuuDtgYp9rBunVW9oLsjFvFXbSWlM1wvJ9ZFSaTfx6CqyTyGMiJemmr1QnTjQ==", + "dependencies": { + "vega-dataflow": "^5.7.3", + "vega-event-selector": "^3.0.0", + "vega-functions": "^5.12.1", + "vega-scale": "^7.1.1", + "vega-util": "^1.16.0" + } + }, + "node_modules/vega-parser/node_modules/vega-event-selector": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-3.0.1.tgz", + "integrity": "sha512-K5zd7s5tjr1LiOOkjGpcVls8GsH/f2CWCrWcpKy74gTCp+llCdwz0Enqo013ZlGaRNjfgD/o1caJRt3GSaec4A==" + }, + "node_modules/vega-projection": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-1.4.5.tgz", + "integrity": "sha512-85kWcPv0zrrNfxescqHtSYpRknilrS0K3CVRZc7IYQxnLtL1oma9WEbrSr1LCmDoCP5hl2Z1kKbomPXkrQX5Ag==", + "dependencies": { + "d3-geo": "^2.0.1", + "d3-geo-projection": "^3.0.0" + } + }, + "node_modules/vega-projection/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-projection/node_modules/d3-geo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.2.tgz", + "integrity": "sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==", + "dependencies": { + "d3-array": "^2.5.0" + } + }, + "node_modules/vega-projection/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-regression": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-1.0.9.tgz", + "integrity": "sha512-KSr3QbCF0vJEAWFVY2MA9X786oiJncTTr3gqRMPoaLr/Yo3f7OPKXRoUcw36RiWa0WCOEMgTYtM28iK6ZuSgaA==", + "dependencies": { + "d3-array": "^2.7.1", + "vega-dataflow": "^5.7.3", + "vega-statistics": "^1.7.9", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-regression/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-regression/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-runtime": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-6.1.4.tgz", + "integrity": "sha512-0dDYXyFLQcxPQ2OQU0WuBVYLRZnm+/CwVu6i6N4idS7R9VXIX5581EkCh3pZ20pQ/+oaA7oJ0pR9rJgJ6rukRQ==", + "dependencies": { + "vega-dataflow": "^5.7.5", + "vega-util": "^1.17.1" + } + }, + "node_modules/vega-runtime/node_modules/vega-util": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz", + "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw==" + }, + "node_modules/vega-scale": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-7.1.1.tgz", + "integrity": "sha512-yE0to0prA9E5PBJ/XP77TO0BMkzyUVyt7TH5PAwj+CZT7PMsMO6ozihelRhoIiVcP0Ae/ByCEQBUQkzN5zJ0ZA==", + "dependencies": { + "d3-array": "^2.7.1", + "d3-interpolate": "^2.0.1", + "d3-scale": "^3.2.2", + "vega-time": "^2.0.4", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-scale/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-scale/node_modules/d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "node_modules/vega-scale/node_modules/d3-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", + "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" + }, + "node_modules/vega-scale/node_modules/d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "dependencies": { + "d3-color": "1 - 2" + } + }, + "node_modules/vega-scale/node_modules/d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "dependencies": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "node_modules/vega-scale/node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "dependencies": { + "d3-array": "2" + } + }, + "node_modules/vega-scale/node_modules/d3-time-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "dependencies": { + "d3-time": "1 - 2" + } + }, + "node_modules/vega-scale/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-scenegraph": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-4.9.4.tgz", + "integrity": "sha512-QaegQzbFE2yhYLNWAmHwAuguW3yTtQrmwvfxYT8tk0g+KKodrQ5WSmNrphWXhqwtsgVSvtdZkfp2IPeumcOQJg==", + "dependencies": { + "d3-path": "^2.0.0", + "d3-shape": "^2.0.0", + "vega-canvas": "^1.2.5", + "vega-loader": "^4.3.3", + "vega-scale": "^7.1.1", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-scenegraph/node_modules/d3-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", + "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + }, + "node_modules/vega-scenegraph/node_modules/d3-shape": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", + "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==", + "dependencies": { + "d3-path": "1 - 2" + } + }, + "node_modules/vega-schema-url-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vega-schema-url-parser/-/vega-schema-url-parser-2.2.0.tgz", + "integrity": "sha512-yAtdBnfYOhECv9YC70H2gEiqfIbVkq09aaE4y/9V/ovEFmH9gPKaEgzIZqgT7PSPQjKhsNkb6jk6XvSoboxOBw==" + }, + "node_modules/vega-selections": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-5.4.2.tgz", + "integrity": "sha512-99FUhYmg0jOJr2/K4TcEURmJRkuibrCDc8KBUX7qcQEITzrZ5R6a4QE+sarCvbb3hi8aA9GV2oyST6MQeA9mgQ==", + "dependencies": { + "d3-array": "3.2.4", + "vega-expression": "^5.0.1", + "vega-util": "^1.17.1" + } + }, + "node_modules/vega-selections/node_modules/vega-expression": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-5.1.1.tgz", + "integrity": "sha512-zv9L1Hm0KHE9M7mldHyz8sXbGu3KmC0Cdk7qfHkcTNS75Jpsem6jkbu6ZAwx5cNUeW91AxUQOu77r4mygq2wUQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-selections/node_modules/vega-util": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz", + "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw==" + }, + "node_modules/vega-statistics": { + "version": "1.7.10", + "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-1.7.10.tgz", + "integrity": "sha512-QLb12gcfpDZ9K5h3TLGrlz4UXDH9wSPyg9LLfOJZacxvvJEPohacUQNrGEAVtFO9ccUCerRfH9cs25ZtHsOZrw==", + "dependencies": { + "d3-array": "^2.7.1" + } + }, + "node_modules/vega-statistics/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-statistics/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-themes": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/vega-themes/-/vega-themes-2.15.0.tgz", + "integrity": "sha512-DicRAKG9z+23A+rH/3w3QjJvKnlGhSbbUXGjBvYGseZ1lvj9KQ0BXZ2NS/+MKns59LNpFNHGi9us/wMlci4TOA==", + "peerDependencies": { + "vega": "*", + "vega-lite": "*" + } + }, + "node_modules/vega-time": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-2.0.4.tgz", + "integrity": "sha512-U314UDR9+ZlWrD3KBaeH+j/c2WSMdvcZq5yJfFT0yTg1jsBKAQBYFGvl+orackD8Zx3FveHOxx3XAObaQeDX+Q==", + "dependencies": { + "d3-array": "^2.7.1", + "d3-time": "^2.0.0", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-time/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-time/node_modules/d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "dependencies": { + "d3-array": "2" + } + }, + "node_modules/vega-time/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-tooltip": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/vega-tooltip/-/vega-tooltip-0.25.1.tgz", + "integrity": "sha512-ugGwGi2/p3OpB8N15xieuzP8DyV5DreqMWcmJ9zpWT8GlkyKtef4dGRXnvHeHQ+iJFmWrq4oZJ+kLTrdiECjAg==", + "dependencies": { + "vega-util": "^1.16.0" + } + }, + "node_modules/vega-transforms": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-4.9.4.tgz", + "integrity": "sha512-JGBhm5Bf6fiGTUSB5Qr5ckw/KU9FJcSV5xIe/y4IobM/i/KNwI1i1fP45LzP4F4yZc0DMTwJod2UvFHGk9plKA==", + "dependencies": { + "d3-array": "^2.7.1", + "vega-dataflow": "^5.7.4", + "vega-statistics": "^1.7.9", + "vega-time": "^2.0.4", + "vega-util": "^1.16.1" + } + }, + "node_modules/vega-transforms/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-transforms/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-typings": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-0.20.0.tgz", + "integrity": "sha512-S+HIRN/3WYiS5zrQjJ4FDEOlvFVHLxPXMJerrnN3YZ6bxCDYo7tEvQUUuByGZ3d19GuKjgejczWS7XHvF3WjDw==", + "dependencies": { + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-util": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.16.1.tgz", + "integrity": "sha512-FdgD72fmZMPJE99FxvFXth0IL4BbLA93WmBg/lvcJmfkK4Uf90WIlvGwaIUdSePIsdpkZjBPyQcHMQ8OcS8Smg==" + }, + "node_modules/vega-view": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-5.10.1.tgz", + "integrity": "sha512-4xvQ5KZcgKdZx1Z7jjenCUumvlyr/j4XcHLRf9gyeFrFvvS596dVpL92V8twhV6O++DmS2+fj+rHagO8Di4nMg==", + "dependencies": { + "d3-array": "^2.7.1", + "d3-timer": "^2.0.0", + "vega-dataflow": "^5.7.3", + "vega-format": "^1.0.4", + "vega-functions": "^5.10.0", + "vega-runtime": "^6.1.3", + "vega-scenegraph": "^4.9.4", + "vega-util": "^1.16.1" + } + }, + "node_modules/vega-view-transforms": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-4.5.9.tgz", + "integrity": "sha512-NxEq4ZD4QwWGRrl2yDLnBRXM9FgCI+vvYb3ZC2+nVDtkUxOlEIKZsMMw31op5GZpfClWLbjCT3mVvzO2xaTF+g==", + "dependencies": { + "vega-dataflow": "^5.7.5", + "vega-scenegraph": "^4.10.2", + "vega-util": "^1.17.1" + } + }, + "node_modules/vega-view-transforms/node_modules/vega-format": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-1.1.2.tgz", + "integrity": "sha512-0kUfAj0dg0U6GcEY0Kp6LiSTCZ8l8jl1qVdQyToMyKmtZg/q56qsiJQZy3WWRr1MtWkTIZL71xSJXgjwjeUaAw==", + "dependencies": { + "d3-array": "^3.2.2", + "d3-format": "^3.1.0", + "d3-time-format": "^4.1.0", + "vega-time": "^2.1.2", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-view-transforms/node_modules/vega-loader": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.5.2.tgz", + "integrity": "sha512-ktIdGz3DRIS3XfTP9lJ6oMT5cKwC86nQkjUbXZbOtwXQFVNE2xVWBuH13GP6FKUZxg5hJCMtb5v/e/fwTvhKsQ==", + "dependencies": { + "d3-dsv": "^3.0.1", + "node-fetch": "^2.6.7", + "topojson-client": "^3.1.0", + "vega-format": "^1.1.2", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-view-transforms/node_modules/vega-scale": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-7.4.1.tgz", + "integrity": "sha512-dArA28DbV/M92O2QvswnzCmQ4bq9WwLKUoyhqFYWCltmDwkmvX7yhqiFLFMWPItIm7mi4Qyoygby6r4DKd1X2A==", + "dependencies": { + "d3-array": "^3.2.2", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "vega-time": "^2.1.2", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-view-transforms/node_modules/vega-scenegraph": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-4.13.0.tgz", + "integrity": "sha512-nfl45XtuqB5CxyIZJ+bbJ+dofzosPCRlmF+eUQo+0J23NkNXsTzur+1krJDSdhcw0SOYs4sbYRoMz1cpuOM4+Q==", + "dependencies": { + "d3-path": "^3.1.0", + "d3-shape": "^3.2.0", + "vega-canvas": "^1.2.7", + "vega-loader": "^4.5.2", + "vega-scale": "^7.4.1", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-view-transforms/node_modules/vega-time": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-2.1.2.tgz", + "integrity": "sha512-6rXc6JdDt8MnCRy6UzUCsa6EeFycPDmvioMddLfKw38OYCV8pRQC5nw44gyddOwXgUTJLiCtn/sp53P0iA542A==", + "dependencies": { + "d3-array": "^3.2.2", + "d3-time": "^3.1.0", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-view-transforms/node_modules/vega-util": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz", + "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw==" + }, + "node_modules/vega-view/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/vega-view/node_modules/d3-timer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz", + "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==" + }, + "node_modules/vega-view/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/vega-voronoi": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-4.1.5.tgz", + "integrity": "sha512-950IkgCFLj0zG33EWLAm1hZcp+FMqWcNQliMYt+MJzOD5S4MSpZpZ7K4wp2M1Jktjw/CLKFL9n38JCI0i3UonA==", + "dependencies": { + "d3-delaunay": "^5.3.0", + "vega-dataflow": "^5.7.3", + "vega-util": "^1.15.2" + } + }, + "node_modules/vega-voronoi/node_modules/d3-delaunay": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", + "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "dependencies": { + "delaunator": "4" + } + }, + "node_modules/vega-voronoi/node_modules/delaunator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", + "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + }, + "node_modules/vega-wordcloud": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-4.1.5.tgz", + "integrity": "sha512-p+qXU3cb9VeWzJ/HEdax0TX2mqDJcSbrCIfo2d/EalOXGkvfSLKobsmMQ8DxPbtVp0uhnpvfCGDyMJw+AzcI2A==", + "dependencies": { + "vega-canvas": "^1.2.7", + "vega-dataflow": "^5.7.6", + "vega-scale": "^7.4.1", + "vega-statistics": "^1.9.0", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-wordcloud/node_modules/vega-scale": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-7.4.1.tgz", + "integrity": "sha512-dArA28DbV/M92O2QvswnzCmQ4bq9WwLKUoyhqFYWCltmDwkmvX7yhqiFLFMWPItIm7mi4Qyoygby6r4DKd1X2A==", + "dependencies": { + "d3-array": "^3.2.2", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "vega-time": "^2.1.2", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-wordcloud/node_modules/vega-statistics": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-1.9.0.tgz", + "integrity": "sha512-GAqS7mkatpXcMCQKWtFu1eMUKLUymjInU0O8kXshWaQrVWjPIO2lllZ1VNhdgE0qGj4oOIRRS11kzuijLshGXQ==", + "dependencies": { + "d3-array": "^3.2.2" + } + }, + "node_modules/vega-wordcloud/node_modules/vega-time": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-2.1.2.tgz", + "integrity": "sha512-6rXc6JdDt8MnCRy6UzUCsa6EeFycPDmvioMddLfKw38OYCV8pRQC5nw44gyddOwXgUTJLiCtn/sp53P0iA542A==", + "dependencies": { + "d3-array": "^3.2.2", + "d3-time": "^3.1.0", + "vega-util": "^1.17.2" + } + }, + "node_modules/vega-wordcloud/node_modules/vega-util": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz", + "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw==" + }, + "node_modules/vite": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", + "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", + "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-plugin-pwa": { + "version": "0.19.8", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.19.8.tgz", + "integrity": "sha512-e1oK0dfhzhDhY3VBuML6c0h8Xfx6EkOVYqolj7g+u8eRfdauZe5RLteCIA/c5gH0CBQ0CNFAuv/AFTx4Z7IXTw==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "pretty-bytes": "^6.1.1", + "workbox-build": "^7.0.0", + "workbox-window": "^7.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^0.2.4", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", + "workbox-build": "^7.0.0", + "workbox-window": "^7.0.0" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, + "node_modules/vite-plugin-svgr": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", + "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.5", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": "^2.6.0 || 3 || 4 || 5" + } + }, + "node_modules/vitest": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", + "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.6.0", + "@vitest/runner": "1.6.0", + "@vitest/snapshot": "1.6.0", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.0", + "@vitest/ui": "1.6.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -13535,6 +15363,11 @@ "node": ">=18" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -13632,6 +15465,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, "node_modules/which-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", @@ -14133,6 +15971,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 09482e2bf..a54378765 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "@types/d3": "^7.4.1", "@types/ejs": "^3.1.5", "@types/file-saver": "^2.0.3", + "@types/lodash.camelcase": "^4.3.9", + "@types/lodash.upperfirst": "^4.3.9", "@types/node": "^18.16.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", @@ -61,8 +63,11 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@microbit-foundation/ml-header-generator": "^0.3.8", + "@microbit-foundation/react-code-view": "^5.0.2", + "@microbit-foundation/react-editor-embed": "^1.0.0-controller.mode.47", "@microbit/microbit-connection": "^0.0.0-alpha.18", - "@tensorflow/tfjs": "^4.4.0", + "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", "@vitejs/plugin-react": "^4.3.1", @@ -72,6 +77,9 @@ "d3": "^7.8.5", "framer-motion": "^10.2.4", "js-cookie": "^3.0.4", + "lodash.camelcase": "^4.3.0", + "lodash.upperfirst": "^4.3.1", + "ml4f": "git://github.com/microsoft/ml4f#v1.10.1", "postcss": "^8.4.23", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/App.tsx b/src/App.tsx index c71c3904b..152cc4531 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,7 @@ import { ConnectionStageProvider } from "./connection-stage-hooks"; import { ConnectProvider } from "./connect-actions-hooks"; import { ConnectStatusProvider } from "./connect-status-hooks"; import { BufferedDataProvider } from "./buffered-data-hooks"; +import { UserProjectsProvider } from "./user-projects-hooks"; export interface ProviderLayoutProps { children: ReactNode; @@ -44,19 +45,21 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - - - - - - {children} - - - - - - + + + + + + + + {children} + + + + + + + diff --git a/src/components/ActionBar.tsx b/src/components/ActionBar.tsx index fa4fea05c..71ce7c590 100644 --- a/src/components/ActionBar.tsx +++ b/src/components/ActionBar.tsx @@ -1,45 +1,36 @@ import { BoxProps, HStack } from "@chakra-ui/react"; import { ReactNode } from "react"; -export type ToolbarVariant = "full-screen" | "primary"; - -const styles = { - primary: { bgColor: "green.500", h: "64px", minH: "64px" }, - "full-screen": { bgColor: "brand.500", h: "4rem" }, -}; - export interface ActionBarProps extends BoxProps { itemsLeft?: ReactNode; itemsCenter?: ReactNode; itemsRight?: ReactNode; - variant?: ToolbarVariant; } const ActionBar = ({ itemsLeft, itemsCenter, itemsRight, - variant = "primary", ...rest }: ActionBarProps) => { return ( - <> - - - {itemsLeft} - - {itemsCenter && {itemsCenter}} - - {itemsRight} - + + + {itemsLeft} + + {itemsCenter && {itemsCenter}} + + {itemsRight} - + ); }; diff --git a/src/components/CodeViewCard.tsx b/src/components/CodeViewCard.tsx new file mode 100644 index 000000000..78fcc6351 --- /dev/null +++ b/src/components/CodeViewCard.tsx @@ -0,0 +1,37 @@ +import { Card, SkeletonText, VStack } from "@chakra-ui/react"; +import { + BlockLayout, + MakeCodeBlocksRendering, + MakeCodeProject, +} from "@microbit-foundation/react-code-view"; + +interface CodeViewCardProps { + project: MakeCodeProject; +} + +const CodeViewCard = ({ project }: CodeViewCardProps) => { + return ( + + + + } + /> + + + ); +}; + +export default CodeViewCard; diff --git a/src/components/CodeViewGridItem.tsx b/src/components/CodeViewGridItem.tsx new file mode 100644 index 000000000..0e439a811 --- /dev/null +++ b/src/components/CodeViewGridItem.tsx @@ -0,0 +1,49 @@ +import { Box, Card, GridItem, SkeletonText } from "@chakra-ui/react"; +import { + BlockLayout, + MakeCodeBlocksRendering, + MakeCodeProject, +} from "@microbit-foundation/react-code-view"; + +interface CodeViewGridItemProps { + project: MakeCodeProject; + gestureName: string; +} + +const CodeViewGridItem = ({ project, gestureName }: CodeViewGridItemProps) => { + return ( + + + + + } + /> + + + + ); +}; + +export default CodeViewGridItem; diff --git a/src/components/ConnectCableDialog.tsx b/src/components/ConnectCableDialog.tsx index 2a6909d81..7688764a6 100644 --- a/src/components/ConnectCableDialog.tsx +++ b/src/components/ConnectCableDialog.tsx @@ -20,14 +20,19 @@ export const getConnectionCableDialogConfig = ( isWebBluetoothSupported: boolean ): Config => { switch (flowType) { - case ConnectionFlowType.Bluetooth: + case ConnectionFlowType.DownloadProject: + return { + headingId: "connectMB.connectCable.heading", + subtitleId: "connectMB.connectCable.subtitle", + }; + case ConnectionFlowType.ConnectBluetooth: return { headingId: "connectMB.connectCable.heading", subtitleId: "connectMB.connectCable.subtitle", linkTextId: "connectMB.connectCable.skip", linkType: "skip", }; - case ConnectionFlowType.RadioRemote: + case ConnectionFlowType.ConnectRadioRemote: return { headingId: "connectMB.connectCableMB1.heading", subtitleId: "connectMB.connectCableMB1.subtitle", @@ -38,7 +43,7 @@ export const getConnectionCableDialogConfig = ( } : {}), }; - case ConnectionFlowType.RadioBridge: + case ConnectionFlowType.ConnectRadioBridge: return { headingId: "connectMB.connectCableMB2.heading", subtitleId: "connectMB.connectCableMB2.subtitle", diff --git a/src/components/ConnectErrorDialog.tsx b/src/components/ConnectErrorDialog.tsx index e16ea4e85..dee696caf 100644 --- a/src/components/ConnectErrorDialog.tsx +++ b/src/components/ConnectErrorDialog.tsx @@ -32,21 +32,21 @@ interface ConnectErrorDialogProps { } const contentConfig = { - bluetooth: { + [ConnectionFlowType.ConnectBluetooth]: { listHeading: "disconnectedWarning.bluetooth2", bullets: [ "disconnectedWarning.bluetooth3", "disconnectedWarning.bluetooth4", ], }, - bridge: { + [ConnectionFlowType.ConnectRadioBridge]: { listHeading: "connectMB.usbTryAgain.replugMicrobit2", bullets: [ "connectMB.usbTryAgain.replugMicrobit3", "connectMB.usbTryAgain.replugMicrobit4", ], }, - remote: { + [ConnectionFlowType.ConnectRadioRemote]: { listHeading: "disconnectedWarning.bluetooth2", bullets: [ "disconnectedWarning.bluetooth3", @@ -69,6 +69,16 @@ const ReconnectErrorDialog = ({ errorStep, }: ConnectErrorDialogProps) => { const errorTextIdPrefix = errorTextIdPrefixConfig[errorStep]; + const flowTypeText = { + [ConnectionFlowType.ConnectBluetooth]: "bluetooth", + [ConnectionFlowType.ConnectRadioBridge]: "bridge", + [ConnectionFlowType.ConnectRadioRemote]: "remote", + [ConnectionFlowType.DownloadProject]: undefined, + }[flowType]; + if (flowType === ConnectionFlowType.DownloadProject) { + // This flow type should not use this dialog + return <>; + } return ( - + diff --git a/src/components/ConnectFirstView.tsx b/src/components/ConnectFirstView.tsx index 8f9f5f786..14889aea6 100644 --- a/src/components/ConnectFirstView.tsx +++ b/src/components/ConnectFirstView.tsx @@ -9,7 +9,7 @@ const ConnectFirstView = () => { const { actions } = useConnectionStage(); const handleConnect = useCallback(() => { - actions.start(); + actions.startConnect(); }, [actions]); return ( diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index 4d5575801..17f6644f6 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -11,7 +11,7 @@ import ConnectBatteryDialog from "./ConnectBatteryDialog"; import ConnectCableDialog, { getConnectionCableDialogConfig, } from "./ConnectCableDialog"; -import DownloadingDialog from "./DownloadingDialog"; +import DownloadingDialog, { getHeadingId } from "./DownloadingDialog"; import EnterBluetoothPatternDialog from "./EnterBluetoothPatternDialog"; import LoadingDialog from "./LoadingDialog"; import ManualFlashingDialog from "./ManualFlashingDialog"; @@ -90,7 +90,7 @@ const ConnectionDialogs = () => { return ( { return <>; } case ConnectionFlowStep.FlashingInProgress: { - const headingIdVariations = { - [ConnectionFlowType.Bluetooth]: "connectMB.usbDownloading.header", - [ConnectionFlowType.RadioRemote]: "connectMB.usbDownloadingMB1.header", - [ConnectionFlowType.RadioBridge]: "connectMB.usbDownloadingMB2.header", - }; return ( diff --git a/src/components/DownloadingDialog.tsx b/src/components/DownloadingDialog.tsx index 5d4ea1811..639591364 100644 --- a/src/components/DownloadingDialog.tsx +++ b/src/components/DownloadingDialog.tsx @@ -9,6 +9,7 @@ import { VStack, } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; +import { ConnectionFlowType } from "../connection-stage-hooks"; export interface DownloadingDialogProps { isOpen: boolean; @@ -16,6 +17,18 @@ export interface DownloadingDialogProps { progress: number; } +export const getHeadingId = (flowType: ConnectionFlowType) => { + switch (flowType) { + case ConnectionFlowType.DownloadProject: + case ConnectionFlowType.ConnectBluetooth: + return "connectMB.usbDownloading.header"; + case ConnectionFlowType.ConnectRadioRemote: + return "connectMB.usbDownloadingMB1.header"; + case ConnectionFlowType.ConnectRadioBridge: + return "connectMB.usbDownloadingMB2.header"; + } +}; + const DownloadingDialog = ({ isOpen, headingId, diff --git a/src/components/EditCodeDialog.tsx b/src/components/EditCodeDialog.tsx new file mode 100644 index 000000000..b9bce5789 --- /dev/null +++ b/src/components/EditCodeDialog.tsx @@ -0,0 +1,65 @@ +import { + Flex, + Modal, + ModalBody, + ModalContent, + ModalOverlay, +} from "@chakra-ui/react"; +import { EditorProject } from "@microbit-foundation/react-editor-embed"; +import Editor from "./Editor"; + +interface EditCodeDialogProps { + isOpen: boolean; + editorVersion: string | undefined; + code: EditorProject; + onBack: () => void; + onDownload: (download: { name: string; hex: string }) => void; + onSave: (save: { name: string; hex: string }) => void; + onChange: (code: EditorProject) => void; +} + +const EditCodeDialog = ({ + editorVersion, + code, + isOpen, + onChange, + onBack, + onDownload, + onSave, +}: EditCodeDialogProps) => { + return ( + {}} + closeOnEsc={false} + blockScrollOnMount={false} + > + + + + + + + + + + + ); +}; + +export default EditCodeDialog; diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx new file mode 100644 index 000000000..e63398663 --- /dev/null +++ b/src/components/Editor.tsx @@ -0,0 +1,42 @@ +import { + EditorProject, + MakeCodeEditor, +} from "@microbit-foundation/react-editor-embed"; +import React from "react"; +import { getMakeCodeLang, useSettings } from "../settings"; + +const controllerId = "MicrobitMachineLearningTool"; + +interface EditorProps { + onBack?: () => void; + onCodeChange?: (code: EditorProject) => void; + onDownload?: (download: { name: string; hex: string }) => void; + onSave?: (save: { name: string; hex: string }) => void; + initialCode: EditorProject; + version: string | undefined; + style?: React.CSSProperties; +} + +const Editor = ({ + style, + initialCode, + version, + ...editorProps +}: EditorProps) => { + const [{ languageId }] = useSettings(); + return ( + + ); +}; + +export default Editor; diff --git a/src/components/GestureNameGridItem.tsx b/src/components/GestureNameGridItem.tsx index b2325815e..ecc64f5aa 100644 --- a/src/components/GestureNameGridItem.tsx +++ b/src/components/GestureNameGridItem.tsx @@ -44,7 +44,7 @@ const GestureNameGridItem = ({ const onChange: React.ChangeEventHandler = useCallback( (e) => { - const name = e.target.value.trim(); + const name = e.target.value; // Validate gesture name length if (name.length >= gestureNameMaxLength && !toast.isActive(toastId)) { toast({ @@ -115,6 +115,7 @@ const GestureNameGridItem = ({ readOnly={readOnly} value={name} borderWidth={0} + maxLength={18} {...(readOnly ? { bgColor: "transparent", size: "lg" } : { bgColor: "gray.25", size: "sm" })} diff --git a/src/components/HeadingGrid.tsx b/src/components/HeadingGrid.tsx index e0bd6b0d6..a24e79f77 100644 --- a/src/components/HeadingGrid.tsx +++ b/src/components/HeadingGrid.tsx @@ -1,14 +1,19 @@ import { Grid, GridItem, GridProps, HStack, Text } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import InfoToolTip, { InfoToolTipProps } from "./InfoToolTip"; +import InfoToolTip from "./InfoToolTip"; +import { ReactNode } from "react"; -type GridColumnHeadingItemProps = InfoToolTipProps; +interface GridColumnHeadingItemProps { + titleId?: string; + descriptionId?: string; +} interface HeadingGridProps extends Omit { headings: GridColumnHeadingItemProps[]; + children?: ReactNode; } -const HeadingGrid = ({ headings, ...props }: HeadingGridProps) => { +const HeadingGrid = ({ headings, children, ...props }: HeadingGridProps) => { return ( { borderColor="gray.200" {...props} > - {headings.map((props, idx) => ( - - ))} + {headings.map((props, idx) => { + return idx < headings.length - 1 ? ( + + ) : ( + + + {children} + + ); + })} ); }; @@ -29,12 +41,17 @@ const HeadingGrid = ({ headings, ...props }: HeadingGridProps) => { const GridColumnHeadingItem = (props: GridColumnHeadingItemProps) => { return ( - - - - - - + {props.titleId && props.descriptionId && ( + + + + + + + )} ); }; diff --git a/src/components/LedIconPicker.tsx b/src/components/LedIconPicker.tsx index b8464bd51..5f8fa3e25 100644 --- a/src/components/LedIconPicker.tsx +++ b/src/components/LedIconPicker.tsx @@ -36,7 +36,7 @@ const LedIconPicker = ({ onIconSelected }: LedIconPicker) => { aria-label="Pick icon" size="sm" > - + diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 93ab82b3e..be6579e15 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -28,13 +28,13 @@ const LiveGraphPanel = ({ isTestModelPage = false }: LiveGraphPanelProps) => { status === ConnectionStatus.FailedToSelectBluetoothDevice ? { textId: "footer.connectButton", - onClick: actions.start, + onClick: actions.startConnect, } : { textId: "actions.reconnect", onClick: actions.reconnect, }; - }, [actions.reconnect, actions.start, status]); + }, [actions.reconnect, actions.startConnect, status]); const confidences = usePrediction(); const [gestures] = useGestureData(); diff --git a/src/components/ManualFlashingDialog.tsx b/src/components/ManualFlashingDialog.tsx index f4a8d9729..a6504556d 100644 --- a/src/components/ManualFlashingDialog.tsx +++ b/src/components/ManualFlashingDialog.tsx @@ -8,8 +8,7 @@ import transferProgramWindows from "../images/transfer_program_windows.gif"; import ConnectContainerDialog, { ConnectContainerDialogProps, } from "./ConnectContainerDialog"; -import { getHexFileUrl } from "../device/get-hex-file"; -import { ConnectionFlowType } from "../connection-stage-hooks"; +import { HexType, getHexFileUrl } from "../device/get-hex-file"; interface ImageProps { src: string; @@ -50,8 +49,10 @@ const ManualFlashingDialog = ({ ...props }: ManualFlashingDialogProps) => { const imageProps = getImageProps(osName); const handleDownload = useCallback(() => { - const hexFileUrl = getHexFileUrl("universal", ConnectionFlowType.Bluetooth); - download(hexFileUrl!, "machine-learning-tool-program.hex"); + download( + getHexFileUrl("universal", HexType.Bluetooth)!, + "machine-learning-tool-program.hex" + ); }, []); useEffect(() => { diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index 758cd14b6..3d6f2b1ff 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -1,11 +1,12 @@ import { Button, HStack, StackProps, useDisclosure } from "@chakra-ui/react"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; import { useGestureActions } from "../gestures-hooks"; import { createStepPageUrl } from "../urls"; import StartOverWarningDialog from "./StartOverWarningDialog"; import { useConnectionStage } from "../connection-stage-hooks"; +import { ConnectionStatus } from "../connect-status-hooks"; const StartResumeActions = ({ ...props }: Partial) => { const gestureActions = useGestureActions(); @@ -13,9 +14,15 @@ const StartResumeActions = ({ ...props }: Partial) => { () => gestureActions.hasGestures(), [gestureActions] ); + const [hasConnectFlowStarted, setHasConnectFlowStarted] = + useState(false); const startOverWarningDialogDisclosure = useDisclosure(); const navigate = useNavigate(); - const { actions: connStageActions, isConnected } = useConnectionStage(); + const { + actions: connStageActions, + isConnected, + status, + } = useConnectionStage(); const handleNavigateToAddData = useCallback(() => { navigate(createStepPageUrl("add-data")); @@ -27,7 +34,8 @@ const StartResumeActions = ({ ...props }: Partial) => { if (isConnected) { handleNavigateToAddData(); } else { - connStageActions.start(); + connStageActions.startConnect(); + setHasConnectFlowStarted(true); } }, [ startOverWarningDialogDisclosure, @@ -37,6 +45,12 @@ const StartResumeActions = ({ ...props }: Partial) => { connStageActions, ]); + useEffect(() => { + if (status === ConnectionStatus.Connected && hasConnectFlowStarted) { + handleNavigateToAddData(); + } + }, [handleNavigateToAddData, hasConnectFlowStarted, status]); + const onClickStartNewSession = useCallback(() => { if (hasExistingSession) { startOverWarningDialogDisclosure.onOpen(); diff --git a/src/components/TestModelGridView.tsx b/src/components/TestModelGridView.tsx index e713e6f24..164b223de 100644 --- a/src/components/TestModelGridView.tsx +++ b/src/components/TestModelGridView.tsx @@ -1,27 +1,38 @@ import { - Card, - CardBody, + Button, Grid, + GridItem, GridProps, HStack, - Text, + Icon, + VStack, VisuallyHidden, + useDisclosure, } from "@chakra-ui/react"; -import React from "react"; +import { + MakeCodeProject, + MakeCodeRenderBlocksProvider, +} from "@microbit-foundation/react-code-view"; +import { EditorProject } from "@microbit-foundation/react-editor-embed"; +import React, { useCallback } from "react"; +import { RiArrowRightLine } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; +import { useConnectionStage } from "../connection-stage-hooks"; import { useGestureActions, useGestureData } from "../gestures-hooks"; import { mlSettings } from "../ml"; import { getPredictedGesture, usePrediction } from "../ml-hooks"; +import { getMakeCodeLang, useSettings } from "../settings"; +import { useMakeCodeProject } from "../user-projects-hooks"; import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; +import CodeViewCard from "./CodeViewCard"; +import CodeViewGridItem from "./CodeViewGridItem"; +import EditCodeDialog from "./EditCodeDialog"; import GestureNameGridItem from "./GestureNameGridItem"; import HeadingGrid from "./HeadingGrid"; -import InfoToolTip from "./InfoToolTip"; -import PercentageDisplay from "./PercentageDisplay"; const gridCommonProps: Partial = { - gridTemplateColumns: "200px 1fr", + gridTemplateColumns: "290px 360px 40px auto", gap: 3, - px: 10, py: 2, w: "100%", }; @@ -35,12 +46,28 @@ const headings = [ titleId: "content.model.output.certainty.descriptionTitle", descriptionId: "content.model.output.certainty.descriptionBody", }, + // Empty heading for arrow column + {}, + { + titleId: "content.model.output.output.descriptionTitle", + descriptionId: "content.model.output.output.descriptionBody", + }, ]; const TestModelGridView = () => { const intl = useIntl(); + const editCodeDialogDisclosure = useDisclosure(); const [gestures] = useGestureData(); const { setRequiredConfidence } = useGestureActions(); + const { actions } = useConnectionStage(); + + const { + hasStoredProject, + userProject, + setUserProject, + createGestureDefaultProject, + } = useMakeCodeProject(gestures.data); + const confidences = usePrediction(); const predictedGesture = getPredictedGesture(gestures, confidences); const predicationLabel = @@ -49,79 +76,142 @@ const TestModelGridView = () => { id: "content.model.output.estimatedGesture.none", }); + const [{ languageId }] = useSettings(); + const makeCodeLang = getMakeCodeLang(languageId); + + const handleCodeChange = useCallback( + (code: EditorProject) => { + setUserProject(code as MakeCodeProject); + }, + [setUserProject] + ); + + const handleResetProject = useCallback(() => { + // Clear stored project + setUserProject(undefined); + }, [setUserProject]); + + const handleSave = useCallback((save: { name: string; hex: string }) => { + const blob = new Blob([save.hex], { type: "application/octet-stream" }); + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = `${save.name}.hex`; + a.click(); + URL.revokeObjectURL(a.href); + }, []); + + const handleDownload = useCallback( + async (download: { name: string; hex: string }) => { + await actions.startDownloadUserProjectHex(download.hex); + }, + [actions] + ); return ( <> - - - - - - - - - - - {predicationLabel} - - - {predictedGesture && confidences && ( - - )} - - - - - - - - {gestures.data.map( - ({ ID, name, icon, requiredConfidence: threshold }, idx) => { - return ( - - - setRequiredConfidence(ID, val)} - currentConfidence={confidences?.[ID]} - requiredConfidence={ - threshold ?? mlSettings.defaultRequiredConfidence - } - isTriggered={predictedGesture?.ID === ID} - /> - - ); - } - )} - + + + + + + + + + + + + + {gestures.data.map((gesture, idx) => { + const { + ID, + name, + icon, + requiredConfidence: threshold, + } = gesture; + return ( + + + + setRequiredConfidence(ID, val) + } + currentConfidence={confidences?.[ID]} + requiredConfidence={ + threshold ?? mlSettings.defaultRequiredConfidence + } + isTriggered={predictedGesture?.ID === ID} + /> + + + + {hasStoredProject ? ( + // Empty div to fill up grid cell + + ) : ( + + )} + + ); + })} + + {hasStoredProject && } + + + ); }; diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 58f4595d9..b06544ce5 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -1,15 +1,16 @@ import { AccelerometerDataEvent, - ConnectionStatusEvent, ButtonEvent, + ConnectionStatusEvent, + ConnectionStatus as DeviceConnectionStatus, DeviceError, MicrobitRadioBridgeConnection, MicrobitWebBluetoothConnection, MicrobitWebUSBConnection, - ConnectionStatus as DeviceConnectionStatus, + createUniversalHexFlashDataSource, } from "@microbit/microbit-connection"; -import { ConnectionFlowType, ConnectionType } from "./connection-stage-hooks"; -import { getFlashDataSource } from "./device/get-hex-file"; +import { ConnectionType } from "./connection-stage-hooks"; +import { HexType, getFlashDataSource } from "./device/get-hex-file"; import { Logging } from "./logging/logging"; export enum ConnectAndFlashResult { @@ -66,7 +67,7 @@ export class ConnectActions { } requestUSBConnectionAndFlash = async ( - hexType: ConnectionFlowType, + hex: string | HexType, progressCallback: (progress: number) => void ): Promise< | { result: ConnectAndFlashResult.Success; deviceId: number } @@ -74,7 +75,7 @@ export class ConnectActions { > => { try { await this.usb.connect(); - const result = await this.flashMicrobit(hexType, progressCallback); + const result = await this.flashMicrobit(hex, progressCallback); // Save remote micro:bit device id is stored for passing it to bridge micro:bit const deviceId = this.usb.getDeviceId(); if (!deviceId) { @@ -90,13 +91,15 @@ export class ConnectActions { }; private flashMicrobit = async ( - flowType: ConnectionFlowType, + hex: string | HexType, progress: (progress: number) => void ): Promise => { if (!this.usb) { return ConnectAndFlashResult.Failed; } - const data = getFlashDataSource(flowType); + const data = Object.values(HexType).includes(hex as HexType) + ? getFlashDataSource(hex as HexType) + : createUniversalHexFlashDataSource(hex); if (!data) { return ConnectAndFlashResult.ErrorMicrobitUnsupported; diff --git a/src/connect-status-hooks.tsx b/src/connect-status-hooks.tsx index 24c3d42e0..431e4912d 100644 --- a/src/connect-status-hooks.tsx +++ b/src/connect-status-hooks.tsx @@ -41,7 +41,7 @@ export enum ConnectionStatus { /** * Failure to select a device by the user. */ - FailedToSelectBluetoothDevice = "FailedToSelectDevice", + FailedToSelectBluetoothDevice = "FailedToSelectBluetoothDevice", /** * Failure to establish initial connection triggered by the user. */ diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index ca335ccbd..6f1a4f34c 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -1,4 +1,3 @@ -import { NavigateFunction } from "react-router"; import { deviceIdToMicrobitName } from "./bt-pattern-utils"; import { ConnectActions, @@ -11,29 +10,28 @@ import { ConnectionStage, ConnectionType, } from "./connection-stage-hooks"; -import { createStepPageUrl } from "./urls"; import { ConnectionStatus } from "./connect-status-hooks"; +import { HexType } from "./device/get-hex-file"; type FlowStage = Pick; export class ConnectionStageActions { constructor( private actions: ConnectActions, - private navigate: NavigateFunction, private stage: ConnectionStage, private setStage: (stage: ConnectionStage) => void, private setStatus: (status: ConnectionStatus) => void ) {} - start = () => { + startConnect = () => { this.setStatus(ConnectionStatus.NotConnected); const { isWebBluetoothSupported, isWebUsbSupported } = this.stage; this.setStage({ ...this.stage, hasFailedToReconnectTwice: false, flowType: !isWebBluetoothSupported - ? ConnectionFlowType.RadioRemote - : ConnectionFlowType.Bluetooth, + ? ConnectionFlowType.ConnectRadioRemote + : ConnectionFlowType.ConnectBluetooth, flowStep: !isWebBluetoothSupported && !isWebUsbSupported ? ConnectionFlowStep.WebUsbBluetoothUnsupported @@ -41,6 +39,20 @@ export class ConnectionStageActions { }); }; + startDownloadUserProjectHex = async (hex: string) => { + // TODO: Only disconnect input micro:bit if user chooses this device. + await this.actions.disconnect(); + this.actions.removeStatusListener(); + this.setStatus(ConnectionStatus.NotConnected); + + this.setStage({ + ...this.stage, + makeCodeHex: hex, + flowType: ConnectionFlowType.DownloadProject, + flowStep: ConnectionFlowStep.ConnectCable, + }); + }; + setFlowStep = (step: ConnectionFlowStep) => { this.setStage({ ...this.stage, flowStep: step }); }; @@ -50,11 +62,9 @@ export class ConnectionStageActions { onSuccess: (stage: ConnectionStage) => void ) => { this.setFlowStep(ConnectionFlowStep.WebUsbChooseMicrobit); + const hex = this.getHexStringOrType(); const { result, deviceId } = - await this.actions.requestUSBConnectionAndFlash( - this.stage.flowType, - progressCallback - ); + await this.actions.requestUSBConnectionAndFlash(hex, progressCallback); if (result !== ConnectAndFlashResult.Success) { return this.handleConnectAndFlashFail(result); } @@ -62,6 +72,16 @@ export class ConnectionStageActions { await this.onFlashSuccess(deviceId, onSuccess); }; + private getHexStringOrType = () => { + return this.stage.flowType === ConnectionFlowType.DownloadProject + ? this.stage.makeCodeHex! + : { + [ConnectionFlowType.ConnectBluetooth]: HexType.Bluetooth, + [ConnectionFlowType.ConnectRadioBridge]: HexType.RadioBridge, + [ConnectionFlowType.ConnectRadioRemote]: HexType.RadioRemote, + }[this.stage.flowType]; + }; + private onFlashSuccess = async ( deviceId: number, onSuccess: (stage: ConnectionStage) => void @@ -70,7 +90,10 @@ export class ConnectionStageActions { // Store radio/bluetooth details. Radio is essential to pass to micro:bit 2. // Bluetooth saves the user from entering the pattern. switch (this.stage.flowType) { - case ConnectionFlowType.Bluetooth: { + case ConnectionFlowType.DownloadProject: { + return this.setFlowStep(ConnectionFlowStep.None); + } + case ConnectionFlowType.ConnectBluetooth: { const microbitName = deviceIdToMicrobitName(deviceId); newStage = { ...this.stage, @@ -81,13 +104,13 @@ export class ConnectionStageActions { }; break; } - case ConnectionFlowType.RadioBridge: { + case ConnectionFlowType.ConnectRadioBridge: { await this.connectMicrobits({ radioBridgeDeviceId: deviceId, }); return; } - case ConnectionFlowType.RadioRemote: { + case ConnectionFlowType.ConnectRadioRemote: { newStage = { ...this.stage, connType: "radio", @@ -102,7 +125,10 @@ export class ConnectionStageActions { }; private handleConnectAndFlashFail = (result: ConnectAndFlashFailResult) => { - if (this.stage.flowType === ConnectionFlowType.Bluetooth) { + if ( + this.stage.flowType === ConnectionFlowType.ConnectBluetooth || + this.stage.flowType === ConnectionFlowType.DownloadProject + ) { return this.setFlowStep(ConnectionFlowStep.ManualFlashingTutorial); } @@ -175,7 +201,6 @@ export class ConnectionStageActions { private onConnected = () => { this.setFlowStep(ConnectionFlowStep.None); - this.navigate(createStepPageUrl("add-data")); }; disconnect = async () => { @@ -240,9 +265,9 @@ export class ConnectionStageActions { this.setStage({ ...this.stage, flowType: - this.stage.flowType === ConnectionFlowType.Bluetooth - ? ConnectionFlowType.RadioRemote - : ConnectionFlowType.Bluetooth, + this.stage.flowType === ConnectionFlowType.ConnectBluetooth + ? ConnectionFlowType.ConnectRadioRemote + : ConnectionFlowType.ConnectBluetooth, }); }; @@ -250,7 +275,7 @@ export class ConnectionStageActions { this.setStage({ ...this.stage, flowStep: ConnectionFlowStep.Start, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }); }; @@ -259,9 +284,15 @@ export class ConnectionStageActions { const isManualFlashing = !this.stage.isWebUsbSupported || this.stage.flowStep === ConnectionFlowStep.ManualFlashingTutorial; - return this.stage.flowType === ConnectionFlowType.Bluetooth - ? bluetoothFlow({ isManualFlashing, isRestartAgain }) - : radioFlow({ isRestartAgain }); + + switch (this.stage.flowType) { + case ConnectionFlowType.DownloadProject: + return downloadProjectFlow({ isManualFlashing }); + case ConnectionFlowType.ConnectBluetooth: + return bluetoothFlow({ isManualFlashing, isRestartAgain }); + default: + return radioFlow({ isRestartAgain }); + } }; private setFlowStage = (flowStage: FlowStage) => { @@ -296,30 +327,30 @@ const bluetoothFlow = ({ flowStep: isRestartAgain ? ConnectionFlowStep.ReconnectFailedTwice : ConnectionFlowStep.Start, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, { flowStep: ConnectionFlowStep.ConnectCable, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, // Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. { flowStep: isManualFlashing ? ConnectionFlowStep.ManualFlashingTutorial : ConnectionFlowStep.WebUsbFlashingTutorial, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, { flowStep: ConnectionFlowStep.ConnectBattery, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, { flowStep: ConnectionFlowStep.EnterBluetoothPattern, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, { flowStep: ConnectionFlowStep.ConnectBluetoothTutorial, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, ]; @@ -328,27 +359,44 @@ const radioFlow = ({ isRestartAgain }: { isRestartAgain: boolean }) => [ flowStep: isRestartAgain ? ConnectionFlowStep.ReconnectFailedTwice : ConnectionFlowStep.Start, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, { flowStep: ConnectionFlowStep.ConnectCable, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, { flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, { flowStep: ConnectionFlowStep.ConnectBattery, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, { flowStep: ConnectionFlowStep.ConnectCable, - flowType: ConnectionFlowType.RadioBridge, + flowType: ConnectionFlowType.ConnectRadioBridge, }, { flowStep: ConnectionFlowStep.WebUsbFlashingTutorial, - flowType: ConnectionFlowType.RadioBridge, + flowType: ConnectionFlowType.ConnectRadioBridge, + }, +]; + +const downloadProjectFlow = ({ + isManualFlashing, +}: { + isManualFlashing: boolean; +}) => [ + { + flowStep: ConnectionFlowStep.ConnectCable, + flowType: ConnectionFlowType.DownloadProject, + }, + { + flowStep: isManualFlashing + ? ConnectionFlowStep.ManualFlashingTutorial + : ConnectionFlowStep.WebUsbFlashingTutorial, + flowType: ConnectionFlowType.DownloadProject, }, ]; diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index 64e927064..a61e9ad7c 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -6,7 +6,6 @@ import { useMemo, useState, } from "react"; -import { useNavigate } from "react-router"; import { ConnectActions } from "./connect-actions"; import { useConnectActions } from "./connect-actions-hooks"; import { ConnectionStageActions } from "./connection-stage-actions"; @@ -18,11 +17,17 @@ import { } from "./connect-status-hooks"; export enum ConnectionFlowType { - Bluetooth = "bluetooth", - RadioBridge = "bridge", - RadioRemote = "remote", + ConnectBluetooth = "ConnectBluetooth", + ConnectRadioBridge = "ConnectRadioBridge", + ConnectRadioRemote = "ConnectRadioRemote", + DownloadProject = "DownloadProject", } +export type InputConnectionFlowType = + | ConnectionFlowType.ConnectBluetooth + | ConnectionFlowType.ConnectRadioBridge + | ConnectionFlowType.ConnectRadioRemote; + export type ConnectionType = "bluetooth" | "radio"; export enum ConnectionFlowStep { @@ -73,6 +78,9 @@ export interface ConnectionStage { radioBridgeDeviceId?: number; radioRemoteDeviceId?: number; hasFailedToReconnectTwice: boolean; + + // User Project + makeCodeHex?: string; } type ConnectionStageContextValue = [ @@ -99,8 +107,8 @@ const getInitialConnectionStageValue = ( ): ConnectionStage => ({ flowStep: ConnectionFlowStep.None, flowType: isWebBluetoothSupported - ? ConnectionFlowType.Bluetooth - : ConnectionFlowType.RadioRemote, + ? ConnectionFlowType.ConnectBluetooth + : ConnectionFlowType.ConnectRadioRemote, bluetoothMicrobitName: config.bluetoothMicrobitName, radioRemoteDeviceId: config.radioRemoteDeviceId, connType: isWebBluetoothSupported ? "bluetooth" : "radio", @@ -157,19 +165,17 @@ export const useConnectionStage = (): { throw new Error("Missing provider"); } const [stage, setStage] = connectionStageContextValue; - const navigate = useNavigate(); const connectActions = useConnectActions(); const [, setStatus] = useConnectStatus(); const actions = useMemo(() => { return new ConnectionStageActions( connectActions, - navigate, stage, setStage, setStatus ); - }, [connectActions, navigate, stage, setStage, setStatus]); + }, [connectActions, stage, setStage, setStatus]); const status = useConnectStatusUpdater( stage.connType, diff --git a/src/device/get-hex-file.ts b/src/device/get-hex-file.ts index d2e02dea1..0faa52881 100644 --- a/src/device/get-hex-file.ts +++ b/src/device/get-hex-file.ts @@ -1,15 +1,21 @@ -import { ConnectionFlowType } from "../connection-stage-hooks"; import { BoardVersion, FlashDataError, FlashDataSource, } from "@microbit/microbit-connection"; +export enum HexType { + RadioRemote = "radio-remote", + RadioBridge = "radio-bridge", + Bluetooth = "bluetooth", + RadioRemoteDev = "radio-remote-dev", + RadioLocal = "radio-local", +} export const getHexFileUrl = ( version: BoardVersion | "universal", - type: ConnectionFlowType | "radio-remote-dev" | "radio-local" + type: HexType ): string | undefined => { - if (type === ConnectionFlowType.Bluetooth) { + if (type === HexType.Bluetooth) { return { V1: "firmware/ml-microbit-cpp-version-combined.hex", V2: "firmware/MICROBIT.hex", @@ -21,17 +27,15 @@ export const getHexFileUrl = ( } return { "radio-remote-dev": "firmware/radio-remote-v0.2.1-dev.hex", - [ConnectionFlowType.RadioRemote]: "firmware/radio-remote-v0.2.1.hex", - [ConnectionFlowType.RadioBridge]: "firmware/radio-bridge-v0.2.1.hex", + "radio-remote": "firmware/radio-remote-v0.2.1.hex", + "radio-bridge": "firmware/radio-bridge-v0.2.1.hex", "radio-local": "firmware/local-sensors-v0.2.1.hex", }[type]; }; -export const getFlashDataSource = ( - type: ConnectionFlowType | "radio-remote-dev" | "radio-local" -): FlashDataSource => { +export const getFlashDataSource = (hex: HexType): FlashDataSource => { return async (boardVersion: BoardVersion) => { - const url = getHexFileUrl(boardVersion, type); + const url = getHexFileUrl(boardVersion, hex); if (!url) { throw new FlashDataError("No hex for board version"); } diff --git a/src/get-next-connection-state.test.ts b/src/get-next-connection-state.test.ts index 3c32056ee..459911420 100644 --- a/src/get-next-connection-state.test.ts +++ b/src/get-next-connection-state.test.ts @@ -86,7 +86,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.Connecting, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -105,7 +105,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.Connected, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -124,7 +124,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.Connected, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -159,7 +159,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToConnect, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -194,7 +194,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.ReconnectingAutomatically, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -213,7 +213,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: true, expectedNextConnectionState: { status: ConnectionStatus.ConnectionLost, - flowType: ConnectionFlowType.RadioBridge, + flowType: ConnectionFlowType.ConnectRadioBridge, }, }); }); @@ -232,7 +232,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: true, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnect, - flowType: ConnectionFlowType.RadioBridge, + flowType: ConnectionFlowType.ConnectRadioBridge, }, }); }); @@ -251,7 +251,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnectTwice, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -270,7 +270,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: true, expectedNextConnectionState: { status: ConnectionStatus.ConnectionLost, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -289,7 +289,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: true, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnect, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -308,7 +308,7 @@ describe("getNextConnectionState for radio connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnectTwice, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }, }); }); @@ -330,7 +330,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.Connecting, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); @@ -349,7 +349,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.Connected, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); @@ -368,7 +368,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.Connected, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); @@ -398,12 +398,12 @@ describe("getNextConnectionState for bluetooth connection", () => { type: "bluetooth", }, initialOnFirstConnectAttempt: false, - expectedOnFirstConnectAttempt: false, + expectedOnFirstConnectAttempt: true, initialHasAttemptedReconnect: false, expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToSelectBluetoothDevice, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); @@ -422,7 +422,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToConnect, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); @@ -457,7 +457,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.ReconnectingAutomatically, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); @@ -476,7 +476,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedHasAttemptedReconnect: true, expectedNextConnectionState: { status: ConnectionStatus.ConnectionLost, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); @@ -495,7 +495,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedHasAttemptedReconnect: true, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnect, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); @@ -514,7 +514,7 @@ describe("getNextConnectionState for bluetooth connection", () => { expectedHasAttemptedReconnect: false, expectedNextConnectionState: { status: ConnectionStatus.FailedToReconnectTwice, - flowType: ConnectionFlowType.Bluetooth, + flowType: ConnectionFlowType.ConnectBluetooth, }, }); }); diff --git a/src/get-next-connection-state.ts b/src/get-next-connection-state.ts index 6af470da5..058bae3f3 100644 --- a/src/get-next-connection-state.ts +++ b/src/get-next-connection-state.ts @@ -37,10 +37,10 @@ export const getNextConnectionState = ({ } const flowType = type === "usb" - ? ConnectionFlowType.RadioBridge + ? ConnectionFlowType.ConnectRadioBridge : type === "radioRemote" - ? ConnectionFlowType.RadioRemote - : ConnectionFlowType.Bluetooth; + ? ConnectionFlowType.ConnectRadioRemote + : ConnectionFlowType.ConnectBluetooth; // We use usb status to infer the radio bridge device status for handling error. if (type === "usb") { @@ -65,7 +65,7 @@ export const getNextConnectionState = ({ setHasAttemptedReconnect(false); return { status: ConnectionStatus.FailedToReconnectTwice, - flowType: ConnectionFlowType.RadioRemote, + flowType: ConnectionFlowType.ConnectRadioRemote, }; } // If bridge micro:bit causes radio bridge reconnect to fail @@ -77,6 +77,15 @@ export const getNextConnectionState = ({ return { status, flowType }; } + const hasStartedOver = + currStatus === ConnectionStatus.NotConnected || + currStatus === ConnectionStatus.FailedToConnect; + + if (hasStartedOver) { + setHasAttemptedReconnect(false); + setOnFirstConnectAttempt(true); + } + if ( // If user starts or restarts connection flow. // Disconnection happens for newly started / restarted @@ -112,6 +121,7 @@ export const getNextConnectionState = ({ } if ( // If fails to reconnect twice. + !onFirstConnectAttempt && hasAttempedReconnect && deviceStatus === DeviceConnectionStatus.DISCONNECTED ) { @@ -120,6 +130,7 @@ export const getNextConnectionState = ({ } if ( // If fails to reconnect by user. + !onFirstConnectAttempt && deviceStatus === DeviceConnectionStatus.DISCONNECTED && (prevDeviceStatus === DeviceConnectionStatus.CONNECTING || prevDeviceStatus === DeviceConnectionStatus.NO_AUTHORIZED_DEVICE) @@ -135,19 +146,16 @@ export const getNextConnectionState = ({ setHasAttemptedReconnect(true); return { status: ConnectionStatus.ConnectionLost, flowType }; } - const hasStartedOver = - currStatus === ConnectionStatus.NotConnected || - currStatus === ConnectionStatus.FailedToConnect; if ( // If connecting. deviceStatus === DeviceConnectionStatus.CONNECTING && hasStartedOver ) { - setOnFirstConnectAttempt(true); return { status: ConnectionStatus.Connecting, flowType }; } if ( // If reconnecting automatically. + !onFirstConnectAttempt && deviceStatus === DeviceConnectionStatus.RECONNECTING ) { return { status: ConnectionStatus.ReconnectingAutomatically, flowType }; diff --git a/src/makecode/generate-custom-scripts.ts b/src/makecode/generate-custom-scripts.ts new file mode 100644 index 000000000..0692b8572 --- /dev/null +++ b/src/makecode/generate-custom-scripts.ts @@ -0,0 +1,96 @@ +/** + * (c) 2024, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ +import { compileModel } from "ml4f"; +import { generateBlob } from "@microbit-foundation/ml-header-generator"; +import { ActionName, actionNamesFromLabels } from "./utils"; +import { LayersModel } from "@tensorflow/tfjs"; +import { mlSettings } from "../ml"; +import { GestureData } from "../gestures-hooks"; + +const createMlEvents = (actionNames: ActionName[]) => { + let code = ""; + actionNames.forEach((an, idx) => { + const stringValue = JSON.stringify(an.actionLabel); + code += ` //% fixedInstance block=${stringValue}\n`; + code += ` export const ${an.actionVar} = new MlEvent(${ + idx + 2 + }, ${stringValue});\n`; + }); + return code; +}; + +const createEventListeners = (actionNames: ActionName[]) => { + actionNames.unshift({ + actionLabel: "unknown", + actionVar: "Unknown", + }); + const totalActions = actionNames.length; + let code = ""; + for (let i = 0; i < totalActions; i++) { + code += ` control.onEvent(MlRunnerIds.MlRunnerInference, ${ + i + 1 + }, () => {\n`; + code += ` maybeUpdateEventStats(event.${actionNames[i].actionVar});\n`; + code += ` });\n`; + } + return code; +}; + +const arrayBufferToHexString = (input: Uint8Array): string => + Array.from(input, (i) => i.toString(16).padStart(2, "0")) + .join("") + .toUpperCase(); + +export const generateCustomTs = (gs: GestureData[], m: LayersModel) => { + const customHeaderBlob = generateBlob({ + samples_period: 25, + samples_length: 80, + sample_dimensions: 3, + actions: gs.map((g) => ({ + label: g.name, + threshold: g.requiredConfidence ?? mlSettings.defaultRequiredConfidence, + })), + }); + const headerHexString = arrayBufferToHexString( + new Uint8Array(customHeaderBlob) + ); + const { machineCode } = compileModel(m, {}); + const modelHexString = arrayBufferToHexString(machineCode); + const actionNames = actionNamesFromLabels(gs.map((g) => g.name)); + + return `// Auto-generated. Do not edit. +namespace ml { + export namespace event { + ${createMlEvents(actionNames)} + } + + events = [event.Unknown,${actionNames + .map((an) => `event.${an.actionVar}`) + .join(",")}]; + +${createEventListeners(actionNames)} + getModelBlob = (): Buffer => { + const result = hex\`${headerHexString + modelHexString}\`; + return result; + }; + + simulatorSendData(); +} + +// Auto-generated. Do not edit. Really. +`; +}; + +export const generateCustomJson = (gs: GestureData[]) => { + return JSON.stringify( + gs.map((g) => ({ + ID: g.ID, + name: g.name, + numRecordings: g.recordings.length, + requiredConfidence: g.requiredConfidence, + })) + ); +}; diff --git a/src/makecode/generate-main-scripts.test.ts b/src/makecode/generate-main-scripts.test.ts new file mode 100644 index 000000000..65e6404ca --- /dev/null +++ b/src/makecode/generate-main-scripts.test.ts @@ -0,0 +1,34 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2024, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import { Gesture } from "../gestures-hooks"; +import { generateMainScript } from "./generate-main-scripts"; + +describe("test generateMainScripts", () => { + it("generates xml blocks", () => { + const expected = ` + + + ml.event.Name + + IconNames.Heart + + + `; + const gs: Gesture[] = [{ name: "name", icon: "Heart", ID: 12 }]; + expect(generateMainScript(gs, "blocks")).toEqual(expected); + }); + + it("generates js", () => { + const expected = + " ml.onStart(ml.event.Name, function () {basic.showIcon(IconNames.Heart)})"; + const gs: Gesture[] = [{ name: "name", icon: "Heart", ID: 12 }]; + expect(generateMainScript(gs, "javascript")).toContain(expected); + }); +}); diff --git a/src/makecode/generate-main-scripts.ts b/src/makecode/generate-main-scripts.ts new file mode 100644 index 000000000..9e611222b --- /dev/null +++ b/src/makecode/generate-main-scripts.ts @@ -0,0 +1,87 @@ +import { Gesture } from "../gestures-hooks"; +import { actionNamesFromLabels } from "./utils"; +/** + * (c) 2024, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ +export interface OnGestureRecognisedConfig { + name: string; + iconName: string; +} + +interface BlockPos { + x: number; + y: number; +} + +const onMLEventBlock = (name: string, children: string, pos: BlockPos) => ` + + ml.event.${name} + + ${children} + + +`; + +type Language = "blocks" | "javascript"; + +interface LanguageStatements { + wrapper: (children: string) => string; + showLeds: (ledPattern: string) => string; + showIcon: (iconName: string) => string; + clearDisplay: () => string; + onMLEvent: (name: string, children: string, _pos: BlockPos) => string; +} + +const statements: Record = { + javascript: { + wrapper: (children) => children, + showLeds: (ledPattern) => `basic.showLeds(\`${ledPattern}\`)`, + showIcon: (iconName) => `basic.showIcon(IconNames.${iconName})`, + clearDisplay: () => "basic.clearScreen()", + onMLEvent: (name, children) => { + return `ml.onStart(ml.event.${name}, function () {${children}})`; + }, + }, + blocks: { + wrapper: (children) => + `${children}`, + showLeds: (ledPattern) => + `\`${ledPattern}\``, + showIcon: (iconName) => + `IconNames.${iconName}`, + clearDisplay: () => ``, + onMLEvent: onMLEventBlock, + }, +}; + +const onMLEventChildren = ( + s: LanguageStatements, + { iconName }: OnGestureRecognisedConfig +) => { + return iconName ? s.showIcon(iconName) : ""; +}; + +const getMakeCodeGestureConfigs = (gs: Gesture[]) => { + const actionNames = actionNamesFromLabels(gs.map((g) => g.name)); + return gs.map((g, idx) => ({ + name: actionNames[idx].actionVar, + iconName: g.icon, + })); +}; + +export const generateMainScript = (gs: Gesture[], lang: Language) => { + const configs = getMakeCodeGestureConfigs(gs); + const s = statements[lang]; + const initPos = { x: 0, y: 0 }; + return s.wrapper(` + ${configs + .map((c, idx) => + s.onMLEvent(c.name, onMLEventChildren(s, c), { + x: initPos.x, + y: initPos.y + idx * 350, + }) + ) + .join("\n")} `); +}; diff --git a/src/makecode/utils.test.ts b/src/makecode/utils.test.ts new file mode 100644 index 000000000..14e405ade --- /dev/null +++ b/src/makecode/utils.test.ts @@ -0,0 +1,126 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2024, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ +import { ActionName, actionNamesFromLabels } from "./utils"; + +describe("test actionNamesFromLabels", () => { + it("removes numbers from start of identifiers", () => { + const expected: ActionName[] = [ + { + actionLabel: " 123 hello", + actionVar: "Hello", + }, + ]; + const userDefined = [" 123 hello"]; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); + + it("removes invalid characters from identifier", () => { + const expected: ActionName[] = [ + { + actionLabel: "!£$%^&*()valid:;'@#~[{]},<.>/?¬`", + actionVar: "Valid", + }, + ]; + const userDefined = ["!£$%^&*()valid:;'@#~[{]},<.>/?¬`"]; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); + + it("generates best effort identifier if no characters are valid", () => { + const expected: ActionName[] = [ + { + actionLabel: " 123 £$%", + actionVar: "Event", + }, + ]; + const userDefined = [" 123 £$%"]; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); + + it("copes with empty strings", () => { + const expected: ActionName[] = [ + { + actionLabel: "", + actionVar: "Event", + }, + { + actionLabel: "", + actionVar: "Event1", + }, + { + actionLabel: "", + actionVar: "Event2", + }, + ]; + const userDefined = ["", "", ""]; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); + + it("replaces double quotes in action labels", () => { + const expected: ActionName[] = [ + { + actionLabel: "my 'action'", + actionVar: "MyAction", + }, + ]; + const userDefined = ['my "action"']; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); + + it("dedups sanitized inputs to create valid identifiers", () => { + const expected: ActionName[] = [ + { + actionLabel: "hello", + actionVar: "Hello", + }, + { + actionLabel: "hello-", + actionVar: "Hello1", + }, + { + actionLabel: "hello--", + actionVar: "Hello2", + }, + ]; + const userDefined = ["hello", "hello-", "hello--"]; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); + + it("removes invalid characters from identifier", () => { + const expected: ActionName[] = [ + { + actionLabel: "!£$%^&*()valid:;'@#~[{]},<.>/?¬`1", + actionVar: "Valid1", + }, + ]; + const userDefined = ["!£$%^&*()valid:;'@#~[{]},<.>/?¬`1"]; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); + + it("copes with newlines", () => { + const expected: ActionName[] = [ + { + actionLabel: "my \naction \n", + actionVar: "MyAction", + }, + ]; + const userDefined = ["my \naction \n"]; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); + + it("copes with different languages", () => { + const expected: ActionName[] = [ + { + actionLabel: "내 행동", + actionVar: "내행동", + }, + ]; + const userDefined = ["내 행동"]; + expect(actionNamesFromLabels(userDefined)).toEqual(expected); + }); +}); diff --git a/src/makecode/utils.ts b/src/makecode/utils.ts new file mode 100644 index 000000000..8149a357d --- /dev/null +++ b/src/makecode/utils.ts @@ -0,0 +1,43 @@ +/** + * @vitest-environment jsdom + */ +/** + * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors + * + * SPDX-License-Identifier: MIT + */ + +import camelCase from "lodash.camelcase"; +import upperFirst from "lodash.upperfirst"; + +export interface ActionName { + actionLabel: string; + actionVar: string; +} + +const sanitizeActionVar = (input: string) => + input + .replace(/[^\p{L}\p{N}_$\s]/gu, "") + .replace(/^(\s|\p{N})+/gu, "") + .trim(); + +const sanitizeActionLabel = (input: string) => input.replace(/"/g, "'"); + +export const actionNamesFromLabels = (actionLabels: string[]): ActionName[] => { + const actionNames: ActionName[] = []; + actionLabels.forEach((actionLabel, i) => { + const sanitizedLabel = sanitizeActionVar(actionLabel); + let actionVar = upperFirst(camelCase(sanitizedLabel)); + if (!actionVar) { + actionVar = `Event`; + } + while (actionNames.map((an) => an.actionVar).includes(actionVar)) { + actionVar += i; + } + actionNames.push({ + actionLabel: sanitizeActionLabel(actionLabel), + actionVar, + }); + }); + return actionNames; +}; diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index a551a2121..e158d2a67 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -1043,6 +1043,18 @@ "value": "None" } ], + "content.model.output.output.descriptionBody": [ + { + "type": 0, + "value": "What the micro:bit will do when each action is detected." + } + ], + "content.model.output.output.descriptionTitle": [ + { + "type": 0, + "value": "Output" + } + ], "content.model.output.recognitionPoint": [ { "type": 0, @@ -1187,6 +1199,12 @@ "value": "micro:bit 1 connection lost" } ], + "edit-in-makecode-action": [ + { + "type": 0, + "value": "Edit in MakeCode" + } + ], "footer.connectButton": [ { "type": 0, @@ -1567,6 +1585,12 @@ "value": "Click to reload the page" } ], + "reset-to-default-action": [ + { + "type": 0, + "value": "Reset to default" + } + ], "resources": [ { "type": 0, diff --git a/src/ml-status-hooks.tsx b/src/ml-status-hooks.tsx index 9fa425b8e..43af32630 100644 --- a/src/ml-status-hooks.tsx +++ b/src/ml-status-hooks.tsx @@ -18,15 +18,17 @@ export enum MlStage { RetrainingNeeded, } +export interface TrainingCompleteMlStatus { + stage: MlStage.TrainingComplete; + model: LayersModel; +} + export type MlStatus = | { stage: MlStage.TrainingInProgress; progressValue: number; } - | { - stage: MlStage.TrainingComplete; - model: LayersModel; - } + | TrainingCompleteMlStatus | { stage: MlStage.RetrainingNeeded; } diff --git a/src/settings.tsx b/src/settings.tsx index 9b3ab79d7..7fbb0ba89 100644 --- a/src/settings.tsx +++ b/src/settings.tsx @@ -10,6 +10,8 @@ export interface Language { name: string; enName: string; preview?: boolean; + // Language supported in Microsoft MakeCode editor. + makeCode: boolean; } // Tag new languages with `preview: true` to enable for beta only. @@ -18,9 +20,13 @@ export const allLanguages: Language[] = [ id: "en", name: "English", enName: "English", + makeCode: true, }, ]; +export const getMakeCodeLang = (languageId: string): string => + allLanguages.find((l) => l.id === languageId)?.makeCode ? languageId : "en"; + export const supportedLanguages: Language[] = allLanguages.filter( (l) => stage !== "production" || !l.preview ); diff --git a/src/user-projects-hooks.tsx b/src/user-projects-hooks.tsx new file mode 100644 index 000000000..62b0cc398 --- /dev/null +++ b/src/user-projects-hooks.tsx @@ -0,0 +1,116 @@ +import { GestureData } from "./gestures-hooks"; +import { generateMainScript } from "./makecode/generate-main-scripts"; +import { + generateCustomJson, + generateCustomTs, +} from "./makecode/generate-custom-scripts"; +import { TrainingCompleteMlStatus, useMlStatus } from "./ml-status-hooks"; +import { + ReactNode, + createContext, + useCallback, + useContext, + useMemo, +} from "react"; +import { MakeCodeProject } from "@microbit-foundation/react-code-view"; +import { useStorage } from "./hooks/use-storage"; +interface UserProjectsContextState { + makeCode?: MakeCodeProject; +} + +type UserProjectsContextValue = [ + UserProjectsContextState, + (userProjects: UserProjectsContextState) => void +]; + +const UserProjectsContext = createContext( + undefined +); + +export const UserProjectsProvider = ({ children }: { children: ReactNode }) => { + const userProjectsContextValue = useStorage( + "local", + "makecodeProject", + { makeCode: undefined } + ); + return ( + + {children} + + ); +}; + +const useUserProjects = (): UserProjectsContextValue => { + const userProjects = useContext(UserProjectsContext); + if (!userProjects) { + throw new Error("Missing provider"); + } + return userProjects; +}; + +enum ProjectFilenames { + MainTs = "main.ts", + MainBlocks = "main.blocks", + CustomTs = "Machine_Learning_POC.ts", + CustomJson = "Machine_Learning_POC.json", + PxtJson = "pxt.json", + ReadMe = "README.md", +} + +const pxt = { + name: "Untitled", + description: "", + dependencies: { + core: "*", + microphone: "*", + radio: "*", // needed for compiling + "Machine Learning POC": + "github:microbit-foundation/pxt-ml-extension-poc#9f843b8f94d0cc5e6c5ec0d020463fb080caf724", + }, + files: Object.values(ProjectFilenames), +}; + +export const useMakeCodeProject = (gestures: GestureData[]) => { + const [userProjects, setUserProjects] = useUserProjects(); + const [status] = useMlStatus(); + const model = (status as TrainingCompleteMlStatus).model; + + const defaultProjectText = useMemo(() => { + return { + [ProjectFilenames.MainTs]: generateMainScript(gestures, "javascript"), + [ProjectFilenames.MainBlocks]: generateMainScript(gestures, "blocks"), + [ProjectFilenames.CustomTs]: generateCustomTs(gestures, model), + [ProjectFilenames.CustomJson]: generateCustomJson(gestures), + [ProjectFilenames.ReadMe]: "", + [ProjectFilenames.PxtJson]: JSON.stringify(pxt), + }; + }, [gestures, model]); + + const createGestureDefaultProject = useCallback( + (gesture: GestureData) => { + const gs = [gesture]; + return { + text: { + ...defaultProjectText, + [ProjectFilenames.MainTs]: generateMainScript(gs, "javascript"), + [ProjectFilenames.MainBlocks]: generateMainScript(gs, "blocks"), + }, + }; + }, + [defaultProjectText] + ); + + const setProject = useCallback( + (project: MakeCodeProject | undefined) => { + setUserProjects({ makeCode: project }); + }, + [setUserProjects] + ); + + return { + hasStoredProject: userProjects.makeCode !== undefined, + createGestureDefaultProject, + userProject: userProjects.makeCode ?? { text: defaultProjectText }, + setUserProject: setProject, + }; +}; From b8b8f5b6d14ec4e1fc902792275ae9a0493c4af2 Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:40:02 +0100 Subject: [PATCH 115/172] Improve performance (#293) * Use style to manage transform on data labels * Tweak recording graphs We don't need to resize or be responsive if we set the width and height. We don't need the animation. * Memo components and useMemo in project hooks --- src/components/CodeViewCard.tsx | 3 +- src/components/CodeViewGridItem.tsx | 77 ++++++++++++++++------------ src/components/LiveGraph.tsx | 20 +++++--- src/components/PercentageMeter.tsx | 2 + src/components/RecordingGraph.tsx | 7 ++- src/components/TestModelGridView.tsx | 22 +++----- src/recording-graph.ts | 3 +- src/user-projects-hooks.tsx | 20 ++++++-- 8 files changed, 91 insertions(+), 63 deletions(-) diff --git a/src/components/CodeViewCard.tsx b/src/components/CodeViewCard.tsx index 78fcc6351..9e0de6724 100644 --- a/src/components/CodeViewCard.tsx +++ b/src/components/CodeViewCard.tsx @@ -4,6 +4,7 @@ import { MakeCodeBlocksRendering, MakeCodeProject, } from "@microbit-foundation/react-code-view"; +import { memo } from "react"; interface CodeViewCardProps { project: MakeCodeProject; @@ -34,4 +35,4 @@ const CodeViewCard = ({ project }: CodeViewCardProps) => { ); }; -export default CodeViewCard; +export default memo(CodeViewCard); diff --git a/src/components/CodeViewGridItem.tsx b/src/components/CodeViewGridItem.tsx index 0e439a811..dad140284 100644 --- a/src/components/CodeViewGridItem.tsx +++ b/src/components/CodeViewGridItem.tsx @@ -2,48 +2,59 @@ import { Box, Card, GridItem, SkeletonText } from "@chakra-ui/react"; import { BlockLayout, MakeCodeBlocksRendering, - MakeCodeProject, } from "@microbit-foundation/react-code-view"; +import { memo, useMemo } from "react"; +import { GestureData } from "../gestures-hooks"; +import { useMakeCodeProject } from "../user-projects-hooks"; interface CodeViewGridItemProps { - project: MakeCodeProject; - gestureName: string; + gesture: GestureData; + hasStoredProject: boolean; } -const CodeViewGridItem = ({ project, gestureName }: CodeViewGridItemProps) => { +const CodeViewGridItem = ({ + gesture, + hasStoredProject, +}: CodeViewGridItemProps) => { + const { createGestureDefaultProject } = useMakeCodeProject(); + const project = useMemo( + () => createGestureDefaultProject(gesture), + [createGestureDefaultProject, gesture] + ); + const width = useMemo( + () => `${120 + gesture.name.length * 5}px`, + [gesture.name.length] + ); return ( - - - - } - /> - - + + + } + /> + + + )} ); }; -export default CodeViewGridItem; +export default memo(CodeViewGridItem); diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 543078ab4..a04cd5055 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -161,12 +161,16 @@ const LiveGraph = () => { element. + style={{ + transform: `translateY(${ + config.arrowHeight - arrowHeightTransformAdjustValue + }rem)`, + }} > @@ -175,10 +179,14 @@ const LiveGraph = () => { fontSize="xl" position="absolute" color={config.color} - transform={`translateY(${ - config.labelHeight - arrowHeightTransformAdjustValue + 0.45 - }rem)`} w="fit-content" + // Use inline style attribute to avoid style tags being + // constantly appended to the element. + style={{ + transform: `translateY(${ + config.labelHeight - arrowHeightTransformAdjustValue + 0.45 + }rem)`, + }} > {config.label} diff --git a/src/components/PercentageMeter.tsx b/src/components/PercentageMeter.tsx index 3f05d38f4..6139c433a 100644 --- a/src/components/PercentageMeter.tsx +++ b/src/components/PercentageMeter.tsx @@ -23,6 +23,8 @@ const PercentageMeter = ({ position="relative" > element. style={{ width: `${value * 100}%`, }} diff --git a/src/components/RecordingGraph.tsx b/src/components/RecordingGraph.tsx index e47d50cc5..8e3f3ddcc 100644 --- a/src/components/RecordingGraph.tsx +++ b/src/components/RecordingGraph.tsx @@ -30,15 +30,18 @@ const RecordingGraph = ({ data }: RecordingGraphProps) => { }; }, [data]); + const containerRef = useRef(null); + return ( - + ); }; diff --git a/src/components/TestModelGridView.tsx b/src/components/TestModelGridView.tsx index 164b223de..ee5970ed8 100644 --- a/src/components/TestModelGridView.tsx +++ b/src/components/TestModelGridView.tsx @@ -1,7 +1,6 @@ import { Button, Grid, - GridItem, GridProps, HStack, Icon, @@ -61,12 +60,8 @@ const TestModelGridView = () => { const { setRequiredConfidence } = useGestureActions(); const { actions } = useConnectionStage(); - const { - hasStoredProject, - userProject, - setUserProject, - createGestureDefaultProject, - } = useMakeCodeProject(gestures.data); + const { hasStoredProject, userProject, setUserProject } = + useMakeCodeProject(); const confidences = usePrediction(); const predictedGesture = getPredictedGesture(gestures, confidences); @@ -195,15 +190,10 @@ const TestModelGridView = () => { color="gray.600" /> - {hasStoredProject ? ( - // Empty div to fill up grid cell - - ) : ( - - )} + ); })} diff --git a/src/recording-graph.ts b/src/recording-graph.ts index 50d8eedf2..a6a982854 100644 --- a/src/recording-graph.ts +++ b/src/recording-graph.ts @@ -79,7 +79,8 @@ export const getConfig = ({ ], }, options: { - responsive: true, + animation: false, + responsive: false, maintainAspectRatio: false, interaction: { diff --git a/src/user-projects-hooks.tsx b/src/user-projects-hooks.tsx index 62b0cc398..5ddb7e2a9 100644 --- a/src/user-projects-hooks.tsx +++ b/src/user-projects-hooks.tsx @@ -1,4 +1,4 @@ -import { GestureData } from "./gestures-hooks"; +import { GestureData, useGestureData } from "./gestures-hooks"; import { generateMainScript } from "./makecode/generate-main-scripts"; import { generateCustomJson, @@ -70,10 +70,12 @@ const pxt = { files: Object.values(ProjectFilenames), }; -export const useMakeCodeProject = (gestures: GestureData[]) => { +export const useMakeCodeProject = () => { + const [gestureData] = useGestureData(); const [userProjects, setUserProjects] = useUserProjects(); const [status] = useMlStatus(); const model = (status as TrainingCompleteMlStatus).model; + const gestures = gestureData.data; const defaultProjectText = useMemo(() => { return { @@ -107,10 +109,20 @@ export const useMakeCodeProject = (gestures: GestureData[]) => { [setUserProjects] ); + const userProject = useMemo( + () => userProjects.makeCode ?? { text: defaultProjectText }, + [defaultProjectText, userProjects.makeCode] + ); + + const hasStoredProject = useMemo( + () => userProjects.makeCode !== undefined, + [userProjects.makeCode] + ); + return { - hasStoredProject: userProjects.makeCode !== undefined, + hasStoredProject, createGestureDefaultProject, - userProject: userProjects.makeCode ?? { text: defaultProjectText }, + userProject, setUserProject: setProject, }; }; From 05e97059342f58f4bede743a080af4f08ea9735a Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:35:49 +0100 Subject: [PATCH 116/172] Add missing icons to gesture data when resuming (#294) --- src/components/UploadDataSamplesMenuItem.tsx | 4 +++- src/gestures-hooks.tsx | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/UploadDataSamplesMenuItem.tsx b/src/components/UploadDataSamplesMenuItem.tsx index 3e1dbdd84..0ccbe3198 100644 --- a/src/components/UploadDataSamplesMenuItem.tsx +++ b/src/components/UploadDataSamplesMenuItem.tsx @@ -38,7 +38,9 @@ const UploadDataSamplesMenuItem = () => { throw new Error("Expected to be called with at least one file"); } const gestureData = await readFileAsText(files[0]); - actions.importGestures(JSON.parse(gestureData) as Partial[]); + actions.validateAndSetGestures( + JSON.parse(gestureData) as Partial[] + ); }, [actions] ); diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx index 336904525..192ec43d9 100644 --- a/src/gestures-hooks.tsx +++ b/src/gestures-hooks.tsx @@ -134,6 +134,10 @@ class GestureActions { if (!this.gestureState.data.length) { this.setGestureState({ data: [this.generateNewGesture(true)] }); } + // If icon is missing from stored data, generate default icons. + if (this.gestureState.data.some((g) => !g.icon)) { + this.validateAndSetGestures(this.gestureState.data); + } } private getDefaultIcon = ({ @@ -179,7 +183,7 @@ class GestureActions { ); }; - importGestures = (gestures: Partial[]) => { + validateAndSetGestures = (gestures: Partial[]) => { const validGestures: GestureData[] = []; const importedGestureIcons: MakeCodeIcon[] = gestures .map((g) => g.icon as MakeCodeIcon) From 0c467cd8db583b36316e234a138a488892e86686 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:17:36 +0100 Subject: [PATCH 117/172] Pull the prediction up to the test model page (#295) Then we can pass it via props to the live graph and grid. Previously we performed the prediction twice. I've also tried to remove the knowledge of where components are rendered from them in favour of using more focussed props. I've also updated the connection lib in preparation for setting the LED. --- package-lock.json | 8 ++++---- package.json | 2 +- src/components/GestureNameGridItem.tsx | 9 ++------- src/components/LedIcon.tsx | 26 +++++++------------------- src/components/LiveGraphPanel.tsx | 22 ++++++++++------------ src/components/TestModelGridView.tsx | 24 ++++++++++++++++-------- src/pages/TestModelPage.tsx | 16 +++++++++++++--- 7 files changed, 53 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a4b6d5fd..a094162f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@microbit-foundation/ml-header-generator": "^0.3.8", "@microbit-foundation/react-code-view": "^5.0.2", "@microbit-foundation/react-editor-embed": "^1.0.0-controller.mode.47", - "@microbit/microbit-connection": "^0.0.0-alpha.18", + "@microbit/microbit-connection": "^0.0.0-alpha.19", "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", @@ -4434,9 +4434,9 @@ } }, "node_modules/@microbit/microbit-connection": { - "version": "0.0.0-alpha.18", - "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.18.tgz", - "integrity": "sha512-/3eFGsdHXwXtQqfyhHfngpsqpX5vM5t9CgR+9qppGW/9eP5J2N+v4zRlGb1dLxmWSsxyRJXE4av4TQ0W9P0zww==", + "version": "0.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.19.tgz", + "integrity": "sha512-VLOLVKkkxGHA6yzla39/dcU9IOFlIjrYyfkvWvR7ypzCnhJ0moJSsDpPduR4m+WLHDv4SlwEhFHLNvyHfveKnQ==", "dependencies": { "@microbit/microbit-universal-hex": "^0.2.2", "@types/web-bluetooth": "^0.0.20", diff --git a/package.json b/package.json index a54378765..7d03b769a 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@microbit-foundation/ml-header-generator": "^0.3.8", "@microbit-foundation/react-code-view": "^5.0.2", "@microbit-foundation/react-editor-embed": "^1.0.0-controller.mode.47", - "@microbit/microbit-connection": "^0.0.0-alpha.18", + "@microbit/microbit-connection": "^0.0.0-alpha.19", "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", diff --git a/src/components/GestureNameGridItem.tsx b/src/components/GestureNameGridItem.tsx index ecc64f5aa..de7cde155 100644 --- a/src/components/GestureNameGridItem.tsx +++ b/src/components/GestureNameGridItem.tsx @@ -35,7 +35,7 @@ const GestureNameGridItem = ({ id, selected = false, readOnly = false, - isTriggered = false, + isTriggered = undefined, }: GestureNameGridItemProps) => { const intl = useIntl(); const toast = useToast(); @@ -100,12 +100,7 @@ const GestureNameGridItem = ({ - - ; + ; {!readOnly && ( )} diff --git a/src/components/LedIcon.tsx b/src/components/LedIcon.tsx index 9036f1e23..80d24caf7 100644 --- a/src/components/LedIcon.tsx +++ b/src/components/LedIcon.tsx @@ -4,17 +4,11 @@ import { icons, LedIconType } from "../utils/icons"; interface LedIconProps { icon: LedIconType; - isTestModelPage?: boolean; isTriggered?: boolean; size?: string | number; } -const LedIcon = ({ - icon, - isTestModelPage, - isTriggered, - size = 20, -}: LedIconProps) => { +const LedIcon = ({ icon, isTriggered, size = 20 }: LedIconProps) => { const iconData = icons[icon]; return ( @@ -25,8 +19,7 @@ const LedIcon = ({ ); })} @@ -61,15 +54,10 @@ const turnOff = keyframes` interface LedIconRowProps { data: string; - isTestModelPage: boolean; - isTriggered: boolean; + isTriggered?: boolean; } -const LedIconRow = ({ - data, - isTestModelPage, - isTriggered, -}: LedIconRowProps) => { +const LedIconRow = ({ data, isTriggered }: LedIconRowProps) => { const turnOnAnimation = `${turnOn} 200ms ease`; const turnOffAnimation = `${turnOff} 200ms ease`; const getBgColor = useCallback( @@ -77,15 +65,15 @@ const LedIconRow = ({ if (!isOn) { return "gray.200"; } - if (isTestModelPage && isTriggered) { + if (typeof isTriggered === "boolean" && isTriggered) { return "green.500"; } - if (isTestModelPage && !isTriggered) { + if (typeof isTriggered === "boolean" && !isTriggered) { return "gray.600"; } return "brand.500"; }, - [isTriggered, isTestModelPage] + [isTriggered] ); return ( diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index be6579e15..4a3da81b7 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -4,17 +4,20 @@ import { MdBolt } from "react-icons/md"; import { FormattedMessage } from "react-intl"; import { ConnectionStatus } from "../connect-status-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; -import { getPredictedGesture, usePrediction } from "../ml-hooks"; import InfoToolTip from "./InfoToolTip"; import LedIcon from "./LedIcon"; import LiveGraph from "./LiveGraph"; -import { useGestureData } from "../gestures-hooks"; +import { Gesture } from "../gestures-hooks"; interface LiveGraphPanelProps { - isTestModelPage?: boolean; + predictedGesture?: Gesture | undefined; + showPredictedGesture?: boolean; } -const LiveGraphPanel = ({ isTestModelPage = false }: LiveGraphPanelProps) => { +const LiveGraphPanel = ({ + showPredictedGesture, + predictedGesture, +}: LiveGraphPanelProps) => { const { actions, status } = useConnectionStage(); const parentPortalRef = useRef(null); const isReconnecting = @@ -36,10 +39,6 @@ const LiveGraphPanel = ({ isTestModelPage = false }: LiveGraphPanelProps) => { }; }, [actions.reconnect, actions.startConnect, status]); - const confidences = usePrediction(); - const [gestures] = useGestureData(); - const predictedGesture = getPredictedGesture(gestures, confidences); - return ( { right={0} px={7} py={4} - w={`calc(100% - ${isTestModelPage ? "160px" : "0"})`} + w={`calc(100% - ${showPredictedGesture ? "160px" : "0"})`} > @@ -91,13 +90,12 @@ const LiveGraphPanel = ({ isTestModelPage = false }: LiveGraphPanelProps) => { - {isTestModelPage && ( + {showPredictedGesture && ( )} diff --git a/src/components/TestModelGridView.tsx b/src/components/TestModelGridView.tsx index ee5970ed8..1a047f457 100644 --- a/src/components/TestModelGridView.tsx +++ b/src/components/TestModelGridView.tsx @@ -17,9 +17,8 @@ import React, { useCallback } from "react"; import { RiArrowRightLine } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import { useConnectionStage } from "../connection-stage-hooks"; -import { useGestureActions, useGestureData } from "../gestures-hooks"; -import { mlSettings } from "../ml"; -import { getPredictedGesture, usePrediction } from "../ml-hooks"; +import { Gesture, useGestureActions, useGestureData } from "../gestures-hooks"; +import { Confidences, mlSettings } from "../ml"; import { getMakeCodeLang, useSettings } from "../settings"; import { useMakeCodeProject } from "../user-projects-hooks"; import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; @@ -53,7 +52,15 @@ const headings = [ }, ]; -const TestModelGridView = () => { +interface TestModelGridViewProps { + predictedGesture: Gesture | undefined; + confidences: Confidences | undefined; +} + +const TestModelGridView = ({ + confidences, + predictedGesture, +}: TestModelGridViewProps) => { const intl = useIntl(); const editCodeDialogDisclosure = useDisclosure(); const [gestures] = useGestureData(); @@ -63,8 +70,6 @@ const TestModelGridView = () => { const { hasStoredProject, userProject, setUserProject } = useMakeCodeProject(); - const confidences = usePrediction(); - const predictedGesture = getPredictedGesture(gestures, confidences); const predicationLabel = predictedGesture?.name ?? intl.formatMessage({ @@ -164,6 +169,9 @@ const TestModelGridView = () => { icon, requiredConfidence: threshold, } = gesture; + const isTriggered = predictedGesture + ? predictedGesture.ID === ID + : false; return ( { name={name} icon={icon} readOnly={true} - isTriggered={predictedGesture?.ID === ID} + isTriggered={isTriggered} /> @@ -181,7 +189,7 @@ const TestModelGridView = () => { requiredConfidence={ threshold ?? mlSettings.defaultRequiredConfidence } - isTriggered={predictedGesture?.ID === ID} + isTriggered={isTriggered} /> { const [{ stage }] = useMlStatus(); - + const confidences = usePrediction(); + const [gestureData] = useGestureData(); + const predictedGesture = getPredictedGesture(gestureData, confidences); return ( {stage === MlStage.TrainingComplete ? ( <> - - + + ) : ( From 76275ccf69577dc9dbb049c0777cc1f94db70e5c Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:28:33 +0100 Subject: [PATCH 118/172] Show the detected action icon on the micro:bit (#296) We're going to discuss whether this is valuable but easier to do it. --- src/components/LiveGraphPanel.tsx | 10 ++--- src/components/TestModelGridView.tsx | 24 +++++------- src/connect-actions.ts | 20 ++++++++++ src/ml-hooks.tsx | 55 ++++++++++++++++++---------- src/ml.ts | 4 +- src/pages/TestModelPage.tsx | 16 +++----- src/utils/icons.test.ts | 17 +++++++++ src/utils/icons.ts | 13 +++++++ 8 files changed, 105 insertions(+), 54 deletions(-) create mode 100644 src/utils/icons.test.ts diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 4a3da81b7..656328a42 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -10,13 +10,13 @@ import LiveGraph from "./LiveGraph"; import { Gesture } from "../gestures-hooks"; interface LiveGraphPanelProps { - predictedGesture?: Gesture | undefined; + detected?: Gesture | undefined; showPredictedGesture?: boolean; } const LiveGraphPanel = ({ showPredictedGesture, - predictedGesture, + detected, }: LiveGraphPanelProps) => { const { actions, status } = useConnectionStage(); const parentPortalRef = useRef(null); @@ -92,11 +92,7 @@ const LiveGraphPanel = ({ {showPredictedGesture && ( - + )} diff --git a/src/components/TestModelGridView.tsx b/src/components/TestModelGridView.tsx index 1a047f457..db78f1e7c 100644 --- a/src/components/TestModelGridView.tsx +++ b/src/components/TestModelGridView.tsx @@ -17,8 +17,9 @@ import React, { useCallback } from "react"; import { RiArrowRightLine } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import { useConnectionStage } from "../connection-stage-hooks"; -import { Gesture, useGestureActions, useGestureData } from "../gestures-hooks"; -import { Confidences, mlSettings } from "../ml"; +import { useGestureActions, useGestureData } from "../gestures-hooks"; +import { mlSettings } from "../ml"; +import { PredictionResult } from "../ml-hooks"; import { getMakeCodeLang, useSettings } from "../settings"; import { useMakeCodeProject } from "../user-projects-hooks"; import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; @@ -53,14 +54,11 @@ const headings = [ ]; interface TestModelGridViewProps { - predictedGesture: Gesture | undefined; - confidences: Confidences | undefined; + prediction: PredictionResult | undefined; } -const TestModelGridView = ({ - confidences, - predictedGesture, -}: TestModelGridViewProps) => { +const TestModelGridView = ({ prediction }: TestModelGridViewProps) => { + const { detected, confidences } = prediction ?? {}; const intl = useIntl(); const editCodeDialogDisclosure = useDisclosure(); const [gestures] = useGestureData(); @@ -70,8 +68,8 @@ const TestModelGridView = ({ const { hasStoredProject, userProject, setUserProject } = useMakeCodeProject(); - const predicationLabel = - predictedGesture?.name ?? + const detectedLabel = + detected?.name ?? intl.formatMessage({ id: "content.model.output.estimatedGesture.none", }); @@ -127,7 +125,7 @@ const TestModelGridView = ({ @@ -169,9 +167,7 @@ const TestModelGridView = ({ icon, requiredConfidence: threshold, } = gesture; - const isTriggered = predictedGesture - ? predictedGesture.ID === ID - : false; + const isTriggered = detected ? detected.ID === ID : false; return ( => { + await this.bluetooth.setLedMatrix(iconToLedMatrix(icon)); + }; + + resetIcon = async (): Promise => { + // Assuming this succeeds we should be back in the connected state. + await this.setIcon("Happy"); + }; + + clearIcon = async (): Promise => { + await this.bluetooth.setLedMatrix([ + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + ]); + }; + disconnect = async () => { await this.bluetooth.disconnect(); await this.radioBridge.disconnect(); diff --git a/src/ml-hooks.tsx b/src/ml-hooks.tsx index 32a4da4f7..8eb667a50 100644 --- a/src/ml-hooks.tsx +++ b/src/ml-hooks.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useBufferedData } from "./buffered-data-hooks"; import { useConnectActions } from "./connect-actions-hooks"; import { ConnectionStatus, useConnectStatus } from "./connect-status-hooks"; @@ -20,24 +20,25 @@ export const useMlActions = () => { return actions; }; +export interface PredictionResult { + confidences: Confidences; + detected: Gesture | undefined; +} + export const usePrediction = () => { const buffer = useBufferedData(); const logging = useLogging(); const [status] = useMlStatus(); const [connectStatus] = useConnectStatus(); const connection = useConnectActions(); - const [confidences, setConfidences] = useState(); + const [prediction, setPrediction] = useState(); const [gestureData] = useGestureData(); - // Avoid re-renders due to threshold changes which update gestureData. - // We could consider storing them elsewhere, perhaps with the model. - const classificationIdsRecalculated = gestureData.data.map((d) => d.ID); - const classificationIdsKey = JSON.stringify(classificationIdsRecalculated); - const classificationIds: number[] = useMemo( - () => JSON.parse(classificationIdsKey) as number[], - [classificationIdsKey] - ); - + // Use a ref to prevent restarting the effect every time thesholds change. + // We only use the ref's value during the setInterval callback not render. + // We can avoid this by storing the thresolds separately in state, even if we unify them when serializing them. + const gestureDataRef = useRef(gestureData); + gestureDataRef.current = gestureData; useEffect(() => { if ( status.stage !== MlStage.TrainingComplete || @@ -50,14 +51,27 @@ export const usePrediction = () => { const input = { model: status.model, data: buffer.getSamples(startTime), - classificationIds, + classificationIds: gestureDataRef.current.data.map((g) => g.ID), }; if (input.data.x.length > mlSettings.minSamples) { - const predictionResult = await predict(input); - if (predictionResult.error) { - logging.error(predictionResult.detail); + const result = await predict(input); + if (result.error) { + logging.error(result.detail); } else { - setConfidences(predictionResult.confidences); + const { confidences } = result; + const detected = getDetectedGesture( + gestureDataRef.current, + result.confidences + ); + if (detected) { + connection.setIcon(detected.icon).catch((e) => logging.error(e)); + } else { + connection.clearIcon().catch((e) => logging.error(e)); + } + setPrediction({ + detected, + confidences, + }); } } }; @@ -66,15 +80,16 @@ export const usePrediction = () => { 1000 / mlSettings.updatesPrSecond ); return () => { - setConfidences(undefined); + connection.resetIcon().catch((e) => logging.error(e)); + setPrediction(undefined); clearInterval(interval); }; - }, [connection, classificationIds, logging, status, connectStatus, buffer]); + }, [connection, logging, status, connectStatus, buffer]); - return confidences; + return prediction; }; -export const getPredictedGesture = ( +export const getDetectedGesture = ( gestureData: GestureContextState, confidences: Confidences | undefined ): Gesture | undefined => { diff --git a/src/ml.ts b/src/ml.ts index 049d89bd0..a862ee790 100644 --- a/src/ml.ts +++ b/src/ml.ts @@ -130,7 +130,7 @@ interface PredictInput { export type Confidences = Record; -export type PredictionResult = +export type ConfidencesResult = | { error: true; detail: unknown } | { error: false; confidences: Confidences }; @@ -139,7 +139,7 @@ export const predict = async ({ model, data, classificationIds, -}: PredictInput): Promise => { +}: PredictInput): Promise => { const input = applyFilters(data); const prediction = model.predict(tf.tensor([input])) as tf.Tensor; try { diff --git a/src/pages/TestModelPage.tsx b/src/pages/TestModelPage.tsx index 3d50d9910..332ee23c8 100644 --- a/src/pages/TestModelPage.tsx +++ b/src/pages/TestModelPage.tsx @@ -3,27 +3,21 @@ import LiveGraphPanel from "../components/LiveGraphPanel"; import TabView from "../components/TabView"; import TestModelGridView from "../components/TestModelGridView"; import TrainModelFirstView from "../components/TrainModelFirstView"; -import { testModelConfig } from "../pages-config"; +import { usePrediction } from "../ml-hooks"; import { MlStage, useMlStatus } from "../ml-status-hooks"; -import { getPredictedGesture, usePrediction } from "../ml-hooks"; -import { useGestureData } from "../gestures-hooks"; +import { testModelConfig } from "../pages-config"; const TestModelPage = () => { const [{ stage }] = useMlStatus(); - const confidences = usePrediction(); - const [gestureData] = useGestureData(); - const predictedGesture = getPredictedGesture(gestureData, confidences); + const prediction = usePrediction(); return ( {stage === MlStage.TrainingComplete ? ( <> - + diff --git a/src/utils/icons.test.ts b/src/utils/icons.test.ts new file mode 100644 index 000000000..a21f22db7 --- /dev/null +++ b/src/utils/icons.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest"; +import { iconToLedMatrix } from "./icons"; + +const x = true; +const _ = false; + +describe("iconToLedMatrix", () => { + it("convers correctly", () => { + expect(iconToLedMatrix("Happy")).toEqual([ + [_, _, _, _, _], + [_, x, _, x, _], + [_, _, _, _, _], + [x, _, _, _, x], + [_, x, x, x, _], + ]); + }); +}); diff --git a/src/utils/icons.ts b/src/utils/icons.ts index b14f57d6d..2467b7454 100644 --- a/src/utils/icons.ts +++ b/src/utils/icons.ts @@ -1,3 +1,5 @@ +import { MicrobitWebBluetoothConnection } from "@microbit/microbit-connection"; + export const makecodeIcons = { Heart: "0101011111111110111000100", SmallHeart: "0000001010011100010000000", @@ -64,3 +66,14 @@ export const defaultIcons: MakeCodeIcon[] = [ "EighthNote", "Pitchfork", ]; + +// TODO: export this from the connection lib or give up and use boolean[][] +export type LedMatrix = Parameters< + MicrobitWebBluetoothConnection["setLedMatrix"] +>[0]; + +export const iconToLedMatrix = (icon: MakeCodeIcon): LedMatrix => { + return makecodeIcons[icon] + .match(/.{5}/g)! + .map((r) => r.split("").map((d) => d !== "0")) as LedMatrix; +}; From 38b994808925bc92a2f064f38113dee87345407a Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:12:27 +0100 Subject: [PATCH 119/172] Return focus to record button during walkthrough (#303) Also add heights to walkthrough svgs to avoid text shift --- src/components/AddDataGridRow.tsx | 26 ++++++++++++++++------ src/components/AddDataGridView.tsx | 27 +++++++++-------------- src/components/AddDataGridWalkThrough.tsx | 27 ++++++++++------------- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/components/AddDataGridRow.tsx b/src/components/AddDataGridRow.tsx index 07420ac35..6d2ce74db 100644 --- a/src/components/AddDataGridRow.tsx +++ b/src/components/AddDataGridRow.tsx @@ -2,6 +2,7 @@ import { GridItem } from "@chakra-ui/react"; import { useCallback } from "react"; import { useIntl } from "react-intl"; import { GestureData, useGestureActions } from "../gestures-hooks"; +import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; import DataRecordingGridItem from "./DataRecordingGridItem"; import GestureNameGridItem from "./GestureNameGridItem"; @@ -10,6 +11,7 @@ interface AddDataGridRowProps { selected: boolean; onSelectRow: () => void; startRecording: () => void; + showWalkThrough: boolean; } const AddDataGridRow = ({ @@ -17,6 +19,7 @@ const AddDataGridRow = ({ selected, onSelectRow, startRecording, + showWalkThrough, }: AddDataGridRowProps) => { const intl = useIntl(); const actions = useGestureActions(); @@ -43,16 +46,25 @@ const AddDataGridRow = ({ selected={selected} readOnly={false} /> - {gesture.name.length > 0 || gesture.recordings.length > 0 ? ( - ) : ( - // Empty grid item to fill column space - + <> + {gesture.name.length > 0 || gesture.recordings.length > 0 ? ( + + ) : ( + // Empty grid item to fill column space + + )} + )} ); diff --git a/src/components/AddDataGridView.tsx b/src/components/AddDataGridView.tsx index b0e39f9ef..1e81568aa 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/AddDataGridView.tsx @@ -1,12 +1,11 @@ import { Grid, GridProps, useDisclosure } from "@chakra-ui/react"; +import { ButtonEvent } from "@microbit/microbit-connection"; import { useEffect, useMemo, useState } from "react"; +import { useConnectActions } from "../connect-actions-hooks"; import { useGestureData } from "../gestures-hooks"; import AddDataGridRow from "./AddDataGridRow"; -import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; import HeadingGrid from "./HeadingGrid"; import RecordingDialog from "./RecordingDialog"; -import { useConnectActions } from "../connect-actions-hooks"; -import { ButtonEvent } from "@microbit/microbit-connection"; const gridCommonProps: Partial = { gridTemplateColumns: "290px 1fr", @@ -77,22 +76,16 @@ const AddDataGridView = () => { flexGrow={1} h={0} > - {showWalkThrough ? ( - ( + setSelectedGestureIdx(idx)} startRecording={onOpen} + showWalkThrough={showWalkThrough} /> - ) : ( - gestures.data.map((g, idx) => ( - setSelectedGestureIdx(idx)} - startRecording={onOpen} - /> - )) - )} + ))} ); diff --git a/src/components/AddDataGridWalkThrough.tsx b/src/components/AddDataGridWalkThrough.tsx index 57b58078c..72813e6ac 100644 --- a/src/components/AddDataGridWalkThrough.tsx +++ b/src/components/AddDataGridWalkThrough.tsx @@ -1,10 +1,9 @@ -import { GridItem, VStack, Image, Text, HStack } from "@chakra-ui/react"; +import { GridItem, HStack, Image, Text, VStack } from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; import { GestureData } from "../gestures-hooks"; -import greetingEmojiWithArrowImage from "../images/greeting-emoji-with-arrow.svg"; import upCurveArrowImage from "../images/curve-arrow-up.svg"; -import GestureNameGridItem from "./GestureNameGridItem"; +import greetingEmojiWithArrowImage from "../images/greeting-emoji-with-arrow.svg"; import DataRecordingGridItem from "./DataRecordingGridItem"; -import { FormattedMessage } from "react-intl"; interface AddDataGridWalkThrough { gesture: GestureData; @@ -17,17 +16,15 @@ const AddDataGridWalkThrough = ({ }: AddDataGridWalkThrough) => { return ( <> - {gesture.name.length === 0 ? ( - + @@ -43,9 +40,9 @@ const AddDataGridWalkThrough = ({ {/* Empty grid item to fill first column of grid */} - - - + + + From 25fc7123fbfdbb36cd45f73c7a9ab631324f88ca Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:18:01 +0100 Subject: [PATCH 120/172] Remove vertical centering for homepage content (#304) --- src/pages/HomePage.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 9f5aff36d..877489cda 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -10,7 +10,13 @@ const HomePage = () => { return ( - + From 01336565619309cf676d7156710e51412853d6a2 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Thu, 5 Sep 2024 09:25:33 +0100 Subject: [PATCH 121/172] Only show prototype warning bar in staging or production (#305) --- src/components/DefaultPageLayout.tsx | 3 ++- src/flags.test.ts | 10 +++++++--- src/flags.ts | 12 ++++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/DefaultPageLayout.tsx b/src/components/DefaultPageLayout.tsx index 32ecbbe33..73e470371 100644 --- a/src/components/DefaultPageLayout.tsx +++ b/src/components/DefaultPageLayout.tsx @@ -11,6 +11,7 @@ import ConnectionDialogs from "./ConnectionFlowDialogs"; import HelpMenu from "./HelpMenu"; import PrototypeVersionWarning from "./PrototypeVersionWarning"; import SettingsMenu from "./SettingsMenu"; +import { flags } from "../flags"; interface DefaultPageLayoutProps { titleId: string; @@ -65,7 +66,7 @@ const DefaultPageLayout = ({ } /> - + {flags.prototypeWarning && } {children} diff --git a/src/flags.test.ts b/src/flags.test.ts index 2e6fd3d6a..b67073de6 100644 --- a/src/flags.test.ts +++ b/src/flags.test.ts @@ -1,12 +1,16 @@ import { flagsForParams } from "./flags"; describe("flags", () => { - it("enables nothing in production", () => { + it("enables nothing in production except prototypeWarning flag", () => { const params = new URLSearchParams([]); const flags = flagsForParams("production", params); - expect(Object.values(flags).every((x) => !x)).toEqual(true); + expect( + Object.entries(flags).every( + ([flag, status]) => !status || (flag === "prototypeWarning" && status) + ) + ).toEqual(true); }); it("enables by stage", () => { @@ -20,7 +24,7 @@ describe("flags", () => { it("enable specific flag", () => { const params = new URLSearchParams([["flag", "exampleOptInA"]]); - const flags = flagsForParams("production", params); + const flags = flagsForParams("local", params); expect( Object.entries(flags).every( diff --git a/src/flags.ts b/src/flags.ts index 2b50c1c50..c3c762404 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -11,8 +11,15 @@ import { Stage, stage as stageFromEnvironment } from "./environment"; * A union of the flag names (alphabetical order). */ export type Flag = - // Example flags used for testing. - "exampleOptInA" | "exampleOptInB"; + /** + * Flag to add a prototype warning. Enabled for staging site and production stages. + */ + | "prototypeWarning" + /** + * Example flags used for testing. + */ + | "exampleOptInA" + | "exampleOptInB"; interface FlagMetadata { defaultOnStages: Stage[]; @@ -21,6 +28,7 @@ interface FlagMetadata { const allFlags: FlagMetadata[] = [ // Alphabetical order. + { name: "prototypeWarning", defaultOnStages: ["staging", "production"] }, { name: "exampleOptInA", defaultOnStages: ["review", "staging"] }, { name: "exampleOptInB", defaultOnStages: [] }, ]; From 87f06e7b0b4676043a4f0c8af2cc35c71e797b17 Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:27:30 +0100 Subject: [PATCH 122/172] Save and load tensorflow model via IndexedDB (#300) * Save and load tensorflow model via IndexedDB * Clear model when starting new session --- src/components/StartResumeActions.tsx | 5 ++ src/ml-status-hooks.tsx | 100 +++++++++++++++----------- 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index 3d6f2b1ff..bf8bf518b 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -1,3 +1,4 @@ +import * as tf from "@tensorflow/tfjs"; import { Button, HStack, StackProps, useDisclosure } from "@chakra-ui/react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { FormattedMessage } from "react-intl"; @@ -7,6 +8,7 @@ import { createStepPageUrl } from "../urls"; import StartOverWarningDialog from "./StartOverWarningDialog"; import { useConnectionStage } from "../connection-stage-hooks"; import { ConnectionStatus } from "../connect-status-hooks"; +import { modelUrl } from "../ml-status-hooks"; const StartResumeActions = ({ ...props }: Partial) => { const gestureActions = useGestureActions(); @@ -31,6 +33,9 @@ const StartResumeActions = ({ ...props }: Partial) => { const handleStartNewSession = useCallback(() => { startOverWarningDialogDisclosure.onClose(); gestureActions.deleteAllGestures(); + tf.io.removeModel(modelUrl).catch(() => { + // Throws if there is no model to remove. + }); if (isConnected) { handleNavigateToAddData(); } else { diff --git a/src/ml-status-hooks.tsx b/src/ml-status-hooks.tsx index 43af32630..275295170 100644 --- a/src/ml-status-hooks.tsx +++ b/src/ml-status-hooks.tsx @@ -1,12 +1,15 @@ +import * as tf from "@tensorflow/tfjs"; +import { LayersModel } from "@tensorflow/tfjs"; import { ReactNode, createContext, useCallback, useContext, + useEffect, + useRef, useState, } from "react"; import { hasSufficientDataForTraining, useGestureData } from "./gestures-hooks"; -import { LayersModel } from "@tensorflow/tfjs"; export enum MlStage { RecordingData, @@ -41,56 +44,73 @@ export type MlStatus = >; }; -interface MlStatusState { - status: MlStatus; - hasTrainedBefore: boolean; -} - -type MlStatusContextValue = [MlStatusState, (status: MlStatusState) => void]; +type MlStatusContextValue = [MlStatus, (status: MlStatus) => void]; const MlStatusContext = createContext( undefined ); +export const useMlStatus = () => { + const mlState = useContext(MlStatusContext); + if (!mlState) { + throw new Error("Missing provider"); + } + return mlState; +}; + +export const modelUrl = "indexeddb://micro:bit-ml-tool-model"; + export const MlStatusProvider = ({ children }: { children: ReactNode }) => { const [gestureState] = useGestureData(); - const statusContextValue = useState({ - status: { - stage: hasSufficientDataForTraining(gestureState.data) - ? MlStage.NotTrained - : MlStage.InsufficientData, - }, - hasTrainedBefore: false, + const [mlState, setMlState] = useState({ + stage: hasSufficientDataForTraining(gestureState.data) + ? MlStage.NotTrained + : MlStage.InsufficientData, }); - return ( - - {children} - - ); -}; -export const useMlStatus = (): [MlStatus, (status: MlStatus) => void] => { - const statusContextValue = useContext(MlStatusContext); - if (!statusContextValue) { - throw new Error("Missing provider"); - } - const [state, setState] = statusContextValue; + const hasTrainedBefore = useRef(false); - const setStatus = useCallback( - (s: MlStatus) => { - const hasTrainedBefore = - s.stage === MlStage.TrainingComplete || state.hasTrainedBefore; + const setStatus = useCallback((s: MlStatus) => { + setMlState((prevState) => ({ ...prevState })); + if (s.stage === MlStage.TrainingComplete) { + s.model.save(modelUrl).catch(() => { + // IndexedDB not available? + }); + } else { + tf.io.removeModel(modelUrl).catch(() => { + // Throws if there is no model to remove. + }); + } - // Update to retrain instead of not trained if has trained before - const status = - hasTrainedBefore && s.stage === MlStage.NotTrained - ? ({ stage: MlStage.RetrainingNeeded } as const) - : s; + hasTrainedBefore.current = + s.stage === MlStage.TrainingComplete || hasTrainedBefore.current; - setState({ ...state, status, hasTrainedBefore }); - }, - [setState, state] - ); + // Update to retrain instead of not trained if has trained before + const status = + hasTrainedBefore.current && s.stage === MlStage.NotTrained + ? ({ stage: MlStage.RetrainingNeeded } as const) + : s; - return [state.status, setStatus]; + setMlState(status); + }, []); + + useEffect(() => { + tf.loadLayersModel(modelUrl) + .then((model) => { + setMlState({ + stage: MlStage.TrainingComplete, + model, + }); + hasTrainedBefore.current = true; + }) + .catch(() => { + // Throws if there is no model to load. + }); + }, []); + + return ( + + {children} + + ); }; From 5c2fb6efbea414600c6c6cb694498cdcf4cb00bd Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:01:28 +0100 Subject: [PATCH 123/172] Upgrade MakeCode extension and update filenames (#306) --- src/makecode/generate-custom-scripts.ts | 4 ++-- src/makecode/generate-main-scripts.test.ts | 8 +++---- src/makecode/generate-main-scripts.ts | 4 ++-- src/user-projects-hooks.tsx | 25 +++++++++++----------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/makecode/generate-custom-scripts.ts b/src/makecode/generate-custom-scripts.ts index 0692b8572..974807fe6 100644 --- a/src/makecode/generate-custom-scripts.ts +++ b/src/makecode/generate-custom-scripts.ts @@ -44,7 +44,7 @@ const arrayBufferToHexString = (input: Uint8Array): string => .join("") .toUpperCase(); -export const generateCustomTs = (gs: GestureData[], m: LayersModel) => { +export const getAutogeneratedTs = (gs: GestureData[], m: LayersModel) => { const customHeaderBlob = generateBlob({ samples_period: 25, samples_length: 80, @@ -84,7 +84,7 @@ ${createEventListeners(actionNames)} `; }; -export const generateCustomJson = (gs: GestureData[]) => { +export const getDataJson = (gs: GestureData[]) => { return JSON.stringify( gs.map((g) => ({ ID: g.ID, diff --git a/src/makecode/generate-main-scripts.test.ts b/src/makecode/generate-main-scripts.test.ts index 65e6404ca..732ea70b9 100644 --- a/src/makecode/generate-main-scripts.test.ts +++ b/src/makecode/generate-main-scripts.test.ts @@ -8,13 +8,13 @@ */ import { Gesture } from "../gestures-hooks"; -import { generateMainScript } from "./generate-main-scripts"; +import { getMainScript } from "./generate-main-scripts"; describe("test generateMainScripts", () => { it("generates xml blocks", () => { const expected = ` - + ml.event.Name IconNames.Heart @@ -22,13 +22,13 @@ describe("test generateMainScripts", () => { `; const gs: Gesture[] = [{ name: "name", icon: "Heart", ID: 12 }]; - expect(generateMainScript(gs, "blocks")).toEqual(expected); + expect(getMainScript(gs, "blocks")).toEqual(expected); }); it("generates js", () => { const expected = " ml.onStart(ml.event.Name, function () {basic.showIcon(IconNames.Heart)})"; const gs: Gesture[] = [{ name: "name", icon: "Heart", ID: 12 }]; - expect(generateMainScript(gs, "javascript")).toContain(expected); + expect(getMainScript(gs, "javascript")).toContain(expected); }); }); diff --git a/src/makecode/generate-main-scripts.ts b/src/makecode/generate-main-scripts.ts index 9e611222b..aab71690b 100644 --- a/src/makecode/generate-main-scripts.ts +++ b/src/makecode/generate-main-scripts.ts @@ -16,7 +16,7 @@ interface BlockPos { } const onMLEventBlock = (name: string, children: string, pos: BlockPos) => ` - + ml.event.${name} ${children} @@ -71,7 +71,7 @@ const getMakeCodeGestureConfigs = (gs: Gesture[]) => { })); }; -export const generateMainScript = (gs: Gesture[], lang: Language) => { +export const getMainScript = (gs: Gesture[], lang: Language) => { const configs = getMakeCodeGestureConfigs(gs); const s = statements[lang]; const initPos = { x: 0, y: 0 }; diff --git a/src/user-projects-hooks.tsx b/src/user-projects-hooks.tsx index 5ddb7e2a9..e31bc1b4a 100644 --- a/src/user-projects-hooks.tsx +++ b/src/user-projects-hooks.tsx @@ -1,8 +1,8 @@ import { GestureData, useGestureData } from "./gestures-hooks"; -import { generateMainScript } from "./makecode/generate-main-scripts"; +import { getMainScript } from "./makecode/generate-main-scripts"; import { - generateCustomJson, - generateCustomTs, + getDataJson, + getAutogeneratedTs, } from "./makecode/generate-custom-scripts"; import { TrainingCompleteMlStatus, useMlStatus } from "./ml-status-hooks"; import { @@ -51,8 +51,8 @@ const useUserProjects = (): UserProjectsContextValue => { enum ProjectFilenames { MainTs = "main.ts", MainBlocks = "main.blocks", - CustomTs = "Machine_Learning_POC.ts", - CustomJson = "Machine_Learning_POC.json", + Autogenerated = "autogenerated.ts", + DataJson = "data.json", PxtJson = "pxt.json", ReadMe = "README.md", } @@ -64,8 +64,7 @@ const pxt = { core: "*", microphone: "*", radio: "*", // needed for compiling - "Machine Learning POC": - "github:microbit-foundation/pxt-ml-extension-poc#9f843b8f94d0cc5e6c5ec0d020463fb080caf724", + "machine-learning": "github:microbit-foundation/pxt-microbit-ml#v0.4.2", }, files: Object.values(ProjectFilenames), }; @@ -79,10 +78,10 @@ export const useMakeCodeProject = () => { const defaultProjectText = useMemo(() => { return { - [ProjectFilenames.MainTs]: generateMainScript(gestures, "javascript"), - [ProjectFilenames.MainBlocks]: generateMainScript(gestures, "blocks"), - [ProjectFilenames.CustomTs]: generateCustomTs(gestures, model), - [ProjectFilenames.CustomJson]: generateCustomJson(gestures), + [ProjectFilenames.MainTs]: getMainScript(gestures, "javascript"), + [ProjectFilenames.MainBlocks]: getMainScript(gestures, "blocks"), + [ProjectFilenames.Autogenerated]: getAutogeneratedTs(gestures, model), + [ProjectFilenames.DataJson]: getDataJson(gestures), [ProjectFilenames.ReadMe]: "", [ProjectFilenames.PxtJson]: JSON.stringify(pxt), }; @@ -94,8 +93,8 @@ export const useMakeCodeProject = () => { return { text: { ...defaultProjectText, - [ProjectFilenames.MainTs]: generateMainScript(gs, "javascript"), - [ProjectFilenames.MainBlocks]: generateMainScript(gs, "blocks"), + [ProjectFilenames.MainTs]: getMainScript(gs, "javascript"), + [ProjectFilenames.MainBlocks]: getMainScript(gs, "blocks"), }, }; }, From c2f108bfa8eaf0d7364e631139f73aabe88b5220 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:42:06 +0100 Subject: [PATCH 124/172] Remove training page and tabs (#299) In favour of using training model dialog flow. - Renamed "Test model" -> "Testing model" page - Renamed "Add data" -> "Data samples" page - Includes removing of insufficient data and retraining needed ML status --- lang/ui.en.json | 72 ++++------- src/App.tsx | 47 +++---- src/components/AddDataGridRow.tsx | 4 +- src/components/BackArrow.tsx | 16 +-- ...ataGridView.tsx => DataSampleGridView.tsx} | 8 +- src/components/DefaultPageLayout.tsx | 23 +++- src/components/HeadingGrid.tsx | 20 +-- src/components/LiveGraphPanel.tsx | 7 +- src/components/MoreMenuButton.tsx | 44 +++++++ src/components/StartResumeActions.tsx | 5 +- src/components/TabView.tsx | 43 ------- ...lGridView.tsx => TestingModelGridView.tsx} | 76 ++++++++---- src/components/TrainModelFirstView.tsx | 85 ------------- src/components/TrainModelFlowDialogs.tsx | 21 ++++ src/components/TrainModelIntroDialog.tsx | 72 +++++++++++ .../TrainingModelProgressDialog.tsx | 51 ++++++++ src/components/TrainingStatusDialog.tsx | 48 ++++++++ src/components/TrainingStatusView.tsx | 116 ------------------ src/messages/ui.en.json | 84 +++++-------- src/ml-status-hooks.tsx | 36 ++---- src/pages-config.ts | 49 +++----- .../{AddDataPage.tsx => DataSamplesPage.tsx} | 88 +++++++------ src/pages/HomePage.tsx | 27 +++- src/pages/TestModelPage.tsx | 31 ----- src/pages/TestingModelPage.tsx | 50 ++++++++ src/pages/TrainModelPage.tsx | 38 ------ src/train-model-dialog-hooks.tsx | 84 +++++++++++++ src/urls.ts | 5 +- 28 files changed, 650 insertions(+), 600 deletions(-) rename src/components/{AddDataGridView.tsx => DataSampleGridView.tsx} (93%) create mode 100644 src/components/MoreMenuButton.tsx delete mode 100644 src/components/TabView.tsx rename src/components/{TestModelGridView.tsx => TestingModelGridView.tsx} (77%) delete mode 100644 src/components/TrainModelFirstView.tsx create mode 100644 src/components/TrainModelFlowDialogs.tsx create mode 100644 src/components/TrainModelIntroDialog.tsx create mode 100644 src/components/TrainingModelProgressDialog.tsx create mode 100644 src/components/TrainingStatusDialog.tsx delete mode 100644 src/components/TrainingStatusView.tsx rename src/pages/{AddDataPage.tsx => DataSamplesPage.tsx} (65%) delete mode 100644 src/pages/TestModelPage.tsx create mode 100644 src/pages/TestingModelPage.tsx delete mode 100644 src/pages/TrainModelPage.tsx create mode 100644 src/train-model-dialog-hooks.tsx diff --git a/lang/ui.en.json b/lang/ui.en.json index 32ae26b2c..e8d2c2165 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -75,6 +75,10 @@ "defaultMessage": "Back", "description": "Back button text" }, + "back-to-data-samples-action": { + "defaultMessage": "Back to data samples", + "description": "Back button text" + }, "cancel-action": { "defaultMessage": "Cancel", "description": "Cancel button text" @@ -543,18 +547,6 @@ "defaultMessage": "machine learning tool", "description": "" }, - "content.model.addData": { - "defaultMessage": "Add data", - "description": "" - }, - "content.model.notEnoughDataInfoBody1": { - "defaultMessage": "You cannot test the model until it has been trained.", - "description": "" - }, - "content.model.notEnoughDataInfoBody2": { - "defaultMessage": "You need at least 3 data samples for 2 actions to train a model.", - "description": "" - }, "content.model.output.action.descriptionBody": { "defaultMessage": "The type of movement you want the machine learning tool to recognise e.g. ‘wave’ or ‘clap’.", "description": "" @@ -611,26 +603,10 @@ "defaultMessage": "Recognition Point:", "description": "" }, - "content.model.retrainModelBody": { - "defaultMessage": "You need to train your model with the current set of data samples before you can test it.", - "description": "" - }, - "content.model.trainModelBody": { - "defaultMessage": "You need to train your model before you can test it.", - "description": "" - }, - "content.model.trainModelFirstHeading": { - "defaultMessage": "Test model", - "description": "" - }, "content.trainer.description": { "defaultMessage": "The computer program spots patterns or differences in your data samples, and uses these to build a mathematical model that allows the micro:bit machine learning tool to recognise different actions when you move your micro:bit.", "description": "" }, - "content.trainer.enoughdata.title": { - "defaultMessage": "Status: You've collected enough data to train the model.", - "description": "" - }, "content.trainer.failure.body": { "defaultMessage": "The training did not result in a usable model. The reason for this is most likely the data used for training. If the data samples for different actions are too similar, this can result in issues in the training process.", "description": "" @@ -652,7 +628,7 @@ "description": "" }, "content.trainer.training.title": { - "defaultMessage": "Status: Training the model in progress.", + "defaultMessage": "Training model...", "description": "" }, "cookies-action": { @@ -667,6 +643,10 @@ "defaultMessage": "Copy", "description": "Copy button text" }, + "data-samples-title": { + "defaultMessage": "Data samples", + "description": "Data samples page title" + }, "disconnectedWarning.bluetooth1": { "defaultMessage": "The connection to the micro:bit has been lost.", "description": "" @@ -703,6 +683,10 @@ "defaultMessage": "micro:bit 1 connection lost", "description": "" }, + "dont-show-again": { + "defaultMessage": "Don't show this again", + "description": "Text to never show a dialog again" + }, "edit-in-makecode-action": { "defaultMessage": "Edit in MakeCode", "description": "Edit in MakeCode button text" @@ -791,10 +775,6 @@ "defaultMessage": "Status: Your model is trained.", "description": "" }, - "menu.trainer.addDataButton": { - "defaultMessage": "Add data", - "description": "" - }, "menu.trainer.addMoreDataButton": { "defaultMessage": "Add more data", "description": "" @@ -807,22 +787,14 @@ "defaultMessage": " Select the ‘Connect’ button to connect a micro:bit.", "description": "" }, - "menu.trainer.notEnoughDataHeader1": { - "defaultMessage": "Status: You don't have enough data.", - "description": "" - }, - "menu.trainer.notEnoughDataInfoBody": { - "defaultMessage": "You need at least 3 data samples for 2 actions to train the model.", - "description": "" - }, - "menu.trainer.testModelButton": { - "defaultMessage": "Test model", - "description": "" - }, "menu.trainer.trainModelButton": { "defaultMessage": "Train model", "description": "" }, + "more-edit-in-makecode-options": { + "defaultMessage": "More edit in MakeCode options", + "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" + }, "not-found": { "defaultMessage": "Machine learning tool home page", "description": "Page not found link text" @@ -843,10 +815,6 @@ "defaultMessage": "Low quality connection", "description": "" }, - "performanceWarning.ignore": { - "defaultMessage": "Don't show this again", - "description": "" - }, "performanceWarning.nextButton": { "defaultMessage": "Next", "description": "" @@ -1023,6 +991,10 @@ "defaultMessage": "Test model", "description": "Test model step title" }, + "testing-model-title": { + "defaultMessage": "Testing model", + "description": "Testing model page title" + }, "train-model-intro-description": { "defaultMessage": "Ask the computer to use your training samples to train the machine learning model to recognise different actions.", "description": "Train model step home page description" @@ -1031,4 +1003,4 @@ "defaultMessage": "Train model", "description": "Train model step title" } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 152cc4531..60e9214d9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,10 +16,10 @@ import HomePage from "./pages/HomePage"; import { createHomePageUrl, createResourcePageUrl, - createStepPageUrl, + createSessionPageUrl, } from "./urls"; import { deployment, useDeployment } from "./deployment"; -import { resourcesConfig, stepsConfig } from "./pages-config"; +import { resourcesConfig, sessionPageConfigs } from "./pages-config"; import { LoggingProvider } from "./logging/logging-hooks"; import { GesturesProvider } from "./gestures-hooks"; import { MlStatusProvider } from "./ml-status-hooks"; @@ -28,6 +28,7 @@ import { ConnectProvider } from "./connect-actions-hooks"; import { ConnectStatusProvider } from "./connect-status-hooks"; import { BufferedDataProvider } from "./buffered-data-hooks"; import { UserProjectsProvider } from "./user-projects-hooks"; +import { TrainModelDialogProvider } from "./train-model-dialog-hooks"; export interface ProviderLayoutProps { children: ReactNode; @@ -44,23 +45,25 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - - - - - - - - {children} - - - - - - - - + + + + + + + + + + {children} + + + + + + + + + @@ -94,10 +97,10 @@ const createRouter = () => { path: createHomePageUrl(), element: , }, - ...stepsConfig.map((step) => { + ...sessionPageConfigs.map((config) => { return { - path: createStepPageUrl(step.id), - element: , + path: createSessionPageUrl(config.id), + element: , }; }), ...resourcesConfig.map((resource) => { diff --git a/src/components/AddDataGridRow.tsx b/src/components/AddDataGridRow.tsx index 6d2ce74db..704024cd5 100644 --- a/src/components/AddDataGridRow.tsx +++ b/src/components/AddDataGridRow.tsx @@ -14,7 +14,7 @@ interface AddDataGridRowProps { showWalkThrough: boolean; } -const AddDataGridRow = ({ +const DataSampleGridRow = ({ gesture, selected, onSelectRow, @@ -70,4 +70,4 @@ const AddDataGridRow = ({ ); }; -export default AddDataGridRow; +export default DataSampleGridRow; diff --git a/src/components/BackArrow.tsx b/src/components/BackArrow.tsx index 052acd01c..80dad60b0 100644 --- a/src/components/BackArrow.tsx +++ b/src/components/BackArrow.tsx @@ -1,16 +1,12 @@ -const BackArrow = () => ( - +import { Icon, IconProps } from "@chakra-ui/react"; + +const BackArrow = (props: IconProps) => ( + - + ); export default BackArrow; diff --git a/src/components/AddDataGridView.tsx b/src/components/DataSampleGridView.tsx similarity index 93% rename from src/components/AddDataGridView.tsx rename to src/components/DataSampleGridView.tsx index 1e81568aa..113f29ae2 100644 --- a/src/components/AddDataGridView.tsx +++ b/src/components/DataSampleGridView.tsx @@ -3,7 +3,7 @@ import { ButtonEvent } from "@microbit/microbit-connection"; import { useEffect, useMemo, useState } from "react"; import { useConnectActions } from "../connect-actions-hooks"; import { useGestureData } from "../gestures-hooks"; -import AddDataGridRow from "./AddDataGridRow"; +import DataSampleGridRow from "./AddDataGridRow"; import HeadingGrid from "./HeadingGrid"; import RecordingDialog from "./RecordingDialog"; @@ -26,7 +26,7 @@ const headings = [ }, ]; -const AddDataGridView = () => { +const DataSamplesGridView = () => { const [gestures] = useGestureData(); const [selectedGestureIdx, setSelectedGestureIdx] = useState(0); const selectedGesture = gestures.data[selectedGestureIdx] ?? gestures.data[0]; @@ -77,7 +77,7 @@ const AddDataGridView = () => { h={0} > {gestures.data.map((g, idx) => ( - { ); }; -export default AddDataGridView; +export default DataSamplesGridView; diff --git a/src/components/DefaultPageLayout.tsx b/src/components/DefaultPageLayout.tsx index 73e470371..7502ddc44 100644 --- a/src/components/DefaultPageLayout.tsx +++ b/src/components/DefaultPageLayout.tsx @@ -1,9 +1,10 @@ -import { Flex, HStack, IconButton, VStack } from "@chakra-ui/react"; +import { Flex, Heading, HStack, IconButton, VStack } from "@chakra-ui/react"; import { ReactNode, useCallback, useEffect } from "react"; import { RiHome2Line } from "react-icons/ri"; -import { useIntl } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router"; import { TOOL_NAME } from "../constants"; +import { flags } from "../flags"; import { createHomePageUrl } from "../urls"; import ActionBar from "./ActionBar"; import AppLogo from "./AppLogo"; @@ -11,18 +12,22 @@ import ConnectionDialogs from "./ConnectionFlowDialogs"; import HelpMenu from "./HelpMenu"; import PrototypeVersionWarning from "./PrototypeVersionWarning"; import SettingsMenu from "./SettingsMenu"; -import { flags } from "../flags"; +import TrainModelFlowDialogs from "./TrainModelFlowDialogs"; interface DefaultPageLayoutProps { titleId: string; children: ReactNode; toolbarItemsRight?: ReactNode; + toolbarItemsLeft?: ReactNode; + showPageTitle?: boolean; } const DefaultPageLayout = ({ titleId, children, toolbarItemsRight, + toolbarItemsLeft, + showPageTitle = false, }: DefaultPageLayoutProps) => { const intl = useIntl(); const navigate = useNavigate(); @@ -38,6 +43,7 @@ const DefaultPageLayout = ({ return ( <> + } + itemsCenter={ + <> + {showPageTitle && ( + + + + )} + + } + itemsLeft={toolbarItemsLeft || } itemsRight={ {toolbarItemsRight} diff --git a/src/components/HeadingGrid.tsx b/src/components/HeadingGrid.tsx index a24e79f77..bb08e7520 100644 --- a/src/components/HeadingGrid.tsx +++ b/src/components/HeadingGrid.tsx @@ -1,39 +1,29 @@ import { Grid, GridItem, GridProps, HStack, Text } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; import InfoToolTip from "./InfoToolTip"; -import { ReactNode } from "react"; interface GridColumnHeadingItemProps { titleId?: string; descriptionId?: string; } -interface HeadingGridProps extends Omit { +interface HeadingGridProps extends GridProps { headings: GridColumnHeadingItemProps[]; - children?: ReactNode; } -const HeadingGrid = ({ headings, children, ...props }: HeadingGridProps) => { +const HeadingGrid = ({ headings, ...props }: HeadingGridProps) => { return ( - {headings.map((props, idx) => { - return idx < headings.length - 1 ? ( - - ) : ( - - - {children} - - ); - })} + {headings.map((props, idx) => ( + + ))} ); }; diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 656328a42..292beb7bc 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -61,7 +61,12 @@ const LiveGraphPanel = ({ {status === ConnectionStatus.Connected ? ( - ) : ( diff --git a/src/components/MoreMenuButton.tsx b/src/components/MoreMenuButton.tsx new file mode 100644 index 000000000..e21a07a80 --- /dev/null +++ b/src/components/MoreMenuButton.tsx @@ -0,0 +1,44 @@ +/** + * (c) 2021 - 2022, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { + IconButton, + MenuButton, + MenuButtonProps, + ThemeTypings, +} from "@chakra-ui/react"; +import React, { ForwardedRef } from "react"; +import { MdMoreVert } from "react-icons/md"; + +interface MoreMenuButtonProps extends MenuButtonProps { + size?: ThemeTypings["components"]["Button"]["sizes"]; + variant?: string; +} + +const MoreMenuButton = React.forwardRef(function MoreMenuButtonInner( + { size, variant, ...props }: MoreMenuButtonProps, + ref: ForwardedRef +) { + return ( + + } + size={size} + {...props} + /> + ); +}); + +export default MoreMenuButton; diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index bf8bf518b..28b00ff33 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -4,10 +4,11 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; import { useGestureActions } from "../gestures-hooks"; -import { createStepPageUrl } from "../urls"; +import { createSessionPageUrl } from "../urls"; import StartOverWarningDialog from "./StartOverWarningDialog"; import { useConnectionStage } from "../connection-stage-hooks"; import { ConnectionStatus } from "../connect-status-hooks"; +import { SessionPageId } from "../pages-config"; import { modelUrl } from "../ml-status-hooks"; const StartResumeActions = ({ ...props }: Partial) => { @@ -27,7 +28,7 @@ const StartResumeActions = ({ ...props }: Partial) => { } = useConnectionStage(); const handleNavigateToAddData = useCallback(() => { - navigate(createStepPageUrl("add-data")); + navigate(createSessionPageUrl(SessionPageId.DataSamples)); }, [navigate]); const handleStartNewSession = useCallback(() => { diff --git a/src/components/TabView.tsx b/src/components/TabView.tsx deleted file mode 100644 index ef8bf68de..000000000 --- a/src/components/TabView.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Tab, TabIndicator, TabList, Tabs, VStack } from "@chakra-ui/react"; -import { useIntl } from "react-intl"; -import { StepId, stepsConfig } from "../pages-config"; -import { createStepPageUrl } from "../urls"; -import { useNavigate } from "react-router"; - -interface TabViewProps { - activeStep: StepId; -} -const TabView = ({ activeStep }: TabViewProps) => { - const intl = useIntl(); - const navigate = useNavigate(); - const activeIndex = stepsConfig.findIndex((s) => s.id === activeStep); - return ( - - - - {stepsConfig.map((step, idx) => ( - navigate(createStepPageUrl(step.id))} - key={step.id} - fontSize="lg" - fontWeight="bold" - px={12} - opacity={0.55} - _selected={{ opacity: 1 }} - > - {`${idx + 1}. ${intl.formatMessage({ id: `${step.id}-title` })}`} - - ))} - - - - - ); -}; - -export default TabView; diff --git a/src/components/TestModelGridView.tsx b/src/components/TestingModelGridView.tsx similarity index 77% rename from src/components/TestModelGridView.tsx rename to src/components/TestingModelGridView.tsx index db78f1e7c..2c698987f 100644 --- a/src/components/TestModelGridView.tsx +++ b/src/components/TestingModelGridView.tsx @@ -1,9 +1,14 @@ import { Button, + ButtonGroup, Grid, GridProps, HStack, Icon, + Menu, + MenuItem, + MenuList, + Portal, VStack, VisuallyHidden, useDisclosure, @@ -14,12 +19,12 @@ import { } from "@microbit-foundation/react-code-view"; import { EditorProject } from "@microbit-foundation/react-editor-embed"; import React, { useCallback } from "react"; -import { RiArrowRightLine } from "react-icons/ri"; +import { RiArrowRightLine, RiDeleteBin2Line } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import { useConnectionStage } from "../connection-stage-hooks"; import { useGestureActions, useGestureData } from "../gestures-hooks"; import { mlSettings } from "../ml"; -import { PredictionResult } from "../ml-hooks"; +import { usePrediction } from "../ml-hooks"; import { getMakeCodeLang, useSettings } from "../settings"; import { useMakeCodeProject } from "../user-projects-hooks"; import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; @@ -28,6 +33,8 @@ import CodeViewGridItem from "./CodeViewGridItem"; import EditCodeDialog from "./EditCodeDialog"; import GestureNameGridItem from "./GestureNameGridItem"; import HeadingGrid from "./HeadingGrid"; +import LiveGraphPanel from "./LiveGraphPanel"; +import MoreMenuButton from "./MoreMenuButton"; const gridCommonProps: Partial = { gridTemplateColumns: "290px 360px 40px auto", @@ -53,11 +60,8 @@ const headings = [ }, ]; -interface TestModelGridViewProps { - prediction: PredictionResult | undefined; -} - -const TestModelGridView = ({ prediction }: TestModelGridViewProps) => { +const TestingModelGridView = () => { + const prediction = usePrediction(); const { detected, confidences } = prediction ?? {}; const intl = useIntl(); const editCodeDialogDisclosure = useDisclosure(); @@ -128,20 +132,7 @@ const TestModelGridView = ({ prediction }: TestModelGridViewProps) => { values={{ action: detectedLabel }} /> - - - - - - + { + + + + + + + + + } + onClick={handleResetProject} + isDisabled={!hasStoredProject} + > + + + + + + + + + ); }; -export default TestModelGridView; +export default TestingModelGridView; diff --git a/src/components/TrainModelFirstView.tsx b/src/components/TrainModelFirstView.tsx deleted file mode 100644 index 70b036d81..000000000 --- a/src/components/TrainModelFirstView.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Button, Heading, Image, Text, VStack } from "@chakra-ui/react"; -import { useCallback } from "react"; -import { FormattedMessage } from "react-intl"; -import { useNavigate } from "react-router"; -import testModelImage from "../images/test_model_black.svg"; -import { StepId } from "../pages-config"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; -import { createStepPageUrl } from "../urls"; -import TrainingButton from "./TrainingButton"; - -interface TrainModelFirstViewConfig { - textIds: string[]; - navigateToStep: StepId; -} - -const getConfig = (status: MlStage): TrainModelFirstViewConfig => { - switch (status) { - case MlStage.InsufficientData: - return { - textIds: [ - "content.model.notEnoughDataInfoBody1", - "content.model.notEnoughDataInfoBody2", - ], - navigateToStep: "add-data", - }; - case MlStage.RetrainingNeeded: - return { - textIds: ["content.model.retrainModelBody"], - navigateToStep: "train-model", - }; - default: - return { - textIds: ["content.model.trainModelBody"], - navigateToStep: "train-model", - }; - } -}; - -const TrainModelFirstView = () => { - const navigate = useNavigate(); - const [{ stage }] = useMlStatus(); - - const navigateToDataPage = useCallback(() => { - navigate(createStepPageUrl("add-data")); - }, [navigate]); - - const navigateToTrainModelPage = useCallback(() => { - navigate(createStepPageUrl("train-model")); - }, [navigate]); - - const config = getConfig(stage); - return ( - - - - - - - - {config.textIds.map((textId, idx) => ( - - - - ))} - - - {config.navigateToStep === "add-data" ? ( - - ) : ( - - )} - - ); -}; - -export default TrainModelFirstView; diff --git a/src/components/TrainModelFlowDialogs.tsx b/src/components/TrainModelFlowDialogs.tsx new file mode 100644 index 000000000..18901d406 --- /dev/null +++ b/src/components/TrainModelFlowDialogs.tsx @@ -0,0 +1,21 @@ +import { + TrainModelDialogStage, + useTrainModelDialog, +} from "../train-model-dialog-hooks"; +import TrainModelIntroDialog from "./TrainModelIntroDialog"; +import TrainingStatusDialog from "./TrainingStatusDialog"; + +const TrainModelFlowDialogs = () => { + const { stage, onIntroNext, onClose } = useTrainModelDialog(); + + switch (stage) { + case TrainModelDialogStage.Closed: + return <>; + case TrainModelDialogStage.ShowingIntroduction: + return ; + case TrainModelDialogStage.ShowingTrainingStatus: + return ; + } +}; + +export default TrainModelFlowDialogs; diff --git a/src/components/TrainModelIntroDialog.tsx b/src/components/TrainModelIntroDialog.tsx new file mode 100644 index 000000000..b18ed2a98 --- /dev/null +++ b/src/components/TrainModelIntroDialog.tsx @@ -0,0 +1,72 @@ +import { + Checkbox, + Heading, + HStack, + Image, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; +import { useCallback, useState } from "react"; +import { FormattedMessage } from "react-intl"; +import trainModelImage from "../images/train_model_black.svg"; +import { useTrainModelDialog } from "../train-model-dialog-hooks"; +import TrainingButton from "./TrainingButton"; + +interface TrainModelIntroDialogProps { + onNext: (isSkipIntro: boolean) => void; +} + +const TrainModelIntroDialog = ({ onNext }: TrainModelIntroDialogProps) => { + const { onClose, isSkipIntro: defaultIsSkipIntro } = useTrainModelDialog(); + const [skip, setSkip] = useState(defaultIsSkipIntro); + const handleOnNext = useCallback(() => onNext(skip), [onNext, skip]); + + return ( + + + + + + + + + + + + + + + + + + + + + setSkip(e.target.checked)} + > + + + + + + + + ); +}; + +export default TrainModelIntroDialog; diff --git a/src/components/TrainingModelProgressDialog.tsx b/src/components/TrainingModelProgressDialog.tsx new file mode 100644 index 000000000..a5c827865 --- /dev/null +++ b/src/components/TrainingModelProgressDialog.tsx @@ -0,0 +1,51 @@ +import { + Heading, + Modal, + ModalBody, + ModalContent, + ModalOverlay, + Progress, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; + +export interface DownloadingDialogProps { + isOpen: boolean; + progress: number; +} + +const TrainingModelProgressDialog = ({ + isOpen, + progress, +}: DownloadingDialogProps) => { + return ( + {}} + size="2xl" + isCentered + > + + + + + + + + + + + + + + ); +}; + +export default TrainingModelProgressDialog; diff --git a/src/components/TrainingStatusDialog.tsx b/src/components/TrainingStatusDialog.tsx new file mode 100644 index 000000000..8274b37f5 --- /dev/null +++ b/src/components/TrainingStatusDialog.tsx @@ -0,0 +1,48 @@ +import { useCallback, useEffect } from "react"; +import { useNavigate } from "react-router"; +import { useMlActions } from "../ml-hooks"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; +import { createSessionPageUrl } from "../urls"; +import TrainingErrorDialog from "./TrainingErrorDialog"; +import TrainingModelProgressDialog from "./TrainingModelProgressDialog"; +import { SessionPageId } from "../pages-config"; + +interface TrainingStatusDialogProps { + onClose: () => void; +} + +const TrainingStatusDialog = ({ onClose }: TrainingStatusDialogProps) => { + const [status] = useMlStatus(); + const actions = useMlActions(); + const navigate = useNavigate(); + + const handleTrain = useCallback(async () => { + await actions.trainModel(); + }, [actions]); + + useEffect(() => { + if (status.stage === MlStage.NotTrained) { + void handleTrain(); + } + if (status.stage === MlStage.TrainingComplete) { + onClose(); + navigate(createSessionPageUrl(SessionPageId.TestingModel)); + } + }, [handleTrain, navigate, onClose, status.stage]); + + switch (status.stage) { + case MlStage.TrainingError: + return ; + case MlStage.TrainingInProgress: + return ( + + ); + default: + return ; + } +}; + +export default TrainingStatusDialog; diff --git a/src/components/TrainingStatusView.tsx b/src/components/TrainingStatusView.tsx deleted file mode 100644 index 4e0311214..000000000 --- a/src/components/TrainingStatusView.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { - Button, - HStack, - Heading, - Progress, - Text, - VStack, - useDisclosure, -} from "@chakra-ui/react"; -import { ReactNode, useCallback } from "react"; -import { FormattedMessage } from "react-intl"; -import { useNavigate } from "react-router"; -import { useMlActions } from "../ml-hooks"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; -import { createStepPageUrl } from "../urls"; -import TrainingButton from "./TrainingButton"; -import TrainingErrorDialog from "./TrainingErrorDialog"; - -const TrainingStatusView = () => { - const navigate = useNavigate(); - const [status] = useMlStatus(); - const actions = useMlActions(); - const trainErrorDialog = useDisclosure(); - - const navigateToDataPage = useCallback(() => { - navigate(createStepPageUrl("add-data")); - }, [navigate]); - - const navigateToTestModelPage = useCallback(() => { - navigate(createStepPageUrl("test-model")); - }, [navigate]); - - const handleTrain = useCallback(async () => { - if ((await actions.trainModel()).error) { - trainErrorDialog.onOpen(); - } - }, [actions, trainErrorDialog]); - - switch (status.stage) { - case MlStage.InsufficientData: - return ( - - - - - - - ); - case MlStage.TrainingError: - case MlStage.NotTrained: - return ( - <> - - - - - - ); - case MlStage.TrainingInProgress: - return ( - - - - ); - case MlStage.TrainingComplete: - return ( - - - - - - - ); - case MlStage.RetrainingNeeded: - return ( - - - - ); - } -}; - -interface TrainingStatusSectionProps { - statusId: string; - children: ReactNode; -} - -const TrainingStatusSection = ({ - statusId, - children, -}: TrainingStatusSectionProps) => { - return ( - - - - - {children} - - ); -}; - -export default TrainingStatusView; diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index e158d2a67..9d3468167 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -139,6 +139,12 @@ "value": "Back" } ], + "back-to-data-samples-action": [ + { + "type": 0, + "value": "Back to data samples" + } + ], "cancel-action": [ { "type": 0, @@ -951,24 +957,6 @@ "value": "machine learning tool" } ], - "content.model.addData": [ - { - "type": 0, - "value": "Add data" - } - ], - "content.model.notEnoughDataInfoBody1": [ - { - "type": 0, - "value": "You cannot test the model until it has been trained." - } - ], - "content.model.notEnoughDataInfoBody2": [ - { - "type": 0, - "value": "You need at least 3 data samples for 2 actions to train a model." - } - ], "content.model.output.action.descriptionBody": [ { "type": 0, @@ -1061,36 +1049,12 @@ "value": "Recognition Point:" } ], - "content.model.retrainModelBody": [ - { - "type": 0, - "value": "You need to train your model with the current set of data samples before you can test it." - } - ], - "content.model.trainModelBody": [ - { - "type": 0, - "value": "You need to train your model before you can test it." - } - ], - "content.model.trainModelFirstHeading": [ - { - "type": 0, - "value": "Test model" - } - ], "content.trainer.description": [ { "type": 0, "value": "The computer program spots patterns or differences in your data samples, and uses these to build a mathematical model that allows the micro:bit machine learning tool to recognise different actions when you move your micro:bit." } ], - "content.trainer.enoughdata.title": [ - { - "type": 0, - "value": "Status: You've collected enough data to train the model." - } - ], "content.trainer.failure.body": [ { "type": 0, @@ -1124,7 +1088,7 @@ "content.trainer.training.title": [ { "type": 0, - "value": "Status: Training the model in progress." + "value": "Training model..." } ], "cookies-action": [ @@ -1145,6 +1109,12 @@ "value": "Copy" } ], + "data-samples-title": [ + { + "type": 0, + "value": "Data samples" + } + ], "disconnectedWarning.bluetooth1": [ { "type": 0, @@ -1199,6 +1169,12 @@ "value": "micro:bit 1 connection lost" } ], + "dont-show-again": [ + { + "type": 0, + "value": "Don't show this again" + } + ], "edit-in-makecode-action": [ { "type": 0, @@ -1366,7 +1342,7 @@ "menu.trainer.notEnoughDataHeader1": [ { "type": 0, - "value": "Status: You don't have enough data." + "value": "You don't have enough data." } ], "menu.trainer.notEnoughDataInfoBody": [ @@ -1375,16 +1351,16 @@ "value": "You need at least 3 data samples for 2 actions to train the model." } ], - "menu.trainer.testModelButton": [ + "menu.trainer.trainModelButton": [ { "type": 0, - "value": "Test model" + "value": "Train model" } ], - "menu.trainer.trainModelButton": [ + "more-edit-in-makecode-options": [ { "type": 0, - "value": "Train model" + "value": "More edit in MakeCode options" } ], "not-found": [ @@ -1417,12 +1393,6 @@ "value": "Low quality connection" } ], - "performanceWarning.ignore": [ - { - "type": 0, - "value": "Don't show this again" - } - ], "performanceWarning.nextButton": [ { "type": 0, @@ -1715,6 +1685,12 @@ "value": "Test model" } ], + "testing-model-title": [ + { + "type": 0, + "value": "Testing model" + } + ], "train-model-intro-description": [ { "type": 0, diff --git a/src/ml-status-hooks.tsx b/src/ml-status-hooks.tsx index 275295170..14f5237d7 100644 --- a/src/ml-status-hooks.tsx +++ b/src/ml-status-hooks.tsx @@ -6,19 +6,17 @@ import { useCallback, useContext, useEffect, - useRef, useState, } from "react"; import { hasSufficientDataForTraining, useGestureData } from "./gestures-hooks"; export enum MlStage { - RecordingData, - InsufficientData, - NotTrained, - TrainingInProgress, - TrainingComplete, - TrainingError, - RetrainingNeeded, + RecordingData = "RecordingData", + InsufficientData = "InsufficientData", + NotTrained = "NotTrained", + TrainingInProgress = "TrainingInProgress", + TrainingComplete = "TrainingComplete", + TrainingError = "TrainingError", } export interface TrainingCompleteMlStatus { @@ -32,15 +30,10 @@ export type MlStatus = progressValue: number; } | TrainingCompleteMlStatus - | { - stage: MlStage.RetrainingNeeded; - } | { stage: Exclude< MlStage, - | MlStage.TrainingInProgress - | MlStage.TrainingComplete - | MlStage.RetrainingNeeded + MlStage.TrainingInProgress | MlStage.TrainingComplete >; }; @@ -68,8 +61,6 @@ export const MlStatusProvider = ({ children }: { children: ReactNode }) => { : MlStage.InsufficientData, }); - const hasTrainedBefore = useRef(false); - const setStatus = useCallback((s: MlStatus) => { setMlState((prevState) => ({ ...prevState })); if (s.stage === MlStage.TrainingComplete) { @@ -81,17 +72,7 @@ export const MlStatusProvider = ({ children }: { children: ReactNode }) => { // Throws if there is no model to remove. }); } - - hasTrainedBefore.current = - s.stage === MlStage.TrainingComplete || hasTrainedBefore.current; - - // Update to retrain instead of not trained if has trained before - const status = - hasTrainedBefore.current && s.stage === MlStage.NotTrained - ? ({ stage: MlStage.RetrainingNeeded } as const) - : s; - - setMlState(status); + setMlState(s); }, []); useEffect(() => { @@ -101,7 +82,6 @@ export const MlStatusProvider = ({ children }: { children: ReactNode }) => { stage: MlStage.TrainingComplete, model, }); - hasTrainedBefore.current = true; }) .catch(() => { // Throws if there is no model to load. diff --git a/src/pages-config.ts b/src/pages-config.ts index beed4b1ec..905abd731 100644 --- a/src/pages-config.ts +++ b/src/pages-config.ts @@ -1,44 +1,29 @@ -import addDataImage from "./images/add_data.svg"; -import testModelImage from "./images/test_model_blue.svg"; -import trainModelImage from "./images/train_model_blue.svg"; import resourceGetStartedImage from "./images/resource-get-started.jpg"; import resourceIntroducingToolImage from "./images/resource-introducing-tool.jpg"; -import AddDataPage from "./pages/AddDataPage"; -import TestModelPage from "./pages/TestModelPage"; -import TrainModelPage from "./pages/TrainModelPage"; +import DataSamplesPage from "./pages/DataSamplesPage"; import GetStartedResourcePage from "./pages/GetStartedResourcePage"; import IntroducingToolResourcePage from "./pages/IntroducingToolResourcePage"; +import TestingModelPage from "./pages/TestingModelPage"; -export type StepId = "add-data" | "train-model" | "test-model"; +export enum SessionPageId { + DataSamples = "data-samples", + TestingModel = "testing-model", +} -export interface StepConfig { - id: StepId; - imgSrc: string; +export interface SessionPageConfig { + id: SessionPageId; pageElement: () => JSX.Element; } -export const addDataConfig: StepConfig = { - id: "add-data", - imgSrc: addDataImage, - pageElement: AddDataPage, -}; - -export const trainModelConfig: StepConfig = { - id: "train-model", - imgSrc: trainModelImage, - pageElement: TrainModelPage, -}; - -export const testModelConfig: StepConfig = { - id: "test-model", - imgSrc: testModelImage, - pageElement: TestModelPage, -}; - -export const stepsConfig: StepConfig[] = [ - addDataConfig, - trainModelConfig, - testModelConfig, +export const sessionPageConfigs: SessionPageConfig[] = [ + { + id: SessionPageId.DataSamples, + pageElement: DataSamplesPage, + }, + { + id: SessionPageId.TestingModel, + pageElement: TestingModelPage, + }, ]; export type ResourceId = diff --git a/src/pages/AddDataPage.tsx b/src/pages/DataSamplesPage.tsx similarity index 65% rename from src/pages/AddDataPage.tsx rename to src/pages/DataSamplesPage.tsx index af0afd148..1913f0a97 100644 --- a/src/pages/AddDataPage.tsx +++ b/src/pages/DataSamplesPage.tsx @@ -11,31 +11,39 @@ import { } from "@chakra-ui/react"; import { useCallback, useMemo } from "react"; import { MdMoreVert } from "react-icons/md"; -import { RiAddLine, RiDeleteBin2Line, RiDownload2Line } from "react-icons/ri"; +import { + RiAddLine, + RiArrowRightLine, + RiDeleteBin2Line, + RiDownload2Line, +} from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router"; -import AddDataGridView from "../components/AddDataGridView"; +import DataSampleGridView from "../components/DataSampleGridView"; import ConnectFirstView from "../components/ConnectFirstView"; import DefaultPageLayout from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; -import TabView from "../components/TabView"; import TrainingButton from "../components/TrainingButton"; import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; +import { ConnectionStatus } from "../connect-status-hooks"; +import { useConnectionStage } from "../connection-stage-hooks"; import { hasSufficientDataForTraining, useGestureActions, useGestureData, } from "../gestures-hooks"; -import { addDataConfig } from "../pages-config"; -import { createStepPageUrl } from "../urls"; -import { useConnectionStage } from "../connection-stage-hooks"; -import { ConnectionStatus } from "../connect-status-hooks"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; +import { SessionPageId } from "../pages-config"; +import { createSessionPageUrl } from "../urls"; +import { useTrainModelDialog } from "../train-model-dialog-hooks"; -const AddDataPage = () => { +const DataSamplesPage = () => { const intl = useIntl(); - const navigate = useNavigate(); const [gestures] = useGestureData(); + const [{ stage }] = useMlStatus(); + const navigate = useNavigate(); const actions = useGestureActions(); + const { onOpen: onOpenTrainModelDialog } = useTrainModelDialog(); const { isConnected, status } = useConnectionStage(); const hasSufficientData = useMemo( @@ -51,27 +59,21 @@ const AddDataPage = () => { ); }, [gestures.data]); - const handleAddNewGesture = useCallback(() => { - actions.addNewGesture(); - }, [actions]); - - const handleDatasetDownload = useCallback(() => { - actions.downloadDataset(); - }, [actions]); - - const navigateToTrainModelPage = useCallback(() => { - navigate(createStepPageUrl("train-model")); + const handleNavigateToModel = useCallback(() => { + navigate(createSessionPageUrl(SessionPageId.TestingModel)); }, [navigate]); return ( - - + {noStoredData && !isConnected && status !== ConnectionStatus.ReconnectingAutomatically ? ( ) : ( - + )} { borderColor="gray.200" alignItems="center" > - - + { } - onClick={handleDatasetDownload} + onClick={actions.downloadDataset} > @@ -126,6 +124,20 @@ const AddDataPage = () => { + {stage === MlStage.TrainingComplete ? ( + + ) : ( + + )} @@ -133,4 +145,4 @@ const AddDataPage = () => { ); }; -export default AddDataPage; +export default DataSamplesPage; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 877489cda..59cbe7db2 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,9 +1,34 @@ import { Grid, Heading, Image, Stack, Text, VStack } from "@chakra-ui/react"; import { FormattedMessage, useIntl } from "react-intl"; +import addDataImage from "../images/add_data.svg"; +import testModelImage from "../images/test_model_blue.svg"; +import trainModelImage from "../images/train_model_blue.svg"; import DefaultPageLayout from "../components/DefaultPageLayout"; import ResourceCard from "../components/ResourceCard"; import StartResumeActions from "../components/StartResumeActions"; -import { resourcesConfig, stepsConfig } from "../pages-config"; +import { resourcesConfig } from "../pages-config"; + +type StepId = "add-data" | "train-model" | "test-model"; + +interface StepConfig { + id: StepId; + imgSrc: string; +} + +const stepsConfig: StepConfig[] = [ + { + id: "add-data", + imgSrc: addDataImage, + }, + { + id: "train-model", + imgSrc: trainModelImage, + }, + { + id: "test-model", + imgSrc: testModelImage, + }, +]; const HomePage = () => { const intl = useIntl(); diff --git a/src/pages/TestModelPage.tsx b/src/pages/TestModelPage.tsx deleted file mode 100644 index 332ee23c8..000000000 --- a/src/pages/TestModelPage.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import DefaultPageLayout from "../components/DefaultPageLayout"; -import LiveGraphPanel from "../components/LiveGraphPanel"; -import TabView from "../components/TabView"; -import TestModelGridView from "../components/TestModelGridView"; -import TrainModelFirstView from "../components/TrainModelFirstView"; -import { usePrediction } from "../ml-hooks"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; -import { testModelConfig } from "../pages-config"; - -const TestModelPage = () => { - const [{ stage }] = useMlStatus(); - const prediction = usePrediction(); - return ( - - - {stage === MlStage.TrainingComplete ? ( - <> - - - - ) : ( - - )} - - ); -}; - -export default TestModelPage; diff --git a/src/pages/TestingModelPage.tsx b/src/pages/TestingModelPage.tsx new file mode 100644 index 000000000..e8933c7fd --- /dev/null +++ b/src/pages/TestingModelPage.tsx @@ -0,0 +1,50 @@ +import { Button } from "@chakra-ui/react"; +import { useCallback, useEffect } from "react"; +import { FormattedMessage } from "react-intl"; +import { useNavigate } from "react-router"; +import BackArrow from "../components/BackArrow"; +import DefaultPageLayout from "../components/DefaultPageLayout"; +import TestingModelGridView from "../components/TestingModelGridView"; +import { MlStage, useMlStatus } from "../ml-status-hooks"; +import { SessionPageId } from "../pages-config"; +import { createSessionPageUrl } from "../urls"; + +const TestingModelPage = () => { + const navigate = useNavigate(); + const [{ stage }] = useMlStatus(); + + const navigateToDataSamples = useCallback(() => { + navigate(createSessionPageUrl(SessionPageId.DataSamples)); + }, [navigate]); + + useEffect(() => { + if (stage !== MlStage.TrainingComplete) { + navigateToDataSamples(); + } + }); + return stage === MlStage.TrainingComplete ? ( + } + variant="plain" + color="white" + onClick={navigateToDataSamples} + pr={3} + pl={3} + > + + + } + > + + + ) : ( + <> + ); +}; + +export default TestingModelPage; diff --git a/src/pages/TrainModelPage.tsx b/src/pages/TrainModelPage.tsx deleted file mode 100644 index f371a85a4..000000000 --- a/src/pages/TrainModelPage.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Heading, Image, Text, VStack } from "@chakra-ui/react"; -import { FormattedMessage } from "react-intl"; -import DefaultPageLayout from "../components/DefaultPageLayout"; -import TabView from "../components/TabView"; -import TrainingStatusView from "../components/TrainingStatusView"; -import trainModelImage from "../images/train_model_black.svg"; -import { trainModelConfig } from "../pages-config"; - -const TrainModelPage = () => { - return ( - - - - - - - - - - - - - - - - - - ); -}; - -export default TrainModelPage; diff --git a/src/train-model-dialog-hooks.tsx b/src/train-model-dialog-hooks.tsx new file mode 100644 index 000000000..f2b42abf1 --- /dev/null +++ b/src/train-model-dialog-hooks.tsx @@ -0,0 +1,84 @@ +import { + createContext, + ReactNode, + useCallback, + useContext, + useState, +} from "react"; + +export enum TrainModelDialogStage { + Closed = "closed", + ShowingIntroduction = "showing introduction", + ShowingTrainingStatus = "showing training status", +} + +interface TrainModelDialogState { + stage: TrainModelDialogStage; + skipIntro: boolean; +} + +type TrainModelDialogStateContextValue = [ + TrainModelDialogState, + (state: TrainModelDialogState) => void +]; + +const TrainModelDialogStateContext = createContext< + TrainModelDialogStateContextValue | undefined +>(undefined); + +export const TrainModelDialogProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const [state, setState] = useState({ + stage: TrainModelDialogStage.Closed, + skipIntro: false, + }); + + return ( + + {children} + + ); +}; + +export const useTrainModelDialog = () => { + const dialogContextValue = useContext(TrainModelDialogStateContext); + if (!dialogContextValue) { + throw new Error("Missing provider"); + } + const [state, setState] = dialogContextValue; + + const onClose = useCallback(() => { + setState({ ...state, stage: TrainModelDialogStage.Closed }); + }, [setState, state]); + + const onOpen = useCallback(() => { + setState({ + ...state, + stage: state.skipIntro + ? TrainModelDialogStage.ShowingTrainingStatus + : TrainModelDialogStage.ShowingIntroduction, + }); + }, [setState, state]); + + const onIntroNext = useCallback( + (isSkipIntro: boolean) => { + setState({ + ...state, + skipIntro: isSkipIntro, + stage: TrainModelDialogStage.ShowingTrainingStatus, + }); + }, + [setState, state] + ); + + return { + stage: state.stage, + isSkipIntro: state.skipIntro, + onIntroNext, + onClose, + onOpen, + }; +}; diff --git a/src/urls.ts b/src/urls.ts index 5cf2ed474..f2b3b187e 100644 --- a/src/urls.ts +++ b/src/urls.ts @@ -1,4 +1,4 @@ -import { ResourceId, StepId } from "./pages-config"; +import { ResourceId, SessionPageId } from "./pages-config"; export const basepath = import.meta.env.BASE_URL ?? "/"; @@ -8,7 +8,8 @@ if (!basepath.endsWith("/")) { export const createHomePageUrl = () => `${basepath}`; -export const createStepPageUrl = (stepId: StepId) => `${basepath}${stepId}`; +export const createSessionPageUrl = (pageId: SessionPageId) => + `${basepath}${pageId}`; export const createResourcePageUrl = (resourceId: ResourceId) => `${basepath}resources/${resourceId}`; From abe0dfb5b3c3247a59a4e71de644e6e392c8e015 Mon Sep 17 00:00:00 2001 From: Robert Knight <95928279+microbit-robert@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:26:33 +0100 Subject: [PATCH 125/172] Fix active styling for secondary buttons (#308) --- src/deployment/default/components/button.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deployment/default/components/button.ts b/src/deployment/default/components/button.ts index a4bd15a2f..c7f0adb46 100644 --- a/src/deployment/default/components/button.ts +++ b/src/deployment/default/components/button.ts @@ -40,7 +40,7 @@ const Button: StyleConfig = { borderColor: "brand.600", }, _active: { - bg: "brand.500", + bg: "brand.50", borderColor: "brand.700", }, }), From eb578dbc3b0d62f2a3b15264d2707397f642d5a6 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Tue, 10 Sep 2024 13:50:21 +0100 Subject: [PATCH 126/172] Remove not needed mlStatus update in RecordingDialog (#307) --- src/components/RecordingDialog.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index 3379b6def..33466f91e 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -69,11 +69,10 @@ const RecordingDialog = ({ const handleCleanup = useCallback(() => { setRecordingStatus(RecordingStatus.None); - setStatus({ stage: MlStage.NotTrained }); setCountdownStageIndex(0); setProgress(0); onClose(); - }, [onClose, setStatus]); + }, [onClose]); const handleOnClose = useCallback(() => { recordingDataSource.cancelRecording(); From 7aa1f44ce0814371ec4da081d15e928c29de28e0 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:18:10 +0100 Subject: [PATCH 127/172] Add hex saving via state refactor and new makecode embedding (#311) - Keep MakeCode around throughout so we can ask it for a hex file at any point. - Introduce a "Save" action in the toolbar that saves a hex that includes all the data. - Introduce drag and drop load that can restore that hex. - Move project and gesture storage to zustand-managed state. They both affect each other in different circumstances so it's much more manageable to colocate them. - Integrate them with MakeCode via new project `@microbit-foundation/makecode-embed` so we can make appropriate updates to MakeCode or app state depending on user actions Known issues: - We struggle to attribute editorChanged messages to MakeCode or the user. MakeCode seems to send more than we'd expect changing formatting and even making no change at all. To try to work out which are from the user we use isEditorOpen but this isn't reliable. The practical consequence is that sometimes we mark a project as user edited (and therefore stop updating it) when it isn't really user edited. This manifests as "unknown" in the dropdown. - Our use of importproject leaks the project in MakeCode as each time a new project is created in the workspace. We might be able to explore using unloadProject to get MakeCode to redo the sync process. We previously tried the combination of unloadProject (sync happens), openHeader but that led to issues where the project in MakeCode could actually be out of date with no clear cause. Lots has changed since though so this might be worth another go. To mitigate this we're only updating MakeCode before save and open rather than on every change. --- .prettierignore | 3 +- lang/ui.en.json | 38 +- package-lock.json | 721 ++----------------- package.json | 15 +- src/App.tsx | 65 +- src/buffered-data.ts | 2 +- src/components/AddDataGridRow.tsx | 9 +- src/components/AddDataGridWalkThrough.tsx | 2 +- src/components/CodeViewCard.tsx | 6 +- src/components/CodeViewGridItem.tsx | 20 +- src/components/DataRecordingGridItem.tsx | 9 +- src/components/DataSampleGridView.tsx | 14 +- src/components/EditCodeDialog.tsx | 105 ++- src/components/Editor.tsx | 46 +- src/components/FileDropTarget.tsx | 74 ++ src/components/GestureNameGridItem.tsx | 13 +- src/components/LanguageDialog.tsx | 12 +- src/components/LiveGraph.tsx | 15 +- src/components/LiveGraphPanel.tsx | 2 +- src/components/ProjectDropTarget.tsx | 28 + src/components/RecordingDialog.tsx | 22 +- src/components/RecordingGraph.tsx | 2 +- src/components/SaveButton.tsx | 65 ++ src/components/SaveHelpDialog.tsx | 76 ++ src/components/SaveProgressDialog.tsx | 46 ++ src/components/SettingsMenu.tsx | 2 +- src/components/StartOverWarningDialog.tsx | 11 +- src/components/StartResumeActions.tsx | 20 +- src/components/TestingModelGridView.tsx | 82 +-- src/components/TrainModelFlowDialogs.tsx | 53 +- src/components/TrainModelIntroDialog.tsx | 12 +- src/components/TrainingButton.tsx | 16 +- src/components/TrainingStatusDialog.tsx | 48 -- src/components/UploadDataSamplesMenuItem.tsx | 40 +- src/flags.test.ts | 8 +- src/flags.ts | 5 + src/gestures-hooks.tsx | 303 -------- src/gestures.test.ts | 42 -- src/{ => hooks}/ml-hooks.tsx | 48 +- src/hooks/project-hooks.tsx | 221 ++++++ src/makecode/generate-custom-scripts.ts | 13 +- src/makecode/generate-main-scripts.test.ts | 9 +- src/makecode/generate-main-scripts.ts | 48 +- src/makecode/utils.test.ts | 12 +- src/makecode/utils.ts | 89 ++- src/messages/TranslationProvider.tsx | 2 +- src/messages/ui.en.json | 70 +- src/ml-actions.ts | 47 -- src/ml-status-hooks.tsx | 96 --- src/ml.test.ts | 2 +- src/ml.ts | 2 +- src/model.test.ts | 39 + src/model.ts | 80 ++ src/pages/DataSamplesPage.tsx | 73 +- src/pages/TestingModelPage.tsx | 13 +- src/recording-graph.ts | 2 +- src/settings.tsx | 53 +- src/store.ts | 559 ++++++++++++++ src/train-model-dialog-hooks.tsx | 84 --- src/user-projects-hooks.tsx | 127 ---- src/utils.ts | 2 - src/utils/fs-util.ts | 30 + 62 files changed, 1824 insertions(+), 1949 deletions(-) create mode 100644 src/components/FileDropTarget.tsx create mode 100644 src/components/ProjectDropTarget.tsx create mode 100644 src/components/SaveButton.tsx create mode 100644 src/components/SaveHelpDialog.tsx create mode 100644 src/components/SaveProgressDialog.tsx delete mode 100644 src/components/TrainingStatusDialog.tsx delete mode 100644 src/gestures-hooks.tsx delete mode 100644 src/gestures.test.ts rename src/{ => hooks}/ml-hooks.tsx (66%) create mode 100644 src/hooks/project-hooks.tsx delete mode 100644 src/ml-actions.ts delete mode 100644 src/ml-status-hooks.tsx create mode 100644 src/model.test.ts create mode 100644 src/model.ts create mode 100644 src/store.ts delete mode 100644 src/train-model-dialog-hooks.tsx delete mode 100644 src/user-projects-hooks.tsx delete mode 100644 src/utils.ts create mode 100644 src/utils/fs-util.ts diff --git a/.prettierignore b/.prettierignore index 4958c8934..f4752710f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ -src/translations.ts \ No newline at end of file +src/translations.ts +src/messages/ diff --git a/lang/ui.en.json b/lang/ui.en.json index e8d2c2165..4c24023ee 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -628,8 +628,8 @@ "description": "" }, "content.trainer.training.title": { - "defaultMessage": "Training model...", - "description": "" + "defaultMessage": "Training model…", + "description": "Progress title" }, "cookies-action": { "defaultMessage": "Cookies", @@ -803,6 +803,10 @@ "defaultMessage": "Page not found", "description": "Page not found page title" }, + "open-file-dropped": { + "defaultMessage": "Open file when dropped", + "description": "Aria label for file drop target" + }, "performanceWarning.content1": { "defaultMessage": "The quality of data being received from your micro:bit is low. This could be due to a connection issue.", "description": "" @@ -939,6 +943,34 @@ "defaultMessage": "introduction video", "description": "" }, + "save-action": { + "defaultMessage": "Save", + "description": "Label for action that saves the project as a file the user downloads" + }, + "save-hex-dialog-heading": { + "defaultMessage": "Save project as a hex file", + "description": "Heading for the dialog shown when saving a hex file" + }, + "save-hex-dialog-message1": { + "defaultMessage": "The download contains your actions, data samples and your MakeCode project. Open it in the micro:bit machine learning tool to continue working.", + "description": "Text in the dialog shown when saving a hex file" + }, + "save-hex-dialog-message2": { + "defaultMessage": "You can also open it with Microsoft MakeCode if you don't need to change the actions and data samples.", + "description": "Text in the dialog shown when saving a hex file" + }, + "saving-description": { + "defaultMessage": "Your download will be ready soon.", + "description": "Saving progress dialog text" + }, + "saving-title": { + "defaultMessage": "Saving…", + "description": "Saving progress title" + }, + "saving-toast-title": { + "defaultMessage": "Your hex file has been downloaded", + "description": "Notification after a hex file has been downloaded" + }, "settings-menu-action": { "defaultMessage": "Settings actions menu", "description": "Label for settings actions menu button" @@ -1003,4 +1035,4 @@ "defaultMessage": "Train model", "description": "Train model step title" } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a094162f5..a9e7116ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,24 +14,19 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit-foundation/ml-header-generator": "^0.3.8", - "@microbit-foundation/react-code-view": "^5.0.2", - "@microbit-foundation/react-editor-embed": "^1.0.0-controller.mode.47", + "@microbit-foundation/ml-header-generator": "^0.4.0", + "@microbit/makecode-embed": "^0.0.0-alpha.7", "@microbit/microbit-connection": "^0.0.0-alpha.19", "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", "@vitejs/plugin-react": "^4.3.1", "bowser": "^2.11.0", - "browser-lang": "^0.2.1", "chart.js": "^4.2.1", - "d3": "^7.8.5", "framer-motion": "^10.2.4", - "js-cookie": "^3.0.4", "lodash.camelcase": "^4.3.0", "lodash.upperfirst": "^4.3.1", "ml4f": "git://github.com/microsoft/ml4f#v1.10.1", - "postcss": "^8.4.23", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^4.12.0", @@ -39,8 +34,7 @@ "react-router": "^6.24.0", "react-router-dom": "^6.24.0", "smoothie": "^1.36.1", - "three": "^0.152.2", - "uuid4": "^2.0.3" + "zustand": "^4.5.5" }, "devDependencies": { "@chakra-ui/cli": "^2.4.1", @@ -49,16 +43,13 @@ "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.0.0", - "@types/browser-lang": "^0.1.1", "@types/d3": "^7.4.1", "@types/ejs": "^3.1.5", - "@types/file-saver": "^2.0.3", "@types/lodash.camelcase": "^4.3.9", "@types/lodash.upperfirst": "^4.3.9", "@types/node": "^18.16.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@types/three": "^0.152.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", @@ -4364,64 +4355,16 @@ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, "node_modules/@microbit-foundation/ml-header-generator": { - "version": "0.3.8", - "resolved": "https://npm.pkg.github.com/download/@microbit-foundation/ml-header-generator/0.3.8/c7faf62d2b931670b8aec55ae3333681b6318ede", - "integrity": "sha512-m6iq2Q/aqEwQ6fPsab9cICse45yiBJQd3/sbD1tgLgMAIfhY3AbjES/VQaFKsljUwqaRwzpizdvqTYN7wIT/rg==", + "version": "0.4.0", + "resolved": "https://npm.pkg.github.com/download/@microbit-foundation/ml-header-generator/0.4.0/fb369777da03babd652557213adf817862a6a673", + "integrity": "sha512-6aBB7pWo/HoB6dGGvtwODMOvFPhe2bktzxdL3qo+wDondleWgd3EEBsPXi1xvKpJSQb0lbeyPxLZi4nLuxaYxg==", "license": "MIT" }, - "node_modules/@microbit-foundation/react-code-view": { - "version": "5.0.2", - "resolved": "https://npm.pkg.github.com/download/@microbit-foundation/react-code-view/5.0.2/679a6240305b1ffdab0fa8965ccb4326a8a2ce9d", - "integrity": "sha512-CRgQMgHM/dIAnUhj4ynzRPjiltW45vLvnQfoDjD1IYgbBGl68N9aa9JNmKFGCpLGJYami/KWJD8L2LE2aob3Fg==", - "license": "MIT", + "node_modules/@microbit/makecode-embed": { + "version": "0.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/@microbit/makecode-embed/-/makecode-embed-0.0.0-alpha.7.tgz", + "integrity": "sha512-tQPLXfQFRCIMpl80OCyZ4c7UlBiSasZb2gl8hUiFG+Q7Rb9Q8Zan9dQ4dNPueEAzn1Osykhekd9wrYhdnn2FDg==", "dependencies": { - "react": "18.2.0", - "react-dom": "18.2.0", - "react-syntax-highlighter": "15.5.0", - "sass": "^1.72.0" - }, - "engines": { - "node": ">=20", - "npm": ">=10" - }, - "peerDependencies": { - "react": "17.x-18.x", - "react-dom": "17.x-18.x", - "tslib": ">=2.0.0" - } - }, - "node_modules/@microbit-foundation/react-code-view/node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@microbit-foundation/react-code-view/node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/@microbit-foundation/react-editor-embed": { - "version": "1.0.0-controller.mode.47", - "resolved": "https://npm.pkg.github.com/download/@microbit-foundation/react-editor-embed/1.0.0-controller.mode.47/1b7a0b15ab1120600436c1348820f0d9b515a73b", - "integrity": "sha512-wK+JC/uu9WyPjcCqk6j7qOXKW4/lL9GJNVKlA2SkLnUtC5mM3EZHlRENb42RAn1t4gGj6hr3haRiZfIMa6mDbw==", - "license": "MIT", - "dependencies": { - "react": ">=18.0.0", - "react-dom": ">=18.0.0", - "rxjs": ">=7.0.0", "tslib": ">=2.0.0" }, "engines": { @@ -5600,12 +5543,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tweenjs/tween.js": { - "version": "18.6.4", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", - "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==", - "dev": true - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -5653,12 +5590,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/browser-lang": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/browser-lang/-/browser-lang-0.1.2.tgz", - "integrity": "sha512-F6UrLn++11o967g8+G4c0mILIuSuyhpE9N989T4Rr+sgHqYpdrAbPgp3KOPPf4AA2A09dQDUB8/3K1GBG9Daeg==", - "dev": true - }, "node_modules/@types/clone": { "version": "0.1.30", "resolved": "https://registry.npmjs.org/@types/clone/-/clone-0.1.30.tgz", @@ -5937,26 +5868,12 @@ "fast-json-stable-stringify": "*" } }, - "node_modules/@types/file-saver": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", - "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", - "dev": true - }, "node_modules/@types/geojson": { "version": "7946.0.13", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==", "dev": true }, - "node_modules/@types/hast": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", - "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", - "dependencies": { - "@types/unist": "^2" - } - }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", @@ -6143,12 +6060,6 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, - "node_modules/@types/stats.js": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", - "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", - "dev": true - }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -6158,30 +6069,12 @@ "@types/jest": "*" } }, - "node_modules/@types/three": { - "version": "0.152.1", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.1.tgz", - "integrity": "sha512-PMOCQnx9JRmq+2OUGTPoY9h1hTWD2L7/nmuW/SyNq1Vbq3Lwt3MNdl3wYSa4DvLTGv62NmIXD9jYdAOwohwJyw==", - "dev": true, - "dependencies": { - "@tweenjs/tween.js": "~18.6.4", - "@types/stats.js": "*", - "@types/webxr": "*", - "fflate": "~0.6.9", - "lil-gui": "~0.17.0" - } - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "dev": true }, - "node_modules/@types/unist": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" - }, "node_modules/@types/usb": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/usb/-/usb-1.5.4.tgz", @@ -6205,12 +6098,6 @@ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" }, - "node_modules/@types/webxr": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.10.tgz", - "integrity": "sha512-n3u5sqXQJhf1CS68mw3Wf16FQ4cRPNBBwdYLFzq3UddiADOim1Pn3Y6PBdDilz1vOJF3ybLxJ8ZEDlLIzrOQZg==", - "dev": true - }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -6724,6 +6611,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -7031,6 +6919,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "engines": { "node": ">=8" }, @@ -7056,6 +6945,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -7063,11 +6953,6 @@ "node": ">=8" } }, - "node_modules/browser-lang": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/browser-lang/-/browser-lang-0.2.1.tgz", - "integrity": "sha512-+xmtsTxVZKWrKHoNUQp4Tm7BEXlnMwOMAHZAh1SSot1+n04qHLFIH0K5anX52k5BkcauggbaNlWT8f3bVwDh/Q==" - }, "node_modules/browserslist": { "version": "4.22.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", @@ -7217,33 +7102,6 @@ "node": ">=4" } }, - "node_modules/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chart.js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", @@ -7271,6 +7129,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -7531,15 +7390,6 @@ "node": ">= 0.8" } }, - "node_modules/comma-separated-tokens": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", - "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -7706,46 +7556,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, - "node_modules/d3": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", - "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -7757,40 +7567,6 @@ "node": ">=12" } }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -7799,48 +7575,6 @@ "node": ">=12" } }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-dsv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", @@ -7865,38 +7599,6 @@ "node": ">=12" } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -7905,17 +7607,6 @@ "node": ">=12" } }, - "node_modules/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-geo-projection": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-3.0.0.tgz", @@ -7960,14 +7651,6 @@ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "engines": { - "node": ">=12" - } - }, "node_modules/d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", @@ -7987,30 +7670,6 @@ "node": ">=12" } }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "engines": { - "node": ">=12" - } - }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -8038,14 +7697,6 @@ "node": ">=12" } }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "engines": { - "node": ">=12" - } - }, "node_modules/d3-shape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", @@ -8079,47 +7730,6 @@ "node": ">=12" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/dapjs": { "version": "2.3.0-microbit.2", "resolved": "git+ssh://git@github.com/microbit-matt-hillsdon/dapjs.git#4529bf7a8dcb124b0a7be2bd87a5655557796456", @@ -8320,14 +7930,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delaunator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", - "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", - "dependencies": { - "robust-predicates": "^3.0.0" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9165,18 +8767,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fault": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", - "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/fbjs": { "version": "0.8.18", "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.18.tgz", @@ -9197,12 +8787,6 @@ "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==", "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js." }, - "node_modules/fflate": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", - "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", - "dev": true - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9240,6 +8824,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -9321,14 +8906,6 @@ "node": ">= 6" } }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/framer-motion": { "version": "10.18.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.18.0.tgz", @@ -9414,6 +8991,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -9583,6 +9161,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -9759,39 +9338,6 @@ "node": ">= 0.4" } }, - "node_modules/hast-util-parse-selector": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", - "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", - "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", - "dependencies": { - "@types/hast": "^2.0.0", - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "engines": { - "node": "*" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -9881,7 +9427,10 @@ "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -9991,28 +9540,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -10081,6 +9608,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -10172,19 +9700,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10228,6 +9748,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -10235,15 +9756,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -10278,6 +9790,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -11091,14 +10604,6 @@ "node": ">=8" } }, - "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "engines": { - "node": ">=14" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11307,12 +10812,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lil-gui": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", - "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==", - "dev": true - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -11440,19 +10939,6 @@ "tslib": "^2.0.3" } }, - "node_modules/lowlight": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", - "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", - "dependencies": { - "fault": "^1.0.0", - "highlight.js": "~10.7.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -11605,6 +11091,7 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, "funding": [ { "type": "github", @@ -11682,6 +11169,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -11946,23 +11434,6 @@ "node": ">=6" } }, - "node_modules/parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -12055,6 +11526,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -12130,6 +11602,7 @@ "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -12224,14 +11697,6 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -12250,18 +11715,6 @@ "react-is": "^16.13.1" } }, - "node_modules/property-information": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", - "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", - "dependencies": { - "xtend": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -12518,25 +11971,11 @@ } } }, - "node_modules/react-syntax-highlighter": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", - "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "highlight.js": "^10.4.1", - "lowlight": "^1.17.0", - "prismjs": "^1.27.0", - "refractor": "^3.6.0" - }, - "peerDependencies": { - "react": ">= 0.14.0" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -12596,28 +12035,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/refractor": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", - "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", - "dependencies": { - "hastscript": "^6.0.0", - "parse-entities": "^2.0.0", - "prismjs": "~1.27.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/refractor/node_modules/prismjs": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", - "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", - "engines": { - "node": ">=6" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -12784,11 +12201,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" - }, "node_modules/rollup": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", @@ -12858,14 +12270,6 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -12929,6 +12333,9 @@ "version": "1.77.8", "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -13125,6 +12532,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -13155,15 +12563,6 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, - "node_modules/space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -13506,11 +12905,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/three": { - "version": "0.152.2", - "resolved": "https://registry.npmjs.org/three/-/three-0.152.2.tgz", - "integrity": "sha512-Ff9zIpSfkkqcBcpdiFo2f35vA9ZucO+N8TNacJOqaEE6DrB0eufItVMib8bK8Pcju/ZNT6a7blE1GhTpkdsILw==" - }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -13557,6 +12951,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -13959,10 +13354,13 @@ } } }, - "node_modules/uuid4": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid4/-/uuid4-2.0.3.tgz", - "integrity": "sha512-CTpAkEVXMNJl2ojgtpLXHgz23dh8z81u6/HEPiQFOvBc/c2pde6TVHmH4uwY0d/GLF3tb7+VDAj4+2eJaQSdZQ==" + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } }, "node_modules/vega": { "version": "5.20.0", @@ -15971,14 +15369,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -16037,6 +15427,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz", + "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==", + "dependencies": { + "use-sync-external-store": "1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 7d03b769a..5439336f7 100644 --- a/package.json +++ b/package.json @@ -29,16 +29,13 @@ "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.0.0", - "@types/browser-lang": "^0.1.1", "@types/d3": "^7.4.1", "@types/ejs": "^3.1.5", - "@types/file-saver": "^2.0.3", "@types/lodash.camelcase": "^4.3.9", "@types/lodash.upperfirst": "^4.3.9", "@types/node": "^18.16.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@types/three": "^0.152.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", @@ -63,24 +60,19 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit-foundation/ml-header-generator": "^0.3.8", - "@microbit-foundation/react-code-view": "^5.0.2", - "@microbit-foundation/react-editor-embed": "^1.0.0-controller.mode.47", + "@microbit-foundation/ml-header-generator": "^0.4.0", + "@microbit/makecode-embed": "^0.0.0-alpha.7", "@microbit/microbit-connection": "^0.0.0-alpha.19", "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", "@vitejs/plugin-react": "^4.3.1", "bowser": "^2.11.0", - "browser-lang": "^0.2.1", "chart.js": "^4.2.1", - "d3": "^7.8.5", "framer-motion": "^10.2.4", - "js-cookie": "^3.0.4", "lodash.camelcase": "^4.3.0", "lodash.upperfirst": "^4.3.1", "ml4f": "git://github.com/microsoft/ml4f#v1.10.1", - "postcss": "^8.4.23", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^4.12.0", @@ -88,7 +80,6 @@ "react-router": "^6.24.0", "react-router-dom": "^6.24.0", "smoothie": "^1.36.1", - "three": "^0.152.2", - "uuid4": "^2.0.3" + "zustand": "^4.5.5" } } diff --git a/src/App.tsx b/src/App.tsx index 60e9214d9..ad7b6277b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,34 +1,33 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ChakraProvider } from "@chakra-ui/react"; -import React, { ReactNode, useMemo } from "react"; +import { MakeCodeFrameDriver } from "@microbit/makecode-embed/react"; +import React, { ReactNode, useMemo, useRef } from "react"; import { Outlet, RouterProvider, ScrollRestoration, createBrowserRouter, } from "react-router-dom"; +import { BufferedDataProvider } from "./buffered-data-hooks"; +import EditCodeDialog from "./components/EditCodeDialog"; import ErrorBoundary from "./components/ErrorBoundary"; import ErrorHandlerErrorView from "./components/ErrorHandlerErrorView"; import NotFound from "./components/NotFound"; +import ProjectDropTarget from "./components/ProjectDropTarget"; +import { ConnectProvider } from "./connect-actions-hooks"; +import { ConnectStatusProvider } from "./connect-status-hooks"; +import { ConnectionStageProvider } from "./connection-stage-hooks"; +import { deployment, useDeployment } from "./deployment"; +import { LoggingProvider } from "./logging/logging-hooks"; import TranslationProvider from "./messages/TranslationProvider"; -import SettingsProvider from "./settings"; +import { resourcesConfig, sessionPageConfigs } from "./pages-config"; import HomePage from "./pages/HomePage"; import { createHomePageUrl, createResourcePageUrl, createSessionPageUrl, } from "./urls"; -import { deployment, useDeployment } from "./deployment"; -import { resourcesConfig, sessionPageConfigs } from "./pages-config"; -import { LoggingProvider } from "./logging/logging-hooks"; -import { GesturesProvider } from "./gestures-hooks"; -import { MlStatusProvider } from "./ml-status-hooks"; -import { ConnectionStageProvider } from "./connection-stage-hooks"; -import { ConnectProvider } from "./connect-actions-hooks"; -import { ConnectStatusProvider } from "./connect-status-hooks"; -import { BufferedDataProvider } from "./buffered-data-hooks"; -import { UserProjectsProvider } from "./user-projects-hooks"; -import { TrainModelDialogProvider } from "./train-model-dialog-hooks"; +import { ProjectProvider } from "./hooks/project-hooks"; export interface ProviderLayoutProps { children: ReactNode; @@ -39,32 +38,30 @@ const logging = deployment.logging; const Providers = ({ children }: ProviderLayoutProps) => { const deployment = useDeployment(); const { ConsentProvider } = deployment.compliance; + const driverRef = useRef(null); return ( - - - - - - - - - - - {children} - - - - - - - - - - + + + + + + + + + + {children} + + + + + + + + diff --git a/src/buffered-data.ts b/src/buffered-data.ts index ecdb59efe..d18e38eba 100644 --- a/src/buffered-data.ts +++ b/src/buffered-data.ts @@ -1,4 +1,4 @@ -import { XYZData } from "./gestures-hooks"; +import { XYZData } from "./model"; export interface TimedXYZ { x: number; diff --git a/src/components/AddDataGridRow.tsx b/src/components/AddDataGridRow.tsx index 704024cd5..e4bc45e9d 100644 --- a/src/components/AddDataGridRow.tsx +++ b/src/components/AddDataGridRow.tsx @@ -1,10 +1,11 @@ import { GridItem } from "@chakra-ui/react"; import { useCallback } from "react"; import { useIntl } from "react-intl"; -import { GestureData, useGestureActions } from "../gestures-hooks"; +import { GestureData } from "../model"; import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; import DataRecordingGridItem from "./DataRecordingGridItem"; import GestureNameGridItem from "./GestureNameGridItem"; +import { useAppStore } from "../store"; interface AddDataGridRowProps { gesture: GestureData; @@ -22,7 +23,7 @@ const DataSampleGridRow = ({ showWalkThrough, }: AddDataGridRowProps) => { const intl = useIntl(); - const actions = useGestureActions(); + const deleteGesture = useAppStore((s) => s.deleteGesture); const handleDeleteDataItem = useCallback(() => { const confirmationText = intl.formatMessage( @@ -32,8 +33,8 @@ const DataSampleGridRow = ({ if (!window.confirm(confirmationText)) { return; } - actions.deleteGesture(gesture.ID); - }, [actions, gesture.ID, gesture.name, intl]); + deleteGesture(gesture.ID); + }, [deleteGesture, gesture.ID, gesture.name, intl]); return ( <> diff --git a/src/components/AddDataGridWalkThrough.tsx b/src/components/AddDataGridWalkThrough.tsx index 72813e6ac..e71eec094 100644 --- a/src/components/AddDataGridWalkThrough.tsx +++ b/src/components/AddDataGridWalkThrough.tsx @@ -1,6 +1,6 @@ import { GridItem, HStack, Image, Text, VStack } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { GestureData } from "../gestures-hooks"; +import { GestureData } from "../model"; import upCurveArrowImage from "../images/curve-arrow-up.svg"; import greetingEmojiWithArrowImage from "../images/greeting-emoji-with-arrow.svg"; import DataRecordingGridItem from "./DataRecordingGridItem"; diff --git a/src/components/CodeViewCard.tsx b/src/components/CodeViewCard.tsx index 9e0de6724..fafbcb040 100644 --- a/src/components/CodeViewCard.tsx +++ b/src/components/CodeViewCard.tsx @@ -2,12 +2,12 @@ import { Card, SkeletonText, VStack } from "@chakra-ui/react"; import { BlockLayout, MakeCodeBlocksRendering, - MakeCodeProject, -} from "@microbit-foundation/react-code-view"; + Project, +} from "@microbit/makecode-embed/react"; import { memo } from "react"; interface CodeViewCardProps { - project: MakeCodeProject; + project: Project; } const CodeViewCard = ({ project }: CodeViewCardProps) => { diff --git a/src/components/CodeViewGridItem.tsx b/src/components/CodeViewGridItem.tsx index dad140284..f46bd6bb2 100644 --- a/src/components/CodeViewGridItem.tsx +++ b/src/components/CodeViewGridItem.tsx @@ -2,24 +2,26 @@ import { Box, Card, GridItem, SkeletonText } from "@chakra-ui/react"; import { BlockLayout, MakeCodeBlocksRendering, -} from "@microbit-foundation/react-code-view"; +} from "@microbit/makecode-embed/react"; import { memo, useMemo } from "react"; -import { GestureData } from "../gestures-hooks"; -import { useMakeCodeProject } from "../user-projects-hooks"; +import { generateProject } from "../makecode/utils"; +import { GestureData } from "../model"; +import { useAppStore } from "../store"; interface CodeViewGridItemProps { gesture: GestureData; - hasStoredProject: boolean; + projectEdited: boolean; } const CodeViewGridItem = ({ gesture, - hasStoredProject, + projectEdited, }: CodeViewGridItemProps) => { - const { createGestureDefaultProject } = useMakeCodeProject(); + const model = useAppStore((s) => s.model); + const gestures = useAppStore((s) => s.gestures); const project = useMemo( - () => createGestureDefaultProject(gesture), - [createGestureDefaultProject, gesture] + () => generateProject({ data: gestures }, model, gesture), + [gesture, gestures, model] ); const width = useMemo( () => `${120 + gesture.name.length * 5}px`, @@ -27,7 +29,7 @@ const CodeViewGridItem = ({ ); return ( - {!hasStoredProject && ( + {!projectEdited && ( { const intl = useIntl(); + const deleteGestureRecording = useAppStore((s) => s.deleteGestureRecording); const closeRecordingDialogFocusRef = useRef(null); - const actions = useGestureActions(); const { isConnected } = useConnectionStage(); const handleDeleteRecording = useCallback( (idx: number) => { - actions.deleteGestureRecording(data.ID, idx); + deleteGestureRecording(data.ID, idx); }, - [actions, data.ID] + [data.ID, deleteGestureRecording] ); return ( diff --git a/src/components/DataSampleGridView.tsx b/src/components/DataSampleGridView.tsx index 113f29ae2..3820d27e2 100644 --- a/src/components/DataSampleGridView.tsx +++ b/src/components/DataSampleGridView.tsx @@ -2,10 +2,10 @@ import { Grid, GridProps, useDisclosure } from "@chakra-ui/react"; import { ButtonEvent } from "@microbit/microbit-connection"; import { useEffect, useMemo, useState } from "react"; import { useConnectActions } from "../connect-actions-hooks"; -import { useGestureData } from "../gestures-hooks"; import DataSampleGridRow from "./AddDataGridRow"; import HeadingGrid from "./HeadingGrid"; import RecordingDialog from "./RecordingDialog"; +import { useAppStore } from "../store"; const gridCommonProps: Partial = { gridTemplateColumns: "290px 1fr", @@ -27,14 +27,14 @@ const headings = [ ]; const DataSamplesGridView = () => { - const [gestures] = useGestureData(); + const gestures = useAppStore((s) => s.gestures); const [selectedGestureIdx, setSelectedGestureIdx] = useState(0); - const selectedGesture = gestures.data[selectedGestureIdx] ?? gestures.data[0]; + const selectedGesture = gestures[selectedGestureIdx] ?? gestures[0]; const showWalkThrough = useMemo( () => - gestures.data.length === 0 || - (gestures.data.length === 1 && gestures.data[0].recordings.length === 0), - [gestures.data] + gestures.length === 0 || + (gestures.length === 1 && gestures[0].recordings.length === 0), + [gestures] ); const { isOpen, onClose, onOpen } = useDisclosure(); @@ -76,7 +76,7 @@ const DataSamplesGridView = () => { flexGrow={1} h={0} > - {gestures.data.map((g, idx) => ( + {gestures.map((g, idx) => ( void; - onDownload: (download: { name: string; hex: string }) => void; - onSave: (save: { name: string; hex: string }) => void; - onChange: (code: EditorProject) => void; -} +interface EditCodeDialogProps {} -const EditCodeDialog = ({ - editorVersion, - code, - isOpen, - onChange, - onBack, - onDownload, - onSave, -}: EditCodeDialogProps) => { - return ( - {}} - closeOnEsc={false} - blockScrollOnMount={false} - > - - - - - - - - - - - ); -}; +const EditCodeDialog = forwardRef( + function EditCodeDialog(_, ref) { + const containerRef = useRef(null); + const isOpen = useAppStore((s) => s.isEditorOpen); + return ( + <> + + {}} + closeOnEsc={false} + blockScrollOnMount={false} + portalProps={{ + containerRef: containerRef, + }} + > + + + + + + + + + + + + ); + } +); -export default EditCodeDialog; +export default memo(EditCodeDialog); diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index e63398663..a0136f48a 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,42 +1,42 @@ import { - EditorProject, - MakeCodeEditor, -} from "@microbit-foundation/react-editor-embed"; -import React from "react"; -import { getMakeCodeLang, useSettings } from "../settings"; + MakeCodeFrame, + MakeCodeFrameDriver, +} from "@microbit/makecode-embed/react"; +import React, { forwardRef, useCallback } from "react"; +import { getMakeCodeLang } from "../settings"; +import { useProject } from "../hooks/project-hooks"; +import { useSettings } from "../store"; const controllerId = "MicrobitMachineLearningTool"; interface EditorProps { - onBack?: () => void; - onCodeChange?: (code: EditorProject) => void; - onDownload?: (download: { name: string; hex: string }) => void; - onSave?: (save: { name: string; hex: string }) => void; - initialCode: EditorProject; version: string | undefined; style?: React.CSSProperties; } -const Editor = ({ - style, - initialCode, - version, - ...editorProps -}: EditorProps) => { +const Editor = forwardRef(function Editor( + props, + ref +) { + const { project, editorCallbacks } = useProject(); + const initialProjects = useCallback(() => { + return Promise.resolve([project]); + }, [project]); const [{ languageId }] = useSettings(); return ( - ); -}; +}); export default Editor; diff --git a/src/components/FileDropTarget.tsx b/src/components/FileDropTarget.tsx new file mode 100644 index 000000000..3ec81b511 --- /dev/null +++ b/src/components/FileDropTarget.tsx @@ -0,0 +1,74 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { Box, Center } from "@chakra-ui/layout"; +import { ReactNode, useCallback, useState } from "react"; +import { RiFolderOpenLine } from "react-icons/ri"; +import { useIntl } from "react-intl"; + +interface FileDropTargetProps { + children: ReactNode; + onFileDrop: (files: File[]) => void; +} + +/** + * An area that handles multiple dropped files. + */ +const FileDropTarget = ({ children, onFileDrop }: FileDropTargetProps) => { + const [dragOver, setDragOver] = useState(false); + + const handleDrop = useCallback( + (event: React.DragEvent) => { + setDragOver(false); + const files = Array.from(event.dataTransfer.files); + if (files.length > 0) { + event.preventDefault(); + event.stopPropagation(); + onFileDrop(files); + } + }, + [onFileDrop] + ); + const handleDragOver = useCallback((event: React.DragEvent) => { + const hasFile = Array.from(event.dataTransfer.types).indexOf("Files") >= 0; + if (hasFile) { + event.preventDefault(); + setDragOver(true); + event.dataTransfer.dropEffect = "copy"; + } + }, []); + const handleDragLeave = useCallback(() => { + setDragOver(false); + }, []); + const intl = useIntl(); + return ( + + {dragOver && ( +
        + +
        + )} + {children} +
        + ); +}; + +export default FileDropTarget; diff --git a/src/components/GestureNameGridItem.tsx b/src/components/GestureNameGridItem.tsx index de7cde155..368d5a19d 100644 --- a/src/components/GestureNameGridItem.tsx +++ b/src/components/GestureNameGridItem.tsx @@ -9,10 +9,10 @@ import { } from "@chakra-ui/react"; import { useCallback } from "react"; import { useIntl } from "react-intl"; -import { useGestureActions } from "../gestures-hooks"; import { MakeCodeIcon } from "../utils/icons"; import LedIcon from "./LedIcon"; import LedIconPicker from "./LedIconPicker"; +import { useAppStore } from "../store"; interface GestureNameGridItemProps { name: string; @@ -40,7 +40,8 @@ const GestureNameGridItem = ({ const intl = useIntl(); const toast = useToast(); const toastId = "name-too-long-toast"; - const actions = useGestureActions(); + const setGestureName = useAppStore((s) => s.setGestureName); + const setGestureIcon = useAppStore((s) => s.setGestureIcon); const onChange: React.ChangeEventHandler = useCallback( (e) => { @@ -60,16 +61,16 @@ const GestureNameGridItem = ({ }); return; } - actions.setGestureName(id, name); + setGestureName(id, name); }, - [actions, id, intl, toast] + [id, intl, setGestureName, toast] ); const handleIconSelected = useCallback( (icon: MakeCodeIcon) => { - actions.setGestureIcon(id, icon); + setGestureIcon(id, icon); }, - [actions, id] + [id, setGestureIcon] ); return ( diff --git a/src/components/LanguageDialog.tsx b/src/components/LanguageDialog.tsx index ff2807000..fbcfb1e38 100644 --- a/src/components/LanguageDialog.tsx +++ b/src/components/LanguageDialog.tsx @@ -15,7 +15,8 @@ import { import { HStack, SimpleGrid, Text, VStack } from "@chakra-ui/react"; import { useCallback } from "react"; import { FormattedMessage } from "react-intl"; -import { Language, supportedLanguages, useSettings } from "../settings"; +import { Language, supportedLanguages } from "../settings"; +import { useSettings } from "../store"; interface LanguageDialogProps { isOpen: boolean; @@ -31,16 +32,13 @@ export const LanguageDialog = ({ onClose, finalFocusRef, }: LanguageDialogProps) => { - const [settings, setSettings] = useSettings(); + const [, setSettings] = useSettings(); const handleChooseLanguage = useCallback( (languageId: string) => { - setSettings({ - ...settings, - languageId, - }); + setSettings({ languageId }); onClose(); }, - [settings, setSettings, onClose] + [setSettings, onClose] ); const hasPreviewLanguages = supportedLanguages.some((l) => l.preview); return ( diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index a04cd5055..50ccca253 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -5,12 +5,12 @@ import { SmoothieChart, TimeSeries } from "smoothie"; import { useConnectActions } from "../connect-actions-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; import { AccelerometerDataEvent } from "@microbit/microbit-connection"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; import { mlSettings } from "../ml"; import { ConnectionStatus } from "../connect-status-hooks"; import { RiArrowDropLeftFill } from "react-icons/ri"; import React from "react"; import { LabelConfig, getUpdatedLabelConfig } from "../live-graph-label-config"; +import { useAppStore } from "../store"; const initialLabelConfigs: LabelConfig[] = [ { label: "x", arrowHeight: 0, labelHeight: 0, color: "#f9808e", id: 0 }, @@ -26,7 +26,6 @@ const smoothenDataPoint = (curr: number, next: number) => { const LiveGraph = () => { const { isConnected, status } = useConnectionStage(); - const [{ stage }] = useMlStatus(); const connectActions = useConnectActions(); const canvasRef = useRef(null); @@ -88,15 +87,17 @@ const LiveGraph = () => { }, [chart, isConnected, status]); // Draw on graph to display that users are recording - const [isRecording, setIsRecording] = useState(false); + // Ideally we'd do this without timing the recording again! + const [isTimingRecording, setIsTimingRecording] = useState(false); + const isRecording = useAppStore((s) => s.isRecording); useEffect(() => { - if (stage === MlStage.RecordingData && !isRecording) { + 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); - setIsRecording(true); + setIsTimingRecording(true); } setTimeout(() => { @@ -104,10 +105,10 @@ const LiveGraph = () => { const now = new Date().getTime(); recordLines.append(now - 1, 2.3, false); recordLines.append(now, -2, false); - setIsRecording(false); + setIsTimingRecording(false); }, mlSettings.duration); } - }, [isRecording, recordLines, stage]); + }, [isTimingRecording, recordLines, isRecording]); const [labelConfigs, setLabelConfigs] = useState(initialLabelConfigs); diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 292beb7bc..88f4c68f6 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -7,7 +7,7 @@ import { useConnectionStage } from "../connection-stage-hooks"; import InfoToolTip from "./InfoToolTip"; import LedIcon from "./LedIcon"; import LiveGraph from "./LiveGraph"; -import { Gesture } from "../gestures-hooks"; +import { Gesture } from "../model"; interface LiveGraphPanelProps { detected?: Gesture | undefined; diff --git a/src/components/ProjectDropTarget.tsx b/src/components/ProjectDropTarget.tsx new file mode 100644 index 000000000..37f772554 --- /dev/null +++ b/src/components/ProjectDropTarget.tsx @@ -0,0 +1,28 @@ +/** + * (c) 2021, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { BoxProps } from "@chakra-ui/layout"; +import { useCallback } from "react"; +import FileDropTarget from "./FileDropTarget"; +import { useProject } from "../hooks/project-hooks"; + +interface ProjectDropTargetProps extends BoxProps { + children: React.ReactElement; +} + +const ProjectDropTarget = ({ children }: ProjectDropTargetProps) => { + const { loadProject } = useProject(); + const handleDrop = useCallback( + (files: File[]) => { + if (files.length === 1) { + loadProject(files[0]); + } + }, + [loadProject] + ); + return {children}; +}; + +export default ProjectDropTarget; diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index 33466f91e..0e5f270cd 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -15,9 +15,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { TimedXYZ } from "../buffered-data"; import { useBufferedData } from "../buffered-data-hooks"; -import { GestureData, useGestureActions, XYZData } from "../gestures-hooks"; import { mlSettings } from "../ml"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; +import { GestureData, XYZData } from "../model"; +import { useAppStore } from "../store"; interface CountdownStage { value: string | number; @@ -46,9 +46,10 @@ const RecordingDialog = ({ }: RecordingDialogProps) => { const intl = useIntl(); const toast = useToast(); - const actions = useGestureActions(); + const recordingStarted = useAppStore((s) => s.recordingStarted); + const recordingStopped = useAppStore((s) => s.recordingStopped); + const addGestureRecordings = useAppStore((s) => s.addGestureRecordings); const recordingDataSource = useRecordingDataSource(); - const [, setStatus] = useMlStatus(); const [recordingStatus, setRecordingStatus] = useState( RecordingStatus.None ); @@ -68,11 +69,12 @@ const RecordingDialog = ({ const [countdownStageIndex, setCountdownStageIndex] = useState(0); const handleCleanup = useCallback(() => { + recordingStopped(); setRecordingStatus(RecordingStatus.None); setCountdownStageIndex(0); setProgress(0); onClose(); - }, [onClose]); + }, [onClose, recordingStopped]); const handleOnClose = useCallback(() => { recordingDataSource.cancelRecording(); @@ -98,12 +100,10 @@ const RecordingDialog = ({ return; } else { setRecordingStatus(RecordingStatus.Recording); - setStatus({ stage: MlStage.RecordingData }); + recordingStarted(); recordingDataSource.startRecording({ onDone(data) { - actions.addGestureRecordings(gestureId, [ - { ID: Date.now(), data }, - ]); + addGestureRecordings(gestureId, [{ ID: Date.now(), data }]); handleCleanup(); }, onError() { @@ -132,14 +132,14 @@ const RecordingDialog = ({ isOpen, recordingStatus, countdownStageIndex, - setStatus, recordingDataSource, - actions, gestureId, handleOnClose, handleCleanup, toast, intl, + recordingStarted, + addGestureRecordings, ]); return ( diff --git a/src/components/RecordingGraph.tsx b/src/components/RecordingGraph.tsx index 8e3f3ddcc..1326b6c65 100644 --- a/src/components/RecordingGraph.tsx +++ b/src/components/RecordingGraph.tsx @@ -8,7 +8,7 @@ import { registerables, } from "chart.js"; import { useEffect, useRef } from "react"; -import { XYZData } from "../gestures-hooks"; +import { XYZData } from "../model"; import { getConfig as getRecordingChartConfig } from "../recording-graph"; interface RecordingGraphProps { diff --git a/src/components/SaveButton.tsx b/src/components/SaveButton.tsx new file mode 100644 index 000000000..10dc280b5 --- /dev/null +++ b/src/components/SaveButton.tsx @@ -0,0 +1,65 @@ +import { Button, useDisclosure, useToast } from "@chakra-ui/react"; +import { useCallback } from "react"; +import { RiDownload2Line } from "react-icons/ri"; +import { FormattedMessage, useIntl } from "react-intl"; +import { useProject } from "../hooks/project-hooks"; +import { useSettings } from "../store"; +import SaveHelpDialog from "./SaveHelpDialog"; +import SaveProgressDialog from "./SaveProgressDialog"; + +const SaveButton = () => { + const { saveProjectHex } = useProject(); + const [settings] = useSettings(); + const preSaveDialogDisclosure = useDisclosure(); + const saveProgressDisclosure = useDisclosure(); + const intl = useIntl(); + const toast = useToast(); + + const handleSave = useCallback(async () => { + preSaveDialogDisclosure.onClose(); + saveProgressDisclosure.onOpen(); + await saveProjectHex(); + saveProgressDisclosure.onClose(); + toast({ + id: "save-complete", + position: "top", + duration: 5_000, + title: intl.formatMessage({ id: "saving-toast-title" }), + status: "info", + }); + }, [ + preSaveDialogDisclosure, + saveProgressDisclosure, + saveProjectHex, + toast, + intl, + ]); + + const handleSaveClick = useCallback(() => { + if (settings.showPreSaveHelp) { + preSaveDialogDisclosure.onOpen(); + } else { + void handleSave(); + } + }, [handleSave, preSaveDialogDisclosure, settings.showPreSaveHelp]); + + return ( + <> + + + + + ); +}; + +export default SaveButton; diff --git a/src/components/SaveHelpDialog.tsx b/src/components/SaveHelpDialog.tsx new file mode 100644 index 000000000..1386efa19 --- /dev/null +++ b/src/components/SaveHelpDialog.tsx @@ -0,0 +1,76 @@ +import { + Button, + Checkbox, + Heading, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Stack, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import { useSettings } from "../store"; +import { ChangeEvent, useCallback } from "react"; + +interface SaveHelpDialogProps { + isOpen: boolean; + onClose: () => void; + onSave: () => void; +} + +const SaveHelpDialog = ({ isOpen, onClose, onSave }: SaveHelpDialogProps) => { + const [settings, setSettings] = useSettings(); + const skip = !settings.showPreSaveHelp; + const handleChangeSkip = useCallback( + (e: ChangeEvent) => { + setSettings({ showPreSaveHelp: !e.currentTarget.checked }); + }, + [setSettings] + ); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default SaveHelpDialog; diff --git a/src/components/SaveProgressDialog.tsx b/src/components/SaveProgressDialog.tsx new file mode 100644 index 000000000..d372c8f76 --- /dev/null +++ b/src/components/SaveProgressDialog.tsx @@ -0,0 +1,46 @@ +import { + Heading, + Modal, + ModalBody, + ModalContent, + ModalOverlay, + Progress, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; + +export interface SavingDialogProps { + isOpen: boolean; +} + +const SaveProgressDialog = ({ isOpen }: SavingDialogProps) => { + return ( + {}} + size="2xl" + isCentered + > + + + + + + + + + + + + + + + + + ); +}; + +export default SaveProgressDialog; diff --git a/src/components/SettingsMenu.tsx b/src/components/SettingsMenu.tsx index 4238a060a..7ecc7f6d0 100644 --- a/src/components/SettingsMenu.tsx +++ b/src/components/SettingsMenu.tsx @@ -5,10 +5,10 @@ import { MenuList, ThemeTypings, } from "@chakra-ui/react"; +import { useRef } from "react"; import { RiSettings2Line } from "react-icons/ri"; import { useIntl } from "react-intl"; import LanguageMenuItem from "./LanguageMenuItem"; -import { useRef } from "react"; interface SettingsMenuProps { variant?: ThemeTypings["components"]["Menu"]["variants"]; diff --git a/src/components/StartOverWarningDialog.tsx b/src/components/StartOverWarningDialog.tsx index 3a94d04fd..ae7fa2899 100644 --- a/src/components/StartOverWarningDialog.tsx +++ b/src/components/StartOverWarningDialog.tsx @@ -11,9 +11,9 @@ import { Text, VStack, } from "@chakra-ui/react"; -import { ReactNode, useCallback } from "react"; +import { ReactNode } from "react"; import { FormattedMessage } from "react-intl"; -import { useGestureActions } from "../gestures-hooks"; +import { useAppStore } from "../store"; interface StartOverWardningDialogProps { isOpen: boolean; @@ -26,10 +26,7 @@ const StartOverWarningDialog = ({ onClose, onStart, }: StartOverWardningDialogProps) => { - const actions = useGestureActions(); - const handleDatasetDownload = useCallback(() => { - actions.downloadDataset(); - }, [actions]); + const downloadDataset = useAppStore((s) => s.downloadDataset); return ( ( diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index 28b00ff33..bae890a32 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -1,22 +1,17 @@ -import * as tf from "@tensorflow/tfjs"; import { Button, HStack, StackProps, useDisclosure } from "@chakra-ui/react"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; -import { useGestureActions } from "../gestures-hooks"; import { createSessionPageUrl } from "../urls"; import StartOverWarningDialog from "./StartOverWarningDialog"; import { useConnectionStage } from "../connection-stage-hooks"; import { ConnectionStatus } from "../connect-status-hooks"; import { SessionPageId } from "../pages-config"; -import { modelUrl } from "../ml-status-hooks"; +import { useAppStore, useHasGestures } from "../store"; const StartResumeActions = ({ ...props }: Partial) => { - const gestureActions = useGestureActions(); - const hasExistingSession = useMemo( - () => gestureActions.hasGestures(), - [gestureActions] - ); + const newSession = useAppStore((s) => s.newSession); + const hasExistingSession = useHasGestures(); const [hasConnectFlowStarted, setHasConnectFlowStarted] = useState(false); const startOverWarningDialogDisclosure = useDisclosure(); @@ -33,10 +28,7 @@ const StartResumeActions = ({ ...props }: Partial) => { const handleStartNewSession = useCallback(() => { startOverWarningDialogDisclosure.onClose(); - gestureActions.deleteAllGestures(); - tf.io.removeModel(modelUrl).catch(() => { - // Throws if there is no model to remove. - }); + newSession(); if (isConnected) { handleNavigateToAddData(); } else { @@ -45,7 +37,7 @@ const StartResumeActions = ({ ...props }: Partial) => { } }, [ startOverWarningDialogDisclosure, - gestureActions, + newSession, isConnected, handleNavigateToAddData, connStageActions, diff --git a/src/components/TestingModelGridView.tsx b/src/components/TestingModelGridView.tsx index 2c698987f..74f3ec403 100644 --- a/src/components/TestingModelGridView.tsx +++ b/src/components/TestingModelGridView.tsx @@ -11,26 +11,19 @@ import { Portal, VStack, VisuallyHidden, - useDisclosure, } from "@chakra-ui/react"; -import { - MakeCodeProject, - MakeCodeRenderBlocksProvider, -} from "@microbit-foundation/react-code-view"; -import { EditorProject } from "@microbit-foundation/react-editor-embed"; -import React, { useCallback } from "react"; +import { MakeCodeRenderBlocksProvider } from "@microbit/makecode-embed/react"; +import React from "react"; import { RiArrowRightLine, RiDeleteBin2Line } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; -import { useConnectionStage } from "../connection-stage-hooks"; -import { useGestureActions, useGestureData } from "../gestures-hooks"; import { mlSettings } from "../ml"; -import { usePrediction } from "../ml-hooks"; -import { getMakeCodeLang, useSettings } from "../settings"; -import { useMakeCodeProject } from "../user-projects-hooks"; +import { usePrediction } from "../hooks/ml-hooks"; +import { getMakeCodeLang } from "../settings"; +import { useAppStore, useSettings } from "../store"; +import { useProject } from "../hooks/project-hooks"; import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; import CodeViewCard from "./CodeViewCard"; import CodeViewGridItem from "./CodeViewGridItem"; -import EditCodeDialog from "./EditCodeDialog"; import GestureNameGridItem from "./GestureNameGridItem"; import HeadingGrid from "./HeadingGrid"; import LiveGraphPanel from "./LiveGraphPanel"; @@ -64,13 +57,9 @@ const TestingModelGridView = () => { const prediction = usePrediction(); const { detected, confidences } = prediction ?? {}; const intl = useIntl(); - const editCodeDialogDisclosure = useDisclosure(); - const [gestures] = useGestureData(); - const { setRequiredConfidence } = useGestureActions(); - const { actions } = useConnectionStage(); - - const { hasStoredProject, userProject, setUserProject } = - useMakeCodeProject(); + const gestures = useAppStore((s) => s.gestures); + const setRequiredConfidence = useAppStore((s) => s.setRequiredConfidence); + const { openEditor, project, resetProject, projectEdited } = useProject(); const detectedLabel = detected?.name ?? @@ -81,44 +70,8 @@ const TestingModelGridView = () => { const [{ languageId }] = useSettings(); const makeCodeLang = getMakeCodeLang(languageId); - const handleCodeChange = useCallback( - (code: EditorProject) => { - setUserProject(code as MakeCodeProject); - }, - [setUserProject] - ); - - const handleResetProject = useCallback(() => { - // Clear stored project - setUserProject(undefined); - }, [setUserProject]); - - const handleSave = useCallback((save: { name: string; hex: string }) => { - const blob = new Blob([save.hex], { type: "application/octet-stream" }); - const a = document.createElement("a"); - a.href = URL.createObjectURL(blob); - a.download = `${save.name}.hex`; - a.click(); - URL.revokeObjectURL(a.href); - }, []); - - const handleDownload = useCallback( - async (download: { name: string; hex: string }) => { - await actions.startDownloadUserProjectHex(download.hex); - }, - [actions] - ); return ( <> - { - {gestures.data.map((gesture, idx) => { + {gestures.map((gesture, idx) => { const { ID, name, @@ -187,13 +140,13 @@ const TestingModelGridView = () => {
        ); })} - {hasStoredProject && } + {projectEdited && } @@ -210,10 +163,7 @@ const TestingModelGridView = () => { > - { } - onClick={handleResetProject} - isDisabled={!hasStoredProject} + onClick={resetProject} + isDisabled={!projectEdited} > diff --git a/src/components/TrainModelFlowDialogs.tsx b/src/components/TrainModelFlowDialogs.tsx index 18901d406..788bdb250 100644 --- a/src/components/TrainModelFlowDialogs.tsx +++ b/src/components/TrainModelFlowDialogs.tsx @@ -1,20 +1,53 @@ -import { - TrainModelDialogStage, - useTrainModelDialog, -} from "../train-model-dialog-hooks"; +import { useCallback } from "react"; +import { useNavigate } from "react-router"; +import { TrainModelDialogStage } from "../model"; +import { SessionPageId } from "../pages-config"; +import { useAppStore, useSettings } from "../store"; +import { createSessionPageUrl } from "../urls"; +import TrainingErrorDialog from "./TrainingErrorDialog"; +import TrainingModelProgressDialog from "./TrainingModelProgressDialog"; import TrainModelIntroDialog from "./TrainModelIntroDialog"; -import TrainingStatusDialog from "./TrainingStatusDialog"; const TrainModelFlowDialogs = () => { - const { stage, onIntroNext, onClose } = useTrainModelDialog(); + const stage = useAppStore((s) => s.trainModelDialogStage); + const closeTrainModelDialogs = useAppStore((s) => s.closeTrainModelDialogs); + const navigate = useNavigate(); + const trainModel = useAppStore((s) => s.trainModel); + const trainModelProgress = useAppStore((s) => s.trainModelProgress); + const [, setSettings] = useSettings(); + + const handleIntroNext = useCallback( + async (isSkipNextTime: boolean) => { + setSettings({ showPreTrainHelp: !isSkipNextTime }); + const result = await trainModel(); + if (result) { + navigate(createSessionPageUrl(SessionPageId.TestingModel)); + } + }, + [navigate, setSettings, trainModel] + ); switch (stage) { case TrainModelDialogStage.Closed: - return <>; + return null; case TrainModelDialogStage.ShowingIntroduction: - return ; - case TrainModelDialogStage.ShowingTrainingStatus: - return ; + return ( + + ); + case TrainModelDialogStage.TrainingError: + return ( + + ); + case TrainModelDialogStage.TrainingInProgress: + return ( + + ); } }; diff --git a/src/components/TrainModelIntroDialog.tsx b/src/components/TrainModelIntroDialog.tsx index b18ed2a98..d6fa0f4d2 100644 --- a/src/components/TrainModelIntroDialog.tsx +++ b/src/components/TrainModelIntroDialog.tsx @@ -15,16 +15,18 @@ import { import { useCallback, useState } from "react"; import { FormattedMessage } from "react-intl"; import trainModelImage from "../images/train_model_black.svg"; -import { useTrainModelDialog } from "../train-model-dialog-hooks"; import TrainingButton from "./TrainingButton"; interface TrainModelIntroDialogProps { - onNext: (isSkipIntro: boolean) => void; + onNext: (isSkipNextTime: boolean) => void; + onClose: () => void; } -const TrainModelIntroDialog = ({ onNext }: TrainModelIntroDialogProps) => { - const { onClose, isSkipIntro: defaultIsSkipIntro } = useTrainModelDialog(); - const [skip, setSkip] = useState(defaultIsSkipIntro); +const TrainModelIntroDialog = ({ + onClose, + onNext, +}: TrainModelIntroDialogProps) => { + const [skip, setSkip] = useState(false); const handleOnNext = useCallback(() => onNext(skip), [onNext, skip]); return ( diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx index 049d12dbe..22b711590 100644 --- a/src/components/TrainingButton.tsx +++ b/src/components/TrainingButton.tsx @@ -1,19 +1,13 @@ import { Button, ButtonProps } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; +import { useAppStore, useHasSufficientDataForTraining } from "../store"; const TrainingButton = (props: ButtonProps) => { - const [{ stage }] = useMlStatus(); - + const hasSufficientData = useHasSufficientDataForTraining(); + const model = useAppStore((s) => s.model); + const isEnabled = hasSufficientData && !model; return ( - ); diff --git a/src/components/TrainingStatusDialog.tsx b/src/components/TrainingStatusDialog.tsx deleted file mode 100644 index 8274b37f5..000000000 --- a/src/components/TrainingStatusDialog.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useCallback, useEffect } from "react"; -import { useNavigate } from "react-router"; -import { useMlActions } from "../ml-hooks"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; -import { createSessionPageUrl } from "../urls"; -import TrainingErrorDialog from "./TrainingErrorDialog"; -import TrainingModelProgressDialog from "./TrainingModelProgressDialog"; -import { SessionPageId } from "../pages-config"; - -interface TrainingStatusDialogProps { - onClose: () => void; -} - -const TrainingStatusDialog = ({ onClose }: TrainingStatusDialogProps) => { - const [status] = useMlStatus(); - const actions = useMlActions(); - const navigate = useNavigate(); - - const handleTrain = useCallback(async () => { - await actions.trainModel(); - }, [actions]); - - useEffect(() => { - if (status.stage === MlStage.NotTrained) { - void handleTrain(); - } - if (status.stage === MlStage.TrainingComplete) { - onClose(); - navigate(createSessionPageUrl(SessionPageId.TestingModel)); - } - }, [handleTrain, navigate, onClose, status.stage]); - - switch (status.stage) { - case MlStage.TrainingError: - return ; - case MlStage.TrainingInProgress: - return ( - - ); - default: - return ; - } -}; - -export default TrainingStatusDialog; diff --git a/src/components/UploadDataSamplesMenuItem.tsx b/src/components/UploadDataSamplesMenuItem.tsx index 0ccbe3198..959ae89b3 100644 --- a/src/components/UploadDataSamplesMenuItem.tsx +++ b/src/components/UploadDataSamplesMenuItem.tsx @@ -2,30 +2,10 @@ import { Input, MenuItem } from "@chakra-ui/react"; import { useCallback, useRef } from "react"; import { RiUpload2Line } from "react-icons/ri"; import { FormattedMessage } from "react-intl"; -import { GestureData, useGestureActions } from "../gestures-hooks"; - -/** - * Reads file as text via a FileReader. - * - * @param file A file (e.g. from a file input or drop operation). - * @returns The a promise of text from that file. - */ -const readFileAsText = async (file: File): Promise => { - const reader = new FileReader(); - return new Promise((resolve, reject) => { - reader.onload = (e: ProgressEvent) => { - resolve(e.target!.result as string); - }; - reader.onerror = (e: ProgressEvent) => { - const error = e.target?.error || new Error("Error reading file as text"); - reject(error); - }; - reader.readAsText(file); - }); -}; +import { useProject } from "../hooks/project-hooks"; const UploadDataSamplesMenuItem = () => { - const actions = useGestureActions(); + const { loadProject } = useProject(); const inputRef = useRef(null); const handleChooseFile = useCallback(() => { @@ -33,27 +13,23 @@ const UploadDataSamplesMenuItem = () => { }, []); const onOpen = useCallback( - async (files: File[]) => { - if (files.length === 0) { - throw new Error("Expected to be called with at least one file"); + (files: File[]) => { + if (files.length === 1) { + loadProject(files[0]); } - const gestureData = await readFileAsText(files[0]); - actions.validateAndSetGestures( - JSON.parse(gestureData) as Partial[] - ); }, - [actions] + [loadProject] ); const handleOpenFile = useCallback( - async (e: React.ChangeEvent) => { + (e: React.ChangeEvent) => { const files = e.target.files; if (files) { const filesArray = Array.from(files); // Clear the input so we're triggered if the user opens the same file again. inputRef.current!.value = ""; if (filesArray.length > 0) { - await onOpen(filesArray); + onOpen(filesArray); } } }, diff --git a/src/flags.test.ts b/src/flags.test.ts index b67073de6..a31e82c3a 100644 --- a/src/flags.test.ts +++ b/src/flags.test.ts @@ -25,12 +25,8 @@ describe("flags", () => { const params = new URLSearchParams([["flag", "exampleOptInA"]]); const flags = flagsForParams("local", params); - - expect( - Object.entries(flags).every( - ([flag, status]) => (flag === "exampleOptInA") === status - ) - ).toEqual(true); + expect(flags.exampleOptInA).toEqual(true); + expect(flags.exampleOptInB).toEqual(false); }); it("enable everything", () => { diff --git a/src/flags.ts b/src/flags.ts index c3c762404..79a8ae884 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -11,6 +11,10 @@ import { Stage, stage as stageFromEnvironment } from "./environment"; * A union of the flag names (alphabetical order). */ export type Flag = + /** + * Flag to enable redux/zustand dev tools. + */ + | "devtools" /** * Flag to add a prototype warning. Enabled for staging site and production stages. */ @@ -28,6 +32,7 @@ interface FlagMetadata { const allFlags: FlagMetadata[] = [ // Alphabetical order. + { name: "devtools", defaultOnStages: ["local"] }, { name: "prototypeWarning", defaultOnStages: ["staging", "production"] }, { name: "exampleOptInA", defaultOnStages: ["review", "staging"] }, { name: "exampleOptInB", defaultOnStages: [] }, diff --git a/src/gestures-hooks.tsx b/src/gestures-hooks.tsx deleted file mode 100644 index 192ec43d9..000000000 --- a/src/gestures-hooks.tsx +++ /dev/null @@ -1,303 +0,0 @@ -import { createContext, ReactNode, useContext, useMemo } from "react"; -import { useStorage } from "./hooks/use-storage"; -import { MlStage, MlStatus, useMlStatus } from "./ml-status-hooks"; -import { isArray } from "./utils"; -import { defaultIcons, MakeCodeIcon } from "./utils/icons"; -export interface XYZData { - x: number[]; - y: number[]; - z: number[]; -} - -interface RecordingData { - ID: number; - data: XYZData; -} - -export interface Gesture { - name: string; - ID: number; - icon: MakeCodeIcon; - requiredConfidence?: number; -} - -export interface GestureData extends Gesture { - recordings: RecordingData[]; -} - -export interface GestureContextState { - data: GestureData[]; -} - -type GestureContextValue = [ - GestureContextState, - (gestureData: GestureContextState) => void -]; - -// Exported for testing -export const isValidStoredGestureData = ( - v: unknown -): v is GestureContextState => { - if (typeof v !== "object") { - return false; - } - const valueObject = v as object; - if (!("data" in valueObject)) { - return false; - } - const data = valueObject.data; - if (!isArray(data)) { - return false; - } - const array = data as unknown[]; - for (const item of array) { - if (typeof item !== "object" || item === null) { - return false; - } - if ( - !("name" in item) || - !("ID" in item) || - !("recordings" in item) || - !isArray(item.recordings) - ) { - return false; - } - const recordings = item.recordings as unknown[]; - for (const rec of recordings) { - if (typeof rec !== "object" || rec === null) { - return false; - } - if (!("data" in rec) || !("ID" in rec) || isArray(rec.data)) { - return false; - } - const xyzData = rec.data as object; - if ( - !("x" in xyzData) || - !("y" in xyzData) || - !("z" in xyzData) || - !isArray(xyzData.x) || - !isArray(xyzData.y) || - !isArray(xyzData.z) - ) { - return false; - } - } - } - return true; -}; - -const GestureContext = createContext( - undefined -); - -export const useGestureData = (): GestureContextValue => { - const gestureData = useContext(GestureContext); - if (!gestureData) { - throw new Error("Missing provider"); - } - return gestureData; -}; - -export const GesturesProvider = ({ children }: { children: ReactNode }) => { - const [state, setState] = useStorage( - "local", - "gestures", - { data: [] }, - isValidStoredGestureData - ); - return ( - - {children} - - ); -}; - -export const useGestureActions = () => { - const [gestures, setGestures] = useGestureData(); - const [status, setStatus] = useMlStatus(); - const actions = useMemo( - () => new GestureActions(gestures, setGestures, status, setStatus), - [gestures, setGestures, setStatus, status] - ); - - return actions; -}; - -class GestureActions { - constructor( - private gestureState: GestureContextState, - private setGestureState: (gestureData: GestureContextState) => void, - private status: MlStatus, - private setStatus: (status: MlStatus) => void - ) { - // Initialize with at least one gesture for walkthrough. - if (!this.gestureState.data.length) { - this.setGestureState({ data: [this.generateNewGesture(true)] }); - } - // If icon is missing from stored data, generate default icons. - if (this.gestureState.data.some((g) => !g.icon)) { - this.validateAndSetGestures(this.gestureState.data); - } - } - - private getDefaultIcon = ({ - isFirstGesture, - iconsInUse, - }: { - isFirstGesture?: boolean; - iconsInUse?: MakeCodeIcon[]; - }): MakeCodeIcon => { - if (isFirstGesture) { - return defaultIcons[0]; - } - if (!iconsInUse) { - iconsInUse = this.gestureState.data.map((g) => g.icon); - } - const useableIcons: MakeCodeIcon[] = []; - for (const icon of defaultIcons) { - if (!iconsInUse.includes(icon)) { - useableIcons.push(icon); - } - } - if (!useableIcons.length) { - // Better than throwing an error. - return "Heart"; - } - return useableIcons[0]; - }; - - private generateNewGesture = ( - isFirstGesture: boolean = false - ): GestureData => ({ - name: "", - recordings: [], - ID: Date.now(), - icon: this.getDefaultIcon({ isFirstGesture }), - }); - - hasGestures = (): boolean => { - return ( - this.gestureState.data.length > 0 && - (this.gestureState.data[0].name.length > 0 || - this.gestureState.data[0].recordings.length > 0) - ); - }; - - validateAndSetGestures = (gestures: Partial[]) => { - const validGestures: GestureData[] = []; - const importedGestureIcons: MakeCodeIcon[] = gestures - .map((g) => g.icon as MakeCodeIcon) - .filter(Boolean); - gestures.forEach((g) => { - if (g.ID && g.name !== undefined && Array.isArray(g.recordings)) { - if (!g.icon) { - g.icon = this.getDefaultIcon({ - iconsInUse: [ - ...validGestures.map((g) => g.icon), - ...importedGestureIcons, - ], - }); - } - validGestures.push(g as GestureData); - } - }); - this.setGestures(validGestures); - }; - - setGestures = (gestures: GestureData[], isRetrainNeeded: boolean = true) => { - const data = - // Always have at least one gesture for walk through - gestures.length === 0 ? [this.generateNewGesture(true)] : gestures; - this.setGestureState({ data }); - - // Update training status - const newTrainingStatus = !hasSufficientDataForTraining(data) - ? { stage: MlStage.InsufficientData as const } - : isRetrainNeeded || this.status.stage === MlStage.InsufficientData - ? // Updating status to retrain status is in status hook - { stage: MlStage.NotTrained as const } - : this.status; - - this.setStatus(newTrainingStatus); - }; - - addNewGesture = () => { - this.setGestures([...this.gestureState.data, this.generateNewGesture()]); - }; - - addGestureRecordings = (id: GestureData["ID"], recs: RecordingData[]) => { - const newGestures = this.gestureState.data.map((g) => { - return id !== g.ID ? g : { ...g, recordings: [...recs, ...g.recordings] }; - }); - this.setGestures(newGestures); - }; - - deleteGesture = (id: GestureData["ID"]) => { - this.setGestures(this.gestureState.data.filter((g) => g.ID !== id)); - }; - - setGestureName = (id: GestureData["ID"], name: string) => { - const newGestures = this.gestureState.data.map((g) => { - return id !== g.ID ? g : { ...g, name }; - }); - this.setGestures(newGestures, false); - }; - - setGestureIcon = (id: GestureData["ID"], icon: MakeCodeIcon) => { - const currentIcon = this.gestureState.data.find((g) => g.ID === id)?.icon; - const newGestures = this.gestureState.data.map((g) => { - if (g.ID === id) { - g.icon = icon; - } else if (g.ID !== id && g.icon === icon && currentIcon) { - g.icon = currentIcon; - } - return g; - }); - this.setGestures(newGestures, false); - }; - - setRequiredConfidence = (id: GestureData["ID"], value: number) => { - const newGestures = this.gestureState.data.map((g) => { - return id !== g.ID ? g : { ...g, requiredConfidence: value }; - }); - this.setGestures(newGestures, false); - }; - - deleteGestureRecording = ( - gestureId: GestureData["ID"], - recordingIdx: number - ) => { - const newGestures = this.gestureState.data.map((g) => { - if (gestureId !== g.ID) { - return g; - } - const recordings = g.recordings.filter((_r, i) => i !== recordingIdx); - return { ...g, recordings }; - }); - this.setGestures(newGestures); - }; - - deleteAllGestures = () => { - this.setGestures([]); - }; - - downloadDataset = () => { - const a = document.createElement("a"); - a.setAttribute( - "href", - "data:application/json;charset=utf-8," + - encodeURIComponent(JSON.stringify(this.gestureState.data, null, 2)) - ); - a.setAttribute("download", "dataset"); - a.style.display = "none"; - a.click(); - }; -} - -export const hasSufficientDataForTraining = ( - gestures: GestureData[] -): boolean => { - return ( - gestures.length >= 2 && gestures.every((g) => g.recordings.length >= 3) - ); -}; diff --git a/src/gestures.test.ts b/src/gestures.test.ts deleted file mode 100644 index 1d0587997..000000000 --- a/src/gestures.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { isValidStoredGestureData } from "./gestures-hooks"; - -describe("isValidStoredGestureData", () => { - it("checks data", () => { - expect(isValidStoredGestureData({})).toEqual(false); - expect(isValidStoredGestureData({ data: [] })).toEqual(true); - expect(isValidStoredGestureData({ data: 123 })).toEqual(false); - expect(isValidStoredGestureData({ data: {} })).toEqual(false); - }); - it("checks data properties", () => { - expect(isValidStoredGestureData({ data: [{ invalid: 3 }] })).toEqual(false); - expect(isValidStoredGestureData({ data: [{ name: 3 }] })).toEqual(false); - expect( - isValidStoredGestureData({ - data: [{ ID: 0, name: "some name", recordings: [] }], - }) - ).toEqual(true); - }); - it("checks data recordings", () => { - const generateData = (recordings: unknown) => ({ - data: [{ ID: 0, name: "some name", recordings }], - }); - expect(isValidStoredGestureData(generateData({}))).toEqual(false); - expect(isValidStoredGestureData(generateData([]))).toEqual(true); - expect( - isValidStoredGestureData(generateData([{ ID: 0, data: [] }])) - ).toEqual(false); - expect( - isValidStoredGestureData(generateData([{ ID: 0, data: {} }])) - ).toEqual(false); - expect( - isValidStoredGestureData( - generateData([{ ID: 0, data: { x: 0, y: 0, z: 0 } }]) - ) - ).toEqual(false); - expect( - isValidStoredGestureData( - generateData([{ ID: 0, data: { x: [], y: [], z: [] } }]) - ) - ).toEqual(true); - }); -}); diff --git a/src/ml-hooks.tsx b/src/hooks/ml-hooks.tsx similarity index 66% rename from src/ml-hooks.tsx rename to src/hooks/ml-hooks.tsx index 8eb667a50..761f3829e 100644 --- a/src/ml-hooks.tsx +++ b/src/hooks/ml-hooks.tsx @@ -1,24 +1,11 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import { useBufferedData } from "./buffered-data-hooks"; -import { useConnectActions } from "./connect-actions-hooks"; -import { ConnectionStatus, useConnectStatus } from "./connect-status-hooks"; -import { Gesture, GestureContextState, useGestureData } from "./gestures-hooks"; -import { useLogging } from "./logging/logging-hooks"; -import { Confidences, mlSettings, predict } from "./ml"; -import { MlActions } from "./ml-actions"; -import { MlStage, useMlStatus } from "./ml-status-hooks"; - -export const useMlActions = () => { - const [gestures] = useGestureData(); - const [, setStatus] = useMlStatus(); - const logger = useLogging(); - - const actions = useMemo( - () => new MlActions(logger, gestures, setStatus), - [gestures, logger, setStatus] - ); - return actions; -}; +import { useEffect, useRef, useState } from "react"; +import { useBufferedData } from "../buffered-data-hooks"; +import { useConnectActions } from "../connect-actions-hooks"; +import { useConnectStatus } from "../connect-status-hooks"; +import { Gesture } from "../model"; +import { useLogging } from "../logging/logging-hooks"; +import { Confidences, mlSettings, predict } from "../ml"; +import { useAppStore } from "../store"; export interface PredictionResult { confidences: Confidences; @@ -28,11 +15,11 @@ export interface PredictionResult { export const usePrediction = () => { const buffer = useBufferedData(); const logging = useLogging(); - const [status] = useMlStatus(); const [connectStatus] = useConnectStatus(); const connection = useConnectActions(); const [prediction, setPrediction] = useState(); - const [gestureData] = useGestureData(); + const gestureData = useAppStore((s) => s.gestures); + const model = useAppStore((s) => s.model); // Use a ref to prevent restarting the effect every time thesholds change. // We only use the ref's value during the setInterval callback not render. @@ -40,18 +27,15 @@ export const usePrediction = () => { const gestureDataRef = useRef(gestureData); gestureDataRef.current = gestureData; useEffect(() => { - if ( - status.stage !== MlStage.TrainingComplete || - connectStatus !== ConnectionStatus.Connected - ) { + if (!model) { return; } const runPrediction = async () => { const startTime = Date.now() - mlSettings.duration; const input = { - model: status.model, + model, data: buffer.getSamples(startTime), - classificationIds: gestureDataRef.current.data.map((g) => g.ID), + classificationIds: gestureDataRef.current.map((g) => g.ID), }; if (input.data.x.length > mlSettings.minSamples) { const result = await predict(input); @@ -84,13 +68,13 @@ export const usePrediction = () => { setPrediction(undefined); clearInterval(interval); }; - }, [connection, logging, status, connectStatus, buffer]); + }, [connection, logging, connectStatus, buffer, model]); return prediction; }; export const getDetectedGesture = ( - gestureData: GestureContextState, + gestures: Gesture[], confidences: Confidences | undefined ): Gesture | undefined => { if (!confidences) { @@ -98,7 +82,7 @@ export const getDetectedGesture = ( } // If more than one meet the threshold pick the highest - const thresholded = gestureData.data + const thresholded = gestures .map((gesture) => ({ gesture, thresholdDelta: diff --git a/src/hooks/project-hooks.tsx b/src/hooks/project-hooks.tsx new file mode 100644 index 000000000..84d68ba39 --- /dev/null +++ b/src/hooks/project-hooks.tsx @@ -0,0 +1,221 @@ +import { + EditorWorkspaceSaveRequest, + MakeCodeFrameDriver, + MakeCodeFrameProps, + Project, +} from "@microbit/makecode-embed/react"; +import { + ReactNode, + RefObject, + createContext, + useCallback, + useContext, + useMemo, + useRef, +} from "react"; +import { useConnectionStage } from "../connection-stage-hooks"; +import { isDatasetUserFileFormat } from "../model"; +import { useAppStore } from "../store"; +import { getLowercaseFileExtension, readFileAsText } from "../utils/fs-util"; + +interface ProjectContext { + openEditor(): Promise; + project: Project; + projectEdited: boolean; + resetProject: () => void; + loadProject: (file: File) => void; + saveProjectHex: () => Promise; + editorCallbacks: Pick< + MakeCodeFrameProps, + "onDownload" | "onWorkspaceSave" | "onSave" | "onBack" + >; +} + +const ProjectContext = createContext(undefined); + +export const useProject = (): ProjectContext => { + const project = useContext(ProjectContext); + if (!project) { + throw new Error("Missing provider"); + } + return project; +}; + +interface ProjectProviderProps { + driverRef: RefObject; + children: ReactNode; +} + +export const ProjectProvider = ({ + driverRef, + children, +}: ProjectProviderProps) => { + const setEditorOpen = useAppStore((s) => s.setEditorOpen); + + // We use this to track when we need special handling of an event from MakeCode + const waitingForEditorContentLoaded = useRef void)>( + undefined + ); + + // We use this to track when we're expecting a native app save from MakeCode + const waitingForDownload = useRef< + undefined | ((download: { hex: string; name: string }) => void) + >(undefined); + const waitForNextDownload = useCallback(() => { + return new Promise<{ hex: string; name: string }>((resolve) => { + waitingForDownload.current = (download: { + hex: string; + name: string; + }) => { + resolve(download); + waitingForDownload.current = undefined; + }; + }); + }, []); + + const project = useAppStore((s) => s.project); + const projectEdited = useAppStore((s) => s.projectEdited); + const expectChangedHeader = useAppStore((s) => s.expectChangedHeader); + const projectFlushedToEditor = useAppStore((s) => s.projectFlushedToEditor); + const appEditNeedsFlushToEditor = useAppStore( + (s) => s.appEditNeedsFlushToEditor + ); + const doAfterMakeCodeUpdate = useCallback( + async (action: () => Promise) => { + if (appEditNeedsFlushToEditor) { + expectChangedHeader(); + await driverRef.current!.importProject({ project }); + projectFlushedToEditor(); + } + return action(); + }, + [ + appEditNeedsFlushToEditor, + driverRef, + expectChangedHeader, + project, + projectFlushedToEditor, + ] + ); + const openEditor = useCallback(async () => { + await doAfterMakeCodeUpdate(() => { + setEditorOpen(true); + return Promise.resolve(); + }); + }, [doAfterMakeCodeUpdate, setEditorOpen]); + + const resetProject = useAppStore((s) => s.resetProject); + const loadDataset = useAppStore((s) => s.loadDataset); + + const loadProject = useCallback( + async (file: File): Promise => { + const fileExtension = getLowercaseFileExtension(file.name); + if (fileExtension === "json") { + const gestureDataString = await readFileAsText(file); + const gestureData = JSON.parse(gestureDataString) as unknown; + if (isDatasetUserFileFormat(gestureData)) { + loadDataset(gestureData); + } else { + // TODO: complain to the user! + } + } else if (fileExtension === "hex") { + driverRef.current!.importFile({ + filename: file.name, + parts: [await readFileAsText(file)], + }); + } + }, + [driverRef, loadDataset] + ); + + const saveProjectHex = useCallback(async (): Promise => { + await doAfterMakeCodeUpdate(async () => { + const downloadPromise = waitForNextDownload(); + await driverRef.current!.compile(); + const download = await downloadPromise; + triggerBrowserDownload(download); + }); + }, [doAfterMakeCodeUpdate, driverRef, waitForNextDownload]); + + // These are event handlers for MakeCode + + const editorChange = useAppStore((s) => s.editorChange); + const onWorkspaceSave = useCallback( + (event: EditorWorkspaceSaveRequest) => { + editorChange(event.project); + }, + [editorChange] + ); + + const onBack = useCallback(() => setEditorOpen(false), [setEditorOpen]); + + const onSave = useCallback((save: { name: string; hex: string }) => { + // Handles the event we get from MakeCode to say a hex needs saving to disk. + // In practice this is via "Download" ... "Save as file" + // TODO: give this the same behaviour as SaveButton in terms of dialogs etc. + triggerBrowserDownload(save); + }, []); + + const onEditorContentLoaded = useCallback(() => { + waitingForEditorContentLoaded.current?.(); + waitingForEditorContentLoaded.current = undefined; + }, []); + + const { actions } = useConnectionStage(); + const onDownload = useCallback( + // Handles the event we get from MakeCode to say a hex needs downloading to the micro:bit. + async (download: { name: string; hex: string }) => { + if (waitingForDownload?.current) { + waitingForDownload.current(download); + } else { + // Ideally we'd preserve the filename here and use it for the fallback if WebUSB fails. + await actions.startDownloadUserProjectHex(download.hex); + } + }, + [actions] + ); + + const value = useMemo( + () => ({ + loadProject, + openEditor, + project, + projectEdited, + resetProject, + saveProjectHex, + editorCallbacks: { + onSave, + onWorkspaceSave, + onDownload, + onBack, + onEditorContentLoaded, + }, + }), + [ + loadProject, + onBack, + onDownload, + onEditorContentLoaded, + onSave, + onWorkspaceSave, + openEditor, + project, + projectEdited, + resetProject, + saveProjectHex, + ] + ); + + return ( + {children} + ); +}; + +const triggerBrowserDownload = (save: { name: string; hex: string }) => { + const blob = new Blob([save.hex], { type: "application/octet-stream" }); + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = `${save.name}.hex`; + a.click(); + URL.revokeObjectURL(a.href); +}; diff --git a/src/makecode/generate-custom-scripts.ts b/src/makecode/generate-custom-scripts.ts index 974807fe6..e733d1076 100644 --- a/src/makecode/generate-custom-scripts.ts +++ b/src/makecode/generate-custom-scripts.ts @@ -8,7 +8,7 @@ import { generateBlob } from "@microbit-foundation/ml-header-generator"; import { ActionName, actionNamesFromLabels } from "./utils"; import { LayersModel } from "@tensorflow/tfjs"; import { mlSettings } from "../ml"; -import { GestureData } from "../gestures-hooks"; +import { DatasetEditorJsonFormat, GestureData } from "../model"; const createMlEvents = (actionNames: ActionName[]) => { let code = ""; @@ -84,13 +84,6 @@ ${createEventListeners(actionNames)} `; }; -export const getDataJson = (gs: GestureData[]) => { - return JSON.stringify( - gs.map((g) => ({ - ID: g.ID, - name: g.name, - numRecordings: g.recordings.length, - requiredConfidence: g.requiredConfidence, - })) - ); +export const getDatasetJson = (gs: DatasetEditorJsonFormat) => { + return JSON.stringify(gs, null, 2); }; diff --git a/src/makecode/generate-main-scripts.test.ts b/src/makecode/generate-main-scripts.test.ts index 732ea70b9..3f138956d 100644 --- a/src/makecode/generate-main-scripts.test.ts +++ b/src/makecode/generate-main-scripts.test.ts @@ -7,27 +7,26 @@ * SPDX-License-Identifier: MIT */ -import { Gesture } from "../gestures-hooks"; +import { Gesture } from "../model"; import { getMainScript } from "./generate-main-scripts"; describe("test generateMainScripts", () => { it("generates xml blocks", () => { - const expected = ` - + const expected = ` ml.event.Name IconNames.Heart - `; +`; const gs: Gesture[] = [{ name: "name", icon: "Heart", ID: 12 }]; expect(getMainScript(gs, "blocks")).toEqual(expected); }); it("generates js", () => { const expected = - " ml.onStart(ml.event.Name, function () {basic.showIcon(IconNames.Heart)})"; + "ml.onStart(ml.event.Name, function () {basic.showIcon(IconNames.Heart)})"; const gs: Gesture[] = [{ name: "name", icon: "Heart", ID: 12 }]; expect(getMainScript(gs, "javascript")).toContain(expected); }); diff --git a/src/makecode/generate-main-scripts.ts b/src/makecode/generate-main-scripts.ts index aab71690b..6612b34ac 100644 --- a/src/makecode/generate-main-scripts.ts +++ b/src/makecode/generate-main-scripts.ts @@ -1,4 +1,4 @@ -import { Gesture } from "../gestures-hooks"; +import { Gesture } from "../model"; import { actionNamesFromLabels } from "./utils"; /** * (c) 2024, Center for Computational Thinking and Design at Aarhus University and contributors @@ -36,7 +36,7 @@ interface LanguageStatements { const statements: Record = { javascript: { - wrapper: (children) => children, + wrapper: (children) => children + "\n", showLeds: (ledPattern) => `basic.showLeds(\`${ledPattern}\`)`, showIcon: (iconName) => `basic.showIcon(IconNames.${iconName})`, clearDisplay: () => "basic.clearScreen()", @@ -46,7 +46,7 @@ const statements: Record = { }, blocks: { wrapper: (children) => - `${children}`, + `${children}`, showLeds: (ledPattern) => `\`${ledPattern}\``, showIcon: (iconName) => @@ -63,25 +63,31 @@ const onMLEventChildren = ( return iconName ? s.showIcon(iconName) : ""; }; -const getMakeCodeGestureConfigs = (gs: Gesture[]) => { +export const getMainScript = ( + gs: Gesture[], + lang: Language, + gestureToRenderAsBlock?: Gesture +) => { const actionNames = actionNamesFromLabels(gs.map((g) => g.name)); - return gs.map((g, idx) => ({ - name: actionNames[idx].actionVar, - iconName: g.icon, - })); -}; - -export const getMainScript = (gs: Gesture[], lang: Language) => { - const configs = getMakeCodeGestureConfigs(gs); + const configs = gs + .map((g, idx) => ({ + id: g.ID, + name: actionNames[idx].actionVar, + iconName: g.icon, + })) + .filter((c) => + gestureToRenderAsBlock ? c.id === gestureToRenderAsBlock.ID : true + ); const s = statements[lang]; const initPos = { x: 0, y: 0 }; - return s.wrapper(` - ${configs - .map((c, idx) => - s.onMLEvent(c.name, onMLEventChildren(s, c), { - x: initPos.x, - y: initPos.y + idx * 350, - }) - ) - .join("\n")} `); + return s.wrapper( + configs + .map((c, idx) => + s.onMLEvent(c.name, onMLEventChildren(s, c), { + x: initPos.x, + y: initPos.y + idx * 350, + }) + ) + .join("\n") + ); }; diff --git a/src/makecode/utils.test.ts b/src/makecode/utils.test.ts index 14e405ade..e9d3a46b3 100644 --- a/src/makecode/utils.test.ts +++ b/src/makecode/utils.test.ts @@ -45,15 +45,15 @@ describe("test actionNamesFromLabels", () => { it("copes with empty strings", () => { const expected: ActionName[] = [ { - actionLabel: "", + actionLabel: "Event", actionVar: "Event", }, { - actionLabel: "", + actionLabel: "Event1", actionVar: "Event1", }, { - actionLabel: "", + actionLabel: "Event2", actionVar: "Event2", }, ]; @@ -79,15 +79,15 @@ describe("test actionNamesFromLabels", () => { actionVar: "Hello", }, { - actionLabel: "hello-", + actionLabel: "hello1", actionVar: "Hello1", }, { - actionLabel: "hello--", + actionLabel: "hello2", actionVar: "Hello2", }, ]; - const userDefined = ["hello", "hello-", "hello--"]; + const userDefined = ["hello", "hello", "hello"]; expect(actionNamesFromLabels(userDefined)).toEqual(expected); }); diff --git a/src/makecode/utils.ts b/src/makecode/utils.ts index 8149a357d..b676ee65d 100644 --- a/src/makecode/utils.ts +++ b/src/makecode/utils.ts @@ -1,15 +1,74 @@ -/** - * @vitest-environment jsdom - */ -/** - * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors - * - * SPDX-License-Identifier: MIT - */ - +import { LayersModel } from "@tensorflow/tfjs"; import camelCase from "lodash.camelcase"; import upperFirst from "lodash.upperfirst"; +import { DatasetEditorJsonFormat, GestureData } from "../model"; +import { getMainScript } from "./generate-main-scripts"; +import { getAutogeneratedTs, getDatasetJson } from "./generate-custom-scripts"; + +export const filenames = { + mainTs: "main.ts", + mainBlocks: "main.blocks", + autogenerated: "autogenerated.ts", + datasetJson: "dataset.json", + pxtJson: "pxt.json", + readme: "README.md", +}; + +const pxt = { + name: "Untitled", + description: "", + dependencies: { + core: "*", + microphone: "*", + radio: "*", // Needed to compile. + "machine-learning": "github:microbit-foundation/pxt-microbit-ml#v0.4.2", + }, + files: Object.values(filenames), + preferredEditor: "blocksprj", +}; + +export const generateProject = ( + gestureState: DatasetEditorJsonFormat, + model: LayersModel | undefined, + gestureToRenderAsBlock?: GestureData +) => { + const { data: gestures } = gestureState; + const useableGestures = model ? gestures : []; + return { + text: { + [filenames.pxtJson]: JSON.stringify(pxt), + [filenames.readme]: "", + [filenames.mainTs]: getMainScript( + useableGestures, + "javascript", + gestureToRenderAsBlock + ), + [filenames.mainBlocks]: getMainScript( + useableGestures, + "blocks", + gestureToRenderAsBlock + ), + ...generateCustomFiles(gestureState, model), + }, + }; +}; + +export const generateCustomFiles = ( + gestureState: DatasetEditorJsonFormat, + model: LayersModel | undefined +) => { + const { data: gestures } = gestureState; + const useableGestures = model ? gestures : []; + return { + [filenames.autogenerated]: model + ? getAutogeneratedTs(useableGestures, model) + : "", + // Save all gestures to dataset.json. + [filenames.datasetJson]: getDatasetJson(gestureState), + }; +}; + export interface ActionName { actionLabel: string; actionVar: string; @@ -26,8 +85,14 @@ const sanitizeActionLabel = (input: string) => input.replace(/"/g, "'"); export const actionNamesFromLabels = (actionLabels: string[]): ActionName[] => { const actionNames: ActionName[] = []; actionLabels.forEach((actionLabel, i) => { - const sanitizedLabel = sanitizeActionVar(actionLabel); - let actionVar = upperFirst(camelCase(sanitizedLabel)); + let sanitizedLabel = sanitizeActionLabel(actionLabel); + if (!sanitizedLabel) { + sanitizedLabel = `Event`; + } + while (actionNames.map((an) => an.actionLabel).includes(sanitizedLabel)) { + sanitizedLabel += i; + } + let actionVar = upperFirst(camelCase(sanitizeActionVar(sanitizedLabel))); if (!actionVar) { actionVar = `Event`; } @@ -35,7 +100,7 @@ export const actionNamesFromLabels = (actionLabels: string[]): ActionName[] => { actionVar += i; } actionNames.push({ - actionLabel: sanitizeActionLabel(actionLabel), + actionLabel: sanitizedLabel, actionVar, }); }); diff --git a/src/messages/TranslationProvider.tsx b/src/messages/TranslationProvider.tsx index d80e2ad69..0d41abd68 100644 --- a/src/messages/TranslationProvider.tsx +++ b/src/messages/TranslationProvider.tsx @@ -1,4 +1,4 @@ -import { useSettings } from "../settings"; +import { useSettings } from "../store"; import { IntlProvider, MessageFormatElement } from "react-intl"; import { ReactNode, useEffect, useState } from "react"; import { retryAsyncLoad } from "./chunk-util"; diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 9d3468167..7dd26b40b 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -1088,7 +1088,7 @@ "content.trainer.training.title": [ { "type": 0, - "value": "Training model..." + "value": "Training model…" } ], "cookies-action": [ @@ -1315,12 +1315,6 @@ "value": "Status: Your model is trained." } ], - "menu.trainer.addDataButton": [ - { - "type": 0, - "value": "Add data" - } - ], "menu.trainer.addMoreDataButton": [ { "type": 0, @@ -1339,18 +1333,6 @@ "value": " Select the ‘Connect’ button to connect a micro:bit." } ], - "menu.trainer.notEnoughDataHeader1": [ - { - "type": 0, - "value": "You don't have enough data." - } - ], - "menu.trainer.notEnoughDataInfoBody": [ - { - "type": 0, - "value": "You need at least 3 data samples for 2 actions to train the model." - } - ], "menu.trainer.trainModelButton": [ { "type": 0, @@ -1375,6 +1357,12 @@ "value": "Page not found" } ], + "open-file-dropped": [ + { + "type": 0, + "value": "Open file when dropped" + } + ], "performanceWarning.content1": [ { "type": 0, @@ -1579,6 +1567,48 @@ "value": "introduction video" } ], + "save-action": [ + { + "type": 0, + "value": "Save" + } + ], + "save-hex-dialog-heading": [ + { + "type": 0, + "value": "Save project as a hex file" + } + ], + "save-hex-dialog-message1": [ + { + "type": 0, + "value": "The download contains your actions, data samples and your MakeCode project. Open it in the micro:bit machine learning tool to continue working." + } + ], + "save-hex-dialog-message2": [ + { + "type": 0, + "value": "You can also open it with Microsoft MakeCode if you don't need to change the actions and data samples." + } + ], + "saving-description": [ + { + "type": 0, + "value": "Your download will be ready soon." + } + ], + "saving-title": [ + { + "type": 0, + "value": "Saving…" + } + ], + "saving-toast-title": [ + { + "type": 0, + "value": "Your hex file has been downloaded" + } + ], "settings-menu-action": [ { "type": 0, @@ -1703,4 +1733,4 @@ "value": "Train model" } ] -} +} \ No newline at end of file diff --git a/src/ml-actions.ts b/src/ml-actions.ts deleted file mode 100644 index bd7244930..000000000 --- a/src/ml-actions.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { GestureContextState } from "./gestures-hooks"; -import { Logging } from "./logging/logging"; -import { TrainingResult, trainModel } from "./ml"; -import { MlStage, MlStatus } from "./ml-status-hooks"; - -export class MlActions { - constructor( - private logger: Logging, - private gestureState: GestureContextState, - private setStatus: (status: MlStatus) => void - ) {} - - trainModel = async (): Promise => { - this.setStatus({ stage: MlStage.TrainingInProgress, progressValue: 0 }); - const { data } = this.gestureState; - const detail = { - numActions: data.length, - numRecordings: data.reduce((acc, d) => d.recordings.length + acc, 0), - }; - const trainingResult = await trainModel({ - data, - onProgress: (progressValue) => - this.setStatus({ stage: MlStage.TrainingInProgress, progressValue }), - }); - - if (trainingResult.error) { - this.logger.event({ - type: "Data", - message: "Training error", - detail, - }); - this.setStatus({ stage: MlStage.TrainingError }); - } else { - this.logger.event({ - type: "Data", - message: "Train model", - detail, - }); - this.setStatus({ - stage: MlStage.TrainingComplete, - model: trainingResult.model, - }); - } - - return trainingResult; - }; -} diff --git a/src/ml-status-hooks.tsx b/src/ml-status-hooks.tsx deleted file mode 100644 index 14f5237d7..000000000 --- a/src/ml-status-hooks.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import * as tf from "@tensorflow/tfjs"; -import { LayersModel } from "@tensorflow/tfjs"; -import { - ReactNode, - createContext, - useCallback, - useContext, - useEffect, - useState, -} from "react"; -import { hasSufficientDataForTraining, useGestureData } from "./gestures-hooks"; - -export enum MlStage { - RecordingData = "RecordingData", - InsufficientData = "InsufficientData", - NotTrained = "NotTrained", - TrainingInProgress = "TrainingInProgress", - TrainingComplete = "TrainingComplete", - TrainingError = "TrainingError", -} - -export interface TrainingCompleteMlStatus { - stage: MlStage.TrainingComplete; - model: LayersModel; -} - -export type MlStatus = - | { - stage: MlStage.TrainingInProgress; - progressValue: number; - } - | TrainingCompleteMlStatus - | { - stage: Exclude< - MlStage, - MlStage.TrainingInProgress | MlStage.TrainingComplete - >; - }; - -type MlStatusContextValue = [MlStatus, (status: MlStatus) => void]; - -const MlStatusContext = createContext( - undefined -); - -export const useMlStatus = () => { - const mlState = useContext(MlStatusContext); - if (!mlState) { - throw new Error("Missing provider"); - } - return mlState; -}; - -export const modelUrl = "indexeddb://micro:bit-ml-tool-model"; - -export const MlStatusProvider = ({ children }: { children: ReactNode }) => { - const [gestureState] = useGestureData(); - const [mlState, setMlState] = useState({ - stage: hasSufficientDataForTraining(gestureState.data) - ? MlStage.NotTrained - : MlStage.InsufficientData, - }); - - const setStatus = useCallback((s: MlStatus) => { - setMlState((prevState) => ({ ...prevState })); - if (s.stage === MlStage.TrainingComplete) { - s.model.save(modelUrl).catch(() => { - // IndexedDB not available? - }); - } else { - tf.io.removeModel(modelUrl).catch(() => { - // Throws if there is no model to remove. - }); - } - setMlState(s); - }, []); - - useEffect(() => { - tf.loadLayersModel(modelUrl) - .then((model) => { - setMlState({ - stage: MlStage.TrainingComplete, - model, - }); - }) - .catch(() => { - // Throws if there is no model to load. - }); - }, []); - - return ( - - {children} - - ); -}; diff --git a/src/ml.test.ts b/src/ml.test.ts index 46ae802f7..4408a5985 100644 --- a/src/ml.test.ts +++ b/src/ml.test.ts @@ -9,7 +9,7 @@ import * as tf from "@tensorflow/tfjs"; import { vi } from "vitest"; -import { GestureData } from "./gestures-hooks"; +import { GestureData } from "./model"; import { prepareFeaturesAndLabels, TrainingResult, trainModel } from "./ml"; import gestureDataBadLabels from "./test-fixtures/gesture-data-bad-labels.json"; import gestureData from "./test-fixtures/gesture-data.json"; diff --git a/src/ml.ts b/src/ml.ts index a862ee790..3d7be3b36 100644 --- a/src/ml.ts +++ b/src/ml.ts @@ -1,5 +1,5 @@ import * as tf from "@tensorflow/tfjs"; -import { GestureData, XYZData } from "./gestures-hooks"; +import { GestureData, XYZData } from "./model"; import { Filter, mlFilters } from "./mlFilters"; import { SymbolicTensor } from "@tensorflow/tfjs"; diff --git a/src/model.test.ts b/src/model.test.ts new file mode 100644 index 000000000..d4f23bc0a --- /dev/null +++ b/src/model.test.ts @@ -0,0 +1,39 @@ +import { isDatasetUserFileFormat } from "./model"; + +describe("isValidStoredGestureData", () => { + it("checks data", () => { + expect(isDatasetUserFileFormat([])).toEqual(true); + expect(isDatasetUserFileFormat(123)).toEqual(false); + expect(isDatasetUserFileFormat({})).toEqual(false); + }); + it("checks data properties", () => { + expect(isDatasetUserFileFormat([{ invalid: 3 }])).toEqual(false); + expect(isDatasetUserFileFormat([{ name: 3 }])).toEqual(false); + expect( + isDatasetUserFileFormat([{ ID: 0, name: "some name", recordings: [] }]) + ).toEqual(true); + }); + it("checks data recordings", () => { + const generateData = (recordings: unknown) => [ + { ID: 0, name: "some name", recordings }, + ]; + expect(isDatasetUserFileFormat(generateData({}))).toEqual(false); + expect(isDatasetUserFileFormat(generateData([]))).toEqual(true); + expect( + isDatasetUserFileFormat(generateData([{ ID: 0, data: [] }])) + ).toEqual(false); + expect( + isDatasetUserFileFormat(generateData([{ ID: 0, data: {} }])) + ).toEqual(false); + expect( + isDatasetUserFileFormat( + generateData([{ ID: 0, data: { x: 0, y: 0, z: 0 } }]) + ) + ).toEqual(false); + expect( + isDatasetUserFileFormat( + generateData([{ ID: 0, data: { x: [], y: [], z: [] } }]) + ) + ).toEqual(true); + }); +}); diff --git a/src/model.ts b/src/model.ts new file mode 100644 index 000000000..b38d0490f --- /dev/null +++ b/src/model.ts @@ -0,0 +1,80 @@ +import { MakeCodeIcon } from "./utils/icons"; + +export interface XYZData { + x: number[]; + y: number[]; + z: number[]; +} + +export interface RecordingData { + ID: number; + data: XYZData; +} + +export interface Gesture { + name: string; + ID: number; + icon: MakeCodeIcon; + requiredConfidence?: number; +} + +export interface GestureData extends Gesture { + recordings: RecordingData[]; +} + +export interface DatasetEditorJsonFormat { + data: GestureData[]; +} + +export type DatasetUserFileFormat = GestureData[]; + +// Exported for testing +export const isDatasetUserFileFormat = ( + v: unknown +): v is DatasetUserFileFormat => { + if (!Array.isArray(v)) { + return false; + } + const array = v as unknown[]; + for (const item of array) { + if (typeof item !== "object" || item === null) { + return false; + } + if ( + !("name" in item) || + !("ID" in item) || + !("recordings" in item) || + !Array.isArray(item.recordings) + ) { + return false; + } + const recordings = item.recordings as unknown[]; + for (const rec of recordings) { + if (typeof rec !== "object" || rec === null) { + return false; + } + if (!("data" in rec) || !("ID" in rec) || Array.isArray(rec.data)) { + return false; + } + const xyzData = rec.data as object; + if ( + !("x" in xyzData) || + !("y" in xyzData) || + !("z" in xyzData) || + !Array.isArray(xyzData.x) || + !Array.isArray(xyzData.y) || + !Array.isArray(xyzData.z) + ) { + return false; + } + } + } + return true; +}; + +export const enum TrainModelDialogStage { + Closed, + ShowingIntroduction, + TrainingError, + TrainingInProgress, +} diff --git a/src/pages/DataSamplesPage.tsx b/src/pages/DataSamplesPage.tsx index 1913f0a97..6320977cd 100644 --- a/src/pages/DataSamplesPage.tsx +++ b/src/pages/DataSamplesPage.tsx @@ -9,7 +9,7 @@ import { MenuList, VStack, } from "@chakra-ui/react"; -import { useCallback, useMemo } from "react"; +import { useCallback } from "react"; import { MdMoreVert } from "react-icons/md"; import { RiAddLine, @@ -19,45 +19,42 @@ import { } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router"; -import DataSampleGridView from "../components/DataSampleGridView"; import ConnectFirstView from "../components/ConnectFirstView"; +import DataSampleGridView from "../components/DataSampleGridView"; import DefaultPageLayout from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; import TrainingButton from "../components/TrainingButton"; import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; import { ConnectionStatus } from "../connect-status-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; -import { - hasSufficientDataForTraining, - useGestureActions, - useGestureData, -} from "../gestures-hooks"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; import { SessionPageId } from "../pages-config"; import { createSessionPageUrl } from "../urls"; -import { useTrainModelDialog } from "../train-model-dialog-hooks"; +import SaveButton from "../components/SaveButton"; +import { + useAppStore, + useHasSufficientDataForTraining as useHasSufficientDataForTraining, +} from "../store"; const DataSamplesPage = () => { const intl = useIntl(); - const [gestures] = useGestureData(); - const [{ stage }] = useMlStatus(); + const gestures = useAppStore((s) => s.gestures); + const addNewGesture = useAppStore((s) => s.addNewGesture); + const downloadDataSet = useAppStore((s) => s.downloadDataset); + const deleteAllGestures = useAppStore((s) => s.deleteAllGestures); + const model = useAppStore((s) => s.model); + const navigate = useNavigate(); - const actions = useGestureActions(); - const { onOpen: onOpenTrainModelDialog } = useTrainModelDialog(); + const trainModelFlowStart = useAppStore((s) => s.trainModelFlowStart); const { isConnected, status } = useConnectionStage(); - const hasSufficientData = useMemo( - () => hasSufficientDataForTraining(gestures.data), - [gestures.data] - ); - - const noStoredData = useMemo(() => { - const gestureData = gestures.data; - return !( - gestureData.length !== 0 && - gestureData.some((g) => g.recordings.length > 0) - ); - }, [gestures.data]); + const hasSufficientData = useHasSufficientDataForTraining(); + const hasAnyRecordings = gestures.some((g) => g.recordings.length > 0); + const isAddNewGestureDisabled = + !isConnected || gestures.some((g) => g.name.length === 0); + const showConnectFirstView = + !hasAnyRecordings && + !isConnected && + status !== ConnectionStatus.ReconnectingAutomatically; const handleNavigateToModel = useCallback(() => { navigate(createSessionPageUrl(SessionPageId.TestingModel)); @@ -66,15 +63,10 @@ const DataSamplesPage = () => { return ( } showPageTitle > - {noStoredData && - !isConnected && - status !== ConnectionStatus.ReconnectingAutomatically ? ( - - ) : ( - - )} + {showConnectFirstView ? : } { @@ -109,22 +99,19 @@ const DataSamplesPage = () => { /> - } - onClick={actions.downloadDataset} - > + } onClick={downloadDataSet}> } - onClick={actions.deleteAllGestures} + onClick={deleteAllGestures} > - {stage === MlStage.TrainingComplete ? ( + {model ? ( ) : ( )} diff --git a/src/pages/TestingModelPage.tsx b/src/pages/TestingModelPage.tsx index e8933c7fd..90fb0be9c 100644 --- a/src/pages/TestingModelPage.tsx +++ b/src/pages/TestingModelPage.tsx @@ -5,24 +5,26 @@ import { useNavigate } from "react-router"; import BackArrow from "../components/BackArrow"; import DefaultPageLayout from "../components/DefaultPageLayout"; import TestingModelGridView from "../components/TestingModelGridView"; -import { MlStage, useMlStatus } from "../ml-status-hooks"; import { SessionPageId } from "../pages-config"; import { createSessionPageUrl } from "../urls"; +import SaveButton from "../components/SaveButton"; +import { useAppStore } from "../store"; const TestingModelPage = () => { const navigate = useNavigate(); - const [{ stage }] = useMlStatus(); + const model = useAppStore((s) => s.model); const navigateToDataSamples = useCallback(() => { navigate(createSessionPageUrl(SessionPageId.DataSamples)); }, [navigate]); useEffect(() => { - if (stage !== MlStage.TrainingComplete) { + if (!model) { navigateToDataSamples(); } - }); - return stage === MlStage.TrainingComplete ? ( + }, [model, navigateToDataSamples]); + + return model ? ( { } + toolbarItemsRight={} > diff --git a/src/recording-graph.ts b/src/recording-graph.ts index a6a982854..e6b7bd218 100644 --- a/src/recording-graph.ts +++ b/src/recording-graph.ts @@ -5,7 +5,7 @@ */ import { ChartConfiguration, ChartTypeRegistry } from "chart.js"; -import { XYZData } from "./gestures-hooks"; +import { XYZData } from "./model"; const smoothen = (d: number[]): number[] => { if (d.length === 0) { diff --git a/src/settings.tsx b/src/settings.tsx index 7fbb0ba89..906068eca 100644 --- a/src/settings.tsx +++ b/src/settings.tsx @@ -1,8 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { createContext, ReactNode, useContext } from "react"; -import { useStorage } from "./hooks/use-storage"; import { stage } from "./environment"; export interface Language { @@ -40,52 +35,12 @@ export const getLanguageFromQuery = (): string => { export const defaultSettings: Settings = { languageId: getLanguageFromQuery(), -}; - -export const isValidSettingsObject = (value: unknown): value is Settings => { - if (typeof value !== "object") { - return false; - } - const object = value as any; - if ( - object.languageId && - !supportedLanguages.find((x) => x.id === object.languageId) - ) { - return false; - } - return true; + showPreSaveHelp: true, + showPreTrainHelp: true, }; export interface Settings { languageId: string; + showPreSaveHelp: boolean; + showPreTrainHelp: boolean; } - -type SettingsContextValue = [Settings, (settings: Settings) => void]; - -const SettingsContext = createContext( - undefined -); - -export const useSettings = (): SettingsContextValue => { - const settings = useContext(SettingsContext); - if (!settings) { - throw new Error("Missing provider"); - } - return settings; -}; - -const SettingsProvider = ({ children }: { children: ReactNode }) => { - const settings = useStorage( - "local", - "settings", - defaultSettings, - isValidSettingsObject - ); - return ( - - {children} - - ); -}; - -export default SettingsProvider; diff --git a/src/store.ts b/src/store.ts new file mode 100644 index 000000000..16267b36d --- /dev/null +++ b/src/store.ts @@ -0,0 +1,559 @@ +import { Project } from "@microbit/makecode-embed/react"; +import * as tf from "@tensorflow/tfjs"; +import { create } from "zustand"; +import { + DatasetEditorJsonFormat, + Gesture, + GestureData, + RecordingData, + TrainModelDialogStage, +} from "./model"; +import { + filenames, + generateCustomFiles, + generateProject, +} from "./makecode/utils"; +import { trainModel } from "./ml"; +import { defaultIcons, MakeCodeIcon } from "./utils/icons"; +import { devtools, persist } from "zustand/middleware"; +import { flags } from "./flags"; +import { defaultSettings, Settings } from "./settings"; +import { useShallow } from "zustand/react/shallow"; + +export const modelUrl = "indexeddb://micro:bit-ml-tool-model"; + +const updateProject = ( + project: Project, + projectEdited: boolean, + gestures: GestureData[], + model: tf.LayersModel | undefined +): Partial => { + const gestureData = { data: gestures }; + const updatedProject = { + ...project, + text: { + ...project.text, + ...(projectEdited + ? generateCustomFiles(gestureData, model) + : generateProject(gestureData, model).text), + }, + }; + return { + project: updatedProject, + projectEdited, + appEditNeedsFlushToEditor: true, + }; +}; + +const generateFirstGesture = () => ({ + icon: defaultIcons[0], + ID: Date.now(), + name: "", + recordings: [], +}); + +export interface Store { + gestures: GestureData[]; + isRecording: boolean; + + addNewGesture(): void; + addGestureRecordings(id: GestureData["ID"], recs: RecordingData[]): void; + deleteGesture(id: GestureData["ID"]): void; + setGestureName(id: GestureData["ID"], name: string): void; + setGestureIcon(id: GestureData["ID"], icon: MakeCodeIcon): void; + setRequiredConfidence(id: GestureData["ID"], value: number): void; + deleteGestureRecording( + gestureId: GestureData["ID"], + recordingIdx: number + ): void; + deleteAllGestures(): void; + downloadDataset(): void; + loadDataset(gestures: GestureData[]): void; + + model: tf.LayersModel | undefined; + + isEditorOpen: boolean; + appEditNeedsFlushToEditor: boolean; + changedHeaderExpected: boolean; + setEditorOpen(open: boolean): void; + + recordingStarted(): void; + recordingStopped(): void; + newSession(): void; + + trainModel(): Promise; + + project: Project; + // false if we're sure the user hasn't changed the project, otherwise true + projectEdited: boolean; + + settings: Settings; + setSettings(update: Partial): void; + + /** + * Resets the project. + */ + resetProject: () => void; + + editorChange: (project: Project) => void; + + expectChangedHeader(): void; + projectFlushedToEditor(): void; + + trainModelProgress: number; + trainModelDialogStage: TrainModelDialogStage; + trainModelFlowStart: () => Promise; + closeTrainModelDialogs: () => void; +} + +export const useAppStore = create()( + devtools( + persist( + (set, get) => ({ + gestures: [generateFirstGesture()], + isRecording: false, + project: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + header: { + target: "microbit", + targetVersion: "7.1.2", + name: "Untitled", + meta: {}, + editor: "blocksprj", + pubId: "", + pubCurrent: false, + _rev: null, + id: "45a3216b-e997-456c-bd4b-6550ddb81c4e", + recentUse: 1726493314, + modificationTime: 1726493314, + cloudUserId: null, + cloudCurrent: false, + cloudVersion: null, + cloudLastSyncTime: 0, + isDeleted: false, + githubCurrent: false, + saveId: null, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + ...generateProject({ data: [] }, undefined), + }, + projectEdited: false, + settings: defaultSettings, + model: undefined, + isEditorOpen: false, + appEditNeedsFlushToEditor: false, + changedHeaderExpected: false, + // This dialog flow spans two pages + trainModelDialogStage: TrainModelDialogStage.Closed, + trainModelProgress: 0, + + setSettings(update: Partial) { + set( + ({ settings }) => ({ + settings: { + ...settings, + ...update, + }, + }), + false, + "setSettings" + ); + }, + + newSession() { + get().deleteAllGestures(); + get().resetProject(); + }, + + setEditorOpen(open: boolean) { + set( + { + isEditorOpen: open, + // We just assume its been edited as spurious changes from MakeCode happen that we can't identify + projectEdited: true, + }, + false, + "setEditorOpen" + ); + }, + + recordingStarted() { + set({ isRecording: true }, false, "recordingStarted"); + }, + recordingStopped() { + set({ isRecording: false }, false, "recordingStopped"); + }, + + addNewGesture() { + return set(({ project, projectEdited, gestures }) => { + const newGestures = [ + ...gestures, + { + icon: gestureIcon({ + isFirstGesture: gestures.length === 0, + existingGestures: gestures, + }), + ID: Date.now(), + name: "", + recordings: [], + }, + ]; + return { + gestures: newGestures, + model: undefined, + ...updateProject(project, projectEdited, newGestures, undefined), + }; + }); + }, + + addGestureRecordings(id: GestureData["ID"], recs: RecordingData[]) { + return set(({ gestures }) => ({ + gestures: gestures.map((g) => { + if (g.ID === id) { + return { ...g, recordings: [...recs, ...g.recordings] }; + } + return g; + }), + model: undefined, + })); + }, + + deleteGesture(id: GestureData["ID"]) { + return set(({ project, projectEdited, gestures }) => { + const newGestures = gestures.filter((g) => g.ID !== id); + return { + gestures: + newGestures.length === 0 + ? [generateFirstGesture()] + : newGestures, + model: undefined, + ...updateProject(project, projectEdited, newGestures, undefined), + }; + }); + }, + + setGestureName(id: GestureData["ID"], name: string) { + return set(({ project, projectEdited, gestures, model }) => { + const newGestures = gestures.map((g) => + id !== g.ID ? g : { ...g, name } + ); + return { + gestures: newGestures, + ...updateProject(project, projectEdited, newGestures, model), + }; + }); + }, + + setGestureIcon(id: GestureData["ID"], icon: MakeCodeIcon) { + return set(({ project, projectEdited, gestures, model }) => { + // If we're changing the `id` gesture to use an icon that's already in use + // then we update the gesture that's using it to use the `id` gesture's current icon + const currentIcon = gestures.find((g) => g.ID === id)?.icon; + const newGestures = gestures.map((g) => { + if (g.ID === id) { + return { ...g, icon }; + } else if (g.ID !== id && g.icon === icon && currentIcon) { + return { ...g, icon: currentIcon }; + } + return g; + }); + return { + gestures: newGestures, + ...updateProject(project, projectEdited, newGestures, model), + }; + }); + }, + + setRequiredConfidence(id: GestureData["ID"], value: number) { + return set(({ project, projectEdited, gestures, model }) => { + const newGestures = gestures.map((g) => + id !== g.ID ? g : { ...g, requiredConfidence: value } + ); + return { + gestures: newGestures, + ...updateProject(project, projectEdited, newGestures, model), + }; + }); + }, + + deleteGestureRecording(id: GestureData["ID"], recordingIdx: number) { + return set(({ project, projectEdited, gestures }) => { + const newGestures = gestures.map((g) => { + if (id !== g.ID) { + return g; + } + const recordings = g.recordings.filter( + (_r, i) => i !== recordingIdx + ); + return { ...g, recordings }; + }); + + return { + gestures: newGestures, + model: undefined, + ...updateProject(project, projectEdited, newGestures, undefined), + }; + }); + }, + + deleteAllGestures() { + return set(({ project, projectEdited }) => ({ + gestures: [generateFirstGesture()], + model: undefined, + ...updateProject(project, projectEdited, [], undefined), + })); + }, + + downloadDataset() { + const { gestures } = get(); + const a = document.createElement("a"); + a.setAttribute( + "href", + "data:application/json;charset=utf-8," + + encodeURIComponent(JSON.stringify(gestures, null, 2)) + ); + a.setAttribute("download", "dataset"); + a.style.display = "none"; + a.click(); + }, + + loadDataset(newGestures: GestureData[]) { + set(({ project, projectEdited }) => { + return { + gestures: (() => { + const copy = newGestures.map((g) => ({ ...g })); + for (const g of copy) { + if (!g.icon) { + g.icon = gestureIcon({ + isFirstGesture: false, + existingGestures: copy, + }); + } + } + return copy; + })(), + model: undefined, + ...updateProject(project, projectEdited, newGestures, undefined), + }; + }); + }, + + closeTrainModelDialogs() { + set({ + trainModelDialogStage: TrainModelDialogStage.Closed, + }); + }, + + async trainModelFlowStart() { + if (get().settings.showPreTrainHelp) { + set({ + // TODO: this should respect the settings which should be in the state + trainModelDialogStage: TrainModelDialogStage.ShowingIntroduction, + }); + } else { + await get().trainModel(); + } + }, + + async trainModel() { + const { gestures } = get(); + const actionName = "trainModel"; + set({ + trainModelDialogStage: TrainModelDialogStage.TrainingInProgress, + trainModelProgress: 0, + }); + const trainingResult = await trainModel({ + data: gestures, + onProgress: (trainModelProgress) => + set({ trainModelProgress }, false, "trainModelProgress"), + }); + const model = trainingResult.error ? undefined : trainingResult.model; + set( + ({ project, projectEdited }) => ({ + model, + trainModelDialogStage: model + ? TrainModelDialogStage.Closed + : TrainModelDialogStage.TrainingError, + ...updateProject(project, projectEdited, gestures, model), + }), + false, + actionName + ); + return !trainingResult.error; + }, + + resetProject(): void { + const { project: previousProject, gestures, model } = get(); + const newProject = { + ...previousProject, + text: { + ...previousProject.text, + ...generateProject({ data: gestures }, model).text, + }, + }; + set( + { + project: newProject, + projectEdited: false, + appEditNeedsFlushToEditor: true, + }, + false, + "resetProject" + ); + }, + editorChange(newProject: Project) { + const actionName = "editorChange"; + set( + (state) => { + const { + project: prevProject, + isEditorOpen, + changedHeaderExpected, + } = state; + const newProjectHeader = newProject.header!.id; + const previousProjectHeader = prevProject.header!.id; + if (newProjectHeader !== previousProjectHeader) { + if (changedHeaderExpected) { + return { + changedHeaderExpected: false, + project: newProject, + }; + } + console.log( + "Detected new project in MakeCode, loading gestures" + ); + // 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: [] }; + + return { + project: newProject, + // New project loaded externally so we can't know whether its edited. + projectEdited: true, + gestures: dataset.data, + model: undefined, + }; + } else if (isEditorOpen) { + return { + project: newProject, + }; + } + return state; + }, + false, + actionName + ); + }, + expectChangedHeader() { + set({ changedHeaderExpected: true }); + }, + projectFlushedToEditor() { + set( + { + appEditNeedsFlushToEditor: false, + }, + false, + "projectFlushedToEditor" + ); + }, + }), + { + name: "ml", + partialize: ({ gestures, project, projectEdited, settings }) => ({ + gestures, + project, + projectEdited, + settings, + // The model itself is in IndexDB + }), + } + ), + { enabled: flags.devtools } + ) +); + +tf.loadLayersModel(modelUrl) + .then((model) => { + if (model) { + useAppStore.setState({ model }, false, "loadModel"); + } + }) + .catch(() => { + // This happens if there's no model. + }); + +useAppStore.subscribe((state, prevState) => { + const { model: newModel } = state; + const { model: previousModel } = prevState; + if (newModel !== previousModel) { + if (!newModel) { + tf.io.removeModel(modelUrl).catch(() => { + // No IndexedDB/no model. + }); + } else { + newModel.save(modelUrl).catch(() => { + // IndexedDB not available? + }); + } + } +}); + +export const useHasGestures = () => { + const gestures = useAppStore((s) => s.gestures); + return ( + (gestures.length > 0 && gestures[0].name.length > 0) || + gestures[0]?.recordings.length > 0 + ); +}; + +const hasSufficientDataForTraining = (gestures: GestureData[]): boolean => { + return ( + gestures.length >= 2 && gestures.every((g) => g.recordings.length >= 3) + ); +}; + +export const useHasSufficientDataForTraining = (): boolean => { + const gestures = useAppStore((s) => s.gestures); + return hasSufficientDataForTraining(gestures); +}; + +export const useHasNoStoredData = (): boolean => { + const gestures = useAppStore((s) => s.gestures); + return !( + gestures.length !== 0 && gestures.some((g) => g.recordings.length > 0) + ); +}; + +type UseSettingsReturn = [Settings, (settings: Partial) => void]; + +export const useSettings = (): UseSettingsReturn => { + return useAppStore(useShallow((s) => [s.settings, s.setSettings])); +}; + +const gestureIcon = ({ + isFirstGesture, + existingGestures, +}: { + isFirstGesture: boolean; + existingGestures: Gesture[]; +}) => { + if (isFirstGesture) { + return defaultIcons[0]; + } + const iconsInUse = existingGestures.map((g) => g.icon); + const useableIcons: MakeCodeIcon[] = []; + for (const icon of defaultIcons) { + if (!iconsInUse.includes(icon)) { + useableIcons.push(icon); + } + } + if (!useableIcons.length) { + // Better than throwing an error. + return "Heart"; + } + return useableIcons[0]; +}; diff --git a/src/train-model-dialog-hooks.tsx b/src/train-model-dialog-hooks.tsx deleted file mode 100644 index f2b42abf1..000000000 --- a/src/train-model-dialog-hooks.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { - createContext, - ReactNode, - useCallback, - useContext, - useState, -} from "react"; - -export enum TrainModelDialogStage { - Closed = "closed", - ShowingIntroduction = "showing introduction", - ShowingTrainingStatus = "showing training status", -} - -interface TrainModelDialogState { - stage: TrainModelDialogStage; - skipIntro: boolean; -} - -type TrainModelDialogStateContextValue = [ - TrainModelDialogState, - (state: TrainModelDialogState) => void -]; - -const TrainModelDialogStateContext = createContext< - TrainModelDialogStateContextValue | undefined ->(undefined); - -export const TrainModelDialogProvider = ({ - children, -}: { - children: ReactNode; -}) => { - const [state, setState] = useState({ - stage: TrainModelDialogStage.Closed, - skipIntro: false, - }); - - return ( - - {children} - - ); -}; - -export const useTrainModelDialog = () => { - const dialogContextValue = useContext(TrainModelDialogStateContext); - if (!dialogContextValue) { - throw new Error("Missing provider"); - } - const [state, setState] = dialogContextValue; - - const onClose = useCallback(() => { - setState({ ...state, stage: TrainModelDialogStage.Closed }); - }, [setState, state]); - - const onOpen = useCallback(() => { - setState({ - ...state, - stage: state.skipIntro - ? TrainModelDialogStage.ShowingTrainingStatus - : TrainModelDialogStage.ShowingIntroduction, - }); - }, [setState, state]); - - const onIntroNext = useCallback( - (isSkipIntro: boolean) => { - setState({ - ...state, - skipIntro: isSkipIntro, - stage: TrainModelDialogStage.ShowingTrainingStatus, - }); - }, - [setState, state] - ); - - return { - stage: state.stage, - isSkipIntro: state.skipIntro, - onIntroNext, - onClose, - onOpen, - }; -}; diff --git a/src/user-projects-hooks.tsx b/src/user-projects-hooks.tsx deleted file mode 100644 index e31bc1b4a..000000000 --- a/src/user-projects-hooks.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { GestureData, useGestureData } from "./gestures-hooks"; -import { getMainScript } from "./makecode/generate-main-scripts"; -import { - getDataJson, - getAutogeneratedTs, -} from "./makecode/generate-custom-scripts"; -import { TrainingCompleteMlStatus, useMlStatus } from "./ml-status-hooks"; -import { - ReactNode, - createContext, - useCallback, - useContext, - useMemo, -} from "react"; -import { MakeCodeProject } from "@microbit-foundation/react-code-view"; -import { useStorage } from "./hooks/use-storage"; -interface UserProjectsContextState { - makeCode?: MakeCodeProject; -} - -type UserProjectsContextValue = [ - UserProjectsContextState, - (userProjects: UserProjectsContextState) => void -]; - -const UserProjectsContext = createContext( - undefined -); - -export const UserProjectsProvider = ({ children }: { children: ReactNode }) => { - const userProjectsContextValue = useStorage( - "local", - "makecodeProject", - { makeCode: undefined } - ); - return ( - - {children} - - ); -}; - -const useUserProjects = (): UserProjectsContextValue => { - const userProjects = useContext(UserProjectsContext); - if (!userProjects) { - throw new Error("Missing provider"); - } - return userProjects; -}; - -enum ProjectFilenames { - MainTs = "main.ts", - MainBlocks = "main.blocks", - Autogenerated = "autogenerated.ts", - DataJson = "data.json", - PxtJson = "pxt.json", - ReadMe = "README.md", -} - -const pxt = { - name: "Untitled", - description: "", - dependencies: { - core: "*", - microphone: "*", - radio: "*", // needed for compiling - "machine-learning": "github:microbit-foundation/pxt-microbit-ml#v0.4.2", - }, - files: Object.values(ProjectFilenames), -}; - -export const useMakeCodeProject = () => { - const [gestureData] = useGestureData(); - const [userProjects, setUserProjects] = useUserProjects(); - const [status] = useMlStatus(); - const model = (status as TrainingCompleteMlStatus).model; - const gestures = gestureData.data; - - const defaultProjectText = useMemo(() => { - return { - [ProjectFilenames.MainTs]: getMainScript(gestures, "javascript"), - [ProjectFilenames.MainBlocks]: getMainScript(gestures, "blocks"), - [ProjectFilenames.Autogenerated]: getAutogeneratedTs(gestures, model), - [ProjectFilenames.DataJson]: getDataJson(gestures), - [ProjectFilenames.ReadMe]: "", - [ProjectFilenames.PxtJson]: JSON.stringify(pxt), - }; - }, [gestures, model]); - - const createGestureDefaultProject = useCallback( - (gesture: GestureData) => { - const gs = [gesture]; - return { - text: { - ...defaultProjectText, - [ProjectFilenames.MainTs]: getMainScript(gs, "javascript"), - [ProjectFilenames.MainBlocks]: getMainScript(gs, "blocks"), - }, - }; - }, - [defaultProjectText] - ); - - const setProject = useCallback( - (project: MakeCodeProject | undefined) => { - setUserProjects({ makeCode: project }); - }, - [setUserProjects] - ); - - const userProject = useMemo( - () => userProjects.makeCode ?? { text: defaultProjectText }, - [defaultProjectText, userProjects.makeCode] - ); - - const hasStoredProject = useMemo( - () => userProjects.makeCode !== undefined, - [userProjects.makeCode] - ); - - return { - hasStoredProject, - createGestureDefaultProject, - userProject, - setUserProject: setProject, - }; -}; diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 6a773b6d4..000000000 --- a/src/utils.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const isArray = (v: unknown) => - typeof v === "object" && Array.isArray(v); diff --git a/src/utils/fs-util.ts b/src/utils/fs-util.ts new file mode 100644 index 000000000..31cf0a936 --- /dev/null +++ b/src/utils/fs-util.ts @@ -0,0 +1,30 @@ +export const getFileExtension = (filename: string): string | undefined => { + const parts = filename.split("."); + return parts.length > 1 ? parts.pop() || undefined : undefined; +}; + +export const getLowercaseFileExtension = ( + filename: string +): string | undefined => { + return getFileExtension(filename)?.toLowerCase(); +}; + +/** + * Reads file as text via a FileReader. + * + * @param file A file (e.g. from a file input or drop operation). + * @returns The a promise of text from that file. + */ +export const readFileAsText = async (file: File): Promise => { + const reader = new FileReader(); + return new Promise((resolve, reject) => { + reader.onload = (e: ProgressEvent) => { + resolve(e.target!.result as string); + }; + reader.onerror = (e: ProgressEvent) => { + const error = e.target?.error || new Error("Error reading file as text"); + reject(error); + }; + reader.readAsText(file); + }); +}; From 7454ed0991d8320ea775345e3a493b693fe011aa Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:31:03 +0100 Subject: [PATCH 128/172] Tidy store types (#312) --- src/components/AddDataGridRow.tsx | 4 +- src/components/CodeViewGridItem.tsx | 6 +-- src/components/DataRecordingGridItem.tsx | 4 +- src/components/DataSampleGridView.tsx | 4 +- src/components/EditCodeDialog.tsx | 4 +- src/components/GestureNameGridItem.tsx | 6 +-- src/components/LiveGraph.tsx | 4 +- src/components/RecordingDialog.tsx | 8 +-- src/components/StartOverWarningDialog.tsx | 4 +- src/components/StartResumeActions.tsx | 4 +- src/components/TestingModelGridView.tsx | 6 +-- src/components/TrainModelFlowDialogs.tsx | 10 ++-- src/components/TrainingButton.tsx | 4 +- src/hooks/ml-hooks.tsx | 6 +-- src/hooks/project-hooks.tsx | 20 +++---- src/pages/DataSamplesPage.tsx | 14 ++--- src/pages/TestingModelPage.tsx | 4 +- src/store.ts | 66 +++++++++++++---------- 18 files changed, 93 insertions(+), 85 deletions(-) diff --git a/src/components/AddDataGridRow.tsx b/src/components/AddDataGridRow.tsx index e4bc45e9d..7d6c83ae2 100644 --- a/src/components/AddDataGridRow.tsx +++ b/src/components/AddDataGridRow.tsx @@ -5,7 +5,7 @@ import { GestureData } from "../model"; import AddDataGridWalkThrough from "./AddDataGridWalkThrough"; import DataRecordingGridItem from "./DataRecordingGridItem"; import GestureNameGridItem from "./GestureNameGridItem"; -import { useAppStore } from "../store"; +import { useStore } from "../store"; interface AddDataGridRowProps { gesture: GestureData; @@ -23,7 +23,7 @@ const DataSampleGridRow = ({ showWalkThrough, }: AddDataGridRowProps) => { const intl = useIntl(); - const deleteGesture = useAppStore((s) => s.deleteGesture); + const deleteGesture = useStore((s) => s.deleteGesture); const handleDeleteDataItem = useCallback(() => { const confirmationText = intl.formatMessage( diff --git a/src/components/CodeViewGridItem.tsx b/src/components/CodeViewGridItem.tsx index f46bd6bb2..1ff4a40fc 100644 --- a/src/components/CodeViewGridItem.tsx +++ b/src/components/CodeViewGridItem.tsx @@ -6,7 +6,7 @@ import { import { memo, useMemo } from "react"; import { generateProject } from "../makecode/utils"; import { GestureData } from "../model"; -import { useAppStore } from "../store"; +import { useStore } from "../store"; interface CodeViewGridItemProps { gesture: GestureData; @@ -17,8 +17,8 @@ const CodeViewGridItem = ({ gesture, projectEdited, }: CodeViewGridItemProps) => { - const model = useAppStore((s) => s.model); - const gestures = useAppStore((s) => s.gestures); + const model = useStore((s) => s.model); + const gestures = useStore((s) => s.gestures); const project = useMemo( () => generateProject({ data: gestures }, model, gesture), [gesture, gestures, model] diff --git a/src/components/DataRecordingGridItem.tsx b/src/components/DataRecordingGridItem.tsx index d55340b1c..969daa045 100644 --- a/src/components/DataRecordingGridItem.tsx +++ b/src/components/DataRecordingGridItem.tsx @@ -13,7 +13,7 @@ import { useConnectionStage } from "../connection-stage-hooks"; import { GestureData } from "../model"; import RecordIcon from "../images/record-icon.svg?react"; import RecordingGraph from "./RecordingGraph"; -import { useAppStore } from "../store"; +import { useStore } from "../store"; interface DataRecordingGridItemProps { data: GestureData; @@ -29,7 +29,7 @@ const DataRecordingGridItem = ({ startRecording, }: DataRecordingGridItemProps) => { const intl = useIntl(); - const deleteGestureRecording = useAppStore((s) => s.deleteGestureRecording); + const deleteGestureRecording = useStore((s) => s.deleteGestureRecording); const closeRecordingDialogFocusRef = useRef(null); const { isConnected } = useConnectionStage(); diff --git a/src/components/DataSampleGridView.tsx b/src/components/DataSampleGridView.tsx index 3820d27e2..67d1e3446 100644 --- a/src/components/DataSampleGridView.tsx +++ b/src/components/DataSampleGridView.tsx @@ -5,7 +5,7 @@ import { useConnectActions } from "../connect-actions-hooks"; import DataSampleGridRow from "./AddDataGridRow"; import HeadingGrid from "./HeadingGrid"; import RecordingDialog from "./RecordingDialog"; -import { useAppStore } from "../store"; +import { useStore } from "../store"; const gridCommonProps: Partial = { gridTemplateColumns: "290px 1fr", @@ -27,7 +27,7 @@ const headings = [ ]; const DataSamplesGridView = () => { - const gestures = useAppStore((s) => s.gestures); + const gestures = useStore((s) => s.gestures); const [selectedGestureIdx, setSelectedGestureIdx] = useState(0); const selectedGesture = gestures[selectedGestureIdx] ?? gestures[0]; const showWalkThrough = useMemo( diff --git a/src/components/EditCodeDialog.tsx b/src/components/EditCodeDialog.tsx index e2444c510..c99f2bb5c 100644 --- a/src/components/EditCodeDialog.tsx +++ b/src/components/EditCodeDialog.tsx @@ -9,14 +9,14 @@ import { import { MakeCodeFrameDriver } from "@microbit/makecode-embed/react"; import { forwardRef, memo, useRef } from "react"; import Editor from "./Editor"; -import { useAppStore } from "../store"; +import { useStore } from "../store"; interface EditCodeDialogProps {} const EditCodeDialog = forwardRef( function EditCodeDialog(_, ref) { const containerRef = useRef(null); - const isOpen = useAppStore((s) => s.isEditorOpen); + const isOpen = useStore((s) => s.isEditorOpen); return ( <> s.setGestureName); - const setGestureIcon = useAppStore((s) => s.setGestureIcon); + const setGestureName = useStore((s) => s.setGestureName); + const setGestureIcon = useStore((s) => s.setGestureIcon); const onChange: React.ChangeEventHandler = useCallback( (e) => { diff --git a/src/components/LiveGraph.tsx b/src/components/LiveGraph.tsx index 50ccca253..3f2085ff5 100644 --- a/src/components/LiveGraph.tsx +++ b/src/components/LiveGraph.tsx @@ -10,7 +10,7 @@ import { ConnectionStatus } from "../connect-status-hooks"; import { RiArrowDropLeftFill } from "react-icons/ri"; import React from "react"; import { LabelConfig, getUpdatedLabelConfig } from "../live-graph-label-config"; -import { useAppStore } from "../store"; +import { useStore } from "../store"; const initialLabelConfigs: LabelConfig[] = [ { label: "x", arrowHeight: 0, labelHeight: 0, color: "#f9808e", id: 0 }, @@ -89,7 +89,7 @@ const LiveGraph = () => { // Draw on graph to display that users are recording // Ideally we'd do this without timing the recording again! const [isTimingRecording, setIsTimingRecording] = useState(false); - const isRecording = useAppStore((s) => s.isRecording); + const isRecording = useStore((s) => s.isRecording); useEffect(() => { if (isRecording && !isTimingRecording) { { diff --git a/src/components/RecordingDialog.tsx b/src/components/RecordingDialog.tsx index 0e5f270cd..cffd46132 100644 --- a/src/components/RecordingDialog.tsx +++ b/src/components/RecordingDialog.tsx @@ -17,7 +17,7 @@ import { TimedXYZ } from "../buffered-data"; import { useBufferedData } from "../buffered-data-hooks"; import { mlSettings } from "../ml"; import { GestureData, XYZData } from "../model"; -import { useAppStore } from "../store"; +import { useStore } from "../store"; interface CountdownStage { value: string | number; @@ -46,9 +46,9 @@ const RecordingDialog = ({ }: RecordingDialogProps) => { const intl = useIntl(); const toast = useToast(); - const recordingStarted = useAppStore((s) => s.recordingStarted); - const recordingStopped = useAppStore((s) => s.recordingStopped); - const addGestureRecordings = useAppStore((s) => s.addGestureRecordings); + const recordingStarted = useStore((s) => s.recordingStarted); + const recordingStopped = useStore((s) => s.recordingStopped); + const addGestureRecordings = useStore((s) => s.addGestureRecordings); const recordingDataSource = useRecordingDataSource(); const [recordingStatus, setRecordingStatus] = useState( RecordingStatus.None diff --git a/src/components/StartOverWarningDialog.tsx b/src/components/StartOverWarningDialog.tsx index ae7fa2899..ed3efac90 100644 --- a/src/components/StartOverWarningDialog.tsx +++ b/src/components/StartOverWarningDialog.tsx @@ -13,7 +13,7 @@ import { } from "@chakra-ui/react"; import { ReactNode } from "react"; import { FormattedMessage } from "react-intl"; -import { useAppStore } from "../store"; +import { useStore } from "../store"; interface StartOverWardningDialogProps { isOpen: boolean; @@ -26,7 +26,7 @@ const StartOverWarningDialog = ({ onClose, onStart, }: StartOverWardningDialogProps) => { - const downloadDataset = useAppStore((s) => s.downloadDataset); + const downloadDataset = useStore((s) => s.downloadDataset); return ( ) => { - const newSession = useAppStore((s) => s.newSession); + const newSession = useStore((s) => s.newSession); const hasExistingSession = useHasGestures(); const [hasConnectFlowStarted, setHasConnectFlowStarted] = useState(false); diff --git a/src/components/TestingModelGridView.tsx b/src/components/TestingModelGridView.tsx index 74f3ec403..0e87cfa7a 100644 --- a/src/components/TestingModelGridView.tsx +++ b/src/components/TestingModelGridView.tsx @@ -19,7 +19,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { mlSettings } from "../ml"; import { usePrediction } from "../hooks/ml-hooks"; import { getMakeCodeLang } from "../settings"; -import { useAppStore, useSettings } from "../store"; +import { useStore, useSettings } from "../store"; import { useProject } from "../hooks/project-hooks"; import CertaintyThresholdGridItem from "./CertaintyThresholdGridItem"; import CodeViewCard from "./CodeViewCard"; @@ -57,8 +57,8 @@ const TestingModelGridView = () => { const prediction = usePrediction(); const { detected, confidences } = prediction ?? {}; const intl = useIntl(); - const gestures = useAppStore((s) => s.gestures); - const setRequiredConfidence = useAppStore((s) => s.setRequiredConfidence); + const gestures = useStore((s) => s.gestures); + const setRequiredConfidence = useStore((s) => s.setRequiredConfidence); const { openEditor, project, resetProject, projectEdited } = useProject(); const detectedLabel = diff --git a/src/components/TrainModelFlowDialogs.tsx b/src/components/TrainModelFlowDialogs.tsx index 788bdb250..c31fc2221 100644 --- a/src/components/TrainModelFlowDialogs.tsx +++ b/src/components/TrainModelFlowDialogs.tsx @@ -2,18 +2,18 @@ import { useCallback } from "react"; import { useNavigate } from "react-router"; import { TrainModelDialogStage } from "../model"; import { SessionPageId } from "../pages-config"; -import { useAppStore, useSettings } from "../store"; +import { useStore, useSettings } from "../store"; import { createSessionPageUrl } from "../urls"; import TrainingErrorDialog from "./TrainingErrorDialog"; import TrainingModelProgressDialog from "./TrainingModelProgressDialog"; import TrainModelIntroDialog from "./TrainModelIntroDialog"; const TrainModelFlowDialogs = () => { - const stage = useAppStore((s) => s.trainModelDialogStage); - const closeTrainModelDialogs = useAppStore((s) => s.closeTrainModelDialogs); + const stage = useStore((s) => s.trainModelDialogStage); + const closeTrainModelDialogs = useStore((s) => s.closeTrainModelDialogs); const navigate = useNavigate(); - const trainModel = useAppStore((s) => s.trainModel); - const trainModelProgress = useAppStore((s) => s.trainModelProgress); + const trainModel = useStore((s) => s.trainModel); + const trainModelProgress = useStore((s) => s.trainModelProgress); const [, setSettings] = useSettings(); const handleIntroNext = useCallback( diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx index 22b711590..1bb29fecb 100644 --- a/src/components/TrainingButton.tsx +++ b/src/components/TrainingButton.tsx @@ -1,10 +1,10 @@ import { Button, ButtonProps } from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; -import { useAppStore, useHasSufficientDataForTraining } from "../store"; +import { useStore, useHasSufficientDataForTraining } from "../store"; const TrainingButton = (props: ButtonProps) => { const hasSufficientData = useHasSufficientDataForTraining(); - const model = useAppStore((s) => s.model); + const model = useStore((s) => s.model); const isEnabled = hasSufficientData && !model; return ( + + + + + + ); +}; diff --git a/src/components/GestureNameGridItem.tsx b/src/components/GestureNameGridItem.tsx index 346833c28..b55060db4 100644 --- a/src/components/GestureNameGridItem.tsx +++ b/src/components/GestureNameGridItem.tsx @@ -17,7 +17,7 @@ import { useStore } from "../store"; interface GestureNameGridItemProps { name: string; icon: MakeCodeIcon; - onCloseClick?: () => void; + onDeleteAction?: () => void; onSelectRow?: () => void; id: number; selected?: boolean; @@ -30,7 +30,7 @@ const gestureNameMaxLength = 18; const GestureNameGridItem = ({ name, icon, - onCloseClick, + onDeleteAction, onSelectRow, id, selected = false, @@ -84,12 +84,12 @@ const GestureNameGridItem = ({ onClick={onSelectRow} position="relative" > - {!readOnly && onCloseClick && ( + {!readOnly && onDeleteAction && ( Date: Tue, 17 Sep 2024 13:02:20 +0100 Subject: [PATCH 130/172] Add insufficient data dialog (#309) I don't think this is enough of a clue but it's useful for screenreader folks at least. --- lang/ui.en.json | 14 +++- src/App.tsx | 2 +- src/components/AddDataGridWalkThrough.tsx | 7 +- src/components/DefaultPageLayout.tsx | 4 +- src/components/TrainModelFlowDialogs.tsx | 53 +++++++-------- ...troDialog.tsx => TrainModelHelpDialog.tsx} | 30 +++++--- .../TrainModelInsufficientDataDialog.tsx | 68 +++++++++++++++++++ src/components/TrainingButton.tsx | 16 ----- src/deployment/default/components/button.ts | 7 ++ src/messages/ui.en.json | 18 +++++ src/model.ts | 3 +- src/pages/DataSamplesPage.tsx | 9 +-- src/pages/HomePage.tsx | 14 +++- src/store.ts | 40 ++++++----- 14 files changed, 203 insertions(+), 82 deletions(-) rename src/components/{TrainModelIntroDialog.tsx => TrainModelHelpDialog.tsx} (70%) create mode 100644 src/components/TrainModelInsufficientDataDialog.tsx delete mode 100644 src/components/TrainingButton.tsx diff --git a/lang/ui.en.json b/lang/ui.en.json index 6ca4e0735..d8d2bb8ad 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -751,6 +751,14 @@ "defaultMessage": "More information about \"{item}\"", "description": "" }, + "insufficient-data-body": { + "defaultMessage": "You need at least 3 data samples for 2 actions to train the model.", + "description": "Insufficient data modal content" + }, + "insufficient-data-title": { + "defaultMessage": "You don't have enough data", + "description": "Insufficient data modal title" + }, "introducing-the-microbit-machine-learning-tool-title": { "defaultMessage": "Introducing the micro:bit machine learning tool", "description": "Title of a resource" @@ -1015,6 +1023,10 @@ "defaultMessage": "Software versions", "description": "Software versions text" }, + "start-training-action": { + "defaultMessage": "Start training", + "description": "Start training button text" + }, "support-request": { "defaultMessage": "Please consider raising a support request.", "description": "Support request link text" @@ -1043,4 +1055,4 @@ "defaultMessage": "Train model", "description": "Train model step title" } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index ad7b6277b..f07e2fc7c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ import { ConnectProvider } from "./connect-actions-hooks"; import { ConnectStatusProvider } from "./connect-status-hooks"; import { ConnectionStageProvider } from "./connection-stage-hooks"; import { deployment, useDeployment } from "./deployment"; +import { ProjectProvider } from "./hooks/project-hooks"; import { LoggingProvider } from "./logging/logging-hooks"; import TranslationProvider from "./messages/TranslationProvider"; import { resourcesConfig, sessionPageConfigs } from "./pages-config"; @@ -27,7 +28,6 @@ import { createResourcePageUrl, createSessionPageUrl, } from "./urls"; -import { ProjectProvider } from "./hooks/project-hooks"; export interface ProviderLayoutProps { children: ReactNode; diff --git a/src/components/AddDataGridWalkThrough.tsx b/src/components/AddDataGridWalkThrough.tsx index e71eec094..db39124c8 100644 --- a/src/components/AddDataGridWalkThrough.tsx +++ b/src/components/AddDataGridWalkThrough.tsx @@ -40,7 +40,12 @@ const AddDataGridWalkThrough = ({ {/* Empty grid item to fill first column of grid */} - + diff --git a/src/components/DefaultPageLayout.tsx b/src/components/DefaultPageLayout.tsx index 7502ddc44..edeac07d6 100644 --- a/src/components/DefaultPageLayout.tsx +++ b/src/components/DefaultPageLayout.tsx @@ -12,7 +12,7 @@ import ConnectionDialogs from "./ConnectionFlowDialogs"; import HelpMenu from "./HelpMenu"; import PrototypeVersionWarning from "./PrototypeVersionWarning"; import SettingsMenu from "./SettingsMenu"; -import TrainModelFlowDialogs from "./TrainModelFlowDialogs"; +import TrainModelDialogs from "./TrainModelFlowDialogs"; interface DefaultPageLayoutProps { titleId: string; @@ -43,7 +43,7 @@ const DefaultPageLayout = ({ return ( <> - + { +const TrainModelDialogs = () => { const stage = useStore((s) => s.trainModelDialogStage); const closeTrainModelDialogs = useStore((s) => s.closeTrainModelDialogs); const navigate = useNavigate(); @@ -16,7 +17,7 @@ const TrainModelFlowDialogs = () => { const trainModelProgress = useStore((s) => s.trainModelProgress); const [, setSettings] = useSettings(); - const handleIntroNext = useCallback( + const handleHelpNext = useCallback( async (isSkipNextTime: boolean) => { setSettings({ showPreTrainHelp: !isSkipNextTime }); const result = await trainModel(); @@ -26,29 +27,27 @@ const TrainModelFlowDialogs = () => { }, [navigate, setSettings, trainModel] ); - - switch (stage) { - case TrainModelDialogStage.Closed: - return null; - case TrainModelDialogStage.ShowingIntroduction: - return ( - - ); - case TrainModelDialogStage.TrainingError: - return ( - - ); - case TrainModelDialogStage.TrainingInProgress: - return ( - - ); - } + return ( + <> + + + + + + ); }; -export default TrainModelFlowDialogs; +export default TrainModelDialogs; diff --git a/src/components/TrainModelIntroDialog.tsx b/src/components/TrainModelHelpDialog.tsx similarity index 70% rename from src/components/TrainModelIntroDialog.tsx rename to src/components/TrainModelHelpDialog.tsx index d6fa0f4d2..76132727d 100644 --- a/src/components/TrainModelIntroDialog.tsx +++ b/src/components/TrainModelHelpDialog.tsx @@ -1,4 +1,5 @@ import { + Button, Checkbox, Heading, HStack, @@ -12,31 +13,29 @@ import { Text, VStack, } from "@chakra-ui/react"; -import { useCallback, useState } from "react"; +import { ComponentProps, useCallback, useState } from "react"; import { FormattedMessage } from "react-intl"; import trainModelImage from "../images/train_model_black.svg"; -import TrainingButton from "./TrainingButton"; -interface TrainModelIntroDialogProps { +interface TrainModelHelpDialogProps + extends Omit, "children"> { onNext: (isSkipNextTime: boolean) => void; - onClose: () => void; } const TrainModelIntroDialog = ({ - onClose, onNext, -}: TrainModelIntroDialogProps) => { + ...props +}: TrainModelHelpDialogProps) => { const [skip, setSkip] = useState(false); - const handleOnNext = useCallback(() => onNext(skip), [onNext, skip]); + const handleNext = useCallback(() => onNext(skip), [onNext, skip]); return ( @@ -47,7 +46,14 @@ const TrainModelIntroDialog = ({ - + @@ -63,7 +69,9 @@ const TrainModelIntroDialog = ({ > - + diff --git a/src/components/TrainModelInsufficientDataDialog.tsx b/src/components/TrainModelInsufficientDataDialog.tsx new file mode 100644 index 000000000..ed402a066 --- /dev/null +++ b/src/components/TrainModelInsufficientDataDialog.tsx @@ -0,0 +1,68 @@ +import { + Button, + Heading, + HStack, + Image, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalOverlay, + Text, + VStack, +} from "@chakra-ui/react"; +import { FormattedMessage } from "react-intl"; +import trainModelImage from "../images/train_model_black.svg"; +import { ComponentProps } from "react"; + +const TrainModelInsufficientDataDialog = ({ + onClose, + ...rest +}: Omit, "children">) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default TrainModelInsufficientDataDialog; diff --git a/src/components/TrainingButton.tsx b/src/components/TrainingButton.tsx deleted file mode 100644 index 1bb29fecb..000000000 --- a/src/components/TrainingButton.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Button, ButtonProps } from "@chakra-ui/react"; -import { FormattedMessage } from "react-intl"; -import { useStore, useHasSufficientDataForTraining } from "../store"; - -const TrainingButton = (props: ButtonProps) => { - const hasSufficientData = useHasSufficientDataForTraining(); - const model = useStore((s) => s.model); - const isEnabled = hasSufficientData && !model; - return ( - - ); -}; - -export default TrainingButton; diff --git a/src/deployment/default/components/button.ts b/src/deployment/default/components/button.ts index c7f0adb46..e11571eb8 100644 --- a/src/deployment/default/components/button.ts +++ b/src/deployment/default/components/button.ts @@ -138,6 +138,13 @@ const Button: StyleConfig = { bg: "gray.100", }, }), + "secondary-disabled": () => ({ + borderWidth: "2px", + borderColor: "brand.500", + color: "brand.700", + bg: "transparent", + opacity: "0.4", + }), }, }; diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index de0a027cb..d2ab57036 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -1279,6 +1279,18 @@ "value": "\"" } ], + "insufficient-data-body": [ + { + "type": 0, + "value": "You need at least 3 data samples for 2 actions to train the model." + } + ], + "insufficient-data-title": [ + { + "type": 0, + "value": "You don't have enough data" + } + ], "introducing-the-microbit-machine-learning-tool-title": [ { "type": 0, @@ -1689,6 +1701,12 @@ "value": "Software versions" } ], + "start-training-action": [ + { + "type": 0, + "value": "Start training" + } + ], "support-request": [ { "type": 0, diff --git a/src/model.ts b/src/model.ts index b38d0490f..eea4ebc16 100644 --- a/src/model.ts +++ b/src/model.ts @@ -74,7 +74,8 @@ export const isDatasetUserFileFormat = ( export const enum TrainModelDialogStage { Closed, - ShowingIntroduction, + InsufficientData, + Help, TrainingError, TrainingInProgress, } diff --git a/src/pages/DataSamplesPage.tsx b/src/pages/DataSamplesPage.tsx index bc78ae0e9..bdcf98a8e 100644 --- a/src/pages/DataSamplesPage.tsx +++ b/src/pages/DataSamplesPage.tsx @@ -23,7 +23,6 @@ import ConnectFirstView from "../components/ConnectFirstView"; import DataSampleGridView from "../components/DataSampleGridView"; import DefaultPageLayout from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; -import TrainingButton from "../components/TrainingButton"; import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; import { ConnectionStatus } from "../connect-status-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; @@ -120,10 +119,12 @@ const DataSamplesPage = () => { ) : ( - + variant={hasSufficientData ? "primary" : "secondary-disabled"} + > + + )} diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 59cbe7db2..33b024dfe 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,4 +1,12 @@ -import { Grid, Heading, Image, Stack, Text, VStack } from "@chakra-ui/react"; +import { + AspectRatio, + Grid, + Heading, + Image, + Stack, + Text, + VStack, +} from "@chakra-ui/react"; import { FormattedMessage, useIntl } from "react-intl"; import addDataImage from "../images/add_data.svg"; import testModelImage from "../images/test_model_blue.svg"; @@ -108,7 +116,9 @@ const Step = ({ title, imgSrc, description }: StepProps) => ( {title} - + + + {description} ); diff --git a/src/store.ts b/src/store.ts index 2ac7d7063..f1e83a4cc 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,6 +1,15 @@ import { Project } from "@microbit/makecode-embed/react"; import * as tf from "@tensorflow/tfjs"; import { create } from "zustand"; +import { devtools, persist } from "zustand/middleware"; +import { useShallow } from "zustand/react/shallow"; +import { flags } from "./flags"; +import { + filenames, + generateCustomFiles, + generateProject, +} from "./makecode/utils"; +import { trainModel } from "./ml"; import { DatasetEditorJsonFormat, Gesture, @@ -8,17 +17,8 @@ import { RecordingData, TrainModelDialogStage, } from "./model"; -import { - filenames, - generateCustomFiles, - generateProject, -} from "./makecode/utils"; -import { trainModel } from "./ml"; -import { defaultIcons, MakeCodeIcon } from "./utils/icons"; -import { devtools, persist } from "zustand/middleware"; -import { flags } from "./flags"; import { defaultSettings, Settings } from "./settings"; -import { useShallow } from "zustand/react/shallow"; +import { defaultIcons, MakeCodeIcon } from "./utils/icons"; export const modelUrl = "indexeddb://micro:bit-ml-tool-model"; @@ -89,14 +89,14 @@ export interface Actions { recordingStarted(): void; recordingStopped(): void; newSession(): void; + trainModelFlowStart: () => Promise; + closeTrainModelDialogs: () => void; trainModel(): Promise; setSettings(update: Partial): void; /** * Resets the project. */ resetProject: () => void; - trainModelFlowStart: () => Promise; - closeTrainModelDialogs: () => void; /** * Used by project hooks for MakeCode integration. @@ -353,13 +353,21 @@ export const useStore = create()( }, async trainModelFlowStart() { - if (get().settings.showPreTrainHelp) { + const { + settings: { showPreTrainHelp }, + gestures, + trainModel, + } = get(); + if (!hasSufficientDataForTraining(gestures)) { + set({ + trainModelDialogStage: TrainModelDialogStage.InsufficientData, + }); + } else if (showPreTrainHelp) { set({ - // TODO: this should respect the settings which should be in the state - trainModelDialogStage: TrainModelDialogStage.ShowingIntroduction, + trainModelDialogStage: TrainModelDialogStage.Help, }); } else { - await get().trainModel(); + await trainModel(); } }, From d6cf115c3919d205839f96ea359c1b2d070aae2d Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:02:54 +0100 Subject: [PATCH 131/172] Remove test model device icon updates (#313) We decided it was better for folks to get this experience in MakeCode as doing it over BT confuses the story about where the model is running. --- src/connect-actions.ts | 20 -------------------- src/hooks/ml-hooks.tsx | 8 +------- src/utils/icons.test.ts | 17 ----------------- src/utils/icons.ts | 13 ------------- 4 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 src/utils/icons.test.ts diff --git a/src/connect-actions.ts b/src/connect-actions.ts index 5409a7ddd..b06544ce5 100644 --- a/src/connect-actions.ts +++ b/src/connect-actions.ts @@ -12,7 +12,6 @@ import { import { ConnectionType } from "./connection-stage-hooks"; import { HexType, getFlashDataSource } from "./device/get-hex-file"; import { Logging } from "./logging/logging"; -import { MakeCodeIcon, iconToLedMatrix } from "./utils/icons"; export enum ConnectAndFlashResult { Success = "Success", @@ -185,25 +184,6 @@ export class ConnectActions { this.radioBridge.removeEventListener(type, listener); }; - setIcon = async (icon: MakeCodeIcon): Promise => { - await this.bluetooth.setLedMatrix(iconToLedMatrix(icon)); - }; - - resetIcon = async (): Promise => { - // Assuming this succeeds we should be back in the connected state. - await this.setIcon("Happy"); - }; - - clearIcon = async (): Promise => { - await this.bluetooth.setLedMatrix([ - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - ]); - }; - disconnect = async () => { await this.bluetooth.disconnect(); await this.radioBridge.disconnect(); diff --git a/src/hooks/ml-hooks.tsx b/src/hooks/ml-hooks.tsx index 34324fba1..25ab331c2 100644 --- a/src/hooks/ml-hooks.tsx +++ b/src/hooks/ml-hooks.tsx @@ -2,9 +2,9 @@ import { useEffect, useRef, useState } from "react"; import { useBufferedData } from "../buffered-data-hooks"; import { useConnectActions } from "../connect-actions-hooks"; import { useConnectStatus } from "../connect-status-hooks"; -import { Gesture } from "../model"; import { useLogging } from "../logging/logging-hooks"; import { Confidences, mlSettings, predict } from "../ml"; +import { Gesture } from "../model"; import { useStore } from "../store"; export interface PredictionResult { @@ -47,11 +47,6 @@ export const usePrediction = () => { gestureDataRef.current, result.confidences ); - if (detected) { - connection.setIcon(detected.icon).catch((e) => logging.error(e)); - } else { - connection.clearIcon().catch((e) => logging.error(e)); - } setPrediction({ detected, confidences, @@ -64,7 +59,6 @@ export const usePrediction = () => { 1000 / mlSettings.updatesPrSecond ); return () => { - connection.resetIcon().catch((e) => logging.error(e)); setPrediction(undefined); clearInterval(interval); }; diff --git a/src/utils/icons.test.ts b/src/utils/icons.test.ts deleted file mode 100644 index a21f22db7..000000000 --- a/src/utils/icons.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { iconToLedMatrix } from "./icons"; - -const x = true; -const _ = false; - -describe("iconToLedMatrix", () => { - it("convers correctly", () => { - expect(iconToLedMatrix("Happy")).toEqual([ - [_, _, _, _, _], - [_, x, _, x, _], - [_, _, _, _, _], - [x, _, _, _, x], - [_, x, x, x, _], - ]); - }); -}); diff --git a/src/utils/icons.ts b/src/utils/icons.ts index 2467b7454..b14f57d6d 100644 --- a/src/utils/icons.ts +++ b/src/utils/icons.ts @@ -1,5 +1,3 @@ -import { MicrobitWebBluetoothConnection } from "@microbit/microbit-connection"; - export const makecodeIcons = { Heart: "0101011111111110111000100", SmallHeart: "0000001010011100010000000", @@ -66,14 +64,3 @@ export const defaultIcons: MakeCodeIcon[] = [ "EighthNote", "Pitchfork", ]; - -// TODO: export this from the connection lib or give up and use boolean[][] -export type LedMatrix = Parameters< - MicrobitWebBluetoothConnection["setLedMatrix"] ->[0]; - -export const iconToLedMatrix = (icon: MakeCodeIcon): LedMatrix => { - return makecodeIcons[icon] - .match(/.{5}/g)! - .map((r) => r.split("").map((d) => d !== "0")) as LedMatrix; -}; From 1091d0bdaf4a65107b89b01141e46f0dd7abcb63 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:40:22 +0100 Subject: [PATCH 132/172] Custom flow for MakeCode flashing (#301) - Handles reuse of the input micro:bit without disconnect errors - Provides a choice to use a 2nd micro:bit to avoid disconnecting the input micro:bit Co-authored-by: Robert Knight Co-authored-by: Matt Hillsdon Co-authored-by: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> --- lang/ui.en.json | 34 ++- package-lock.json | 8 +- package.json | 2 +- src/components/ConnectCableDialog.tsx | 13 +- src/components/ConnectContainerDialog.tsx | 3 + src/components/ConnectErrorDialog.tsx | 5 - src/components/ConnectionFlowDialogs.tsx | 5 +- src/components/DefaultPageLayout.tsx | 13 +- .../DownloadProjectChooseMicrobitDialog.tsx | 156 ++++++++++++ src/components/DownloadProjectDialogs.tsx | 85 ++++++ src/components/DownloadProjectHelpDialog.tsx | 83 ++++++ src/components/DownloadingDialog.tsx | 1 - src/components/ManualFlashingDialog.tsx | 46 ++-- src/connect-actions.ts | 56 +++- src/connection-stage-actions.ts | 73 ++---- src/connection-stage-hooks.tsx | 1 - src/hooks/download-hooks.tsx | 241 ++++++++++++++++++ src/hooks/project-hooks.tsx | 82 ++---- src/messages/ui.en.json | 48 ++++ src/model.ts | 51 ++++ src/settings.tsx | 2 + src/store.ts | 45 +++- src/utils/fs-util.ts | 30 +++ 23 files changed, 901 insertions(+), 182 deletions(-) create mode 100644 src/components/DownloadProjectChooseMicrobitDialog.tsx create mode 100644 src/components/DownloadProjectDialogs.tsx create mode 100644 src/components/DownloadProjectHelpDialog.tsx create mode 100644 src/hooks/download-hooks.tsx diff --git a/lang/ui.en.json b/lang/ui.en.json index d8d2bb8ad..de79e84aa 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -195,6 +195,10 @@ "defaultMessage": "animation showing a USB cable being connected to the top of a micro:bit", "description": "" }, + "connectMB.connectCable.downloadProject.subtitle": { + "defaultMessage": "Connect a micro:bit to this computer with a USB cable to download your machine learning MakeCode program to it.", + "description": "" + }, "connectMB.connectCable.heading": { "defaultMessage": "Connect USB cable to micro:bit", "description": "" @@ -695,6 +699,34 @@ "defaultMessage": "Don't show this again", "description": "Text to never show a dialog again" }, + "download-action": { + "defaultMessage": "Download", + "description": "Download button text" + }, + "download-project-choose-microbit-subtitle": { + "defaultMessage": "Downloading your project on to the same micro:bit will disconnect the micro:bit from the tool. You will need to reconnect the micro:bit if you want to record more data samples.", + "description": "Download project choose micro:bit dialog subtitle" + }, + "download-project-choose-microbit-title": { + "defaultMessage": "Which micro:bit do you want to use?", + "description": "Download project choose micro:bit dialog title" + }, + "download-project-different-microbit-option": { + "defaultMessage": "Different micro:bit", + "description": "Download project different micro:bit option" + }, + "download-project-intro-description": { + "defaultMessage": "Your program includes the machine learning model you created. The model runs on the micro:bit so you can use it without being connected to the micro:bit machine learning tool.", + "description": "Download project intro dialog description" + }, + "download-project-intro-title": { + "defaultMessage": "Download your machine learning MakeCode project", + "description": "Download project intro dialog title" + }, + "download-project-same-microbit-option": { + "defaultMessage": "Same micro:bit", + "description": "Download project same micro:bit option" + }, "edit-in-makecode-action": { "defaultMessage": "Edit in MakeCode", "description": "Edit in MakeCode button text" @@ -1055,4 +1087,4 @@ "defaultMessage": "Train model", "description": "Train model step title" } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a9e7116ac..4cb624493 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@emotion/styled": "^11.11.5", "@microbit-foundation/ml-header-generator": "^0.4.0", "@microbit/makecode-embed": "^0.0.0-alpha.7", - "@microbit/microbit-connection": "^0.0.0-alpha.19", + "@microbit/microbit-connection": "^0.0.0-alpha.21", "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", @@ -4377,9 +4377,9 @@ } }, "node_modules/@microbit/microbit-connection": { - "version": "0.0.0-alpha.19", - "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.19.tgz", - "integrity": "sha512-VLOLVKkkxGHA6yzla39/dcU9IOFlIjrYyfkvWvR7ypzCnhJ0moJSsDpPduR4m+WLHDv4SlwEhFHLNvyHfveKnQ==", + "version": "0.0.0-alpha.21", + "resolved": "https://registry.npmjs.org/@microbit/microbit-connection/-/microbit-connection-0.0.0-alpha.21.tgz", + "integrity": "sha512-qIyGL+yas89w5/sHIvTRIfT+1iDFe19MBzDCWSpYg18Vjv+6pcTdai/2qLvtzYrr6uODvlZJSpaW9LEoSFl1Eg==", "dependencies": { "@microbit/microbit-universal-hex": "^0.2.2", "@types/web-bluetooth": "^0.0.20", diff --git a/package.json b/package.json index 5439336f7..082898b48 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@emotion/styled": "^11.11.5", "@microbit-foundation/ml-header-generator": "^0.4.0", "@microbit/makecode-embed": "^0.0.0-alpha.7", - "@microbit/microbit-connection": "^0.0.0-alpha.19", + "@microbit/microbit-connection": "^0.0.0-alpha.21", "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", diff --git a/src/components/ConnectCableDialog.tsx b/src/components/ConnectCableDialog.tsx index 7688764a6..0f3724be5 100644 --- a/src/components/ConnectCableDialog.tsx +++ b/src/components/ConnectCableDialog.tsx @@ -20,11 +20,6 @@ export const getConnectionCableDialogConfig = ( isWebBluetoothSupported: boolean ): Config => { switch (flowType) { - case ConnectionFlowType.DownloadProject: - return { - headingId: "connectMB.connectCable.heading", - subtitleId: "connectMB.connectCable.subtitle", - }; case ConnectionFlowType.ConnectBluetooth: return { headingId: "connectMB.connectCable.heading", @@ -60,8 +55,8 @@ export const getConnectionCableDialogConfig = ( export interface ConnectCableDialogProps extends Omit { config: Config; - onSkip: () => void; - onSwitch: () => void; + onSkip?: () => void; + onSwitch?: () => void; } const ConnectCableDialog = ({ @@ -77,7 +72,9 @@ const ConnectCableDialog = ({ {...props} footerLeft={ linkTextId && - linkType && ( + linkType && + onSkip && + onSwitch && ( )} + {additionalActions} {onNextClick && ( + + + + + + + ); +}; + +export default DownloadProjectHelpDialog; diff --git a/src/components/DownloadingDialog.tsx b/src/components/DownloadingDialog.tsx index 639591364..cb86a9764 100644 --- a/src/components/DownloadingDialog.tsx +++ b/src/components/DownloadingDialog.tsx @@ -19,7 +19,6 @@ export interface DownloadingDialogProps { export const getHeadingId = (flowType: ConnectionFlowType) => { switch (flowType) { - case ConnectionFlowType.DownloadProject: case ConnectionFlowType.ConnectBluetooth: return "connectMB.usbDownloading.header"; case ConnectionFlowType.ConnectRadioRemote: diff --git a/src/components/ManualFlashingDialog.tsx b/src/components/ManualFlashingDialog.tsx index a6504556d..413dc16ca 100644 --- a/src/components/ManualFlashingDialog.tsx +++ b/src/components/ManualFlashingDialog.tsx @@ -1,6 +1,6 @@ -import { Image, Text, VStack } from "@chakra-ui/react"; +import { Button, Image, Text, VStack } from "@chakra-ui/react"; import Bowser from "bowser"; -import { ReactNode, useCallback, useEffect } from "react"; +import { ReactNode, useCallback } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import transferProgramChromeOS from "../images/transfer_program_chromeos.gif"; import transferProgramMacOS from "../images/transfer_program_macos.gif"; @@ -8,7 +8,8 @@ import transferProgramWindows from "../images/transfer_program_windows.gif"; import ConnectContainerDialog, { ConnectContainerDialogProps, } from "./ConnectContainerDialog"; -import { HexType, getHexFileUrl } from "../device/get-hex-file"; +import { HexData, HexUrl } from "../model"; +import { downloadHex } from "../utils/fs-util"; interface ImageProps { src: string; @@ -30,18 +31,16 @@ const getImageProps = (os: string): ImageProps => { }; export interface ManualFlashingDialogProps - extends Omit {} - -const download = (fileUrl: string, filename: string) => { - const a = document.createElement("a"); - a.download = filename; - a.href = fileUrl; - a.click(); - a.remove(); -}; + extends Omit { + hex: HexData | HexUrl; + closeIsPrimaryAction?: boolean; +} -// Only bluetooth mode has this fallback, the radio bridge mode requires working WebUSB. -const ManualFlashingDialog = ({ ...props }: ManualFlashingDialogProps) => { +const ManualFlashingDialog = ({ + hex, + closeIsPrimaryAction, + ...props +}: ManualFlashingDialogProps) => { const intl = useIntl(); const browser = Bowser.getParser(window.navigator.userAgent); const osName = browser.getOS().name ?? "unknown"; @@ -49,24 +48,23 @@ const ManualFlashingDialog = ({ ...props }: ManualFlashingDialogProps) => { const imageProps = getImageProps(osName); const handleDownload = useCallback(() => { - download( - getHexFileUrl("universal", HexType.Bluetooth)!, - "machine-learning-tool-program.hex" - ); - }, []); - - useEffect(() => { - handleDownload(); - }, [handleDownload]); + downloadHex(hex); + }, [hex]); return ( + + + ) : undefined + } {...props} > - {" "} void; +export interface ConnectionAndFlashOptions { + temporaryUsbConnection: MicrobitWebUSBConnection; + callbackIfDeviceIsSame?: () => Promise; +} + export class ConnectActions { private statusListeners: StatusListeners = { bluetooth: () => {}, @@ -68,19 +74,33 @@ export class ConnectActions { requestUSBConnectionAndFlash = async ( hex: string | HexType, - progressCallback: (progress: number) => void + progressCallback: (progress: number) => void, + // Used for MakeCode hex downloads. + options?: ConnectionAndFlashOptions ): Promise< | { result: ConnectAndFlashResult.Success; deviceId: number } | { result: ConnectAndFlashFailResult; deviceId?: number } > => { + const usb = options?.temporaryUsbConnection ?? this.usb; try { - await this.usb.connect(); - const result = await this.flashMicrobit(hex, progressCallback); + await usb.connect(); // Save remote micro:bit device id is stored for passing it to bridge micro:bit - const deviceId = this.usb.getDeviceId(); + const deviceId = usb.getDeviceId(); + if ( + options?.temporaryUsbConnection && + options?.callbackIfDeviceIsSame && + deviceId === this.usb.getDeviceId() + ) { + await options?.callbackIfDeviceIsSame(); + } if (!deviceId) { return { result: ConnectAndFlashResult.Failed }; } + const result = await this.flashMicrobit( + hex, + progressCallback, + options?.temporaryUsbConnection + ); return { result, deviceId }; } catch (e) { this.logging.error( @@ -92,9 +112,11 @@ export class ConnectActions { private flashMicrobit = async ( hex: string | HexType, - progress: (progress: number) => void + progress: (progress: number) => void, + temporaryUsbConnection?: MicrobitWebUSBConnection ): Promise => { - if (!this.usb) { + const usb = temporaryUsbConnection ?? this.usb; + if (!usb) { return ConnectAndFlashResult.Failed; } const data = Object.values(HexType).includes(hex as HexType) @@ -105,7 +127,7 @@ export class ConnectActions { return ConnectAndFlashResult.ErrorMicrobitUnsupported; } try { - await this.usb.flash(data, { + await usb.flash(data, { partial: true, progress: (v: number | undefined) => progress(v ?? 100), }); @@ -139,6 +161,26 @@ export class ConnectActions { await this.radioBridge.connect(); }; + getUsbDeviceId = () => { + return this.usb.getDeviceId(); + }; + + isUsbDeviceConnected = () => { + return this.usb.status === ConnectionStatus.CONNECTED; + }; + + getUsbConnection = () => { + return this.usb; + }; + + getUsbDevice = () => { + return this.usb.getDevice(); + }; + + clearUsbDevice = async () => { + await this.usb.clearDevice(); + }; + connectBluetooth = async ( name: string | undefined, clearDevice: boolean diff --git a/src/connection-stage-actions.ts b/src/connection-stage-actions.ts index 6f1a4f34c..35bbf7e74 100644 --- a/src/connection-stage-actions.ts +++ b/src/connection-stage-actions.ts @@ -4,17 +4,24 @@ import { ConnectAndFlashFailResult, ConnectAndFlashResult, } from "./connect-actions"; +import { ConnectionStatus } from "./connect-status-hooks"; import { ConnectionFlowStep, ConnectionFlowType, ConnectionStage, ConnectionType, } from "./connection-stage-hooks"; -import { ConnectionStatus } from "./connect-status-hooks"; -import { HexType } from "./device/get-hex-file"; +import { getHexFileUrl, HexType } from "./device/get-hex-file"; +import { HexUrl } from "./model"; +import { downloadHex } from "./utils/fs-util"; type FlowStage = Pick; +export const bluetoothUniversalHex: HexUrl = { + url: getHexFileUrl("universal", HexType.Bluetooth)!, + name: "machine-learning-tool-program", +}; + export class ConnectionStageActions { constructor( private actions: ConnectActions, @@ -39,18 +46,10 @@ export class ConnectionStageActions { }); }; - startDownloadUserProjectHex = async (hex: string) => { - // TODO: Only disconnect input micro:bit if user chooses this device. + disconnectInputMicrobit = async () => { await this.actions.disconnect(); this.actions.removeStatusListener(); this.setStatus(ConnectionStatus.NotConnected); - - this.setStage({ - ...this.stage, - makeCodeHex: hex, - flowType: ConnectionFlowType.DownloadProject, - flowStep: ConnectionFlowStep.ConnectCable, - }); }; setFlowStep = (step: ConnectionFlowStep) => { @@ -62,7 +61,7 @@ export class ConnectionStageActions { onSuccess: (stage: ConnectionStage) => void ) => { this.setFlowStep(ConnectionFlowStep.WebUsbChooseMicrobit); - const hex = this.getHexStringOrType(); + const hex = this.getHexType(); const { result, deviceId } = await this.actions.requestUSBConnectionAndFlash(hex, progressCallback); if (result !== ConnectAndFlashResult.Success) { @@ -72,14 +71,12 @@ export class ConnectionStageActions { await this.onFlashSuccess(deviceId, onSuccess); }; - private getHexStringOrType = () => { - return this.stage.flowType === ConnectionFlowType.DownloadProject - ? this.stage.makeCodeHex! - : { - [ConnectionFlowType.ConnectBluetooth]: HexType.Bluetooth, - [ConnectionFlowType.ConnectRadioBridge]: HexType.RadioBridge, - [ConnectionFlowType.ConnectRadioRemote]: HexType.RadioRemote, - }[this.stage.flowType]; + private getHexType = () => { + return { + [ConnectionFlowType.ConnectBluetooth]: HexType.Bluetooth, + [ConnectionFlowType.ConnectRadioBridge]: HexType.RadioBridge, + [ConnectionFlowType.ConnectRadioRemote]: HexType.RadioRemote, + }[this.stage.flowType]; }; private onFlashSuccess = async ( @@ -90,9 +87,6 @@ export class ConnectionStageActions { // Store radio/bluetooth details. Radio is essential to pass to micro:bit 2. // Bluetooth saves the user from entering the pattern. switch (this.stage.flowType) { - case ConnectionFlowType.DownloadProject: { - return this.setFlowStep(ConnectionFlowStep.None); - } case ConnectionFlowType.ConnectBluetooth: { const microbitName = deviceIdToMicrobitName(deviceId); newStage = { @@ -125,10 +119,8 @@ export class ConnectionStageActions { }; private handleConnectAndFlashFail = (result: ConnectAndFlashFailResult) => { - if ( - this.stage.flowType === ConnectionFlowType.ConnectBluetooth || - this.stage.flowType === ConnectionFlowType.DownloadProject - ) { + if (this.stage.flowType === ConnectionFlowType.ConnectBluetooth) { + downloadHex(bluetoothUniversalHex); return this.setFlowStep(ConnectionFlowStep.ManualFlashingTutorial); } @@ -285,14 +277,10 @@ export class ConnectionStageActions { !this.stage.isWebUsbSupported || this.stage.flowStep === ConnectionFlowStep.ManualFlashingTutorial; - switch (this.stage.flowType) { - case ConnectionFlowType.DownloadProject: - return downloadProjectFlow({ isManualFlashing }); - case ConnectionFlowType.ConnectBluetooth: - return bluetoothFlow({ isManualFlashing, isRestartAgain }); - default: - return radioFlow({ isRestartAgain }); + if (this.stage.flowType === ConnectionFlowType.ConnectBluetooth) { + return bluetoothFlow({ isManualFlashing, isRestartAgain }); } + return radioFlow({ isRestartAgain }); }; private setFlowStage = (flowStage: FlowStage) => { @@ -383,23 +371,6 @@ const radioFlow = ({ isRestartAgain }: { isRestartAgain: boolean }) => [ }, ]; -const downloadProjectFlow = ({ - isManualFlashing, -}: { - isManualFlashing: boolean; -}) => [ - { - flowStep: ConnectionFlowStep.ConnectCable, - flowType: ConnectionFlowType.DownloadProject, - }, - { - flowStep: isManualFlashing - ? ConnectionFlowStep.ManualFlashingTutorial - : ConnectionFlowStep.WebUsbFlashingTutorial, - flowType: ConnectionFlowType.DownloadProject, - }, -]; - const getFlowStageIdx = ( { flowStep, flowType }: FlowStage, order: FlowStage[] diff --git a/src/connection-stage-hooks.tsx b/src/connection-stage-hooks.tsx index a61e9ad7c..efa3425d7 100644 --- a/src/connection-stage-hooks.tsx +++ b/src/connection-stage-hooks.tsx @@ -20,7 +20,6 @@ export enum ConnectionFlowType { ConnectBluetooth = "ConnectBluetooth", ConnectRadioBridge = "ConnectRadioBridge", ConnectRadioRemote = "ConnectRadioRemote", - DownloadProject = "DownloadProject", } export type InputConnectionFlowType = diff --git a/src/hooks/download-hooks.tsx b/src/hooks/download-hooks.tsx new file mode 100644 index 000000000..56a288356 --- /dev/null +++ b/src/hooks/download-hooks.tsx @@ -0,0 +1,241 @@ +import { + MicrobitWebUSBConnection, + ConnectionStatus as UsbConnectionStatus, +} from "@microbit/microbit-connection"; +import { useMemo } from "react"; +import { + ConnectActions, + ConnectAndFlashResult, + ConnectionAndFlashOptions, +} from "../connect-actions"; +import { useConnectActions } from "../connect-actions-hooks"; +import { ConnectionStatus } from "../connect-status-hooks"; +import { ConnectionStageActions } from "../connection-stage-actions"; +import { useConnectionStage } from "../connection-stage-hooks"; +import { + DownloadProjectStage, + DownloadProjectStep, + HexData, + MicrobitToFlash, +} from "../model"; +import { Settings } from "../settings"; +import { useSettings, useStore } from "../store"; +import { downloadHex } from "../utils/fs-util"; + +export class DownloadProjectActions { + constructor( + private stage: DownloadProjectStage, + private setStage: (stage: DownloadProjectStage) => void, + private settings: Settings, + private setSettings: (settings: Partial) => void, + private connectActions: ConnectActions, + private connectionStageActions: ConnectionStageActions, + private connectionStatus: ConnectionStatus + ) {} + + clearMakeCodeUsbDevice = () => { + this.setStage({ ...this.stage, usbDevice: undefined }); + }; + + start = async (download: HexData) => { + if (this.stage.usbDevice) { + if (this.stage.usbDevice.status === UsbConnectionStatus.CONNECTED) { + const newStage = { + ...this.stage, + step: DownloadProjectStep.FlashingInProgress, + project: download, + }; + this.setStage(newStage); + return this.flashMicrobit(newStage, { + temporaryUsbConnection: this.stage.usbDevice, + }); + } + if (!this.settings.showPreDownloadHelp) { + this.updateStage({ project: download }); + return this.onHelpNext(true); + } + return this.updateStage({ + project: download, + step: DownloadProjectStep.ConnectCable, + }); + } + this.updateStage({ + step: DownloadProjectStep.Help, + project: download, + }); + }; + + onHelpNext = (isSkipNextTime: boolean) => { + this.setSettings({ showPreDownloadHelp: !isSkipNextTime }); + + // If we've connected to a micro:bit in the session, we make the user + // choose a device even if the connection has been lost since. + // This makes reconnect easier if the user has two micro:bits. + if (this.connectionStatus !== ConnectionStatus.NotConnected) { + return this.updateStage({ + step: DownloadProjectStep.ChooseSameOrAnotherMicrobit, + }); + } + this.updateStage({ + step: DownloadProjectStep.ConnectCable, + }); + }; + + onSkipIntro = (skipIntro: boolean) => + this.setSettings({ showPreDownloadHelp: !skipIntro }); + + onBackToIntro = () => this.setStep(DownloadProjectStep.Help); + + onChosenSameMicrobit = async () => { + if (this.connectActions.isUsbDeviceConnected()) { + const newStage = { ...this.stage, microbitToFlash: MicrobitToFlash.Same }; + // Can flash directly without choosing device. + return this.connectAndFlashMicrobit(newStage); + } + this.updateStage({ + step: DownloadProjectStep.ConnectCable, + microbitToFlash: MicrobitToFlash.Same, + }); + }; + + onChosenDifferentMicrobit = () => { + this.updateStage({ + step: DownloadProjectStep.ConnectCable, + microbitToFlash: MicrobitToFlash.Different, + }); + }; + + connectAndFlashMicrobit = async (stage: DownloadProjectStage) => { + let connectionAndFlashOptions: ConnectionAndFlashOptions | undefined; + if (stage.microbitToFlash === MicrobitToFlash.Same) { + // Disconnect input micro:bit to not trigger connection lost warning. + await this.connectionStageActions.disconnectInputMicrobit(); + } + if (stage.microbitToFlash === MicrobitToFlash.Different) { + // Use a temporary USB connection to flash the MakeCode program. + // Disconnect the input micro:bit if the user selects this device from the + // list by mistake. + const temporaryUsbConnection = new MicrobitWebUSBConnection(); + const connectedDevice = this.connectActions.getUsbDevice(); + if (connectedDevice) { + temporaryUsbConnection.setRequestDeviceExclusionFilters([ + { serialNumber: connectedDevice.serialNumber }, + ]); + } + connectionAndFlashOptions = { + temporaryUsbConnection, + callbackIfDeviceIsSame: + this.connectionStageActions.disconnectInputMicrobit, + }; + } + if (!stage.project) { + throw new Error("Project hex/name is not set!"); + } + this.updateStage({ step: DownloadProjectStep.WebUsbChooseMicrobit }); + await this.flashMicrobit(stage, connectionAndFlashOptions); + }; + + flashMicrobit = async ( + stage: DownloadProjectStage, + connectionAndFlashOptions?: ConnectionAndFlashOptions + ) => { + if (!stage.project) { + throw new Error("Project hex/name is not set!"); + } + const { result } = await this.connectActions.requestUSBConnectionAndFlash( + stage.project.hex, + this.flashingProgressCallback, + connectionAndFlashOptions + ); + const newStage = { + usbDevice: + connectionAndFlashOptions?.temporaryUsbConnection ?? + this.connectActions.getUsbConnection(), + step: + result === ConnectAndFlashResult.Success + ? DownloadProjectStep.None + : DownloadProjectStep.ManualFlashingTutorial, + flashProgress: 0, + }; + this.updateStage(newStage); + if (newStage.step === DownloadProjectStep.ManualFlashingTutorial) { + downloadHex(stage.project); + } + }; + + private flashingProgressCallback = (progress: number) => { + this.setStage({ + ...this.stage, + step: DownloadProjectStep.FlashingInProgress, + flashProgress: progress, + }); + }; + + close = () => this.setStep(DownloadProjectStep.None); + getOnNext = () => this.getOnNextIfPossible(1); + getOnBack = () => this.getOnNextIfPossible(-1); + + private getOnNextIfPossible = (inc: number) => + this.getNextStep(inc) + ? () => this.setStep(this.getNextStep(inc)) + : undefined; + + private getNextStep = (inc: number): DownloadProjectStep => { + const orderedSteps = this.downloadProjectStepOrder(); + const currIdx = orderedSteps.indexOf(this.stage.step); + const nextIdx = currIdx + inc; + if (currIdx < 0 || nextIdx < 0 || nextIdx === orderedSteps.length) { + undefined; + } + return orderedSteps[nextIdx]; + }; + + private downloadProjectStepOrder = () => [ + ...(this.settings.showPreDownloadHelp ? [DownloadProjectStep.Help] : []), + ...(this.stage.step === DownloadProjectStep.ChooseSameOrAnotherMicrobit || + this.stage.microbitToFlash !== MicrobitToFlash.Default + ? [DownloadProjectStep.ChooseSameOrAnotherMicrobit] + : []), + DownloadProjectStep.ConnectCable, + this.stage.step === DownloadProjectStep.ManualFlashingTutorial + ? DownloadProjectStep.ManualFlashingTutorial + : DownloadProjectStep.WebUsbFlashingTutorial, + ]; + + private updateStage = (partialStage: Partial) => { + this.setStage({ ...this.stage, ...partialStage } as DownloadProjectStage); + }; + + private setStep = (step: DownloadProjectStep) => + this.setStage({ ...this.stage, step }); +} + +export const useDownloadActions = (): DownloadProjectActions => { + const stage = useStore((s) => s.downloadStage); + const setStage = useStore((s) => s.setDownloadStage); + const [settings, setSettings] = useSettings(); + const connectActions = useConnectActions(); + const { actions: connectionStageActions, status: connectionStatus } = + useConnectionStage(); + return useMemo( + () => + new DownloadProjectActions( + stage, + setStage, + settings, + setSettings, + connectActions, + connectionStageActions, + connectionStatus + ), + [ + connectActions, + connectionStageActions, + connectionStatus, + setSettings, + setStage, + settings, + stage, + ] + ); +}; diff --git a/src/hooks/project-hooks.tsx b/src/hooks/project-hooks.tsx index e6e8064c5..79ec45ba7 100644 --- a/src/hooks/project-hooks.tsx +++ b/src/hooks/project-hooks.tsx @@ -13,10 +13,14 @@ import { useMemo, useRef, } from "react"; -import { useConnectionStage } from "../connection-stage-hooks"; -import { isDatasetUserFileFormat } from "../model"; +import { HexData, isDatasetUserFileFormat } from "../model"; import { useStore } from "../store"; -import { getLowercaseFileExtension, readFileAsText } from "../utils/fs-util"; +import { + downloadHex, + getLowercaseFileExtension, + readFileAsText, +} from "../utils/fs-util"; +import { useDownloadActions } from "./download-hooks"; interface ProjectContext { openEditor(): Promise; @@ -51,31 +55,9 @@ export const ProjectProvider = ({ children, }: ProjectProviderProps) => { const setEditorOpen = useStore((s) => s.setEditorOpen); - - // We use this to track when we need special handling of an event from MakeCode - const waitingForEditorContentLoaded = useRef void)>( - undefined - ); - - // We use this to track when we're expecting a native app save from MakeCode - const waitingForDownload = useRef< - undefined | ((download: { hex: string; name: string }) => void) - >(undefined); - const waitForNextDownload = useCallback(() => { - return new Promise<{ hex: string; name: string }>((resolve) => { - waitingForDownload.current = (download: { - hex: string; - name: string; - }) => { - resolve(download); - waitingForDownload.current = undefined; - }; - }); - }, []); - const project = useStore((s) => s.project); const projectEdited = useStore((s) => s.projectEdited); - const expectChangedHeader = useStore((s) => s.expectChangedHeader); + const expectChangedHeader = useStore((s) => s.setChangedHeaderExpected); const projectFlushedToEditor = useStore((s) => s.projectFlushedToEditor); const appEditNeedsFlushToEditor = useStore( (s) => s.appEditNeedsFlushToEditor @@ -128,14 +110,13 @@ export const ProjectProvider = ({ [driverRef, loadDataset] ); + const saveNextDownloadRef = useRef(false); const saveProjectHex = useCallback(async (): Promise => { await doAfterMakeCodeUpdate(async () => { - const downloadPromise = waitForNextDownload(); + saveNextDownloadRef.current = true; await driverRef.current!.compile(); - const download = await downloadPromise; - triggerBrowserDownload(download); }); - }, [doAfterMakeCodeUpdate, driverRef, waitForNextDownload]); + }, [doAfterMakeCodeUpdate, driverRef]); // These are event handlers for MakeCode @@ -148,33 +129,19 @@ export const ProjectProvider = ({ ); const onBack = useCallback(() => setEditorOpen(false), [setEditorOpen]); - - const onSave = useCallback((save: { name: string; hex: string }) => { - // Handles the event we get from MakeCode to say a hex needs saving to disk. - // In practice this is via "Download" ... "Save as file" - // TODO: give this the same behaviour as SaveButton in terms of dialogs etc. - triggerBrowserDownload(save); - }, []); - - const onEditorContentLoaded = useCallback(() => { - waitingForEditorContentLoaded.current?.(); - waitingForEditorContentLoaded.current = undefined; - }, []); - - const { actions } = useConnectionStage(); + const onSave = useStore((s) => s.editorSave); + const downloadActions = useDownloadActions(); const onDownload = useCallback( - // Handles the event we get from MakeCode to say a hex needs downloading to the micro:bit. - async (download: { name: string; hex: string }) => { - if (waitingForDownload?.current) { - waitingForDownload.current(download); + (download: HexData) => { + if (saveNextDownloadRef.current) { + saveNextDownloadRef.current = false; + downloadHex(download); } else { - // Ideally we'd preserve the filename here and use it for the fallback if WebUSB fails. - await actions.startDownloadUserProjectHex(download.hex); + void downloadActions.start(download); } }, - [actions] + [downloadActions] ); - const value = useMemo( () => ({ loadProject, @@ -188,14 +155,12 @@ export const ProjectProvider = ({ onWorkspaceSave, onDownload, onBack, - onEditorContentLoaded, }, }), [ loadProject, onBack, onDownload, - onEditorContentLoaded, onSave, onWorkspaceSave, openEditor, @@ -210,12 +175,3 @@ export const ProjectProvider = ({ {children} ); }; - -const triggerBrowserDownload = (save: { name: string; hex: string }) => { - const blob = new Blob([save.hex], { type: "application/octet-stream" }); - const a = document.createElement("a"); - a.href = URL.createObjectURL(blob); - a.download = `${save.name}.hex`; - a.click(); - URL.revokeObjectURL(a.href); -}; diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index d2ab57036..d6d3dfc5c 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -319,6 +319,12 @@ "value": "animation showing a USB cable being connected to the top of a micro:bit" } ], + "connectMB.connectCable.downloadProject.subtitle": [ + { + "type": 0, + "value": "Connect a micro:bit to this computer with a USB cable to download your machine learning MakeCode program to it." + } + ], "connectMB.connectCable.heading": [ { "type": 0, @@ -1187,6 +1193,48 @@ "value": "Don't show this again" } ], + "download-action": [ + { + "type": 0, + "value": "Download" + } + ], + "download-project-choose-microbit-subtitle": [ + { + "type": 0, + "value": "Downloading your project on to the same micro:bit will disconnect the micro:bit from the tool. You will need to reconnect the micro:bit if you want to record more data samples." + } + ], + "download-project-choose-microbit-title": [ + { + "type": 0, + "value": "Which micro:bit do you want to use?" + } + ], + "download-project-different-microbit-option": [ + { + "type": 0, + "value": "Different micro:bit" + } + ], + "download-project-intro-description": [ + { + "type": 0, + "value": "Your program includes the machine learning model you created. The model runs on the micro:bit so you can use it without being connected to the micro:bit machine learning tool." + } + ], + "download-project-intro-title": [ + { + "type": 0, + "value": "Download your machine learning MakeCode project" + } + ], + "download-project-same-microbit-option": [ + { + "type": 0, + "value": "Same micro:bit" + } + ], "edit-in-makecode-action": [ { "type": 0, diff --git a/src/model.ts b/src/model.ts index eea4ebc16..d4ff56112 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,3 +1,4 @@ +import { MicrobitWebUSBConnection } from "@microbit/microbit-connection"; import { MakeCodeIcon } from "./utils/icons"; export interface XYZData { @@ -72,6 +73,26 @@ export const isDatasetUserFileFormat = ( return true; }; +export interface HexData { + /** + * Hex data. + */ + hex: string; + /** + * Filename without the .hex extension. + */ + name: string; +} + +export interface HexUrl { + url: string; + + /** + * Filename without the .hex extension. + */ + name: string; +} + export const enum TrainModelDialogStage { Closed, InsufficientData, @@ -79,3 +100,33 @@ export const enum TrainModelDialogStage { TrainingError, TrainingInProgress, } + +export enum DownloadProjectStep { + None = "None", + Help = "Introduction", + ChooseSameOrAnotherMicrobit = "ChooseSameOrAnotherMicrobit", + ConnectCable = "ConnectCable", + WebUsbFlashingTutorial = "WebUsbFlashingTutorial", + WebUsbChooseMicrobit = "WebUsbChooseMicrobit", + FlashingInProgress = "FlashingInProgress", + ManualFlashingTutorial = "ManualFlashingTutorial", +} + +export enum MicrobitToFlash { + // No micro:bit is connected. + Default = "default", + // Same as the connected micro:bit. + Same = "same", + // Different from the connected micro:bit. + Different = "different", +} + +export interface DownloadProjectStage { + step: DownloadProjectStep; + microbitToFlash: MicrobitToFlash; + flashProgress: number; + project?: HexData; + // The micro:bit used to flash the hex. We remember your choice for easy code + // iteration for as long as the editor is open. + usbDevice?: MicrobitWebUSBConnection; +} diff --git a/src/settings.tsx b/src/settings.tsx index 906068eca..87b2989d8 100644 --- a/src/settings.tsx +++ b/src/settings.tsx @@ -37,10 +37,12 @@ export const defaultSettings: Settings = { languageId: getLanguageFromQuery(), showPreSaveHelp: true, showPreTrainHelp: true, + showPreDownloadHelp: true, }; export interface Settings { languageId: string; showPreSaveHelp: boolean; showPreTrainHelp: boolean; + showPreDownloadHelp: boolean; } diff --git a/src/store.ts b/src/store.ts index f1e83a4cc..9e38c4117 100644 --- a/src/store.ts +++ b/src/store.ts @@ -12,12 +12,17 @@ import { import { trainModel } from "./ml"; import { DatasetEditorJsonFormat, + DownloadProjectStage, + DownloadProjectStep, Gesture, GestureData, + HexData, + MicrobitToFlash, RecordingData, TrainModelDialogStage, } from "./model"; import { defaultSettings, Settings } from "./settings"; +import { downloadHex } from "./utils/fs-util"; import { defaultIcons, MakeCodeIcon } from "./utils/icons"; export const modelUrl = "indexeddb://micro:bit-ml-tool-model"; @@ -64,6 +69,7 @@ export interface State { changedHeaderExpected: boolean; appEditNeedsFlushToEditor: boolean; isEditorOpen: boolean; + downloadStage: DownloadProjectStage; settings: Settings; @@ -96,20 +102,16 @@ export interface Actions { /** * Resets the project. */ - resetProject: () => void; + resetProject(): void; /** - * Used by project hooks for MakeCode integration. - */ - editorChange: (project: Project) => void; - /** - * Used by project hooks for MakeCode integration. - */ - expectChangedHeader(): void; - /** - * Used by project hooks for MakeCode integration. + * Remainer are used by project hooks for MakeCode integration. */ + editorChange(project: Project): void; + editorSave(project: HexData): void; + setChangedHeaderExpected(): void; projectFlushedToEditor(): void; + setDownloadStage(stage: DownloadProjectStage): void; } type Store = State & Actions; @@ -145,6 +147,11 @@ export const useStore = create()( } as any, ...generateProject({ data: [] }, undefined), }, + downloadStage: { + step: DownloadProjectStep.None, + microbitToFlash: MicrobitToFlash.Default, + flashProgress: 0, + }, projectEdited: false, settings: defaultSettings, model: undefined, @@ -175,11 +182,15 @@ export const useStore = create()( setEditorOpen(open: boolean) { set( - { + ({ downloadStage }) => ({ isEditorOpen: open, // We just assume its been edited as spurious changes from MakeCode happen that we can't identify projectEdited: true, - }, + downloadStage: { + ...downloadStage, + usbDevice: undefined, + }, + }), false, "setEditorOpen" ); @@ -464,7 +475,15 @@ export const useStore = create()( actionName ); }, - expectChangedHeader() { + setDownloadStage(downloadStage: DownloadProjectStage) { + set({ downloadStage }); + }, + editorSave(project: HexData) { + // TODO: We'd like to trigger the same UI as we do for the "Save" + // buttob but with this project rather than poking MakeCode. + downloadHex(project); + }, + setChangedHeaderExpected() { set({ changedHeaderExpected: true }); }, projectFlushedToEditor() { diff --git a/src/utils/fs-util.ts b/src/utils/fs-util.ts index 31cf0a936..d2bffb2bc 100644 --- a/src/utils/fs-util.ts +++ b/src/utils/fs-util.ts @@ -1,3 +1,5 @@ +import { HexData, HexUrl } from "../model"; + export const getFileExtension = (filename: string): string | undefined => { const parts = filename.split("."); return parts.length > 1 ? parts.pop() || undefined : undefined; @@ -28,3 +30,31 @@ export const readFileAsText = async (file: File): Promise => { reader.readAsText(file); }); }; + +const isHexUrl = (hex: HexData | HexUrl): hex is HexUrl => { + return "url" in hex; +}; + +export const downloadHex = (hex: HexData | HexUrl) => { + if (isHexUrl(hex)) { + downloadUrl(hex.url, `${hex.name}.hex`); + } else { + downloadHexData(hex); + } +}; + +const downloadHexData = (hex: HexData) => { + const blob = new Blob([hex.hex], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + downloadUrl(url, `${hex.name}.hex`); + URL.revokeObjectURL(url); +}; + +export const downloadUrl = (url: string, download?: string) => { + const a = document.createElement("a"); + a.href = url; + if (download) { + a.download = download; + } + a.click(); +}; From b907119b9200c1b0875cf49379be626e15029132 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:19:32 +0100 Subject: [PATCH 133/172] Navigate to data samples page when starting new session (#315) --- src/components/StartResumeActions.tsx | 32 ++++++--------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/src/components/StartResumeActions.tsx b/src/components/StartResumeActions.tsx index cf8880880..24150d775 100644 --- a/src/components/StartResumeActions.tsx +++ b/src/components/StartResumeActions.tsx @@ -1,26 +1,19 @@ import { Button, HStack, StackProps, useDisclosure } from "@chakra-ui/react"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router"; -import { createSessionPageUrl } from "../urls"; -import StartOverWarningDialog from "./StartOverWarningDialog"; import { useConnectionStage } from "../connection-stage-hooks"; -import { ConnectionStatus } from "../connect-status-hooks"; import { SessionPageId } from "../pages-config"; -import { useStore, useHasGestures } from "../store"; +import { useHasGestures, useStore } from "../store"; +import { createSessionPageUrl } from "../urls"; +import StartOverWarningDialog from "./StartOverWarningDialog"; const StartResumeActions = ({ ...props }: Partial) => { const newSession = useStore((s) => s.newSession); const hasExistingSession = useHasGestures(); - const [hasConnectFlowStarted, setHasConnectFlowStarted] = - useState(false); const startOverWarningDialogDisclosure = useDisclosure(); const navigate = useNavigate(); - const { - actions: connStageActions, - isConnected, - status, - } = useConnectionStage(); + const { actions: connStageActions } = useConnectionStage(); const handleNavigateToAddData = useCallback(() => { navigate(createSessionPageUrl(SessionPageId.DataSamples)); @@ -29,26 +22,15 @@ const StartResumeActions = ({ ...props }: Partial) => { const handleStartNewSession = useCallback(() => { startOverWarningDialogDisclosure.onClose(); newSession(); - if (isConnected) { - handleNavigateToAddData(); - } else { - connStageActions.startConnect(); - setHasConnectFlowStarted(true); - } + handleNavigateToAddData(); + connStageActions.startConnect(); }, [ startOverWarningDialogDisclosure, newSession, - isConnected, handleNavigateToAddData, connStageActions, ]); - useEffect(() => { - if (status === ConnectionStatus.Connected && hasConnectFlowStarted) { - handleNavigateToAddData(); - } - }, [handleNavigateToAddData, hasConnectFlowStarted, status]); - const onClickStartNewSession = useCallback(() => { if (hasExistingSession) { startOverWarningDialogDisclosure.onOpen(); From e8e151135904a24cf17d4125c8c35abd320d5f71 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Wed, 18 Sep 2024 15:24:31 +0100 Subject: [PATCH 134/172] Improve UI state after loading a project (#316) - Close editor if in MakeCode - Pop a toast to acknowledge the load - Ensure we're on the data samples page Bit of a pain as we only find out about it via MakeCode so we add a timestamp to the state that we can watch change. --- lang/ui.en.json | 4 ++++ src/components/DefaultPageLayout.tsx | 33 ++++++++++++++++++++++++++-- src/messages/ui.en.json | 6 +++++ src/store.ts | 9 ++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/lang/ui.en.json b/lang/ui.en.json index de79e84aa..5aff4c6ae 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -935,6 +935,10 @@ "defaultMessage": "Privacy policy", "description": "Link to view privacy policy" }, + "project-loaded": { + "defaultMessage": "Project loaded", + "description": "Toast when a new project is loaded" + }, "prototype.warning": { "defaultMessage": "This is a prototype version and is subject to change without notice", "description": "" diff --git a/src/components/DefaultPageLayout.tsx b/src/components/DefaultPageLayout.tsx index 5043f981a..6a55a2093 100644 --- a/src/components/DefaultPageLayout.tsx +++ b/src/components/DefaultPageLayout.tsx @@ -1,11 +1,18 @@ -import { Flex, Heading, HStack, IconButton, VStack } from "@chakra-ui/react"; +import { + Flex, + Heading, + HStack, + IconButton, + useToast, + VStack, +} from "@chakra-ui/react"; import { ReactNode, useCallback, useEffect } from "react"; import { RiHome2Line } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router"; import { TOOL_NAME } from "../constants"; import { flags } from "../flags"; -import { createHomePageUrl } from "../urls"; +import { createHomePageUrl, createSessionPageUrl } from "../urls"; import ActionBar from "./ActionBar"; import AppLogo from "./AppLogo"; import ConnectionDialogs from "./ConnectionFlowDialogs"; @@ -15,6 +22,7 @@ import SettingsMenu from "./SettingsMenu"; import TrainModelDialogs from "./TrainModelFlowDialogs"; import DownloadProjectDialogs from "./DownloadProjectDialogs"; import { useStore } from "../store"; +import { SessionPageId } from "../pages-config"; interface DefaultPageLayoutProps { titleId: string; @@ -39,6 +47,27 @@ const DefaultPageLayout = ({ document.title = intl.formatMessage({ id: titleId }); }, [intl, titleId]); + const toast = useToast(); + useEffect(() => { + return useStore.subscribe( + ( + { projectLoadTimestamp }, + { projectLoadTimestamp: prevProjectLoadTimestamp } + ) => { + if (projectLoadTimestamp > prevProjectLoadTimestamp) { + // Side effects of loading a project, which MakeCode notifies us of. + navigate(createSessionPageUrl(SessionPageId.DataSamples)); + toast({ + position: "top", + duration: 5_000, + title: intl.formatMessage({ id: "project-loaded" }), + status: "info", + }); + } + } + ); + }, [intl, navigate, toast]); + const handleHomeClick = useCallback(() => { navigate(createHomePageUrl()); }, [navigate]); diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index d6d3dfc5c..4a4455fba 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -1555,6 +1555,12 @@ "value": "Privacy policy" } ], + "project-loaded": [ + { + "type": 0, + "value": "Project loaded" + } + ], "prototype.warning": [ { "type": 0, diff --git a/src/store.ts b/src/store.ts index 9e38c4117..8a17f1d5f 100644 --- a/src/store.ts +++ b/src/store.ts @@ -64,6 +64,11 @@ export interface State { isRecording: boolean; project: Project; + /** + * We use this for the UI to tell when we've switched new project, + * e.g. to show a toast. + */ + projectLoadTimestamp: number; // false if we're sure the user hasn't changed the project, otherwise true projectEdited: boolean; changedHeaderExpected: boolean; @@ -147,6 +152,7 @@ export const useStore = create()( } as any, ...generateProject({ data: [] }, undefined), }, + projectLoadTimestamp: 0, downloadStage: { step: DownloadProjectStep.None, microbitToFlash: MicrobitToFlash.Default, @@ -428,6 +434,7 @@ export const useStore = create()( "resetProject" ); }, + editorChange(newProject: Project) { const actionName = "editorChange"; set( @@ -459,10 +466,12 @@ export const useStore = create()( return { project: newProject, + projectLoadTimestamp: Date.now(), // New project loaded externally so we can't know whether its edited. projectEdited: true, gestures: dataset.data, model: undefined, + isEditorOpen: false, }; } else if (isEditorOpen) { return { From 53ef191fc22af58a27f99dfb3b231c3083413abd Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:44:41 +0100 Subject: [PATCH 135/172] Download a hex before starting a new session (#317) ...rather than a dataset which doesn't have your code/project. --- lang/ui.en.json | 6 +++--- src/components/StartOverWarningDialog.tsx | 6 +++--- src/messages/ui.en.json | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lang/ui.en.json b/lang/ui.en.json index 5aff4c6ae..a6b22123b 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -544,15 +544,15 @@ "description": "" }, "content.index.dataWarning.subtitleOne": { - "defaultMessage": "You have existing data that will be lost when you start a new session.", + "defaultMessage": "You have existing data samples and code that will be lost when you start a new session.", "description": "" }, "content.index.dataWarning.subtitleTwo": { - "defaultMessage": "To save your data, download all data samples before continuing.", + "defaultMessage": "To save your work, download a hex file containing your data samples and code before continuing.", "description": "" }, "content.index.dataWarning.title": { - "defaultMessage": "Existing data samples will be lost", + "defaultMessage": "Existing data samples and code will be lost", "description": "" }, "content.index.title": { diff --git a/src/components/StartOverWarningDialog.tsx b/src/components/StartOverWarningDialog.tsx index ed3efac90..ad284e86b 100644 --- a/src/components/StartOverWarningDialog.tsx +++ b/src/components/StartOverWarningDialog.tsx @@ -13,7 +13,7 @@ import { } from "@chakra-ui/react"; import { ReactNode } from "react"; import { FormattedMessage } from "react-intl"; -import { useStore } from "../store"; +import { useProject } from "../hooks/project-hooks"; interface StartOverWardningDialogProps { isOpen: boolean; @@ -26,7 +26,7 @@ const StartOverWarningDialog = ({ onClose, onStart, }: StartOverWardningDialogProps) => { - const downloadDataset = useStore((s) => s.downloadDataset); + const { saveProjectHex } = useProject(); return ( ( diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 4a4455fba..7b3d2be7c 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -940,19 +940,19 @@ "content.index.dataWarning.subtitleOne": [ { "type": 0, - "value": "You have existing data that will be lost when you start a new session." + "value": "You have existing data samples and code that will be lost when you start a new session." } ], "content.index.dataWarning.subtitleTwo": [ { "type": 0, - "value": "To save your data, " + "value": "To save your work, " }, { "children": [ { "type": 0, - "value": "download all data samples" + "value": "download a hex file" } ], "type": 8, @@ -960,13 +960,13 @@ }, { "type": 0, - "value": " before continuing." + "value": " containing your data samples and code before continuing." } ], "content.index.dataWarning.title": [ { "type": 0, - "value": "Existing data samples will be lost" + "value": "Existing data samples and code will be lost" } ], "content.index.title": [ From 9c600b55cf9292cc09343a06a4d947f8892563de Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:17:47 +0100 Subject: [PATCH 136/172] Unify the two ways to save (#319) Now you get the same flow whether triggered from MakeCode or the app. Co-authored-by: Grace --- src/App.tsx | 24 ++--- src/components/ConnectionFlowDialogs.tsx | 4 +- src/components/DefaultPageLayout.tsx | 6 +- ...g.tsx => DownloadChooseMicrobitDialog.tsx} | 12 +-- ...ProjectDialogs.tsx => DownloadDialogs.tsx} | 36 +++---- ...tHelpDialog.tsx => DownloadHelpDialog.tsx} | 8 +- ...gDialog.tsx => DownloadProgressDialog.tsx} | 8 +- src/components/ProjectDropTarget.tsx | 6 +- src/components/SaveButton.tsx | 67 +++--------- src/components/SaveDialogs.tsx | 33 ++++++ src/components/StartOverWarningDialog.tsx | 9 +- src/components/UploadDataSamplesMenuItem.tsx | 6 +- src/hooks/download-hooks.tsx | 80 +++++++------- src/hooks/project-hooks.tsx | 100 ++++++++++++++---- src/model.ts | 23 +++- src/store.ts | 40 +++---- 16 files changed, 272 insertions(+), 190 deletions(-) rename src/components/{DownloadProjectChooseMicrobitDialog.tsx => DownloadChooseMicrobitDialog.tsx} (92%) rename src/components/{DownloadProjectDialogs.tsx => DownloadDialogs.tsx} (69%) rename src/components/{DownloadProjectHelpDialog.tsx => DownloadHelpDialog.tsx} (93%) rename src/components/{DownloadingDialog.tsx => DownloadProgressDialog.tsx} (90%) create mode 100644 src/components/SaveDialogs.tsx diff --git a/src/App.tsx b/src/App.tsx index f07e2fc7c..2cc80d3df 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -44,24 +44,24 @@ const Providers = ({ children }: ProviderLayoutProps) => { - - - - - - + + + + + + {children} - - - - - - + + + + + + diff --git a/src/components/ConnectionFlowDialogs.tsx b/src/components/ConnectionFlowDialogs.tsx index 2054182d6..cdf7f09cd 100644 --- a/src/components/ConnectionFlowDialogs.tsx +++ b/src/components/ConnectionFlowDialogs.tsx @@ -12,7 +12,7 @@ import ConnectCableDialog, { getConnectionCableDialogConfig, } from "./ConnectCableDialog"; import ConnectErrorDialog from "./ConnectErrorDialog"; -import DownloadingDialog, { getHeadingId } from "./DownloadingDialog"; +import DownloadProgressDialog, { getHeadingId } from "./DownloadProgressDialog"; import EnterBluetoothPatternDialog from "./EnterBluetoothPatternDialog"; import LoadingDialog from "./LoadingDialog"; import ManualFlashingDialog from "./ManualFlashingDialog"; @@ -177,7 +177,7 @@ const ConnectionDialogs = () => { } case ConnectionFlowStep.FlashingInProgress: { return ( - )} - + + { onSameMicrobitClick: () => void; onDifferentMicrobitClick: () => void; - stage: DownloadProjectStage; + stage: DownloadState; } type MicrobitOption = MicrobitToFlash.Same | MicrobitToFlash.Different; -const DownloadProjectChooseMicrobitDialog = ({ +const DownloadChooseMicrobitDialog = ({ onSameMicrobitClick, onDifferentMicrobitClick, stage, ...props -}: DownloadProjectChooseMicrobitDialogProps) => { +}: DownloadChooseMicrobitDialogProps) => { const defaultValue = stage.microbitToFlash === MicrobitToFlash.Default ? MicrobitToFlash.Same @@ -153,4 +153,4 @@ const RadioCard = ({ id, imgSrc, isSelected, ...props }: RadioCardProps) => { ); }; -export default DownloadProjectChooseMicrobitDialog; +export default DownloadChooseMicrobitDialog; diff --git a/src/components/DownloadProjectDialogs.tsx b/src/components/DownloadDialogs.tsx similarity index 69% rename from src/components/DownloadProjectDialogs.tsx rename to src/components/DownloadDialogs.tsx index 160952c30..fb677553d 100644 --- a/src/components/DownloadProjectDialogs.tsx +++ b/src/components/DownloadDialogs.tsx @@ -1,33 +1,33 @@ import { useCallback } from "react"; import ConnectCableDialog from "./ConnectCableDialog"; -import DownloadingDialog from "./DownloadingDialog"; -import DownloadProjectChooseMicrobitDialog from "./DownloadProjectChooseMicrobitDialog"; -import DownloadProjectHelpDialog from "./DownloadProjectHelpDialog"; +import DownloadProgressDialog from "./DownloadProgressDialog"; +import DownloadChooseMicrobitDialog from "./DownloadChooseMicrobitDialog"; +import DownloadHelpDialog from "./DownloadHelpDialog"; import ManualFlashingDialog from "./ManualFlashingDialog"; import SelectMicrobitUsbDialog from "./SelectMicrobitUsbDialog"; -import { DownloadProjectStep } from "../model"; +import { DownloadStep as DownloadStep } from "../model"; import { useDownloadActions } from "../hooks/download-hooks"; import { useStore } from "../store"; -const DownloadProjectDialogs = () => { +const DownloadDialogs = () => { const actions = useDownloadActions(); - const stage = useStore((s) => s.downloadStage); + const stage = useStore((s) => s.download); const handleDownloadProject = useCallback(async () => { await actions.connectAndFlashMicrobit(stage); }, [actions, stage]); switch (stage.step) { - case DownloadProjectStep.Help: + case DownloadStep.Help: return ( - ); - case DownloadProjectStep.ChooseSameOrAnotherMicrobit: + case DownloadStep.ChooseSameOrAnotherMicrobit: return ( - { stage={stage} /> ); - case DownloadProjectStep.ConnectCable: + case DownloadStep.ConnectCable: return ( { }} /> ); - case DownloadProjectStep.WebUsbFlashingTutorial: + case DownloadStep.WebUsbFlashingTutorial: return ( { onNextClick={handleDownloadProject} /> ); - case DownloadProjectStep.FlashingInProgress: + case DownloadStep.FlashingInProgress: return ( - ); - case DownloadProjectStep.ManualFlashingTutorial: - if (!stage.project) { + case DownloadStep.ManualFlashingTutorial: + if (!stage.hex) { throw new Error("Project expected"); } return ( @@ -82,4 +82,4 @@ const DownloadProjectDialogs = () => { return <>; }; -export default DownloadProjectDialogs; +export default DownloadDialogs; diff --git a/src/components/DownloadProjectHelpDialog.tsx b/src/components/DownloadHelpDialog.tsx similarity index 93% rename from src/components/DownloadProjectHelpDialog.tsx rename to src/components/DownloadHelpDialog.tsx index 8c9ae4142..6f8acf4e7 100644 --- a/src/components/DownloadProjectHelpDialog.tsx +++ b/src/components/DownloadHelpDialog.tsx @@ -17,16 +17,16 @@ import { ComponentProps, useCallback, useState } from "react"; import { FormattedMessage } from "react-intl"; import testModelImage from "../images/test_model_black.svg"; -export interface DownloadProjectIntroDialogProps +export interface DownloadHelpDialogProps extends Omit, "children"> { onNext: (isSkipNextTime: boolean) => void; } -const DownloadProjectHelpDialog = ({ +const DownloadHelpDialog = ({ onClose, onNext, ...rest -}: DownloadProjectIntroDialogProps) => { +}: DownloadHelpDialogProps) => { const [isSkipNextTime, setSkipNextTime] = useState(false); const handleOnNext = useCallback(() => { onNext(isSkipNextTime); @@ -80,4 +80,4 @@ const DownloadProjectHelpDialog = ({ ); }; -export default DownloadProjectHelpDialog; +export default DownloadHelpDialog; diff --git a/src/components/DownloadingDialog.tsx b/src/components/DownloadProgressDialog.tsx similarity index 90% rename from src/components/DownloadingDialog.tsx rename to src/components/DownloadProgressDialog.tsx index cb86a9764..9f7dc36ee 100644 --- a/src/components/DownloadingDialog.tsx +++ b/src/components/DownloadProgressDialog.tsx @@ -11,7 +11,7 @@ import { import { FormattedMessage } from "react-intl"; import { ConnectionFlowType } from "../connection-stage-hooks"; -export interface DownloadingDialogProps { +export interface DownloadProgressDialogProps { isOpen: boolean; headingId: string; progress: number; @@ -28,11 +28,11 @@ export const getHeadingId = (flowType: ConnectionFlowType) => { } }; -const DownloadingDialog = ({ +const DownloadProgressDialog = ({ isOpen, headingId, progress, -}: DownloadingDialogProps) => { +}: DownloadProgressDialogProps) => { return ( { - const { loadProject } = useProject(); + const { loadFile } = useProject(); const handleDrop = useCallback( (files: File[]) => { if (files.length === 1) { - loadProject(files[0]); + loadFile(files[0]); } }, - [loadProject] + [loadFile] ); return {children}; }; diff --git a/src/components/SaveButton.tsx b/src/components/SaveButton.tsx index 10dc280b5..76873e4d9 100644 --- a/src/components/SaveButton.tsx +++ b/src/components/SaveButton.tsx @@ -1,64 +1,31 @@ -import { Button, useDisclosure, useToast } from "@chakra-ui/react"; +import { Button } from "@chakra-ui/react"; import { useCallback } from "react"; import { RiDownload2Line } from "react-icons/ri"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import { useProject } from "../hooks/project-hooks"; -import { useSettings } from "../store"; -import SaveHelpDialog from "./SaveHelpDialog"; -import SaveProgressDialog from "./SaveProgressDialog"; +import { SaveStep } from "../model"; +import { useSettings, useStore } from "../store"; const SaveButton = () => { - const { saveProjectHex } = useProject(); + const setSave = useStore((s) => s.setSave); + const { saveHex } = useProject(); const [settings] = useSettings(); - const preSaveDialogDisclosure = useDisclosure(); - const saveProgressDisclosure = useDisclosure(); - const intl = useIntl(); - const toast = useToast(); - const handleSave = useCallback(async () => { - preSaveDialogDisclosure.onClose(); - saveProgressDisclosure.onOpen(); - await saveProjectHex(); - saveProgressDisclosure.onClose(); - toast({ - id: "save-complete", - position: "top", - duration: 5_000, - title: intl.formatMessage({ id: "saving-toast-title" }), - status: "info", - }); - }, [ - preSaveDialogDisclosure, - saveProgressDisclosure, - saveProjectHex, - toast, - intl, - ]); - - const handleSaveClick = useCallback(() => { + const handleSave = useCallback(() => { if (settings.showPreSaveHelp) { - preSaveDialogDisclosure.onOpen(); + setSave({ step: SaveStep.PreSaveHelp }); } else { - void handleSave(); + void saveHex(); } - }, [handleSave, preSaveDialogDisclosure, settings.showPreSaveHelp]); - + }, [saveHex, setSave, settings.showPreSaveHelp]); return ( - <> - - - - + ); }; diff --git a/src/components/SaveDialogs.tsx b/src/components/SaveDialogs.tsx new file mode 100644 index 000000000..4e4618cab --- /dev/null +++ b/src/components/SaveDialogs.tsx @@ -0,0 +1,33 @@ +import { useCallback } from "react"; +import { useProject } from "../hooks/project-hooks"; +import { useStore } from "../store"; +import SaveHelpDialog from "./SaveHelpDialog"; +import SaveProgressDialog from "./SaveProgressDialog"; +import { SaveStep } from "../model"; + +const SaveDialogs = () => { + const setSave = useStore((s) => s.setSave); + const { step, hex } = useStore((s) => s.save); + const { saveHex } = useProject(); + + const handleSave = useCallback(async () => { + await saveHex(hex); + }, [hex, saveHex]); + + const handleClose = useCallback(() => { + setSave({ step: SaveStep.None }); + }, [setSave]); + + switch (step) { + case SaveStep.PreSaveHelp: + return ( + + ); + case SaveStep.SaveProgress: + return ; + default: + return null; + } +}; + +export default SaveDialogs; diff --git a/src/components/StartOverWarningDialog.tsx b/src/components/StartOverWarningDialog.tsx index ad284e86b..b01fed7d4 100644 --- a/src/components/StartOverWarningDialog.tsx +++ b/src/components/StartOverWarningDialog.tsx @@ -11,7 +11,7 @@ import { Text, VStack, } from "@chakra-ui/react"; -import { ReactNode } from "react"; +import { ReactNode, useCallback } from "react"; import { FormattedMessage } from "react-intl"; import { useProject } from "../hooks/project-hooks"; @@ -26,7 +26,10 @@ const StartOverWarningDialog = ({ onClose, onStart, }: StartOverWardningDialogProps) => { - const { saveProjectHex } = useProject(); + const { saveHex } = useProject(); + const handleSaveHex = useCallback(async () => { + await saveHex(); + }, [saveHex]); return ( ( diff --git a/src/components/UploadDataSamplesMenuItem.tsx b/src/components/UploadDataSamplesMenuItem.tsx index 959ae89b3..1e80fab60 100644 --- a/src/components/UploadDataSamplesMenuItem.tsx +++ b/src/components/UploadDataSamplesMenuItem.tsx @@ -5,7 +5,7 @@ import { FormattedMessage } from "react-intl"; import { useProject } from "../hooks/project-hooks"; const UploadDataSamplesMenuItem = () => { - const { loadProject } = useProject(); + const { loadFile } = useProject(); const inputRef = useRef(null); const handleChooseFile = useCallback(() => { @@ -15,10 +15,10 @@ const UploadDataSamplesMenuItem = () => { const onOpen = useCallback( (files: File[]) => { if (files.length === 1) { - loadProject(files[0]); + loadFile(files[0]); } }, - [loadProject] + [loadFile] ); const handleOpenFile = useCallback( diff --git a/src/hooks/download-hooks.tsx b/src/hooks/download-hooks.tsx index 56a288356..ff9c45637 100644 --- a/src/hooks/download-hooks.tsx +++ b/src/hooks/download-hooks.tsx @@ -13,8 +13,8 @@ import { ConnectionStatus } from "../connect-status-hooks"; import { ConnectionStageActions } from "../connection-stage-actions"; import { useConnectionStage } from "../connection-stage-hooks"; import { - DownloadProjectStage, - DownloadProjectStep, + DownloadState, + DownloadStep, HexData, MicrobitToFlash, } from "../model"; @@ -24,8 +24,8 @@ import { downloadHex } from "../utils/fs-util"; export class DownloadProjectActions { constructor( - private stage: DownloadProjectStage, - private setStage: (stage: DownloadProjectStage) => void, + private stage: DownloadState, + private setStage: (stage: DownloadState) => void, private settings: Settings, private setSettings: (settings: Partial) => void, private connectActions: ConnectActions, @@ -42,7 +42,7 @@ export class DownloadProjectActions { if (this.stage.usbDevice.status === UsbConnectionStatus.CONNECTED) { const newStage = { ...this.stage, - step: DownloadProjectStep.FlashingInProgress, + step: DownloadStep.FlashingInProgress, project: download, }; this.setStage(newStage); @@ -51,17 +51,17 @@ export class DownloadProjectActions { }); } if (!this.settings.showPreDownloadHelp) { - this.updateStage({ project: download }); + this.updateStage({ hex: download }); return this.onHelpNext(true); } return this.updateStage({ - project: download, - step: DownloadProjectStep.ConnectCable, + hex: download, + step: DownloadStep.ConnectCable, }); } this.updateStage({ - step: DownloadProjectStep.Help, - project: download, + step: DownloadStep.Help, + hex: download, }); }; @@ -73,18 +73,18 @@ export class DownloadProjectActions { // This makes reconnect easier if the user has two micro:bits. if (this.connectionStatus !== ConnectionStatus.NotConnected) { return this.updateStage({ - step: DownloadProjectStep.ChooseSameOrAnotherMicrobit, + step: DownloadStep.ChooseSameOrAnotherMicrobit, }); } this.updateStage({ - step: DownloadProjectStep.ConnectCable, + step: DownloadStep.ConnectCable, }); }; onSkipIntro = (skipIntro: boolean) => this.setSettings({ showPreDownloadHelp: !skipIntro }); - onBackToIntro = () => this.setStep(DownloadProjectStep.Help); + onBackToIntro = () => this.setStep(DownloadStep.Help); onChosenSameMicrobit = async () => { if (this.connectActions.isUsbDeviceConnected()) { @@ -93,19 +93,19 @@ export class DownloadProjectActions { return this.connectAndFlashMicrobit(newStage); } this.updateStage({ - step: DownloadProjectStep.ConnectCable, + step: DownloadStep.ConnectCable, microbitToFlash: MicrobitToFlash.Same, }); }; onChosenDifferentMicrobit = () => { this.updateStage({ - step: DownloadProjectStep.ConnectCable, + step: DownloadStep.ConnectCable, microbitToFlash: MicrobitToFlash.Different, }); }; - connectAndFlashMicrobit = async (stage: DownloadProjectStage) => { + connectAndFlashMicrobit = async (stage: DownloadState) => { let connectionAndFlashOptions: ConnectionAndFlashOptions | undefined; if (stage.microbitToFlash === MicrobitToFlash.Same) { // Disconnect input micro:bit to not trigger connection lost warning. @@ -128,22 +128,22 @@ export class DownloadProjectActions { this.connectionStageActions.disconnectInputMicrobit, }; } - if (!stage.project) { + if (!stage.hex) { throw new Error("Project hex/name is not set!"); } - this.updateStage({ step: DownloadProjectStep.WebUsbChooseMicrobit }); + this.updateStage({ step: DownloadStep.WebUsbChooseMicrobit }); await this.flashMicrobit(stage, connectionAndFlashOptions); }; flashMicrobit = async ( - stage: DownloadProjectStage, + stage: DownloadState, connectionAndFlashOptions?: ConnectionAndFlashOptions ) => { - if (!stage.project) { + if (!stage.hex) { throw new Error("Project hex/name is not set!"); } const { result } = await this.connectActions.requestUSBConnectionAndFlash( - stage.project.hex, + stage.hex.hex, this.flashingProgressCallback, connectionAndFlashOptions ); @@ -153,25 +153,25 @@ export class DownloadProjectActions { this.connectActions.getUsbConnection(), step: result === ConnectAndFlashResult.Success - ? DownloadProjectStep.None - : DownloadProjectStep.ManualFlashingTutorial, + ? DownloadStep.None + : DownloadStep.ManualFlashingTutorial, flashProgress: 0, }; this.updateStage(newStage); - if (newStage.step === DownloadProjectStep.ManualFlashingTutorial) { - downloadHex(stage.project); + if (newStage.step === DownloadStep.ManualFlashingTutorial) { + downloadHex(stage.hex); } }; private flashingProgressCallback = (progress: number) => { this.setStage({ ...this.stage, - step: DownloadProjectStep.FlashingInProgress, + step: DownloadStep.FlashingInProgress, flashProgress: progress, }); }; - close = () => this.setStep(DownloadProjectStep.None); + close = () => this.setStep(DownloadStep.None); getOnNext = () => this.getOnNextIfPossible(1); getOnBack = () => this.getOnNextIfPossible(-1); @@ -180,7 +180,7 @@ export class DownloadProjectActions { ? () => this.setStep(this.getNextStep(inc)) : undefined; - private getNextStep = (inc: number): DownloadProjectStep => { + private getNextStep = (inc: number): DownloadStep => { const orderedSteps = this.downloadProjectStepOrder(); const currIdx = orderedSteps.indexOf(this.stage.step); const nextIdx = currIdx + inc; @@ -191,28 +191,28 @@ export class DownloadProjectActions { }; private downloadProjectStepOrder = () => [ - ...(this.settings.showPreDownloadHelp ? [DownloadProjectStep.Help] : []), - ...(this.stage.step === DownloadProjectStep.ChooseSameOrAnotherMicrobit || + ...(this.settings.showPreDownloadHelp ? [DownloadStep.Help] : []), + ...(this.stage.step === DownloadStep.ChooseSameOrAnotherMicrobit || this.stage.microbitToFlash !== MicrobitToFlash.Default - ? [DownloadProjectStep.ChooseSameOrAnotherMicrobit] + ? [DownloadStep.ChooseSameOrAnotherMicrobit] : []), - DownloadProjectStep.ConnectCable, - this.stage.step === DownloadProjectStep.ManualFlashingTutorial - ? DownloadProjectStep.ManualFlashingTutorial - : DownloadProjectStep.WebUsbFlashingTutorial, + DownloadStep.ConnectCable, + this.stage.step === DownloadStep.ManualFlashingTutorial + ? DownloadStep.ManualFlashingTutorial + : DownloadStep.WebUsbFlashingTutorial, ]; - private updateStage = (partialStage: Partial) => { - this.setStage({ ...this.stage, ...partialStage } as DownloadProjectStage); + private updateStage = (partialStage: Partial) => { + this.setStage({ ...this.stage, ...partialStage } as DownloadState); }; - private setStep = (step: DownloadProjectStep) => + private setStep = (step: DownloadStep) => this.setStage({ ...this.stage, step }); } export const useDownloadActions = (): DownloadProjectActions => { - const stage = useStore((s) => s.downloadStage); - const setStage = useStore((s) => s.setDownloadStage); + const stage = useStore((s) => s.download); + const setStage = useStore((s) => s.setDownload); const [settings, setSettings] = useSettings(); const connectActions = useConnectActions(); const { actions: connectionStageActions, status: connectionStatus } = diff --git a/src/hooks/project-hooks.tsx b/src/hooks/project-hooks.tsx index 79ec45ba7..c6a732ce1 100644 --- a/src/hooks/project-hooks.tsx +++ b/src/hooks/project-hooks.tsx @@ -1,3 +1,4 @@ +import { useToast } from "@chakra-ui/react"; import { EditorWorkspaceSaveRequest, MakeCodeFrameDriver, @@ -5,15 +6,16 @@ import { Project, } from "@microbit/makecode-embed/react"; import { + createContext, ReactNode, RefObject, - createContext, useCallback, useContext, useMemo, useRef, } from "react"; -import { HexData, isDatasetUserFileFormat } from "../model"; +import { useIntl } from "react-intl"; +import { HexData, isDatasetUserFileFormat, SaveStep } from "../model"; import { useStore } from "../store"; import { downloadHex, @@ -27,8 +29,19 @@ interface ProjectContext { project: Project; projectEdited: boolean; resetProject: () => void; - loadProject: (file: File) => void; - saveProjectHex: () => Promise; + loadFile: (file: File) => void; + /** + * Called to request a save. + * + * Pass a project if we already have the content to download. Otherwise it will + * be requested from the editor. + * + * The save is not necessarily complete when this returns as we may be waiting + * on MakeCode or a dialog flow. The progress will be reflected in the `save` + * state field. + */ + saveHex: (hex?: HexData) => Promise; + editorCallbacks: Pick< MakeCodeFrameProps, "onDownload" | "onWorkspaceSave" | "onSave" | "onBack" @@ -54,6 +67,8 @@ export const ProjectProvider = ({ driverRef, children, }: ProjectProviderProps) => { + const intl = useIntl(); + const toast = useToast(); const setEditorOpen = useStore((s) => s.setEditorOpen); const project = useStore((s) => s.project); const projectEdited = useStore((s) => s.projectEdited); @@ -62,7 +77,7 @@ export const ProjectProvider = ({ const appEditNeedsFlushToEditor = useStore( (s) => s.appEditNeedsFlushToEditor ); - const doAfterMakeCodeUpdate = useCallback( + const doAfterEditorUpdate = useCallback( async (action: () => Promise) => { if (appEditNeedsFlushToEditor) { expectChangedHeader(); @@ -80,16 +95,16 @@ export const ProjectProvider = ({ ] ); const openEditor = useCallback(async () => { - await doAfterMakeCodeUpdate(() => { + await doAfterEditorUpdate(() => { setEditorOpen(true); return Promise.resolve(); }); - }, [doAfterMakeCodeUpdate, setEditorOpen]); + }, [doAfterEditorUpdate, setEditorOpen]); const resetProject = useStore((s) => s.resetProject); const loadDataset = useStore((s) => s.loadDataset); - const loadProject = useCallback( + const loadFile = useCallback( async (file: File): Promise => { const fileExtension = getLowercaseFileExtension(file.name); if (fileExtension === "json") { @@ -110,13 +125,56 @@ export const ProjectProvider = ({ [driverRef, loadDataset] ); + const setSave = useStore((s) => s.setSave); + const save = useStore((s) => s.save); + const settings = useStore((s) => s.settings); const saveNextDownloadRef = useRef(false); - const saveProjectHex = useCallback(async (): Promise => { - await doAfterMakeCodeUpdate(async () => { - saveNextDownloadRef.current = true; - await driverRef.current!.compile(); - }); - }, [doAfterMakeCodeUpdate, driverRef]); + const saveHex = useCallback( + async (hex?: HexData): Promise => { + const { step } = save; + if (hex) { + if (settings.showPreSaveHelp && step === SaveStep.None) { + // All we do is trigger the help and remember the project. + setSave({ + step: SaveStep.PreSaveHelp, + hex: hex, + }); + } else { + // We can just go ahead and download. Either the project came from + // the editor or via the dialog flow. + downloadHex(hex); + setSave({ + step: SaveStep.None, + }); + toast({ + id: "save-complete", + position: "top", + duration: 5_000, + title: intl.formatMessage({ id: "saving-toast-title" }), + status: "info", + }); + } + } else { + // We need to request something to save. + setSave({ + step: SaveStep.SaveProgress, + }); + await doAfterEditorUpdate(async () => { + saveNextDownloadRef.current = true; + await driverRef.current!.compile(); + }); + } + }, + [ + doAfterEditorUpdate, + driverRef, + intl, + save, + setSave, + settings.showPreSaveHelp, + toast, + ] + ); // These are event handlers for MakeCode @@ -129,27 +187,27 @@ export const ProjectProvider = ({ ); const onBack = useCallback(() => setEditorOpen(false), [setEditorOpen]); - const onSave = useStore((s) => s.editorSave); + const onSave = saveHex; const downloadActions = useDownloadActions(); const onDownload = useCallback( (download: HexData) => { if (saveNextDownloadRef.current) { saveNextDownloadRef.current = false; - downloadHex(download); + void saveHex(download); } else { void downloadActions.start(download); } }, - [downloadActions] + [downloadActions, saveHex] ); const value = useMemo( () => ({ - loadProject, + loadFile, openEditor, project, projectEdited, resetProject, - saveProjectHex, + saveHex, editorCallbacks: { onSave, onWorkspaceSave, @@ -158,7 +216,7 @@ export const ProjectProvider = ({ }, }), [ - loadProject, + loadFile, onBack, onDownload, onSave, @@ -167,7 +225,7 @@ export const ProjectProvider = ({ project, projectEdited, resetProject, - saveProjectHex, + saveHex, ] ); diff --git a/src/model.ts b/src/model.ts index d4ff56112..a538b510a 100644 --- a/src/model.ts +++ b/src/model.ts @@ -101,7 +101,7 @@ export const enum TrainModelDialogStage { TrainingInProgress, } -export enum DownloadProjectStep { +export enum DownloadStep { None = "None", Help = "Introduction", ChooseSameOrAnotherMicrobit = "ChooseSameOrAnotherMicrobit", @@ -121,12 +121,27 @@ export enum MicrobitToFlash { Different = "different", } -export interface DownloadProjectStage { - step: DownloadProjectStep; +export interface DownloadState { + step: DownloadStep; microbitToFlash: MicrobitToFlash; flashProgress: number; - project?: HexData; + hex?: HexData; // The micro:bit used to flash the hex. We remember your choice for easy code // iteration for as long as the editor is open. usbDevice?: MicrobitWebUSBConnection; } + +export interface SaveState { + step: SaveStep; + hex?: HexData; +} + +export enum SaveStep { + None = "none", + PreSaveHelp = "help", + /** + * We only show this state if we initiated the save and need to wait for the editor. + * Otherwise we already have the project data in the state and save it directly. + */ + SaveProgress = "progress", +} diff --git a/src/store.ts b/src/store.ts index 8a17f1d5f..1e5001703 100644 --- a/src/store.ts +++ b/src/store.ts @@ -12,17 +12,17 @@ import { import { trainModel } from "./ml"; import { DatasetEditorJsonFormat, - DownloadProjectStage, - DownloadProjectStep, + DownloadState, + DownloadStep, Gesture, GestureData, - HexData, MicrobitToFlash, RecordingData, + SaveState, + SaveStep, TrainModelDialogStage, } from "./model"; import { defaultSettings, Settings } from "./settings"; -import { downloadHex } from "./utils/fs-util"; import { defaultIcons, MakeCodeIcon } from "./utils/icons"; export const modelUrl = "indexeddb://micro:bit-ml-tool-model"; @@ -74,7 +74,9 @@ export interface State { changedHeaderExpected: boolean; appEditNeedsFlushToEditor: boolean; isEditorOpen: boolean; - downloadStage: DownloadProjectStage; + + download: DownloadState; + save: SaveState; settings: Settings; @@ -113,10 +115,11 @@ export interface Actions { * Remainer are used by project hooks for MakeCode integration. */ editorChange(project: Project): void; - editorSave(project: HexData): void; setChangedHeaderExpected(): void; projectFlushedToEditor(): void; - setDownloadStage(stage: DownloadProjectStage): void; + + setDownload(state: DownloadState): void; + setSave(state: SaveState): void; } type Store = State & Actions; @@ -153,11 +156,14 @@ export const useStore = create()( ...generateProject({ data: [] }, undefined), }, projectLoadTimestamp: 0, - downloadStage: { - step: DownloadProjectStep.None, + download: { + step: DownloadStep.None, microbitToFlash: MicrobitToFlash.Default, flashProgress: 0, }, + save: { + step: SaveStep.None, + }, projectEdited: false, settings: defaultSettings, model: undefined, @@ -188,12 +194,12 @@ export const useStore = create()( setEditorOpen(open: boolean) { set( - ({ downloadStage }) => ({ + ({ download }) => ({ isEditorOpen: open, // We just assume its been edited as spurious changes from MakeCode happen that we can't identify projectEdited: true, - downloadStage: { - ...downloadStage, + download: { + ...download, usbDevice: undefined, }, }), @@ -484,13 +490,11 @@ export const useStore = create()( actionName ); }, - setDownloadStage(downloadStage: DownloadProjectStage) { - set({ downloadStage }); + setDownload(download: DownloadState) { + set({ download }); }, - editorSave(project: HexData) { - // TODO: We'd like to trigger the same UI as we do for the "Save" - // buttob but with this project rather than poking MakeCode. - downloadHex(project); + setSave(save: SaveState) { + set({ save }); }, setChangedHeaderExpected() { set({ changedHeaderExpected: true }); From 444d4fc2cb1f48a43fbb44fd489a1ff2809bbb83 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:30:40 +0100 Subject: [PATCH 137/172] Add open project button on Homepage toolbar and hamburger behaviour (#318) We're not sure this is the final location. We might instead redesign the home page to make it clear in the body that this is one of your choices. --- lang/ui.en.json | 8 ++ src/components/DefaultPageLayout.tsx | 110 ++++++++++++++---- src/components/HelpMenu.tsx | 15 +-- src/components/LanguageDialog.tsx | 2 +- src/components/LanguageMenuItem.tsx | 2 +- src/components/LoadProjectMenuItem.tsx | 64 ++++++++++ ...DataSamplesMenuItem.tsx => OpenButton.tsx} | 21 ++-- src/components/SaveButton.tsx | 32 ----- src/components/SaveHelpDialog.tsx | 2 + src/components/ToolbarMenu.tsx | 49 ++++++++ src/messages/ui.en.json | 12 ++ src/pages/DataSamplesPage.tsx | 16 +-- src/pages/HomePage.tsx | 2 +- src/pages/TestingModelPage.tsx | 6 +- 14 files changed, 253 insertions(+), 88 deletions(-) create mode 100644 src/components/LoadProjectMenuItem.tsx rename src/components/{UploadDataSamplesMenuItem.tsx => OpenButton.tsx} (74%) delete mode 100644 src/components/SaveButton.tsx create mode 100644 src/components/ToolbarMenu.tsx diff --git a/lang/ui.en.json b/lang/ui.en.json index a6b22123b..d8dcc1cd4 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -767,6 +767,10 @@ "defaultMessage": "Help & support", "description": "Link or menu item link to support site" }, + "home-action": { + "defaultMessage": "Home", + "description": "Home button text" + }, "homepage-subtitle": { "defaultMessage": "Introduce students to machine learning concepts through physical movement and data", "description": "Subtitle of machine learning tool home page" @@ -851,6 +855,10 @@ "defaultMessage": "Page not found", "description": "Page not found page title" }, + "open-file-action": { + "defaultMessage": "Open…", + "description": "Open file button text" + }, "open-file-dropped": { "defaultMessage": "Open file when dropped", "description": "Aria label for file drop target" diff --git a/src/components/DefaultPageLayout.tsx b/src/components/DefaultPageLayout.tsx index 19a3439a8..cd0e0e072 100644 --- a/src/components/DefaultPageLayout.tsx +++ b/src/components/DefaultPageLayout.tsx @@ -1,54 +1,70 @@ import { + Button, Flex, Heading, HStack, + Icon, IconButton, + MenuDivider, + MenuItem, useToast, VStack, } from "@chakra-ui/react"; import { ReactNode, useCallback, useEffect } from "react"; -import { RiHome2Line } from "react-icons/ri"; +import { RiDownload2Line, RiFolderOpenLine, RiHome2Line } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router"; import { TOOL_NAME } from "../constants"; import { flags } from "../flags"; +import { useProject } from "../hooks/project-hooks"; +import { SaveStep } from "../model"; +import { SessionPageId } from "../pages-config"; +import { useSettings, useStore } from "../store"; import { createHomePageUrl, createSessionPageUrl } from "../urls"; import ActionBar from "./ActionBar"; import AppLogo from "./AppLogo"; import ConnectionDialogs from "./ConnectionFlowDialogs"; +import DownloadDialogs from "./DownloadDialogs"; import HelpMenu from "./HelpMenu"; +import LanguageMenuItem from "./LanguageMenuItem"; +import LoadProjectMenuItem from "./LoadProjectMenuItem"; +import OpenButton from "./OpenButton"; import PrototypeVersionWarning from "./PrototypeVersionWarning"; +import SaveDialogs from "./SaveDialogs"; import SettingsMenu from "./SettingsMenu"; +import ToolbarMenu from "./ToolbarMenu"; import TrainModelDialogs from "./TrainModelFlowDialogs"; -import DownloadDialogs from "./DownloadDialogs"; -import { useStore } from "../store"; -import { SessionPageId } from "../pages-config"; -import SaveDialogs from "./SaveDialogs"; interface DefaultPageLayoutProps { titleId: string; children: ReactNode; - toolbarItemsRight?: ReactNode; toolbarItemsLeft?: ReactNode; showPageTitle?: boolean; + showHomeButton?: boolean; + showSaveButton?: boolean; + showOpenButton?: boolean; } const DefaultPageLayout = ({ titleId, children, - toolbarItemsRight, toolbarItemsLeft, showPageTitle = false, + showHomeButton = false, + showSaveButton = false, + showOpenButton = false, }: DefaultPageLayoutProps) => { const intl = useIntl(); const navigate = useNavigate(); const isEditorOpen = useStore((s) => s.isEditorOpen); + const { saveHex } = useProject(); + const [settings] = useSettings(); + const toast = useToast(); useEffect(() => { document.title = intl.formatMessage({ id: titleId }); }, [intl, titleId]); - const toast = useToast(); useEffect(() => { return useStore.subscribe( ( @@ -73,6 +89,15 @@ const DefaultPageLayout = ({ navigate(createHomePageUrl()); }, [navigate]); + const setSave = useStore((s) => s.setSave); + const handleSave = useCallback(() => { + if (settings.showPreSaveHelp) { + setSave({ step: SaveStep.PreSaveHelp }); + } else { + void saveHex(); + } + }, [saveHex, setSave, settings.showPreSaveHelp]); + return ( <> {/* Suppress connection and train dialogs when MakeCode editor is open */} @@ -106,19 +131,64 @@ const DefaultPageLayout = ({ } itemsLeft={toolbarItemsLeft || } itemsRight={ - - {toolbarItemsRight} - } - aria-label={intl.formatMessage({ id: "homepage.Link" })} + <> + + {showOpenButton && } + {showSaveButton && ( + + )} + {showHomeButton && ( + } + aria-label={intl.formatMessage({ id: "homepage.Link" })} + variant="plain" + size="lg" + fontSize="xl" + /> + )} + + + + - - - + label={intl.formatMessage({ id: "main-menu" })} + > + {showOpenButton && ( + } + accept=".hex" + > + + + )} + {showSaveButton && ( + } + > + + + )} + + {showHomeButton && ( + } + > + + + )} + + + } /> {flags.prototypeWarning && } diff --git a/src/components/HelpMenu.tsx b/src/components/HelpMenu.tsx index 8507551f5..ec185d6e7 100644 --- a/src/components/HelpMenu.tsx +++ b/src/components/HelpMenu.tsx @@ -23,20 +23,13 @@ import { manageCookies } from "../compliance"; interface HelpMenuProps { isMobile?: boolean; appName: string; - mode: "default" | "nextgen"; cookies?: boolean; } /** * A help button that triggers a drop-down menu with actions. */ -const HelpMenu = ({ - isMobile, - appName, - mode, - cookies, - ...rest -}: HelpMenuProps) => { +const HelpMenu = ({ isMobile, appName, cookies, ...rest }: HelpMenuProps) => { const aboutDialogDisclosure = useDisclosure(); const intl = useIntl(); const MenuButtonRef = useRef(null); @@ -88,11 +81,7 @@ const HelpMenu = ({ } diff --git a/src/components/LanguageDialog.tsx b/src/components/LanguageDialog.tsx index fbcfb1e38..c4c870f1e 100644 --- a/src/components/LanguageDialog.tsx +++ b/src/components/LanguageDialog.tsx @@ -21,7 +21,7 @@ import { useSettings } from "../store"; interface LanguageDialogProps { isOpen: boolean; onClose: () => void; - finalFocusRef: React.RefObject; + finalFocusRef?: React.RefObject; } /** diff --git a/src/components/LanguageMenuItem.tsx b/src/components/LanguageMenuItem.tsx index a2c55072e..e56c3f58e 100644 --- a/src/components/LanguageMenuItem.tsx +++ b/src/components/LanguageMenuItem.tsx @@ -4,7 +4,7 @@ import { IoMdGlobe } from "react-icons/io"; import { LanguageDialog } from "./LanguageDialog"; interface LanguageMenuItemProps { - finalFocusRef: React.RefObject; + finalFocusRef?: React.RefObject; } const LanguageMenuItem = ({ finalFocusRef }: LanguageMenuItemProps) => { diff --git a/src/components/LoadProjectMenuItem.tsx b/src/components/LoadProjectMenuItem.tsx new file mode 100644 index 000000000..42b359d6f --- /dev/null +++ b/src/components/LoadProjectMenuItem.tsx @@ -0,0 +1,64 @@ +import { Input, MenuItem, MenuItemProps } from "@chakra-ui/react"; +import { useCallback, useRef } from "react"; +import { useProject } from "../hooks/project-hooks"; + +interface LoadProjectMenuItemProps extends MenuItemProps { + /** + * + * File input tag accept attribute. + * A project can be opened from .json or .hex file. + */ + accept?: ".json" | ".hex"; +} + +const LoadProjectMenuItem = ({ + accept, + ...props +}: LoadProjectMenuItemProps) => { + const { loadFile } = useProject(); + const inputRef = useRef(null); + + const handleChooseFile = useCallback(() => { + inputRef.current && inputRef.current.click(); + }, []); + + const onOpen = useCallback( + (files: File[]) => { + if (files.length === 1) { + loadFile(files[0]); + } + }, + [loadFile] + ); + + const handleOpenFile = useCallback( + (e: React.ChangeEvent) => { + const files = e.target.files; + if (files) { + const filesArray = Array.from(files); + // Clear the input so we're triggered if the user opens the same file again. + inputRef.current!.value = ""; + if (filesArray.length > 0) { + onOpen(filesArray); + } + } + }, + [onOpen] + ); + + return ( + <> + + + + ); +}; + +export default LoadProjectMenuItem; diff --git a/src/components/UploadDataSamplesMenuItem.tsx b/src/components/OpenButton.tsx similarity index 74% rename from src/components/UploadDataSamplesMenuItem.tsx rename to src/components/OpenButton.tsx index 1e80fab60..d52de7c04 100644 --- a/src/components/UploadDataSamplesMenuItem.tsx +++ b/src/components/OpenButton.tsx @@ -1,10 +1,10 @@ -import { Input, MenuItem } from "@chakra-ui/react"; +import { Button, Input } from "@chakra-ui/react"; import { useCallback, useRef } from "react"; -import { RiUpload2Line } from "react-icons/ri"; +import { RiFolderOpenLine } from "react-icons/ri"; import { FormattedMessage } from "react-intl"; import { useProject } from "../hooks/project-hooks"; -const UploadDataSamplesMenuItem = () => { +const OpenButton = () => { const { loadFile } = useProject(); const inputRef = useRef(null); @@ -35,17 +35,20 @@ const UploadDataSamplesMenuItem = () => { }, [onOpen] ); - return ( <> - } onClick={handleChooseFile}> - - + @@ -53,4 +56,4 @@ const UploadDataSamplesMenuItem = () => { ); }; -export default UploadDataSamplesMenuItem; +export default OpenButton; diff --git a/src/components/SaveButton.tsx b/src/components/SaveButton.tsx deleted file mode 100644 index 76873e4d9..000000000 --- a/src/components/SaveButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Button } from "@chakra-ui/react"; -import { useCallback } from "react"; -import { RiDownload2Line } from "react-icons/ri"; -import { FormattedMessage } from "react-intl"; -import { useProject } from "../hooks/project-hooks"; -import { SaveStep } from "../model"; -import { useSettings, useStore } from "../store"; - -const SaveButton = () => { - const setSave = useStore((s) => s.setSave); - const { saveHex } = useProject(); - const [settings] = useSettings(); - - const handleSave = useCallback(() => { - if (settings.showPreSaveHelp) { - setSave({ step: SaveStep.PreSaveHelp }); - } else { - void saveHex(); - } - }, [saveHex, setSave, settings.showPreSaveHelp]); - return ( - - ); -}; - -export default SaveButton; diff --git a/src/components/SaveHelpDialog.tsx b/src/components/SaveHelpDialog.tsx index 1386efa19..4773ce580 100644 --- a/src/components/SaveHelpDialog.tsx +++ b/src/components/SaveHelpDialog.tsx @@ -4,6 +4,7 @@ import { Heading, Modal, ModalBody, + ModalCloseButton, ModalContent, ModalFooter, ModalHeader, @@ -48,6 +49,7 @@ const SaveHelpDialog = ({ isOpen, onClose, onSave }: SaveHelpDialogProps) => { + diff --git a/src/components/ToolbarMenu.tsx b/src/components/ToolbarMenu.tsx new file mode 100644 index 000000000..9a4b0a797 --- /dev/null +++ b/src/components/ToolbarMenu.tsx @@ -0,0 +1,49 @@ +import { + Box, + IconButton, + Menu, + MenuButton, + MenuList, + ThemeTypings, +} from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { RiMenuLine } from "react-icons/ri"; + +interface ToolbarMenuProps { + label: string; + isMobile?: boolean; + children: ReactNode; + icon?: JSX.Element; + variant?: ThemeTypings["components"]["Menu"]["variants"]; + onDarkBackground?: boolean; +} + +const ToolbarMenu = ({ + label, + icon, + children, + isMobile, + variant, + onDarkBackground = true, +}: ToolbarMenuProps) => { + return ( + + + } + variant={variant} + size="lg" + fontSize="xl" + _focusVisible={{ + boxShadow: onDarkBackground ? "outlineDark" : "outline", + }} + /> + {children} + + + ); +}; + +export default ToolbarMenu; diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index 7b3d2be7c..fcd8f6bf6 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -1295,6 +1295,12 @@ "value": "Help & support" } ], + "home-action": [ + { + "type": 0, + "value": "Home" + } + ], "homepage-subtitle": [ { "type": 0, @@ -1429,6 +1435,12 @@ "value": "Page not found" } ], + "open-file-action": [ + { + "type": 0, + "value": "Open…" + } + ], "open-file-dropped": [ { "type": 0, diff --git a/src/pages/DataSamplesPage.tsx b/src/pages/DataSamplesPage.tsx index bdcf98a8e..dad3cece7 100644 --- a/src/pages/DataSamplesPage.tsx +++ b/src/pages/DataSamplesPage.tsx @@ -16,6 +16,7 @@ import { RiArrowRightLine, RiDeleteBin2Line, RiDownload2Line, + RiUpload2Line, } from "react-icons/ri"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router"; @@ -23,16 +24,12 @@ import ConnectFirstView from "../components/ConnectFirstView"; import DataSampleGridView from "../components/DataSampleGridView"; import DefaultPageLayout from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; -import UploadDataSamplesMenuItem from "../components/UploadDataSamplesMenuItem"; +import LoadProjectMenuItem from "../components/LoadProjectMenuItem"; import { ConnectionStatus } from "../connect-status-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; import { SessionPageId } from "../pages-config"; +import { useHasSufficientDataForTraining, useStore } from "../store"; import { createSessionPageUrl } from "../urls"; -import SaveButton from "../components/SaveButton"; -import { - useStore, - useHasSufficientDataForTraining as useHasSufficientDataForTraining, -} from "../store"; const DataSamplesPage = () => { const intl = useIntl(); @@ -62,8 +59,9 @@ const DataSamplesPage = () => { return ( } showPageTitle + showHomeButton + showSaveButton > {showConnectFirstView ? : } @@ -97,7 +95,9 @@ const DataSamplesPage = () => { isRound /> - + } accept=".json"> + + } onClick={downloadDataSet}> diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 33b024dfe..867744561 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -42,7 +42,7 @@ const HomePage = () => { const intl = useIntl(); return ( - + { const navigate = useNavigate(); @@ -28,6 +27,8 @@ const TestingModelPage = () => { { } - toolbarItemsRight={} > From ed37bda1b618b5c6f7adc69771a1ee9e102b7df6 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:07:41 +0100 Subject: [PATCH 138/172] Only showConnectFirstView when not connected (#322) --- src/pages/DataSamplesPage.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/DataSamplesPage.tsx b/src/pages/DataSamplesPage.tsx index dad3cece7..a43fee9cd 100644 --- a/src/pages/DataSamplesPage.tsx +++ b/src/pages/DataSamplesPage.tsx @@ -44,13 +44,10 @@ const DataSamplesPage = () => { const { isConnected, status } = useConnectionStage(); const hasSufficientData = useHasSufficientDataForTraining(); - const hasAnyRecordings = gestures.some((g) => g.recordings.length > 0); const isAddNewGestureDisabled = !isConnected || gestures.some((g) => g.name.length === 0); const showConnectFirstView = - !hasAnyRecordings && - !isConnected && - status !== ConnectionStatus.ReconnectingAutomatically; + !isConnected && status !== ConnectionStatus.ReconnectingAutomatically; const handleNavigateToModel = useCallback(() => { navigate(createSessionPageUrl(SessionPageId.TestingModel)); From 79f6c3d036ddc70369857b86d43af8fe0c0c592e Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:17:53 +0100 Subject: [PATCH 139/172] Show data even if micro:bit not connected (#323) --- src/pages/DataSamplesPage.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/DataSamplesPage.tsx b/src/pages/DataSamplesPage.tsx index a43fee9cd..bfbcba0a4 100644 --- a/src/pages/DataSamplesPage.tsx +++ b/src/pages/DataSamplesPage.tsx @@ -46,8 +46,16 @@ const DataSamplesPage = () => { const hasSufficientData = useHasSufficientDataForTraining(); const isAddNewGestureDisabled = !isConnected || gestures.some((g) => g.name.length === 0); + + const hasNoData = + gestures.length === 1 && + gestures[0].name.length === 0 && + gestures[0].recordings.length === 0; + const showConnectFirstView = - !isConnected && status !== ConnectionStatus.ReconnectingAutomatically; + hasNoData && + !isConnected && + status !== ConnectionStatus.ReconnectingAutomatically; const handleNavigateToModel = useCallback(() => { navigate(createSessionPageUrl(SessionPageId.TestingModel)); From 6ff5e3d7f827bf7764e5aa174dfcb495dfdbd8e5 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:46:42 +0100 Subject: [PATCH 140/172] V2 only note for radio bridge (#324) https://microbit-global.monday.com/boards/1550536443/views/16565217/pulses/1588668224?filter=XQAAAAK-AAAAAAAAAABBqQqHk62Wwa8yt9cbbXI48HpL2ei1XixEiNVOhiWmf4IrTn4lrcVxz0d8EzlqEcVGoDD7teW4AAMtefSWCWLEL8wuAeXWGkU-75_ePN9eFS9zFnxu93i1GnOynn9FlpnZULWUW9ogjZBIgQnrub1HpbPS2TEsLD8KkKaTaQ7eU0reScc3--pytyZI2Lmhtdp4yl8_z5HCiMbJb2ac97Wz___dCsAA --- lang/ui.en.json | 4 ++++ src/components/WhatYouWillNeedDialog.tsx | 1 + src/messages/ui.en.json | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/lang/ui.en.json b/lang/ui.en.json index d8dcc1cd4..1326ce6c6 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -279,6 +279,10 @@ "defaultMessage": "2 micro:bits", "description": "" }, + "connectMB.radioStart.requirements1.subtitle": { + "defaultMessage": "V2 only", + "description": "" + }, "connectMB.radioStart.requirements2": { "defaultMessage": "Computer", "description": "" diff --git a/src/components/WhatYouWillNeedDialog.tsx b/src/components/WhatYouWillNeedDialog.tsx index efc6455fe..0a3f3b9c4 100644 --- a/src/components/WhatYouWillNeedDialog.tsx +++ b/src/components/WhatYouWillNeedDialog.tsx @@ -16,6 +16,7 @@ const itemsConfig = { { imgSrc: twoMicrobitsImage, titleId: "connectMB.radioStart.requirements1", + subtitleId: "connectMB.radioStart.requirements1.subtitle", }, { imgSrc: computerImage, diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index fcd8f6bf6..ccd27aa3c 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -453,6 +453,12 @@ "value": "2 micro:bits" } ], + "connectMB.radioStart.requirements1.subtitle": [ + { + "type": 0, + "value": "V2 only" + } + ], "connectMB.radioStart.requirements2": [ { "type": 0, From 60ef67849e7cb1bccdcf5c8421e53e588639e883 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:52:30 +0100 Subject: [PATCH 141/172] stub e2e tests (#325) Sets up e2e with some stub fixtures. Significant work will need some code changes to allow us to fake a device but there's also quite a lot you can access by loading a hex or dataset JSON file. --------- Co-authored-by: Grace <145345672+microbit-grace@users.noreply.github.com> --- .github/workflows/build.yml | 18 +++++++++++++++--- .gitignore | 2 ++ package-lock.json | 26 +++++++------------------ package.json | 2 +- playwright.config.ts | 38 +++++++++++++++++++++++++++++++++++++ src/e2e/app/data-samples.ts | 10 ++++++++++ src/e2e/app/home-page.ts | 27 ++++++++++++++++++++++++++ src/e2e/app/shared.ts | 13 +++++++++++++ src/e2e/app/test-model.ts | 10 ++++++++++ src/e2e/fixtures.ts | 22 +++++++++++++++++++++ src/e2e/home-page.spec.ts | 11 +++++++++++ vite.config.ts | 2 ++ 12 files changed, 158 insertions(+), 23 deletions(-) create mode 100644 playwright.config.ts create mode 100644 src/e2e/app/data-samples.ts create mode 100644 src/e2e/app/home-page.ts create mode 100644 src/e2e/app/shared.ts create mode 100644 src/e2e/app/test-model.ts create mode 100644 src/e2e/fixtures.ts create mode 100644 src/e2e/home-page.spec.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80f3b786e..6a6c1c3b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: types: [created] push: branches: - - '**' + - "**" concurrency: group: ${{ github.workflow }}-${{ startsWith(github.ref, 'refs/tags/v') && 'release' || github.ref }} @@ -30,8 +30,8 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 20 - cache: 'npm' - registry-url: 'https://npm.pkg.github.com' + cache: "npm" + registry-url: "https://npm.pkg.github.com" - uses: microbit-foundation/npm-package-versioner-action@v1 - run: npm ci env: @@ -42,6 +42,18 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: node ./bin/print-ci-env.cjs >> $GITHUB_ENV - run: npm run ci + - name: Run Playwright tests + if: env.STAGE == 'REVIEW' || env.STAGE == 'STAGING' + uses: docker://mcr.microsoft.com/playwright:v1.45.0-jammy + with: + args: npx playwright test + - name: Store reports + if: (env.STAGE == 'REVIEW' || env.STAGE == 'STAGING') && failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: playwright-report/ + retention-days: 3 - run: npx website-deploy-aws if: github.repository_owner == 'microbit-foundation' && (env.STAGE == 'REVIEW' || success()) env: diff --git a/.gitignore b/.gitignore index d26ae032d..c78e4dcff 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ .env.* /src/.DS_Store +/playwright-report/ +/test-results/ diff --git a/package-lock.json b/package-lock.json index 4cb624493..833b36f49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "eslint-plugin-react-refresh": "^0.4.6", "jsdom": "^24.0.0", "playwright": "^1.42.1", - "prettier": "2.3.2", + "prettier": "2.8.8", "typescript": "^5.4.2", "vite": "^5.1.5", "vite-plugin-pwa": "^0.19.8", @@ -2310,21 +2310,6 @@ "@esbuild/win32-x64": "0.17.19" } }, - "node_modules/@chakra-ui/cli/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/@chakra-ui/clickable": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@chakra-ui/clickable/-/clickable-2.1.0.tgz", @@ -11642,15 +11627,18 @@ } }, "node_modules/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/pretty-bytes": { diff --git a/package.json b/package.json index 082898b48..0d5e1dac5 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "eslint-plugin-react-refresh": "^0.4.6", "jsdom": "^24.0.0", "playwright": "^1.42.1", - "prettier": "2.3.2", + "prettier": "2.8.8", "typescript": "^5.4.2", "vite": "^5.1.5", "vite-plugin-pwa": "^0.19.8", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..098a75182 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,38 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./src/e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + use: { + trace: "on-first-retry", + ignoreHTTPSErrors: true, + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + /* Run local dev server before starting the tests */ + webServer: { + ...(process.env.CI + ? { + command: `npx vite preview --port 5173 --base ${process.env.BASE_URL}`, + url: `http://localhost:5173${process.env.BASE_URL}`, + } + : { + command: "npx vite dev", + url: "http://localhost:5173/", + }), + reuseExistingServer: !process.env.CI, + stdout: "pipe", + ignoreHTTPSErrors: true, + }, +}); diff --git a/src/e2e/app/data-samples.ts b/src/e2e/app/data-samples.ts new file mode 100644 index 000000000..c2c6aa0e1 --- /dev/null +++ b/src/e2e/app/data-samples.ts @@ -0,0 +1,10 @@ +import { type Page } from "@playwright/test"; +import { Navbar } from "./shared"; + +export class DataSamplesPage { + public readonly navbar: Navbar; + + constructor(public readonly page: Page) { + this.navbar = new Navbar(page); + } +} diff --git a/src/e2e/app/home-page.ts b/src/e2e/app/home-page.ts new file mode 100644 index 000000000..2e6c013b5 --- /dev/null +++ b/src/e2e/app/home-page.ts @@ -0,0 +1,27 @@ +import { expect, type Page } from "@playwright/test"; +import { Navbar } from "./shared"; + +export class HomePage { + public navbar: Navbar; + private url: string; + + constructor(public readonly page: Page) { + this.url = `http://localhost:5173${ + process.env.CI ? process.env.BASE_URL : "/" + }`; + this.navbar = new Navbar(page); + } + + async goto(flags: string[] = ["open"]) { + const response = await this.page.goto(this.url); + await this.page.evaluate( + (flags) => localStorage.setItem("flags", flags.join(",")), + flags + ); + return response; + } + + expectOnHomePage() { + expect(this.page.url()).toEqual(this.url); + } +} diff --git a/src/e2e/app/shared.ts b/src/e2e/app/shared.ts new file mode 100644 index 000000000..a91fc6e2a --- /dev/null +++ b/src/e2e/app/shared.ts @@ -0,0 +1,13 @@ +import type { Locator, Page } from "@playwright/test"; + +export class Navbar { + private saveButton: Locator; + + constructor(public readonly page: Page) { + this.saveButton = page.getByRole("button", { name: "Save" }).first(); + } + + async save() { + await this.saveButton.click(); + } +} diff --git a/src/e2e/app/test-model.ts b/src/e2e/app/test-model.ts new file mode 100644 index 000000000..f2472613c --- /dev/null +++ b/src/e2e/app/test-model.ts @@ -0,0 +1,10 @@ +import { type Page } from "@playwright/test"; +import { Navbar } from "./shared"; + +export class TestModelPage { + public readonly navbar: Navbar; + + constructor(public readonly page: Page) { + this.navbar = new Navbar(page); + } +} diff --git a/src/e2e/fixtures.ts b/src/e2e/fixtures.ts new file mode 100644 index 000000000..c06240e17 --- /dev/null +++ b/src/e2e/fixtures.ts @@ -0,0 +1,22 @@ +import { test as base } from "@playwright/test"; +import { HomePage } from "./app/home-page"; +import { DataSamplesPage } from "./app/data-samples"; +import { TestModelPage } from "./app/test-model"; + +type MyFixtures = { + homePage: HomePage; + dataSamplesPage: DataSamplesPage; + testModelPage: TestModelPage; +}; + +export const test = base.extend({ + homePage: async ({ page }, use) => { + await use(new HomePage(page)); + }, + dataSamplesPage: async ({ page }, use) => { + await use(new DataSamplesPage(page)); + }, + testModelPage: async ({ page }, use) => { + await use(new TestModelPage(page)); + }, +}); diff --git a/src/e2e/home-page.spec.ts b/src/e2e/home-page.spec.ts new file mode 100644 index 000000000..7e22e67ba --- /dev/null +++ b/src/e2e/home-page.spec.ts @@ -0,0 +1,11 @@ +import { test } from "./fixtures"; + +test.describe("home page", () => { + test.beforeEach(async ({ homePage }) => { + await homePage.goto(); + }); + + test("stub", ({ homePage }) => { + homePage.expectOnHomePage(); + }); +}); diff --git a/vite.config.ts b/vite.config.ts index 756f91b21..267840c24 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,6 +8,7 @@ import react from "@vitejs/plugin-react"; import fs from "node:fs"; import path from "node:path"; import { UserConfig, defineConfig, loadEnv } from "vite"; +import { configDefaults } from "vitest/config"; import svgr from "vite-plugin-svgr"; // Support optionally pulling in external branding if the module is installed. @@ -46,6 +47,7 @@ export default defineConfig(({ mode }): UserConfig => { test: { globals: true, environment: "jsdom", + exclude: [...configDefaults.exclude, "**/e2e/**"], poolOptions: { threads: { // threads disabled for now due to https://github.com/vitest-dev/vitest/issues/1982 From 177afc3c8404be06a022c189de31fe7ae3aabbda Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:01:13 +0100 Subject: [PATCH 142/172] Use npm release of header generator (#327) You should get a clean `npm i` with no config now we've moved it off GitHub Packages. --- package-lock.json | 13 ++++++------- package.json | 2 +- src/makecode/generate-custom-scripts.ts | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 833b36f49..5babecf29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,9 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit-foundation/ml-header-generator": "^0.4.0", "@microbit/makecode-embed": "^0.0.0-alpha.7", "@microbit/microbit-connection": "^0.0.0-alpha.21", + "@microbit/ml-header-generator": "^0.4.3", "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", @@ -4339,12 +4339,6 @@ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, - "node_modules/@microbit-foundation/ml-header-generator": { - "version": "0.4.0", - "resolved": "https://npm.pkg.github.com/download/@microbit-foundation/ml-header-generator/0.4.0/fb369777da03babd652557213adf817862a6a673", - "integrity": "sha512-6aBB7pWo/HoB6dGGvtwODMOvFPhe2bktzxdL3qo+wDondleWgd3EEBsPXi1xvKpJSQb0lbeyPxLZi4nLuxaYxg==", - "license": "MIT" - }, "node_modules/@microbit/makecode-embed": { "version": "0.0.0-alpha.7", "resolved": "https://registry.npmjs.org/@microbit/makecode-embed/-/makecode-embed-0.0.0-alpha.7.tgz", @@ -4386,6 +4380,11 @@ "tslib": ">=1.11.1" } }, + "node_modules/@microbit/ml-header-generator": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@microbit/ml-header-generator/-/ml-header-generator-0.4.3.tgz", + "integrity": "sha512-aMdo074VvHr4Ol1ctx8zvvaqX/FjOBD7bv2I+CLyV051OeZ24PdYB6FMf5nH6ULJVyUgKGNjIadAxRbieaPauA==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index 0d5e1dac5..a28086c14 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,9 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@microbit-foundation/ml-header-generator": "^0.4.0", "@microbit/makecode-embed": "^0.0.0-alpha.7", "@microbit/microbit-connection": "^0.0.0-alpha.21", + "@microbit/ml-header-generator": "^0.4.3", "@tensorflow/tfjs": "^4.20.0", "@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-usb": "^1.0.6", diff --git a/src/makecode/generate-custom-scripts.ts b/src/makecode/generate-custom-scripts.ts index e733d1076..f277fd0b1 100644 --- a/src/makecode/generate-custom-scripts.ts +++ b/src/makecode/generate-custom-scripts.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ import { compileModel } from "ml4f"; -import { generateBlob } from "@microbit-foundation/ml-header-generator"; +import { generateBlob } from "@microbit/ml-header-generator"; import { ActionName, actionNamesFromLabels } from "./utils"; import { LayersModel } from "@tensorflow/tfjs"; import { mlSettings } from "../ml"; From 846881c5e8ed903f28986bb2fc51a07fa5db6729 Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <44397098+microbit-matt-hillsdon@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:08:16 +0100 Subject: [PATCH 143/172] Add a mark to the recognition point slider (#329) * Add a mark to the recognition point slider * Sort/document props --- src/components/CertaintyThresholdGridItem.tsx | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/components/CertaintyThresholdGridItem.tsx b/src/components/CertaintyThresholdGridItem.tsx index e3e9b967a..7182b7c10 100644 --- a/src/components/CertaintyThresholdGridItem.tsx +++ b/src/components/CertaintyThresholdGridItem.tsx @@ -5,6 +5,7 @@ import { HStack, Slider, SliderFilledTrack, + SliderMark, SliderThumb, SliderTrack, Text, @@ -15,6 +16,8 @@ import { FormattedMessage, useIntl } from "react-intl"; import PercentageDisplay from "./PercentageDisplay"; import PercentageMeter from "./PercentageMeter"; +const markClass = "CertaintyThresholdGridItem--mark"; + interface CertaintyThresholdGridItemProps { requiredConfidence?: number; currentConfidence?: number; @@ -39,6 +42,7 @@ const CertaintyThresholdGridItem = ({ (val: number) => onThresholdChange(val * 0.01), [onThresholdChange] ); + const sliderValue = requiredConfidence * 100; return ( + + {sliderValue.toFixed(0)}% + From f6824b1a86c0db8c316216310c79d35d5ede4921 Mon Sep 17 00:00:00 2001 From: Grace <145345672+microbit-grace@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:26:17 +0100 Subject: [PATCH 144/172] Rename "machine learning tool" to "AI creator" (#328) Includes renaming strings, tool app logo (top left of green bar), and MacOS program transfer gif Equivalent gifs for other platforms remain to do separately once the name is final After discussion we've also tried for more clarity around the role of the micro:bits and the names of the programs we're downloading to them. The details of this are still subject to feedback but we'll merge for now. Related: https://microbit-global.monday.com/boards/1550536443/pulses/1643568195 --- index.html | 24 ++++-- lang/ui.en.json | 86 +++++++------------ package-lock.json | 4 +- package.json | 2 +- src/components/AppLogo.tsx | 2 +- src/components/DefaultPageLayout.tsx | 7 +- src/components/ManualFlashingDialog.tsx | 3 +- src/connection-stage-actions.ts | 2 +- src/constants.ts | 2 +- src/images/app-name.svg | 6 +- src/images/transfer_program_macos.gif | Bin 496243 -> 138166 bytes src/messages/ui.en.json | 96 ++++++---------------- src/pages-config.ts | 6 +- src/pages/GetStartedResourcePage.tsx | 17 ++-- src/pages/HomePage.tsx | 2 +- src/pages/IntroducingToolResourcePage.tsx | 22 ++--- src/store.ts | 2 +- 17 files changed, 109 insertions(+), 174 deletions(-) diff --git a/index.html b/index.html index 27b6b9644..dd15486f2 100644 --- a/index.html +++ b/index.html @@ -3,32 +3,38 @@ SPDX-License-Identifier: MIT --> - + - micro:bit machine learning tool - + micro:bit AI creator + + content="Use our free micro:bit AI creator. Explore machine learning, by training a computer model with your own data, then testing it to see if it works." + /> + content="Use your own movement data to train a computer model" + /> - - + +

        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 diff --git a/public/webfonts/fa-solid-900.svg b/public/webfonts/fa-solid-900.svg deleted file mode 100644 index 00296e959..000000000 --- a/public/webfonts/fa-solid-900.svg +++ /dev/null @@ -1,5034 +0,0 @@ - - - - -Created by FontForge 20201107 at Wed Aug 4 12:25:29 2021 - By Robert Madole -Copyright (c) Font Awesome - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/webfonts/fa-solid-900.ttf b/public/webfonts/fa-solid-900.ttf deleted file mode 100644 index 25abf389e22db851b03dd14d87ca10acb8b6b44b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 202744 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 R_Uou~eShdq`k`O*{{v{mwVeO} diff --git a/public/webfonts/fa-solid-900.woff b/public/webfonts/fa-solid-900.woff deleted file mode 100644 index 23ee663443a7c6d2393dbcb713b07c566f40c925..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101648 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