diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c38b2960..eb50872c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: npm install --no-save @microbit-foundation/ml-trainer-microbit@0.2.0-dev.75 @microbit-foundation/website-deploy-aws@0.6 @microbit-foundation/website-deploy-aws-config@0.10 + - run: npm install --no-save @microbit-foundation/ml-trainer-microbit@0.2.0-dev.78 @microbit-foundation/website-deploy-aws@0.6 @microbit-foundation/website-deploy-aws-config@0.10 if: github.repository_owner == 'microbit-foundation' env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/lang/ui.ca.json b/lang/ui.ca.json index 63b02c458..5cc9b2123 100644 --- a/lang/ui.ca.json +++ b/lang/ui.ca.json @@ -55,6 +55,14 @@ "defaultMessage": "Afegir una acció", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "Temporitzador d'activitat d'IA", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "Cable micro USB", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "Connecta una micro:bit de recollida de dades o importa mostres de dades", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "Copia el patró", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "Més edició a les opcions de MakeCode", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "Anomena una acció que vulguis que reconegui la micro:bit", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "Següent", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "No hi ha mostres de dades", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "Aquest projecte no contenia cap mostra de dades, de manera que només s'ha obert el programa. Has d'entrenar un model abans de poder utilitzar el programa.", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "Prem per gravar una mostra de dades o prem el botó B de la teva micro:bit de recollida de dades.", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "Enregistra {numSamples} mostres", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "Entrenant un model", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "Entrenant un model", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "desconnecta i torna a connectar el cable USB", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.en.json b/lang/ui.en.json index aed989dd0..3e48560fe 100644 --- a/lang/ui.en.json +++ b/lang/ui.en.json @@ -55,6 +55,14 @@ "defaultMessage": "Add action", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "AI activity timer", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "Micro USB cable", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "Connect a data collection micro:bit or import data samples", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "Copy pattern", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,8 +1183,12 @@ "defaultMessage": "More edit in MakeCode options", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { - "defaultMessage": "Name an action you want the micro:bit to recognise", + "defaultMessage": "Name an action you want CreateAI to recognise, like ‘waving’ or ‘jumping’", "description": "Hint shown when you have an unnamed action" }, "name-project": { @@ -1251,10 +1259,6 @@ "defaultMessage": "Next", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "No data samples", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "This project did not contain any data samples, so only the code has been opened. You need to train a model before you can use the code.", "description": "Content of import non-CreateAI hex dialog" @@ -1392,13 +1396,21 @@ "description": "Aria label for record button" }, "record-hint": { - "defaultMessage": "Press to record a data sample.", + "defaultMessage": "Press record to collect a movement data sample.", "description": "Hint when you have named the first action but not recorded any data samples" }, "record-hint-button-b": { - "defaultMessage": "Press to record a data sample or press button B on your data collection micro:bit.", + "defaultMessage": "Press record or button B on the micro:bit to collect a movement data sample.", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "Record {numSamples} samples", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "Training a model", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "Train model", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "unplug and replug the USB cable", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.es-es.json b/lang/ui.es-es.json index e9d4c5c9d..26f2b2867 100644 --- a/lang/ui.es-es.json +++ b/lang/ui.es-es.json @@ -55,6 +55,14 @@ "defaultMessage": "Añadir acción", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "Temporizador de actividad de IA", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "Cable micro USB", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "Conecta una colección de datos de micro:bit o importa muestras de datos", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "Copiar patrón", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "Más edición en opciones de MakeCode", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "Nombra una acción que quieras que reconozca el micro:bit", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "Siguiente", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "No hay muestras de datos", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "Este proyecto no contenía ninguna muestra de datos, por lo que sólo se ha abierto el código. Necesitas entrenar un modelo antes de poder utilizar el código.", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "Pulsa para grabar una muestra de datos o pulsa el botón B de tu micro:bit de recogida de datos.", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "Grabar {numSamples} muestras", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "Entrenar un modelo", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "Entrenar modelo", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "desenchufa y vuelve a enchufar el cable USB", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.fr.json b/lang/ui.fr.json index a157e961c..e4ab42c75 100644 --- a/lang/ui.fr.json +++ b/lang/ui.fr.json @@ -55,6 +55,14 @@ "defaultMessage": "Ajouter une action", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "Minuteur d'activité IA", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "Câble Micro USB", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "Connecter un micro:bit de collecte de données ou importer des échantillons de données", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "Copier le motif", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "Plus de modifications dans les options MakeCode", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "Nommez une action que vous voulez que le micro:bit reconnaisse", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "Suivant", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "Aucun échantillon de données", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "Ce projet ne contenait pas d'échantillons de données, donc seul le code a été ouvert. Vous devez entraîner un modèle avant de pouvoir utiliser le code.", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "Appuyez pour enregistrer un échantillon de données ou appuyez sur le bouton B de votre micro:bit de collecte de données.", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "Enregistrer {numSamples} échantillons ", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "Entraînement d'un modèle", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "Entraîner le modèle", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "débrancher et rebrancher le câble USB", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.ja.json b/lang/ui.ja.json index 96aa34ece..67e680411 100644 --- a/lang/ui.ja.json +++ b/lang/ui.ja.json @@ -55,6 +55,14 @@ "defaultMessage": "アクションを追加", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "AI活動タイマー", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "マイクロUSBケーブル", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "データ収集用micro:bitを接続 または データサンプルのインポート", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "パターンをコピー", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "MakeCodeのオプションをさらに編集する", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "micro:bitに認識させたいアクションに名前を付けてください", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "次", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "データサンプルがありません", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "このプロジェクトにはデータサンプルが含まれていないため、プログラムだけが開かれています。 プログラムを使う前に、モデルを訓練する必要があります。", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "データサンプルを記録するには、データ収集用micro:bitのボタンBを押します。", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "{numSamples} サンプルを記録", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "モデルのトレーニング", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "モデルのトレーニング", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "USBケーブルを抜いて再接続", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.ko.json b/lang/ui.ko.json index 4e83005e2..b053d1589 100644 --- a/lang/ui.ko.json +++ b/lang/ui.ko.json @@ -55,6 +55,14 @@ "defaultMessage": "행동 추가", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "AI 활동 타이머", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "마이크로 USB 케이블", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "데이터 수집 micro:bit에 연결하기 또는 데이터 샘플 가져오기를 하세요.", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "패턴 복사", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "MakeCode 옵션에서 추가 편집", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "micro:bit가 인식할 행동의 이름 지정하기", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "다음", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "데이터 샘플 없음", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "이 프로젝트에는 데이터 샘플이 없어 코드만 공개되었습니다. 코드를 사용하기 전에 모델을 훈련해야 합니다.", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "눌러서 데이터 샘플을 기록하거나 데이터 수집 micro:bit의 B 버튼을 누르세요.", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "샘플 {numSamples}개 기록", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "모델 훈련 중", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "모델 훈련", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "USB 케이블을 분리했다가 다시 연결하세요.", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.lol.json b/lang/ui.lol.json index 7a853407d..120af14fe 100644 --- a/lang/ui.lol.json +++ b/lang/ui.lol.json @@ -55,6 +55,14 @@ "defaultMessage": "crwdns362672:0crwdne362672:0", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "crwdns362674:0crwdne362674:0", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "crwdns362770:0crwdne362770:0", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "crwdns362772:0crwdne362772:0", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "crwdns362774:0crwdne362774:0", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "crwdns363178:0crwdne363178:0", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "crwdns363180:0crwdne363180:0", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "crwdns363214:0crwdne363214:0", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "crwdns363216:0crwdne363216:0", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "crwdns363218:0crwdne363218:0", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "crwdns363274:0crwdne363274:0", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "crwdns363276:0{numSamples}crwdne363276:0", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "crwdns363434:0crwdne363434:0", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "crwdns363436:0crwdne363436:0", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "crwdns363476:0crwdne363476:0", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.nl.json b/lang/ui.nl.json index 77d7b1140..7e4f1f1a0 100644 --- a/lang/ui.nl.json +++ b/lang/ui.nl.json @@ -55,6 +55,14 @@ "defaultMessage": "Voeg actie toe", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "AI activiteiten timer", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "Micro USB-kabel", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "Verbind een micro:bit die gegevens verzamelt of importeer data samples", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "Kopieer patroon", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "Meer opties voor bewerken in MakeCode", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "Geef een naam aan een actie die je wilt dat de micro:bit herkent", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "Volgende", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "Geen data samples", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "Dit project bevatte geen data samples, dus alleen de code is geopend. Je moet een model trainen voordat je de code kunt gebruiken.", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "Klik om een data sample op te nemen of druk op knop B op jouw data collectie micro:bit.", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "Neem {numSamples} samples op", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "Model trainen", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "Train model", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "de USB-kabel ontkoppelen en opnieuw aansluiten", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.pl.json b/lang/ui.pl.json index c8167b527..b3473dc3b 100644 --- a/lang/ui.pl.json +++ b/lang/ui.pl.json @@ -55,6 +55,14 @@ "defaultMessage": "Dodaj działanie", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "Licznik aktywności AI", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "Kabel Micro USB", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "Podłącz micro:bit zbierający dane lub importuj próbki danych", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "Kopiuj wzór", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "Więcej edycji w opcjach MakeCode", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "Nazwij akcję którą chcesz, aby micro:bit rozpoznał", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "Dalej", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "Brak próbek danych", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "Ten projekt nie zawierał żadnych próbek danych, tylko kod został otwarty. Musisz wytrenować model, zanim będziesz mógł użyć kodu.", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "Naciśnij, aby nagrać próbkę danych lub naciśnij przycisk B na micro:bicie do zbierana danych.", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "Zapisz próbki {numSamples}", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "Trenowanie modelu", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "Trenuj model", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "odłącz i podłącz kabel USB", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.pt-br.json b/lang/ui.pt-br.json index ba2526cae..00e65ab2d 100644 --- a/lang/ui.pt-br.json +++ b/lang/ui.pt-br.json @@ -55,6 +55,14 @@ "defaultMessage": "Adicionar ação", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "Cronômetro de atividades de IA", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "Cabo Micro USB", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "Conecte um micro:bit de coleta de dados ou importe as amostras de dados", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "Copiar padrão", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "Mais opções de edição no MakeCode.", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "Nomeie uma ação que você deseja que o micro:bit reconheça.", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "Próximo", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "Nenhuma amostra de dados", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "Este projeto não contém amostras de dados, portanto, apenas o código foi aberto. Você precisa treinar um modelo antes de poder usar o código.", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "Pressione para gravar uma amostra de dados ou pressione o botão B no seu micro:bit de coleta de dados.", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "Gravar {numSamples} amostras", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "Treinando um modelo.", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "Treinar modelo.", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "Desconecte e reconecte o cabo USB.", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/lang/ui.zh-tw.json b/lang/ui.zh-tw.json index 81b58739b..0c870a737 100644 --- a/lang/ui.zh-tw.json +++ b/lang/ui.zh-tw.json @@ -55,6 +55,14 @@ "defaultMessage": "新增動作", "description": "Button to add an action (movement related, e.g. clapping)" }, + "add-action-hint": { + "defaultMessage": "Finished recording for {actionName}?Add another action", + "description": "Hint when you have recorded enough data samples for one action" + }, + "add-action-hint-label": { + "defaultMessage": "Finished recording for {actionName}? Press ‘Add action’ button to add another action", + "description": "Hint when you have recorded enough data samples for one action aria label" + }, "ai-activity-timer-resource-title": { "defaultMessage": "AI 活動計時器", "description": "Home page resource card title" @@ -259,10 +267,6 @@ "defaultMessage": "微型 USB 纜線", "description": "Label for cable icon in list of requirements" }, - "connect-or-import": { - "defaultMessage": "連線數據收集用的 micro:bit匯入數據樣本", - "description": "Empty data samples page text" - }, "connect-pattern-heading": { "defaultMessage": "複製圖案", "description": "Heading for Bluetooth pattern connection dialog" @@ -1179,6 +1183,10 @@ "defaultMessage": "在 MakeCode 選項中進行更多編輯", "description": "Aria label for the additional actions menu to the right of the Edit in MakeCode button" }, + "move-hint": { + "defaultMessage": "Move the micro:bit to explore how different actions change the graph", + "description": "Hint when you have just connected a micro:bit" + }, "name-action-hint": { "defaultMessage": "命名您希望 micro:bit 辨識的動作", "description": "Hint shown when you have an unnamed action" @@ -1251,10 +1259,6 @@ "defaultMessage": "下一個", "description": "Next button text for dialogs and similar" }, - "no-data-samples": { - "defaultMessage": "沒有數據樣本", - "description": "Empty data samples page status text" - }, "not-create-ai-hex-import-dialog-content": { "defaultMessage": "這項專案不包含任何數據樣本,因此僅開啟程式碼。您需要先訓練模型,然後才能使用程式碼。", "description": "Content of import non-CreateAI hex dialog" @@ -1399,6 +1403,14 @@ "defaultMessage": "按下以記錄數據樣本或是按下您的數據收集用的 micro:bit 上的按鍵 B。", "description": "Hint when you have named the first action but not recorded any data samples and have a micro:bit connected" }, + "record-more-hint": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}}", + "description": "Hint when you have not recorded enough data samples. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, + "record-more-hint-label": { + "defaultMessage": "Record at least {numSamples, plural, one {{numSamples} more data sample} other {{numSamples} more data samples}} for {actionName}", + "description": "Hint when you have not recorded enough data samples aria label. Uses ICU syntax for pluralisation: https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format." + }, "record-samples": { "defaultMessage": "記錄 {numSamples} 個樣本", "description": "Additional recording action text" @@ -1723,6 +1735,14 @@ "defaultMessage": "訓練模型", "description": "Training dialog" }, + "train-hint": { + "defaultMessage": "Finished recording?Train the model", + "description": "Hint when you have recorded enough data samples to train a model" + }, + "train-hint-label": { + "defaultMessage": "Finished recording? Press ‘Train the model’ button to train the model", + "description": "Hint when you have recorded enough data samples to train a model aria label" + }, "train-model": { "defaultMessage": "訓練模型", "description": "Action to train a model" @@ -1806,5 +1826,9 @@ "webusb-retry-replug4": { "defaultMessage": "拔下並重新插入 USB 纜線", "description": "WebUSB error dialog" + }, + "welcome-title": { + "defaultMessage": "Welcome to micro:bit CreateAI", + "description": "Welcome dialog" } } \ No newline at end of file diff --git a/src/buffered-data-hooks.tsx b/src/buffered-data-hooks.tsx index 3f5fa45c9..8237e4e7d 100644 --- a/src/buffered-data-hooks.tsx +++ b/src/buffered-data-hooks.tsx @@ -4,7 +4,10 @@ * * SPDX-License-Identifier: MIT */ -import { AccelerometerDataEvent } from "@microbit/microbit-connection"; +import { + AccelerometerData, + AccelerometerDataEvent, +} from "@microbit/microbit-connection"; import { ReactNode, createContext, @@ -73,3 +76,60 @@ const useBufferedDataInternal = (): BufferedData => { }, [connection, connectStatus, getBuffer]); return getBuffer(); }; + +export const useHasMoved = (): boolean => { + const hasMoved = useStore((s) => s.hasMoved); + const setHasMoved = useStore((s) => s.setHasMoved); + const [connectStatus] = useConnectStatus(); + const connection = useConnectActions(); + useEffect(() => { + if (connectStatus !== ConnectionStatus.Connected) { + setHasMoved(false); + } + let ignore = false; + const delta: AccelerometerData = { x: 0, y: 0, z: 0 }; + let lastSample: AccelerometerData | undefined; + const threshold = 40_000; + const minDelta = 100; + const skipSamples = 10; + let skipped = 0; + const listener = (e: AccelerometerDataEvent) => { + if (skipped < skipSamples) { + skipped++; + } else if (lastSample) { + const deltaX = Math.abs(lastSample.x - e.data.x); + if (deltaX > minDelta) { + delta.x += deltaX; + } + const deltaY = Math.abs(lastSample.y - e.data.y); + if (deltaY > minDelta) { + delta.y += deltaY; + } + const deltaZ = Math.abs(lastSample.z - e.data.z); + if (deltaZ > minDelta) { + delta.z += deltaZ; + } + } + lastSample = e.data; + if ( + (delta.x > threshold ? 1 : 0) + + (delta.y > threshold ? 1 : 0) + + (delta.z > threshold ? 1 : 0) > + 1 + ) { + connection.removeAccelerometerListener(listener); + if (!ignore) { + setHasMoved(true); + } + } + }; + if (!hasMoved) { + connection.addAccelerometerListener(listener); + } + return () => { + ignore = true; + connection.removeAccelerometerListener(listener); + }; + }, [connection, connectStatus, hasMoved, setHasMoved]); + return hasMoved; +}; diff --git a/src/components/ActionNameCard.tsx b/src/components/ActionNameCard.tsx index 196df69af..6d0eb0d9a 100644 --- a/src/components/ActionNameCard.tsx +++ b/src/components/ActionNameCard.tsx @@ -55,6 +55,7 @@ const ActionNameCard = ({ const toastId = "name-too-long-toast"; const setActionName = useStore((s) => s.setActionName); const setActionIcon = useStore((s) => s.setActionIcon); + const setHint = useStore((s) => s.setHint); const { icon, ID: id } = value; const [localName, setLocalName] = useState(value.name); const predictionResult = useStore((s) => s.predictionResult); @@ -70,11 +71,22 @@ const ActionNameCard = ({ setActionName(id, name); }, 400, + // Allowing the first 'Record' button to appear immediately so that + // users can keyboard navigate to the button immediately after naming + // their first action. { leading: true } ), [setActionName] ); + const debouncedSetHint = useMemo( + () => + // Set hint on the trailing end of inputting action name to avoid + // aria-live for hint from being interrupted by inputting of action name. + debounce(() => setHint(false), 400, { leading: false, trailing: true }), + [setHint] + ); + const onChange: React.ChangeEventHandler = useCallback( (e) => { const name = e.target.value; @@ -95,8 +107,9 @@ const ActionNameCard = ({ } setLocalName(name); debouncedSetActionName(id, name); + debouncedSetHint(); }, - [debouncedSetActionName, id, intl, toast] + [debouncedSetActionName, debouncedSetHint, id, intl, toast] ); const handleIconSelected = useCallback( diff --git a/src/components/ConnectFirstDialog.tsx b/src/components/ConnectFirstDialog.tsx index 5235f0129..ffbf40228 100644 --- a/src/components/ConnectFirstDialog.tsx +++ b/src/components/ConnectFirstDialog.tsx @@ -35,107 +35,127 @@ const ConnectFirstDialog = ({ isOpen, ...rest }: ConnectFirstDialogProps) => { + const { handleClose, isConnecting, handleConnect } = useConnectFirst({ + isOpen, + onClose, + onConnect: onChooseConnect, + connectOptions: options, + }); + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const useConnectFirst = ({ + isOpen, + onClose, + onConnect, + connectOptions, +}: { + isOpen: boolean; + onClose: () => void; + onConnect?: () => void; + connectOptions?: ConnectOptions; +}) => { const { actions, status: connStatus, isDialogOpen: isConnectionDialogOpen, } = useConnectionStage(); - const [isWaiting, setIsWaiting] = useState(false); + const [isConnecting, setIsConnecting] = useState(false); - const handleOnClose = useCallback(() => { - setIsWaiting(false); + const handleClose = useCallback(() => { + setIsConnecting(false); onClose(); }, [onClose]); const handleConnect = useCallback(async () => { - onChooseConnect?.(); + onConnect?.(); switch (connStatus) { case ConnectionStatus.FailedToConnect: case ConnectionStatus.FailedToReconnectTwice: case ConnectionStatus.FailedToSelectBluetoothDevice: case ConnectionStatus.NotConnected: { // Start connection flow. - actions.startConnect(options); - return handleOnClose(); + actions.startConnect(connectOptions); + return handleClose(); } case ConnectionStatus.ConnectionLost: case ConnectionStatus.FailedToReconnect: case ConnectionStatus.Disconnected: { // Reconnect. await actions.reconnect(); - return handleOnClose(); + return handleClose(); } case ConnectionStatus.ReconnectingAutomatically: { // Wait for reconnection to happen. - setIsWaiting(true); + setIsConnecting(true); return; } case ConnectionStatus.Connected: { // Connected whilst dialog is up. - return handleOnClose(); + return handleClose(); } case ConnectionStatus.ReconnectingExplicitly: case ConnectionStatus.Connecting: { // Impossible cases. - return handleOnClose(); + return handleClose(); } } - }, [onChooseConnect, connStatus, actions, options, handleOnClose]); + }, [onConnect, connStatus, actions, connectOptions, handleClose]); useEffect(() => { if ( isOpen && (isConnectionDialogOpen || - (isWaiting && connStatus === ConnectionStatus.Connected)) + (isConnecting && connStatus === ConnectionStatus.Connected)) ) { // Close dialog if connection dialog is opened, or // once connected after waiting. - handleOnClose(); + handleClose(); return; } }, [ connStatus, - handleOnClose, + handleClose, isConnectionDialogOpen, isOpen, - isWaiting, + isConnecting, onClose, ]); - return ( - - - - - - - - - - - - - - - - - - - - - ); + return { handleConnect, isConnecting, handleClose }; }; export default ConnectFirstDialog; diff --git a/src/components/DataSamplesTable.tsx b/src/components/DataSamplesTable.tsx index 1f6d7ffcf..b781388b6 100644 --- a/src/components/DataSamplesTable.tsx +++ b/src/components/DataSamplesTable.tsx @@ -4,42 +4,27 @@ * * SPDX-License-Identifier: MIT */ -import { - Button, - Grid, - GridProps, - HStack, - Text, - VStack, -} from "@chakra-ui/react"; +import { Grid, GridProps, HStack, Text } from "@chakra-ui/react"; import { ButtonEvent } from "@microbit/microbit-connection"; -import { - ReactNode, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; +import { useCallback, useEffect, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useConnectActions } from "../connect-actions-hooks"; import { useConnectionStage } from "../connection-stage-hooks"; -import { ActionData } from "../model"; +import { keyboardShortcuts, useShortcut } from "../keyboard-shortcut-hooks"; +import { ActionData, DataSamplesPageHint } from "../model"; import { useStore } from "../store"; +import { recordButtonId } from "./ActionDataSamplesCard"; +import { actionNameInputId } from "./ActionNameCard"; +import { ConfirmDialog } from "./ConfirmDialog"; import ConnectFirstDialog from "./ConnectFirstDialog"; import DataSamplesMenu from "./DataSamplesMenu"; import DataSamplesTableRow from "./DataSamplesTableRow"; import HeadingGrid, { GridColumnHeadingItemProps } from "./HeadingGrid"; -import LoadProjectInput, { LoadProjectInputRef } from "./LoadProjectInput"; import RecordingDialog, { RecordingCompleteDetail, RecordingOptions, } from "./RecordingDialog"; import ShowGraphsCheckbox from "./ShowGraphsCheckbox"; -import { ConfirmDialog } from "./ConfirmDialog"; -import { actionNameInputId } from "./ActionNameCard"; -import { recordButtonId } from "./ActionDataSamplesCard"; -import { keyboardShortcuts, useShortcut } from "../keyboard-shortcut-hooks"; const gridCommonProps: Partial = { gridTemplateColumns: "290px 1fr", @@ -68,22 +53,18 @@ const headings: GridColumnHeadingItemProps[] = [ interface DataSamplesTableProps { selectedActionIdx: number; setSelectedActionIdx: (idx: number) => void; + hint: DataSamplesPageHint; } const DataSamplesTable = ({ selectedActionIdx: selectedActionIdx, setSelectedActionIdx: setSelectedActionIdx, + hint, }: DataSamplesTableProps) => { const actions = useStore((s) => s.actions); // Default to first action being selected if last action is deleted. const selectedAction: ActionData = actions[selectedActionIdx] ?? actions[0]; - const showHints = useMemo( - () => - actions.length === 0 || - (actions.length === 1 && actions[0].recordings.length === 0), - [actions] - ); const intl = useIntl(); const isDeleteActionConfirmOpen = useStore((s) => s.isDeleteActionDialogOpen); const deleteActionConfirmOnOpen = useStore((s) => s.deleteActionDialogOnOpen); @@ -99,19 +80,13 @@ const DataSamplesTable = ({ const closeDialog = useStore((s) => s.closeDialog); const connection = useConnectActions(); - const { actions: connActions } = useConnectionStage(); const { isConnected } = useConnectionStage(); - const loadProjectInputRef = useRef(null); // For adding flashing animation for new recording. const [newRecordingId, setNewRecordingId] = useState( undefined ); - const handleConnect = useCallback(() => { - connActions.startConnect(); - }, [connActions]); - useEffect(() => { const listener = (e: ButtonEvent) => { if (!isRecordingDialogOpen && e.state) { @@ -227,73 +202,31 @@ const DataSamplesTable = ({ {...gridCommonProps} headings={headings} /> - {actions.length === 0 ? ( - - - - - - {!isConnected && ( - - ( - - ), - link2: (chunks: ReactNode) => ( - - ), - }} - /> - - )} - - ) : ( - - {actions.map((action, idx) => ( - setNewRecordingId(undefined)} - selected={selectedAction.ID === action.ID} - onSelectRow={() => setSelectedActionIdx(idx)} - onRecord={handleRecord} - showHints={showHints} - onDeleteAction={deleteActionConfirmOnOpen} - renameShortcutScopeRef={renameActionShortcutScopeRef} - /> - ))} - - )} + + {actions.map((action, idx) => ( + setNewRecordingId(undefined)} + selected={selectedAction.ID === action.ID} + onSelectRow={() => setSelectedActionIdx(idx)} + onRecord={handleRecord} + // Only show hint for the last row. + hint={idx === actions.length - 1 ? hint : null} + onDeleteAction={deleteActionConfirmOnOpen} + renameShortcutScopeRef={renameActionShortcutScopeRef} + /> + ))} + ); }; diff --git a/src/components/DataSamplesTableHints.tsx b/src/components/DataSamplesTableHints.tsx index b95951387..f154fecd3 100644 --- a/src/components/DataSamplesTableHints.tsx +++ b/src/components/DataSamplesTableHints.tsx @@ -3,70 +3,322 @@ * * SPDX-License-Identifier: MIT */ -import { GridItem, HStack, Text, VStack } from "@chakra-ui/react"; +import { + AspectRatio, + Box, + HStack, + Image, + Stack, + Text, + usePrefersReducedMotion, + VisuallyHidden, + VStack, +} from "@chakra-ui/react"; import { FormattedMessage } from "react-intl"; import { useConnectionStage } from "../connection-stage-hooks"; -import { ActionData } from "../model"; -import ActionDataSamplesCard from "./ActionDataSamplesCard"; -import GreetingEmojiWithArrow from "./GreetingEmojiWithArrow"; -import { RecordingOptions } from "./RecordingDialog"; +import microbitButtonB from "../images/microbit-button-b.svg"; +import moveMicrobitImage from "../images/move-microbit.svg"; +import { Action } from "../model"; +import Emoji, { animations, EmojiAi } from "./Emoji"; +import EmojiArrow from "./EmojiArrow"; import UpCurveArrow from "./UpCurveArrow"; -interface DataSamplesTableHintsProps { - action: ActionData; - onRecord?: (recordingOptions: RecordingOptions) => void; -} +export const NameFirstActionHint = () => { + return ( + + + + + + + + + + + + ); +}; -const DataSamplesTableHints = ({ - action, - onRecord, -}: DataSamplesTableHintsProps) => { - const { isConnected } = useConnectionStage(); +export const NameActionHint = () => { + return ( + + + + + + + + + + + + + + ); +}; + +export const NameActionWithSamplesHint = () => { + return ( + + + + + + + + + + + + ); +}; + +const RecordHintWithButtonB = () => { return ( <> - {action.name.length === 0 ? ( - - - - - - - - + + {chunks} }} + /> + + + + ); +}; + +export const RecordFirstActionHint = () => { + const { isConnected } = useConnectionStage(); + return ( + + + {isConnected ? ( + ) : ( - <> - - - - {/* Empty grid item to fill first column of grid */} - - - - - {isConnected ? ( - - - - ) : ( - - - - )} - - - + + + )} - + ); }; -export default DataSamplesTableHints; +export const RecordHint = () => { + const { isConnected } = useConnectionStage(); + return ( + + + + + + {isConnected ? ( + + ) : ( + + + + )} + + + ); +}; + +export const RecordMoreHint = ({ + recorded, + actionName, +}: { + recorded: number; + actionName: string; +}) => { + const numSamples = recorded === 1 ? 2 : 1; + return ( + + + + + + + + + + + + + + + + + ); +}; + +export const AddActionHint = ({ action }: { action: Action }) => { + return ( + + + + + + + + + + + + + + ( + <> +
+ {chunks} + + ), + }} + /> +
+
+ ); +}; + +export const MoveMicrobitHint = () => { + const prefersReducedMotion = usePrefersReducedMotion(); + return ( + + + + + {/* Ratio hides excess whitespace */} + + + + + + + + + ); +}; + +export const TrainHint = () => { + return ( + + + + + + + + + + + ( + <> +
+ {chunks} + + ), + }} + /> +
+ +
+
+ ); +}; diff --git a/src/components/DataSamplesTableRow.tsx b/src/components/DataSamplesTableRow.tsx index dee9446a5..be470bb36 100644 --- a/src/components/DataSamplesTableRow.tsx +++ b/src/components/DataSamplesTableRow.tsx @@ -5,13 +5,20 @@ * SPDX-License-Identifier: MIT */ import { Box, GridItem } from "@chakra-ui/react"; +import { RefType } from "react-hotkeys-hook/dist/types"; import { useIntl } from "react-intl"; -import { ActionData } from "../model"; +import { ActionData, DataSamplesPageHint } from "../model"; import ActionDataSamplesCard from "./ActionDataSamplesCard"; import ActionNameCard, { ActionCardNameViewMode } from "./ActionNameCard"; -import DataSamplesTableHints from "./DataSamplesTableHints"; +import { + NameActionHint, + NameActionWithSamplesHint, + NameFirstActionHint, + RecordFirstActionHint, + RecordHint, + RecordMoreHint, +} from "./DataSamplesTableHints"; import { RecordingOptions } from "./RecordingDialog"; -import { RefType } from "react-hotkeys-hook/dist/types"; interface DataSamplesTableRowProps { preview?: boolean; @@ -19,7 +26,7 @@ interface DataSamplesTableRowProps { selected: boolean; onSelectRow?: () => void; onRecord?: (recordingOptions: RecordingOptions) => void; - showHints: boolean; + hint: DataSamplesPageHint; newRecordingId?: number; clearNewRecordingId?: () => void; onDeleteAction?: () => void; @@ -32,14 +39,13 @@ const DataSamplesTableRow = ({ onSelectRow, onRecord, preview, - showHints, + hint, newRecordingId, clearNewRecordingId, onDeleteAction, renameShortcutScopeRef, }: DataSamplesTableRowProps) => { const intl = useIntl(); - return ( <> - {showHints ? ( - - ) : ( - - {(action.name.length > 0 || action.recordings.length > 0) && ( - - )} + {(hint === "name-first-action" || hint === "name-action") && ( + + {hint === "name-first-action" && } + {hint === "name-action" && } )} + + {(action.name.length > 0 || action.recordings.length > 0) && ( + + )} + {hint === "record-action" && } + {hint === "record-more-action" && ( + + )} + + {hint === "name-action-with-samples" && ( + + + + )} + {hint === "record-first-action" && ( + <> + {/* Skip first column to correctly place hint. */} + + + {hint === "record-first-action" && } + + + )} ); diff --git a/src/components/Emoji.tsx b/src/components/Emoji.tsx new file mode 100644 index 000000000..6318a50af --- /dev/null +++ b/src/components/Emoji.tsx @@ -0,0 +1,189 @@ +import { + Icon, + IconProps, + keyframes, + usePrefersReducedMotion, +} from "@chakra-ui/react"; + +export const animations = { + wobble: `${keyframes({ + "0%": { + transform: "rotate(15deg)", + }, + "25%": { + transform: "rotate(-15deg)", + }, + "50%": { + transform: "rotate(15deg)", + }, + "75%": { + transform: "rotate(-15deg)", + }, + })} 2s`, + tada: `${keyframes({ + "0%": { + transform: "scale(1) rotate(0deg)", + }, + "10%, 20%": { + transform: "scale(0.95) rotate(-3deg)", + }, + "30%, 50%, 70%, 90%": { + transform: "scale(1.1) rotate(3deg)", + }, + "40%, 60%, 80%": { + transform: "scale(1.1) rotate(-3deg)", + }, + "100%": { + transform: "scale(1) rotate(0deg)", + }, + })} 1s ease-in-out`, + spin: `${keyframes({ + "0%": { + transform: "rotate3d(0, 1, 0, 0deg)", + }, + "100%": { + transform: "rotate3d(0, 1, 0, 360deg)", + }, + })} 2s`, +}; + +type Eye = "round" | "tick" | "heart"; + +type Side = "left" | "right"; + +interface EmojiProps extends IconProps { + leftEye?: Eye; + rightEye?: Eye; +} + +const Emoji = ({ + leftEye = "round", + rightEye = "round", + boxSize = 16, + color = "brand.500", + animation, + ...props +}: EmojiProps) => { + const prefersReducedMotion = usePrefersReducedMotion(); + return ( + + {/* Outline */} + + + + {/* Smile */} + + + ); +}; + +const Eye = ({ type, side }: { type: Eye; side: Side }) => { + switch (type) { + case "round": + return ; + case "tick": + return ; + case "heart": + return ; + } +}; + +const RoundEye = ({ side }: { side: Side }) => { + return ( + + ); +}; + +const TickEye = ({ position }: { position: Side }) => { + return ( + + + + + ); +}; + +const HeartEye = ({ side }: { side: Side }) => { + return ( + + ); +}; + +export const EmojiAi = ({ + boxSize = 16, + color = "brand.500", + animation, + ...props +}: EmojiProps) => { + const prefersReducedMotion = usePrefersReducedMotion(); + return ( + + + + + + + + + ); +}; + +export default Emoji; diff --git a/src/components/EmojiArrow.tsx b/src/components/EmojiArrow.tsx new file mode 100644 index 000000000..d3b532486 --- /dev/null +++ b/src/components/EmojiArrow.tsx @@ -0,0 +1,14 @@ +import { Icon, IconProps } from "@chakra-ui/react"; + +const EmojiArrow = (props: IconProps) => { + return ( + + + + ); +}; + +export default EmojiArrow; diff --git a/src/components/GreetingEmojiWithArrow.tsx b/src/components/GreetingEmojiWithArrow.tsx deleted file mode 100644 index 71c2921ee..000000000 --- a/src/components/GreetingEmojiWithArrow.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/** - * (c) 2024, Micro:bit Educational Foundation and contributors - * - * SPDX-License-Identifier: MIT - */ -import { Icon } from "@chakra-ui/react"; - -interface GreetingEmojiWithArrowProps { - w: string; - h: string; - color?: string; -} - -const GreetingEmojiWithArrow = ({ - w, - h, - color, -}: GreetingEmojiWithArrowProps) => { - return ( - - - - - ); -}; - -export default GreetingEmojiWithArrow; diff --git a/src/components/LiveGraphPanel.tsx b/src/components/LiveGraphPanel.tsx index 502a1b36f..aee586d09 100644 --- a/src/components/LiveGraphPanel.tsx +++ b/src/components/LiveGraphPanel.tsx @@ -29,6 +29,7 @@ import PredictedAction from "./PredictedAction"; interface LiveGraphPanelProps { showPredictedAction?: boolean; + showDisconnectedOverlay?: boolean; disconnectedTextId: string; } @@ -37,6 +38,7 @@ export const predictedActionDisplayWidth = 180; const LiveGraphPanel = ({ showPredictedAction, disconnectedTextId, + showDisconnectedOverlay = true, }: LiveGraphPanelProps) => { const { actions, status, isConnected } = useConnectionStage(); const parentPortalRef = useRef(null); @@ -87,7 +89,7 @@ const LiveGraphPanel = ({ bgColor="white" className={tourElClassname.liveGraph} > - {isDisconnected && ( + {isDisconnected && showDisconnectedOverlay && ( { return ( - + , "children">; + +const WelcomeDialog = ({ onClose, isOpen, ...rest }: WelcomeDialogProps) => { + const { handleClose, isConnecting, handleConnect } = useConnectFirst({ + isOpen, + onClose, + }); + + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default WelcomeDialog; diff --git a/src/deployment/default/images/homepage-short-clip.mp4 b/src/deployment/default/images/homepage-short-clip.mp4 new file mode 100644 index 000000000..24d9d5121 Binary files /dev/null and b/src/deployment/default/images/homepage-short-clip.mp4 differ diff --git a/src/e2e/app/data-samples.ts b/src/e2e/app/data-samples.ts index 0ac48c3db..15c61f847 100644 --- a/src/e2e/app/data-samples.ts +++ b/src/e2e/app/data-samples.ts @@ -13,6 +13,7 @@ export class DataSamplesPage { private url: string; private heading: Locator; private connectBtn: Locator; + public welcomeDialog: WelcomeDialog; constructor(public readonly page: Page) { this.url = `http://localhost:5173${ @@ -20,6 +21,7 @@ export class DataSamplesPage { }data-samples`; this.navbar = new Navbar(page); this.heading = this.page.getByRole("heading", { name: "Data samples" }); + this.welcomeDialog = new WelcomeDialog(page); this.connectBtn = this.page.getByLabel("Connect to micro:bit"); } @@ -41,7 +43,7 @@ export class DataSamplesPage { } async connect() { - await this.connectBtn.click(); + await this.welcomeDialog.connect(); const connectionDialogs = new ConnectionDialogs(this.page); return connectionDialogs; } @@ -54,15 +56,11 @@ export class DataSamplesPage { } async expectOnPage() { + await this.welcomeDialog.close(); await expect(this.heading).toBeVisible(); this.expectUrl(); } - async expectCorrectInitialState() { - this.expectUrl(); - await expect(this.heading).toBeVisible({ timeout: 10000 }); - } - async expectActions(expectedActions: string[]) { const actionInputs = this.page.getByRole("textbox", { name: "Name of action", @@ -81,3 +79,24 @@ export class DataSamplesPage { return new TrainModelDialog(this.page); } } + +class WelcomeDialog { + private heading: Locator; + + constructor(public readonly page: Page) { + this.page = page; + this.heading = this.page.getByText("Welcome to micro:bit CreateAI"); + } + + async expectOpen() { + await expect(this.heading).toBeVisible(); + } + + async close() { + await this.page.getByRole("button", { name: "Close" }).click(); + } + + async connect() { + await this.page.getByRole("button", { name: "Connect" }).click(); + } +} diff --git a/src/e2e/new-page.spec.ts b/src/e2e/new-page.spec.ts index 99744b6b3..e12e5747a 100644 --- a/src/e2e/new-page.spec.ts +++ b/src/e2e/new-page.spec.ts @@ -26,6 +26,7 @@ test.describe("new page", () => { test("resume session", async ({ newPage, homePage, dataSamplesPage }) => { await newPage.startNewSession(); + await dataSamplesPage.welcomeDialog.close(); await dataSamplesPage.navbar.home(); await homePage.getStarted(); await newPage.expectResumeButtonToShowProjectName("Untitled"); diff --git a/src/e2e/test-model-page.spec.ts b/src/e2e/test-model-page.spec.ts index a9d0eb231..8e127d744 100644 --- a/src/e2e/test-model-page.spec.ts +++ b/src/e2e/test-model-page.spec.ts @@ -1,11 +1,12 @@ import { test } from "./fixtures"; test.describe("test model page", () => { - test.beforeEach(async ({ homePage, newPage }) => { + test.beforeEach(async ({ homePage, newPage, dataSamplesPage }) => { await homePage.setupContext(); await homePage.goto(); await homePage.getStarted(); await newPage.continueSavedSession("test-data/dataset.json"); + await dataSamplesPage.welcomeDialog.close(); }); test("initial state", async ({ dataSamplesPage, testModelPage }) => { diff --git a/src/images/createai-animation.mp4 b/src/images/createai-animation.mp4 new file mode 100644 index 000000000..5b068cdba Binary files /dev/null and b/src/images/createai-animation.mp4 differ diff --git a/src/images/microbit-button-b.svg b/src/images/microbit-button-b.svg new file mode 100644 index 000000000..256c66c2d --- /dev/null +++ b/src/images/microbit-button-b.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/move-microbit.svg b/src/images/move-microbit.svg new file mode 100644 index 000000000..8db1c179b --- /dev/null +++ b/src/images/move-microbit.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/pre-connect-video.mp4 b/src/images/pre-connect-video.mp4 new file mode 100644 index 000000000..8fbeb1e5b Binary files /dev/null and b/src/images/pre-connect-video.mp4 differ diff --git a/src/images/step-by-step.svg b/src/images/step-by-step.svg new file mode 100644 index 000000000..d4bbc450e --- /dev/null +++ b/src/images/step-by-step.svg @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/live-region-hook.ts b/src/live-region-hook.ts new file mode 100644 index 000000000..d42372799 --- /dev/null +++ b/src/live-region-hook.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react"; +import { LiveRegion, LiveRegionOptions } from "./live-region"; + +export const useLiveRegion = ( + parentNode: HTMLElement | null, + options: Partial = {} +) => { + const [liveRegion] = useState(() => new LiveRegion(options)); + + useEffect(() => { + if (parentNode) { + liveRegion.setupLiveRegion(parentNode); + } + + return () => liveRegion.destroy(); + }, [liveRegion, parentNode]); + + return liveRegion; +}; diff --git a/src/live-region.ts b/src/live-region.ts new file mode 100644 index 000000000..4e74de4b3 --- /dev/null +++ b/src/live-region.ts @@ -0,0 +1,70 @@ +export interface LiveRegionOptions { + id: string; + "aria-live": "polite" | "assertive"; + role: "status" | "alert" | "log"; + "aria-atomic": React.AriaAttributes["aria-atomic"]; +} + +const defaultOptions: LiveRegionOptions = { + id: "live-region", + "aria-live": "polite", + role: "status", + "aria-atomic": true, +}; + +export class LiveRegion { + region: HTMLElement | null; + options: Required; + + constructor(options: Partial = {}) { + this.options = { ...defaultOptions, ...options }; + this.region = null; + } + + setupLiveRegion(parentNode: HTMLElement) { + if (this.region) { + // Region already setup. + return; + } + this.region = document.createElement("div"); + setup(this.region, this.options); + parentNode.appendChild(this.region); + } + + speak(message: string) { + this.clear(); + if (this.region) { + this.region.innerText = message; + } + } + + destroy() { + if (this.region) { + this.region.parentNode?.removeChild(this.region); + } + } + + clear() { + if (this.region) { + this.region.innerText = ""; + } + } +} + +const setup = (region: HTMLElement, options: LiveRegionOptions) => { + region.id = options.id; + region.setAttribute("aria-live", options["aria-live"]); + region.setAttribute("role", options.role); + region.setAttribute("aria-atomic", String(options["aria-atomic"])); + Object.assign(region.style, { + border: "0px", + clip: "rect(0px, 0px, 0px, 0px)", + height: "1px", + width: "1px", + margin: "-1px", + padding: "0px", + overflow: "hidden", + whiteSpace: "nowrap", + position: "absolute", + }); +}; diff --git a/src/messages/ui.ca.json b/src/messages/ui.ca.json index 5713979a5..704a738b4 100644 --- a/src/messages/ui.ca.json +++ b/src/messages/ui.ca.json @@ -117,6 +117,44 @@ "value": "Afegir una acció" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -439,32 +477,6 @@ "value": "Cable micro USB" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "Connecta una micro:bit de recollida de dades" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " o " - }, - { - "children": [ - { - "type": 0, - "value": "importa mostres de dades" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1985,6 +1997,12 @@ "value": "Més edició a les opcions de MakeCode" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2135,12 +2153,6 @@ "value": "Següent" } ], - "no-data-samples": [ - { - "type": 0, - "value": "No hi ha mostres de dades" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2391,6 +2403,90 @@ "value": "Prem per gravar una mostra de dades o prem el botó B de la teva micro:bit de recollida de dades." } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -3015,6 +3111,28 @@ "value": "Entrenant un model" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3182,5 +3300,11 @@ "type": 0, "value": "desconnecta i torna a connectar el cable USB" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.en.json b/src/messages/ui.en.json index fcd46eeeb..8b65eaf52 100644 --- a/src/messages/ui.en.json +++ b/src/messages/ui.en.json @@ -117,6 +117,44 @@ "value": "Add action" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -439,32 +477,6 @@ "value": "Micro USB cable" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "Connect a data collection micro:bit" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " or " - }, - { - "children": [ - { - "type": 0, - "value": "import data samples" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1999,10 +2011,16 @@ "value": "More edit in MakeCode options" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, - "value": "Name an action you want the micro:bit to recognise" + "value": "Name an action you want CreateAI to recognise, like ‘waving’ or ‘jumping’" } ], "name-project": [ @@ -2149,12 +2167,6 @@ "value": "Next" } ], - "no-data-samples": [ - { - "type": 0, - "value": "No data samples" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2396,13 +2408,111 @@ "record-hint": [ { "type": 0, - "value": "Press to record a data sample." + "value": "Press record to collect a movement data sample." } ], "record-hint-button-b": [ { "type": 0, - "value": "Press to record a data sample or press button B on your data collection micro:bit." + "value": "Press record " + }, + { + "children": [ + { + "type": 0, + "value": "or" + } + ], + "type": 8, + "value": "mark" + }, + { + "type": 0, + "value": " button B on the micro:bit to collect a movement data sample." + } + ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" } ], "record-samples": [ @@ -3043,6 +3153,28 @@ "value": "Training a model" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3210,5 +3342,11 @@ "type": 0, "value": "unplug and replug the USB cable" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.es-es.json b/src/messages/ui.es-es.json index bce2abee6..9ba144979 100644 --- a/src/messages/ui.es-es.json +++ b/src/messages/ui.es-es.json @@ -117,6 +117,44 @@ "value": "Añadir acción" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -439,32 +477,6 @@ "value": "Cable micro USB" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "Conecta una colección de datos de micro:bit" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " o " - }, - { - "children": [ - { - "type": 0, - "value": "importa muestras de datos" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1999,6 +2011,12 @@ "value": "Más edición en opciones de MakeCode" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2149,12 +2167,6 @@ "value": "Siguiente" } ], - "no-data-samples": [ - { - "type": 0, - "value": "No hay muestras de datos" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2405,6 +2417,90 @@ "value": "Pulsa para grabar una muestra de datos o pulsa el botón B de tu micro:bit de recogida de datos." } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -3029,6 +3125,28 @@ "value": "Entrenar un modelo" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3196,5 +3314,11 @@ "type": 0, "value": "desenchufa y vuelve a enchufar el cable USB" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.fr.json b/src/messages/ui.fr.json index 4770d3cd7..2dc8b329b 100644 --- a/src/messages/ui.fr.json +++ b/src/messages/ui.fr.json @@ -117,6 +117,44 @@ "value": "Ajouter une action" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -439,32 +477,6 @@ "value": "Câble Micro USB" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "Connecter un micro:bit de collecte de données" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " ou " - }, - { - "children": [ - { - "type": 0, - "value": "importer des échantillons de données" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -2003,6 +2015,12 @@ "value": "Plus de modifications dans les options MakeCode" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2153,12 +2171,6 @@ "value": "Suivant" } ], - "no-data-samples": [ - { - "type": 0, - "value": "Aucun échantillon de données" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2409,6 +2421,90 @@ "value": "Appuyez pour enregistrer un échantillon de données ou appuyez sur le bouton B de votre micro:bit de collecte de données." } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -3047,6 +3143,28 @@ "value": "Entraînement d'un modèle" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3214,5 +3332,11 @@ "type": 0, "value": "débrancher et rebrancher le câble USB" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.ja.json b/src/messages/ui.ja.json index cc8fa68e8..982b8d3bf 100644 --- a/src/messages/ui.ja.json +++ b/src/messages/ui.ja.json @@ -113,6 +113,44 @@ "value": "アクションを追加" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -431,32 +469,6 @@ "value": "マイクロUSBケーブル" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "データ収集用micro:bitを接続" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " または " - }, - { - "children": [ - { - "type": 0, - "value": "データサンプルのインポート" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1979,6 +1991,12 @@ "value": "MakeCodeのオプションをさらに編集する" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2129,12 +2147,6 @@ "value": "次" } ], - "no-data-samples": [ - { - "type": 0, - "value": "データサンプルがありません" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2385,6 +2397,90 @@ "value": "データサンプルを記録するには、データ収集用micro:bitのボタンBを押します。" } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 1, @@ -2993,6 +3089,28 @@ "value": "モデルのトレーニング" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3160,5 +3278,11 @@ "type": 0, "value": "USBケーブルを抜いて再接続" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.ko.json b/src/messages/ui.ko.json index c20240790..ba2c4a208 100644 --- a/src/messages/ui.ko.json +++ b/src/messages/ui.ko.json @@ -113,6 +113,44 @@ "value": "행동 추가" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -431,36 +469,6 @@ "value": "마이크로 USB 케이블" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "데이터 수집 micro:bit에 연결하기" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " 또는 " - }, - { - "children": [ - { - "type": 0, - "value": "데이터 샘플 가져오기" - } - ], - "type": 8, - "value": "link2" - }, - { - "type": 0, - "value": "를 하세요." - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1987,6 +1995,12 @@ "value": "MakeCode 옵션에서 추가 편집" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2137,12 +2151,6 @@ "value": "다음" } ], - "no-data-samples": [ - { - "type": 0, - "value": "데이터 샘플 없음" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2393,6 +2401,90 @@ "value": "눌러서 데이터 샘플을 기록하거나 데이터 수집 micro:bit의 B 버튼을 누르세요." } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -3001,6 +3093,28 @@ "value": "모델 훈련 중" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3168,5 +3282,11 @@ "type": 0, "value": "USB 케이블을 분리했다가 다시 연결하세요." } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.lol.json b/src/messages/ui.lol.json index a4f219e4a..75679fd06 100644 --- a/src/messages/ui.lol.json +++ b/src/messages/ui.lol.json @@ -107,6 +107,44 @@ "value": "crwdns362672:0crwdne362672:0" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -429,12 +467,6 @@ "value": "crwdns362770:0crwdne362770:0" } ], - "connect-or-import": [ - { - "type": 0, - "value": "crwdns362772:0crwdne362772:0" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1897,6 +1929,12 @@ "value": "crwdns363178:0crwdne363178:0" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2021,12 +2059,6 @@ "value": "crwdns363214:0crwdne363214:0" } ], - "no-data-samples": [ - { - "type": 0, - "value": "crwdns363216:0crwdne363216:0" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2281,6 +2313,90 @@ "value": "crwdns363274:0crwdne363274:0" } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -2871,6 +2987,28 @@ "value": "crwdns363434:0crwdne363434:0" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -2996,5 +3134,11 @@ "type": 0, "value": "crwdns363476:0crwdne363476:0" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.nl.json b/src/messages/ui.nl.json index 84d6457ea..0619157ea 100644 --- a/src/messages/ui.nl.json +++ b/src/messages/ui.nl.json @@ -117,6 +117,44 @@ "value": "Voeg actie toe" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -439,32 +477,6 @@ "value": "Micro USB-kabel" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "Verbind een micro:bit die gegevens verzamelt" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " of " - }, - { - "children": [ - { - "type": 0, - "value": "importeer data samples" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1999,6 +2011,12 @@ "value": "Meer opties voor bewerken in MakeCode" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2149,12 +2167,6 @@ "value": "Volgende" } ], - "no-data-samples": [ - { - "type": 0, - "value": "Geen data samples" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2405,6 +2417,90 @@ "value": "Klik om een data sample op te nemen of druk op knop B op jouw data collectie micro:bit." } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -3029,6 +3125,28 @@ "value": "Model trainen" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3196,5 +3314,11 @@ "type": 0, "value": "de USB-kabel ontkoppelen en opnieuw aansluiten" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.pl.json b/src/messages/ui.pl.json index 599e433a9..fb179a658 100644 --- a/src/messages/ui.pl.json +++ b/src/messages/ui.pl.json @@ -117,6 +117,44 @@ "value": "Dodaj działanie" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -439,32 +477,6 @@ "value": "Kabel Micro USB" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "Podłącz micro:bit zbierający dane" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " lub " - }, - { - "children": [ - { - "type": 0, - "value": "importuj próbki danych" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -2003,6 +2015,12 @@ "value": "Więcej edycji w opcjach MakeCode" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2153,12 +2171,6 @@ "value": "Dalej" } ], - "no-data-samples": [ - { - "type": 0, - "value": "Brak próbek danych" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2409,6 +2421,90 @@ "value": "Naciśnij, aby nagrać próbkę danych lub naciśnij przycisk B na micro:bicie do zbierana danych." } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -3025,6 +3121,28 @@ "value": "Trenowanie modelu" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3192,5 +3310,11 @@ "type": 0, "value": "odłącz i podłącz kabel USB" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.pt-br.json b/src/messages/ui.pt-br.json index 16a454864..7c925a890 100644 --- a/src/messages/ui.pt-br.json +++ b/src/messages/ui.pt-br.json @@ -117,6 +117,44 @@ "value": "Adicionar ação" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -439,32 +477,6 @@ "value": "Cabo Micro USB" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "Conecte um micro:bit de coleta de dados" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " ou " - }, - { - "children": [ - { - "type": 0, - "value": "importe as amostras de dados" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1999,6 +2011,12 @@ "value": "Mais opções de edição no MakeCode." } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2129,12 +2147,6 @@ "value": "Próximo" } ], - "no-data-samples": [ - { - "type": 0, - "value": "Nenhuma amostra de dados" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2389,6 +2401,90 @@ "value": "Pressione para gravar uma amostra de dados ou pressione o botão B no seu micro:bit de coleta de dados." } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -3009,6 +3105,28 @@ "value": "Treinando um modelo." } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3162,5 +3280,11 @@ "type": 0, "value": "Desconecte e reconecte o cabo USB." } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/messages/ui.zh-tw.json b/src/messages/ui.zh-tw.json index 5a3710b42..12ce58558 100644 --- a/src/messages/ui.zh-tw.json +++ b/src/messages/ui.zh-tw.json @@ -121,6 +121,44 @@ "value": "新增動作" } ], + "add-action-hint": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "?" + }, + { + "children": [ + { + "type": 0, + "value": "Add another action" + } + ], + "type": 8, + "value": "mark" + } + ], + "add-action-hint-label": [ + { + "type": 0, + "value": "Finished recording for " + }, + { + "type": 1, + "value": "actionName" + }, + { + "type": 0, + "value": "? Press ‘Add action’ button to add another action" + } + ], "ai-activity-timer-resource-title": [ { "type": 0, @@ -439,32 +477,6 @@ "value": "微型 USB 纜線" } ], - "connect-or-import": [ - { - "children": [ - { - "type": 0, - "value": "連線數據收集用的 micro:bit" - } - ], - "type": 8, - "value": "link1" - }, - { - "type": 0, - "value": " 或" - }, - { - "children": [ - { - "type": 0, - "value": "匯入數據樣本" - } - ], - "type": 8, - "value": "link2" - } - ], "connect-pattern-heading": [ { "type": 0, @@ -1995,6 +2007,12 @@ "value": "在 MakeCode 選項中進行更多編輯" } ], + "move-hint": [ + { + "type": 0, + "value": "Move the micro:bit to explore how different actions change the graph" + } + ], "name-action-hint": [ { "type": 0, @@ -2145,12 +2163,6 @@ "value": "下一個" } ], - "no-data-samples": [ - { - "type": 0, - "value": "沒有數據樣本" - } - ], "not-create-ai-hex-import-dialog-content": [ { "type": 0, @@ -2401,6 +2413,90 @@ "value": "按下以記錄數據樣本或是按下您的數據收集用的 micro:bit 上的按鍵 B。" } ], + "record-more-hint": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + } + ], + "record-more-hint-label": [ + { + "type": 0, + "value": "Record at least " + }, + { + "offset": 0, + "options": { + "one": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data sample" + } + ] + }, + "other": { + "value": [ + { + "type": 1, + "value": "numSamples" + }, + { + "type": 0, + "value": " more data samples" + } + ] + } + }, + "pluralType": "cardinal", + "type": 6, + "value": "numSamples" + }, + { + "type": 0, + "value": " for " + }, + { + "type": 1, + "value": "actionName" + } + ], "record-samples": [ { "type": 0, @@ -3021,6 +3117,28 @@ "value": "訓練模型" } ], + "train-hint": [ + { + "type": 0, + "value": "Finished recording?" + }, + { + "children": [ + { + "type": 0, + "value": "Train the model" + } + ], + "type": 8, + "value": "mark" + } + ], + "train-hint-label": [ + { + "type": 0, + "value": "Finished recording? Press ‘Train the model’ button to train the model" + } + ], "train-model": [ { "type": 0, @@ -3188,5 +3306,11 @@ "type": 0, "value": "拔下並重新插入 USB 纜線" } + ], + "welcome-title": [ + { + "type": 0, + "value": "Welcome to micro:bit CreateAI" + } ] } \ No newline at end of file diff --git a/src/model.ts b/src/model.ts index 3e05c4828..c39d5e255 100644 --- a/src/model.ts +++ b/src/model.ts @@ -223,6 +223,18 @@ export enum DataSamplesView { GraphAndDataFeatures = "graph and data features", } +export type DataSamplesPageHint = + | null + | "move-microbit" + | "name-first-action" + | "record-first-action" + | "record-more-action" + | "add-action" + | "name-action" + | "record-action" + | "train" + | "name-action-with-samples"; + export enum PostImportDialogState { None = "none", Error = "error", diff --git a/src/pages/DataSamplesPage.tsx b/src/pages/DataSamplesPage.tsx index 584aa3221..d4ac00208 100644 --- a/src/pages/DataSamplesPage.tsx +++ b/src/pages/DataSamplesPage.tsx @@ -4,23 +4,45 @@ * * SPDX-License-Identifier: MIT */ -import { Button, Flex, HStack, VStack } from "@chakra-ui/react"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { + Button, + Flex, + HStack, + useDisclosure, + usePrefersReducedMotion, + VStack, +} from "@chakra-ui/react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { RiAddLine, RiArrowRightLine } from "react-icons/ri"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage, IntlFormatters, useIntl } from "react-intl"; import { useNavigate } from "react-router"; +import { useHasMoved } from "../buffered-data-hooks"; import DataSamplesTable from "../components/DataSamplesTable"; +import { + AddActionHint, + MoveMicrobitHint, + TrainHint, +} from "../components/DataSamplesTableHints"; import DefaultPageLayout, { ProjectMenuItems, ProjectToolbarItems, } from "../components/DefaultPageLayout"; import LiveGraphPanel from "../components/LiveGraphPanel"; import TrainModelDialogs from "../components/TrainModelFlowDialogs"; +import WelcomeDialog from "../components/WelcomeDialog"; import { useConnectionStage } from "../connection-stage-hooks"; import { keyboardShortcuts, useShortcut } from "../keyboard-shortcut-hooks"; +import { + ActionData, + DataSamplesPageHint, + PostImportDialogState, +} from "../model"; import { useHasSufficientDataForTraining, useStore } from "../store"; import { tourElClassname } from "../tours"; import { createTestingModelPageUrl } from "../urls"; +import { animations } from "../components/Emoji"; +import { useLiveRegion } from "../live-region-hook"; +import debounce from "lodash.debounce"; const DataSamplesPage = () => { const actions = useStore((s) => s.actions); @@ -32,7 +54,8 @@ const DataSamplesPage = () => { const trainModelFlowStart = useStore((s) => s.trainModelFlowStart); const tourStart = useStore((s) => s.tourStart); - const { isConnected } = useConnectionStage(); + const { isConnected, isDialogOpen: isConnectionDialogOpen } = + useConnectionStage(); useEffect(() => { // If a user first connects on "Testing model" this can result in the tour when they return to the "Data samples" page. if (isConnected) { @@ -56,8 +79,72 @@ const DataSamplesPage = () => { enabled: !isAddNewActionDisabled, }); const intl = useIntl(); + const prefersReducedMotion = usePrefersReducedMotion(); + const welcomeDialogDisclosure = useDisclosure({ + defaultIsOpen: !isConnected && !model, + }); + const hasMoved = useHasMoved(); + const tourInProgress = useStore((s) => !!s.tourState); + const isRecordingDialogOpen = useStore((s) => !!s.isRecordingDialogOpen); + const isPostImportDialogOpen = useStore( + (s) => s.postImportDialogState !== PostImportDialogState.None + ); + const isDialogOpen = + welcomeDialogDisclosure.isOpen || + isConnectionDialogOpen || + tourInProgress || + isRecordingDialogOpen || + isPostImportDialogOpen; + const hint = useStore((s) => s.hint); + const setHint = useStore((s) => s.setHint); + useEffect(() => { + // Initialise hint on first load. + setHint(true); + }, [setHint]); + const dataSamplesHint: DataSamplesPageHint = isDialogOpen + ? null + : isConnected && !hasMoved + ? "move-microbit" + : hint; + + const pageRef = useRef(null); + const region = useLiveRegion(pageRef.current); + + // To avoid aria-live interruptions, particularly when inputting action name. + const debouncedSpeakHint = useMemo( + () => + debounce( + (hintText: string) => { + region.speak(hintText); + }, + 1000, + { leading: false, trailing: true } + ), + [region] + ); + + useEffect(() => { + if (!dataSamplesHint) { + return; + } + const actionWithHint = actions[actions.length - 1]; + const hintText = getHintText( + intl, + dataSamplesHint, + isConnected, + actionWithHint + ); + debouncedSpeakHint(hintText); + }, [actions, dataSamplesHint, debouncedSpeakHint, intl, isConnected, region]); + return ( <> + {welcomeDialogDisclosure.isOpen && !isPostImportDialogOpen && ( + + )} { menuItems={} toolbarItemsRight={} > - + @@ -85,6 +173,7 @@ const DataSamplesPage = () => { borderTopWidth={3} borderColor="gray.200" alignItems="center" + position="relative" > + {dataSamplesHint === "add-action" && ( + + )} {model ? ( )} + {dataSamplesHint === "train" && } + {dataSamplesHint === "move-microbit" && } - + + ); }; +const getHintText = ( + intl: IntlFormatters, + hint: DataSamplesPageHint, + isConnected: boolean, + action: ActionData +): string => { + if (!hint) { + return ""; + } + switch (hint) { + case "add-action": { + return intl.formatMessage( + { id: "add-action-hint-label" }, + { actionName: action.name } + ); + } + case "move-microbit": { + return intl.formatMessage({ id: "move-hint" }); + } + case "record-first-action": + case "record-action": { + return isConnected + ? intl + .formatMessage( + { id: "record-hint-button-b" }, + { mark: (chunks: string[]) => chunks } + ) + .toString() + : intl.formatMessage({ id: "record-hint" }); + } + case "name-first-action": + case "name-action-with-samples": + case "name-action": { + return intl.formatMessage({ id: "name-action-hint" }); + } + case "record-more-action": { + return intl.formatMessage( + { id: "record-more-hint-label" }, + { + numSamples: action.recordings.length === 1 ? 2 : 1, + actionName: action.name, + } + ); + } + case "train": { + return intl.formatMessage({ id: "train-hint-label" }); + } + } +}; + export default DataSamplesPage; diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index c1f878514..784a2cad0 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -31,7 +31,7 @@ import projectImage2 from "theme-package/images/simple-ai-exercise-timer.png"; import projectImage1 from "theme-package/images/ai-storytelling-friend.png"; import homepageVideo from "theme-package/images/homepage-short-clip.mp4"; import HomepageBannerVideo from "../components/HomepageBannerVideo"; -import StepByStepIllustration from "../components/StepByStepIllustration"; +import stepByStep from "../images/step-by-step.svg"; import { landingPageUrl, projectUrl, @@ -133,6 +133,19 @@ const HomePage = () => { )} + + + + + + + + @@ -163,18 +176,6 @@ const HomePage = () => { )} - - - - - - - - {flags.websiteContent && ( diff --git a/src/pages/OpenSharedProjectPage.tsx b/src/pages/OpenSharedProjectPage.tsx index 33195128c..eadcd6a1c 100644 --- a/src/pages/OpenSharedProjectPage.tsx +++ b/src/pages/OpenSharedProjectPage.tsx @@ -296,7 +296,7 @@ const PreviewData = ({ dataset }: PreviewDataProps) => { key={action.ID} action={action} selected={false} - showHints={false} + hint={null} /> ))} diff --git a/src/store.ts b/src/store.ts index 6120e675a..35df3ee31 100644 --- a/src/store.ts +++ b/src/store.ts @@ -35,6 +35,7 @@ import { EditorStartUp, TourTriggerName, tourSequence, + DataSamplesPageHint, } from "./model"; import { defaultSettings, Settings } from "./settings"; import { getTotalNumSamples } from "./utils/actions"; @@ -149,6 +150,15 @@ export interface State { timestamp: number | undefined; + /** + * Hint to trigger depending on actions. + */ + hint: DataSamplesPageHint; + /** + * Set to true if user has moved the micro:bit. + * This is used to decide whether to show the move hint. + */ + hasMoved: boolean; isRecording: boolean; project: MakeCodeProject; @@ -238,6 +248,8 @@ export interface Actions { loadDataset(actions: ActionData[]): void; loadProject(project: MakeCodeProject, name: string): void; setEditorOpen(open: boolean): void; + setHint(suppressTrainAndAddActionHint?: boolean): void; + setHasMoved(hasMoved: boolean): void; recordingStarted(): void; recordingStopped(): void; newSession(projectName?: string): void; @@ -315,8 +327,10 @@ const createMlStore = (logging: Logging) => { persist( (set, get) => ({ timestamp: undefined, - actions: [], + actions: [createFirstAction()], dataWindow: currentDataWindow, + hint: null, + hasMoved: false, isRecording: false, project: createUntitledProject(), projectLoadTimestamp: 0, @@ -405,9 +419,10 @@ const createMlStore = (logging: Logging) => { newSession(projectName?: string) { const untitledProject = createUntitledProject(); + const actions = [createFirstAction()]; set( { - actions: [], + actions, dataWindow: currentDataWindow, model: undefined, project: projectName @@ -416,6 +431,7 @@ const createMlStore = (logging: Logging) => { projectEdited: false, appEditNeedsFlushToEditor: true, timestamp: Date.now(), + hint: getHint(actions, false), }, false, "newSession" @@ -436,6 +452,21 @@ const createMlStore = (logging: Logging) => { ); }, + setHint(suppressTrainAndAddActionHint = false) { + set( + ({ actions }) => { + const hint = getHint(actions, suppressTrainAndAddActionHint); + return { hint }; + }, + false, + "setHint" + ); + }, + + setHasMoved(hasMoved) { + set({ hasMoved }, false, "setHasMoved"); + }, + recordingStarted() { set({ isRecording: true }, false, "recordingStarted"); }, @@ -459,6 +490,7 @@ const createMlStore = (logging: Logging) => { ]; return { actions: newActions, + hint: getHint(newActions, false), model: undefined, ...updateProject( project, @@ -484,6 +516,7 @@ const createMlStore = (logging: Logging) => { }); return { actions: updatedActions, + hint: getHint(updatedActions, false), model: undefined, }; }); @@ -491,12 +524,16 @@ const createMlStore = (logging: Logging) => { deleteAction(id: ActionData["ID"]) { return set(({ project, projectEdited, actions, dataWindow }) => { - const newActions = actions.filter((a) => a.ID !== id); + const remainingActions = actions.filter((a) => a.ID !== id); + const newActions = + remainingActions.length === 0 + ? [createFirstAction()] + : remainingActions; const newDataWindow = newActions.length === 0 ? currentDataWindow : dataWindow; return { - actions: - newActions.length === 0 ? [createFirstAction()] : newActions, + actions: newActions, + hint: getHint(newActions, false), dataWindow: newDataWindow, model: undefined, ...updateProject( @@ -512,12 +549,24 @@ const createMlStore = (logging: Logging) => { setActionName(id: ActionData["ID"], name: string) { return set( - ({ project, projectEdited, actions, model, dataWindow }) => { + ({ + project, + projectEdited, + actions, + model, + dataWindow, + hint, + }) => { const newActions = actions.map((action) => id !== action.ID ? action : { ...action, name } ); + const newHint = getHint(newActions, false); return { actions: newActions, + // Hint is set separately in DataSamplesPage.tsx and debounced + // to avoid aria-live interruptions. Hint is set to null when + // the hint will change to avoid showing incorrect hint. + ...(hint === newHint ? {} : { hint: null }), ...updateProject( project, projectEdited, @@ -601,6 +650,7 @@ const createMlStore = (logging: Logging) => { numRecordings === 0 ? currentDataWindow : dataWindow; return { actions: newActions, + hint: getHint(newActions, false), dataWindow: newDataWindow, model: undefined, ...updateProject( @@ -615,18 +665,22 @@ const createMlStore = (logging: Logging) => { }, deleteAllActions() { - return set(({ project, projectEdited }) => ({ - actions: [createFirstAction()], - dataWindow: currentDataWindow, - model: undefined, - ...updateProject( - project, - projectEdited, - [], - undefined, - currentDataWindow - ), - })); + return set(({ project, projectEdited }) => { + const actions = [createFirstAction()]; + return { + actions, + hint: getHint(actions, false), + dataWindow: currentDataWindow, + model: undefined, + ...updateProject( + project, + projectEdited, + [], + undefined, + currentDataWindow + ), + }; + }); }, downloadDataset() { @@ -645,8 +699,12 @@ const createMlStore = (logging: Logging) => { a.click(); }, - loadDataset(newActions: ActionData[]) { + loadDataset(datasetActions: ActionData[]) { set(({ project, projectEdited, settings }) => { + const newActions = + datasetActions.length > 0 + ? datasetActions + : [createFirstAction()]; const dataWindow = getDataWindowFromActions(newActions); return { settings: { @@ -670,6 +728,7 @@ const createMlStore = (logging: Logging) => { dataWindow, model: undefined, timestamp: Date.now(), + hint: getHint(newActions, true), ...updateProject( project, projectEdited, @@ -686,7 +745,11 @@ const createMlStore = (logging: Logging) => { * from microbit.org we have the JSON already and use this route. */ loadProject(project: MakeCodeProject, name: string) { - const newActions = getActionsFromProject(project); + const projectActions = getActionsFromProject(project); + const newActions = + projectActions.length > 0 + ? projectActions + : [createFirstAction()]; set(({ settings, project: prevProject }) => { project = renameProject(project, name); project = { @@ -707,6 +770,7 @@ const createMlStore = (logging: Logging) => { }, actions: newActions, dataWindow: getDataWindowFromActions(newActions), + hint: getHint(newActions, true), model: undefined, project, projectEdited: true, @@ -924,7 +988,11 @@ const createMlStore = (logging: Logging) => { `[MakeCode] Updating state from MakeCode header change. ID change: ${prevProject.header?.id} -> ${newProject.header?.id}` ); const timestamp = Date.now(); - const newActions = getActionsFromProject(newProject); + const projectActions = getActionsFromProject(newProject); + const newActions = + projectActions.length > 0 + ? projectActions + : [createFirstAction()]; return { settings: { ...settings, @@ -942,6 +1010,7 @@ const createMlStore = (logging: Logging) => { projectEdited: true, actions: newActions, dataWindow: getDataWindowFromActions(newActions), + hint: getHint(newActions, true), model: undefined, isEditorOpen: false, isEditorLoadingFile: false, @@ -1458,3 +1527,43 @@ const renameProject = ( }, }; }; + +const getHint = ( + actions: ActionData[], + suppressTrainAndAddActionHint: boolean +): DataSamplesPageHint => { + const sufficientDataForTraining = hasSufficientDataForTraining(actions); + + // We don't let you have zero. If you have > 2 you've seen it all before. + if (actions.length === 0 || actions.length > 2) { + if (sufficientDataForTraining && !suppressTrainAndAddActionHint) { + return "train"; + } + return null; + } + const lastActionIdx = actions.length - 1; + const action = actions[lastActionIdx]; + const isFirstAction = lastActionIdx === 0; + + if (action.name.length === 0) { + if (action.recordings.length === 0) { + return isFirstAction ? "name-first-action" : "name-action"; + } else { + return "name-action-with-samples"; + } + } + + if (action.recordings.length === 0) { + return isFirstAction ? "record-first-action" : "record-action"; + } + if (action.recordings.length < 3) { + return "record-more-action"; + } + if (isFirstAction && !suppressTrainAndAddActionHint) { + return "add-action"; + } + if (sufficientDataForTraining && !suppressTrainAndAddActionHint) { + return "train"; + } + return null; +};