diff --git a/.vscode/settings.json b/.vscode/settings.json index fa0a10487..0b79e8a23 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,7 +5,6 @@ }, "search.exclude": { "out": true // set this to false to include "out" folder in search results - }, + } // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" } diff --git a/README.md b/README.md index 4a74ba96a..7ac82f9ff 100644 --- a/README.md +++ b/README.md @@ -78,78 +78,6 @@ As we only support CPX library now, other libraries (i.e. simpleio) can’t run - [Download Firmware .uf2 file](https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-quickstart) - [Download the latest version of the Adafruit CPX library](https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries) -### How to use - -To use Device Simulator Express, install the extension from the marketplace and reload VS Code. - -#### I. Take a look at the "Device Simulator Express: Getting Started" Command. -1. Type in `"Device Simulator Express: [Circuit Playground Express] New File"` in the command palette (`CTRL + SHIFT + P` / `CMD + SHIFT + P` to open the command palette). -Getting Started -2. Choose the `CPX` option from the dropdown. -3. Read, copy and learn some of the things you can do with the simulator! - -#### II. Start with the "Device Simulator Express [Circuit Playground Express]: New File" Command. - -1. Type in `"Device Simulator Express: [Circuit Playground Express] New File"` in the command palette (`CTRL + SHIFT + P` / `CMD + SHIFT + P` to open the command palette). - "New CPX File" animation -2. Name and save your file somewhere, and we’re good to go! -3. Start with some examples: you can find examples files and tutorials inside the comments, as well as in the notification pop up when you run the `"Device Simulator Express: [Circuit Playground Express] New File"` Command. - -#### III. Start from an existing Python file. - -1. Open the folder or your .py file in Visual Studio Code. -2. Run `Device Simulator Express: [Circuit Playground Express] Open Simulator` from the command palette or icon in the editor toolbar. - -#### IV. Run your code on the simulator. - -How to run the CPX simulator animation - -1. Run `Run Simulator` from the command palette or use the `Play` button on the simulator webview. - -#### V. Deploy your code to the physical device - -Before deploying the Python code to your CPX device, you need to format your device by following these tutorials: - -1. Download the firmware with the .uf2 file (link: https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-quickstart). -2. Download the lastest version of the cpx library (link: https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries). - -Deploy to CPX Device - -#### VI. Use the Serial Monitor for your Adafruit CPX device (available on Windows and Mac only) - -1. Plug in your CPX device (make sure it’s formatted properly already). -2. Run the command `"Device Simulator Express: Open Serial Monitor"`. -3. Select your baud rate for the serial port. -4. The `print()` statements in your code will show in the output console. - -#### VII. Use the sensors in the Device Simulator Express - -Generating input for the sensors can be done by interacting directly with device on the webview -or by using the toolbar. - -- **Switch, push buttons and capacitive touch:** click directly on the corresponding element on the device or use the keybindings. -- **Temperature sensor, Light sensor, Acceleration sensor:** click on the corresponding button in the toolbar and change the value using the slider or the input box attached to it. -- **Shake detection:** go to the motion sensor section in the toolbar and click on the shake button. - -#### VIII. Debug your project on the simulator - -1. Add breakpoints in your code -2. Press F5 to enter the debugging mode, and you can start debugging line by line! - -### Commands - -Device Simulator Express provides several commands in the Command Palette (`F1` or `CTRL + SHIFT + P` / `CMD + SHIFT + P` for Mac OS) for working with \*.py files: - -- `Device Simulator Express: Getting Started`: Opens a page in VS Code that helps users get started with the extension. Here, users can browse through code that they can use to play with the simulators. -- `Device Simulator Express: Run Simulator`: Runs Python code on the simulator. -- `Device Simulator Express: [Circuit Playground Express] New File`: Opens an unsaved .py file with template code, also opens the simulator. -- `Device Simulator Express: [Circuit Playground Express] Open Simulator`: Opens the simulator in the webView -- `Device Simulator Express: [Circuit Playground Express] Deploy to Device`: Copies the current file to CIRCUITPY drive if detected a CPX is plugged in. -- `Device Simulator Express: Open Serial Monitor`: Opens the serial monitor in the integrated output window. -- `Device Simulator Express: Close Serial Monitor`: Stops the serial monitor and releases the serial port. -- `Device Simulator Express: Change Baud Rate`: Changes the baud rate of the selected serial port. For Adafruit CPX, the default baud rate is 115200. -- `Device Simulator Express: Select Serial Port`: Changes the current serial port. - ### Keybindings In Device Simulator Express, you can use keyboard to interact with the device: @@ -172,7 +100,7 @@ In Device Simulator Express, you can use keyboard to interact with the device: - 25 LEDs - Light sensor - Motion sensors - - Acceleration detection + - Acceleration detection including gesture detection - Temperature sensor ### Useful Links @@ -180,74 +108,84 @@ In Device Simulator Express, you can use keyboard to interact with the device: - [MicroPython documentation](https://microbit-micropython.readthedocs.io/en/latest/) - [BBC micro:bit examples on the official micro:bit website](https://microbit.org/projects/make-it-code-it/?filters=python) -### How to use +### Keybindings +- Push Button `A for A, B for B, C for A & B` +- Refresh the simulator: `SHIFT + R` +- Run the simulator: `SHIFT + F` + +## How to use -#### I. Take a look at the "Device Simulator Express: Getting Started" Command. -1. Type in `"Device Simulator Express: [Circuit Playground Express] New File"` in the command palette (`CTRL + SHIFT + P` / `CMD + SHIFT + P` to open the command palette). -Getting Started -2. Choose the `micro:bit` option from the dropdown. +To use Device Simulator Express, install the extension from the marketplace and reload VS Code. + +### I. Take a look at the "Device Simulator Express: Getting Started" Command. +1. Type in `"Device Simulator Express: Getting Started"` in the command palette (`CTRL + SHIFT + P` / `CMD + SHIFT + P` to open the command palette). +2. Choose the the device you want to play with from the dropdown. 3. Read, copy and learn some of the things you can do with the simulator! - -#### II. Start with the "Device Simulator Express [micro:bit]: New File" Command. -1. Type in `"Device Simulator Express: [micro:bit] New File"` in the command palette (`CTRL + SHFT + P / CMD + SHIFT + P` to open the command palette). - "New micro:bit File" animation -2. Name and save your file somewhere, and we’re good to go! +Getting Started + +### II. Start with the "Device Simulator Express: New File" Command. +1. Type in `"Device Simulator Express: New File"` in the command palette (`CTRL + SHIFT + P` / `CMD + SHIFT + P` to open the command palette). +2. Select the device you want to use. +3. Name and save your file somewhere, and we’re good to go! +4. Start with some examples: you can find examples files and tutorials inside the comments at the top of the file. -#### III. Start from an existing Python file. +"New File" animation -1. Open the folder or your .py file in Visual Studio Code. -2. Run `Device Simulator Express: [micro:bit] Open Simulator` from the command palette or icon in the editor toolbar. +### III. Start from an existing Python file. -#### IV. Run your code on the simulator. +1. Open the folder or your .py file in Visual Studio Code. +2. Run `Device Simulator Express: Open Simulator` from the command palette or icon in the editor toolbar. +3. Select the device you want to use. -How to run the micro:bit simulator animation +### IV. Run your code on the simulator. 1. Run `Run Simulator` from the command palette or use the `Play` button on the simulator webview. -#### V. Deploy your code to the physical device +How to run the simulator animation -1. Run `[micro:bit] Deploy to Device` from the command palette +### V. Deploy your code to the physical device -Deploy to micro:bit device +Before deploying the Python code to your CPX device, you need to format your device by following these tutorials: -#### VI. Use the Serial Monitor for your BBC micro:bit device (available on Windows and Mac only) +- *For the CPX*: + - Download the firmware with the .uf2 file (link: https://learn.adafruit.com/adafruit-circuit-playground-express/circuitpython-quickstart). + - Download the lastest versions of the cpx libraries (link: https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries). -1. Plug in your BBC micro:bit. -2. Run the command `"Device Simulator Express: Open Serial Monitor"`. -3. Select your baud rate for the serial port. -4. The `print()` statements in your code will show in the output console. +- *For the micro:bit*: + - Download the firmware with the .hex file (link: https://microbit.org/get-started/user-guide/firmware/). -#### VII. Use the sensors in the Device Simulator Express +1. Plug in your device (make sure it’s formatted properly already). +2. Run the command `"Device Simulator Express: Deploy to Device"`. -Generating input for the sensors can be done by interacting directly with device on the webview -or by using the toolbar. +Deploy to Device -- **Push buttons:** click directly on the buttons on the device or use the keybindings. -- **Temperature sensor, Light sensor, Acceleration sensor:** click on the corresponding button in the toolbar and change the value using the slider or the input box attached to it. +### VI. Use the Serial Monitor for your device (available on Windows and Mac only) + +1. Plug in your device (make sure it’s formatted properly already). +2. Run the command `"Device Simulator Express: Open Serial Monitor"`. +3. Select your baud rate for the serial port. +4. The `print()` statements in your code will show in the output console. -#### VIII. Debug your project on the simulator +### VII. Debug your project on the simulator 1. Add breakpoints in your code 2. Press F5 to enter the debugging mode, and you can start debugging line by line! ### Commands -Using the simulator for the micro:bit is similar to using the one for the CPX. The only difference is that the commands in the command palette display `Device Simulator Express: [micro:bit] ` instead of `Device Simulator Express: [Circuit Playground Express] `. Currently, we support the following commands for micro:bit: + +Device Simulator Express provides several commands in the Command Palette (`F1` or `CTRL + SHIFT + P` / `CMD + SHIFT + P` for Mac OS) for working with \*.py files: + - `Device Simulator Express: Getting Started`: Opens a page in VS Code that helps users get started with the extension. Here, users can browse through code that they can use to play with the simulators. - `Device Simulator Express: Run Simulator`: Runs Python code on the simulator. -- `Device Simulator Express: [micro:bit] Open Simulator`: Opens an unsaved .py file with template code, also opens the simulator. -- `Device Simulator Express: [micro:bit] New File`: Opens the simulator in the webView. -- `Device Simulator Express: [micro:bit] Deploy to Device`: Copies the current file to the micro:bit if the device is detected. +- `Device Simulator Express: New File`: Opens an unsaved .py file with template code, also opens the simulator for the selected device. +- `Device Simulator Express: Open Simulator`: Opens the simulator in the simulator window for the selected device +- `Device Simulator Express: Deploy to Device`: Copies the current file to the selected device. - `Device Simulator Express: Open Serial Monitor`: Opens the serial monitor in the integrated output window. - `Device Simulator Express: Close Serial Monitor`: Stops the serial monitor and releases the serial port. -- `Device Simulator Express: Change Baud Rate`: Changes the baud rate of the selected serial port. For BBC micro:bit, the default baud rate is 115200. +- `Device Simulator Express: Change Baud Rate`: Changes the baud rate of the selected serial port. For Adafruit CPX, the default baud rate is 115200. - `Device Simulator Express: Select Serial Port`: Changes the current serial port. -### Keybindings -- Push Button `A for A, B for B, C for A & B` -- Refresh the simulator: `SHIFT + R` -- Run the simulator: `SHIFT + F` - ## Contribute [See here for steps to run the extension locally.](https://github.com/microsoft/vscode-python-devicesimulator/blob/dev/docs/developers-setup.md) diff --git a/assets/readmeFiles/deploy.png b/assets/readmeFiles/deploy.png new file mode 100644 index 000000000..8dd4a3473 Binary files /dev/null and b/assets/readmeFiles/deploy.png differ diff --git a/assets/readmeFiles/new_file.gif b/assets/readmeFiles/new_file.gif new file mode 100644 index 000000000..d67891a5b Binary files /dev/null and b/assets/readmeFiles/new_file.gif differ diff --git a/assets/readmeFiles/run.gif b/assets/readmeFiles/run.gif index fb1fa4c96..4481ee49a 100644 Binary files a/assets/readmeFiles/run.gif and b/assets/readmeFiles/run.gif differ diff --git a/locales/en/out/constants.i18n.json b/locales/en/out/constants.i18n.json index 109169527..d6df54300 100644 --- a/locales/en/out/constants.i18n.json +++ b/locales/en/out/constants.i18n.json @@ -30,6 +30,8 @@ "error.invalidFileExtensionDebug": "The file you tried to run isn\\'t a Python file.", "info.newFile": "New to Python or the Circuit Playground Express? We are here to help!", "info.noDeviceChosenToDeployTo": "\n[INFO] No device was selected to deploy to.\n", + "info.noDeviceChosenToSimulateTo": "\n[INFO] No device was selected to simulate.\n", + "info.noDeviceChosenForNewFile": "\n[INFO] No device was selected to open a template file for.\n", "info.redirect": "You are being redirected.", "info.runningCode": "Running user code", "info.privacyStatement": "Privacy Statement", diff --git a/locales/en/package.i18n.json b/locales/en/package.i18n.json index 0a678fa83..4f34f2536 100644 --- a/locales/en/package.i18n.json +++ b/locales/en/package.i18n.json @@ -8,12 +8,8 @@ "deviceSimulatorExpressExtension.commands.common.openSerialMonitor": "Open Serial Monitor", "deviceSimulatorExpressExtension.commands.common.selectSerialPort": "Select Serial Port", "deviceSimulatorExpressExtension.commands.common.deployToDevice": "Deploy to Device", - "deviceSimulatorExpressExtension.commands.cpx.openSimulator": "[Circuit Playground Express] Open Simulator", - "deviceSimulatorExpressExtension.commands.cpx.newFile": "[Circuit Playground Express] New File", - "deviceSimulatorExpressExtension.commands.microbit.openSimulator": "[micro:bit] Open Simulator", - "deviceSimulatorExpressExtension.commands.microbit.newFile": "[micro:bit] New File", - "deviceSimulatorExpressExtension.commands.clue.openSimulator": "[Clue] Open Simulator", - "deviceSimulatorExpressExtension.commands.clue.newFile": "[Clue] New File", + "deviceSimulatorExpressExtension.commands.common.openSimulator": "Open Simulator", + "deviceSimulatorExpressExtension.commands.common.newFile": "New File", "deviceSimulatorExpressExtension.configuration.title": "Device Simulator Express configuration", "deviceSimulatorExpressExtension.configuration.properties.configEnvOnChange": "When you change the Python interpreter, the Device Simulator Express will automatically configure itself for the required dependencies.", "deviceSimulatorExpressExtension.configuration.properties.debuggerPort": "The port the Server will listen on for communication with the debugger.", diff --git a/package.json b/package.json index 12ae8eef1..1820abebd 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,8 @@ "onCommand:deviceSimulatorExpress.common.selectSerialPort", "onCommand:deviceSimulatorExpress.common.gettingStarted", "onCommand:deviceSimulatorExpress.common.deployToDevice", - "onCommand:deviceSimulatorExpress.cpx.newFile", - "onCommand:deviceSimulatorExpress.cpx.openSimulator", - "onCommand:deviceSimulatorExpress.microbit.newFile", - "onCommand:deviceSimulatorExpress.microbit.openSimulator", - "onCommand:deviceSimulatorExpress.clue.newFile", - "onCommand:deviceSimulatorExpress.clue.openSimulator", + "onCommand:deviceSimulatorExpress.common.newFile", + "onCommand:deviceSimulatorExpress.common.openSimulator", "onDebug" ], "main": "./out/extension.js", @@ -88,33 +84,13 @@ "category": "%deviceSimulatorExpressExtension.commands.common.label%" }, { - "command": "deviceSimulatorExpress.cpx.newFile", - "title": "%deviceSimulatorExpressExtension.commands.cpx.newFile%", + "command": "deviceSimulatorExpress.common.newFile", + "title": "%deviceSimulatorExpressExtension.commands.common.newFile%", "category": "%deviceSimulatorExpressExtension.commands.common.label%" }, { - "command": "deviceSimulatorExpress.cpx.openSimulator", - "title": "%deviceSimulatorExpressExtension.commands.cpx.openSimulator%", - "category": "%deviceSimulatorExpressExtension.commands.common.label%" - }, - { - "command": "deviceSimulatorExpress.microbit.openSimulator", - "title": "%deviceSimulatorExpressExtension.commands.microbit.openSimulator%", - "category": "%deviceSimulatorExpressExtension.commands.common.label%" - }, - { - "command": "deviceSimulatorExpress.microbit.newFile", - "title": "%deviceSimulatorExpressExtension.commands.microbit.newFile%", - "category": "%deviceSimulatorExpressExtension.commands.common.label%" - }, - { - "command": "deviceSimulatorExpress.clue.openSimulator", - "title": "%deviceSimulatorExpressExtension.commands.clue.openSimulator%", - "category": "%deviceSimulatorExpressExtension.commands.common.label%" - }, - { - "command": "deviceSimulatorExpress.clue.newFile", - "title": "%deviceSimulatorExpressExtension.commands.clue.newFile%", + "command": "deviceSimulatorExpress.common.openSimulator", + "title": "%deviceSimulatorExpressExtension.commands.common.openSimulator%", "category": "%deviceSimulatorExpressExtension.commands.common.label%" } ], @@ -348,4 +324,4 @@ "extensionDependencies": [ "ms-python.python" ] -} \ No newline at end of file +} diff --git a/package.nls.json b/package.nls.json index f85718a6d..e662c005a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -8,12 +8,8 @@ "deviceSimulatorExpressExtension.commands.common.openSerialMonitor": "Open Serial Monitor", "deviceSimulatorExpressExtension.commands.common.selectSerialPort": "Select Serial Port", "deviceSimulatorExpressExtension.commands.common.deployToDevice": "Deploy to Device", - "deviceSimulatorExpressExtension.commands.cpx.openSimulator": "[Circuit Playground Express] Open Simulator", - "deviceSimulatorExpressExtension.commands.cpx.newFile": "[Circuit Playground Express] New File", - "deviceSimulatorExpressExtension.commands.microbit.openSimulator": "[micro:bit] Open Simulator", - "deviceSimulatorExpressExtension.commands.microbit.newFile": "[micro:bit] New File", - "deviceSimulatorExpressExtension.commands.clue.openSimulator": "[Clue] Open Simulator", - "deviceSimulatorExpressExtension.commands.clue.newFile": "[Clue] New File", + "deviceSimulatorExpressExtension.commands.common.openSimulator": "Open Simulator", + "deviceSimulatorExpressExtension.commands.common.newFile": "New File", "deviceSimulatorExpressExtension.configuration.title": "Device Simulator Express configuration", "deviceSimulatorExpressExtension.configuration.properties.configEnvOnChange": "When you change the Python interpreter, the Device Simulator Express will automatically configure itself for the required dependencies.", "deviceSimulatorExpressExtension.configuration.properties.debuggerPort": "The port the Server will listen on for communication with the debugger.", diff --git a/src/base_circuitpython/base_cp_constants.py b/src/base_circuitpython/base_cp_constants.py index 53e627109..67cea36e0 100644 --- a/src/base_circuitpython/base_cp_constants.py +++ b/src/base_circuitpython/base_cp_constants.py @@ -6,3 +6,5 @@ IMG_DIR_NAME = "img" SCREEN_HEIGHT_WIDTH = 240 + +EXPECTED_INPUT_BUTTONS = ["button_a", "button_b"] diff --git a/src/base_circuitpython/displayio/group.py b/src/base_circuitpython/displayio/group.py index 2d3f3b8af..0eee8f96f 100644 --- a/src/base_circuitpython/displayio/group.py +++ b/src/base_circuitpython/displayio/group.py @@ -94,3 +94,6 @@ def __len__(self): return 0 else: return len(self.__contents) + + def pop(self, i=-1): + return self.__contents.pop(i) diff --git a/src/base_circuitpython/neopixel_write.py b/src/base_circuitpython/neopixel_write.py index 450b40aa3..8862863e8 100644 --- a/src/base_circuitpython/neopixel_write.py +++ b/src/base_circuitpython/neopixel_write.py @@ -30,10 +30,6 @@ def neopixel_write(gpio, buf): def send_clue(buf): sendable_json = {CONSTANTS.PIXELS: tuple(buf)} - - # for now, just print pixels - print(sendable_json) - utils.send_to_simulator(sendable_json, CONSTANTS.CLUE) diff --git a/src/clue/adafruit_clue.py b/src/clue/adafruit_clue.py index bb4ef83b7..1a2f8cd72 100644 --- a/src/clue/adafruit_clue.py +++ b/src/clue/adafruit_clue.py @@ -199,15 +199,65 @@ class Clue: # pylint: disable=too-many-instance-attributes, too-many-public-met RAINBOW = (RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE) def __init__(self): + self._a = False + self._b = False + self.__pressed_buttons = set() self._pixel = neopixel.NeoPixel( pin=CONSTANTS.CLUE_PIN, n=1, pixel_order=neopixel.RGB ) + @property + def button_a(self): + """``True`` when Button A is pressed. ``False`` if not. + This example prints when button A is pressed. + To use with the CLUE: + .. code-block:: python + from adafruit_clue import clue + while True: + if clue.button_a: + print("Button A pressed") + """ + return self._a + + @property + def button_b(self): + """``True`` when Button B is pressed. ``False`` if not. + This example prints when button B is pressed. + To use with the CLUE: + .. code-block:: python + from adafruit_clue import clue + while True: + if clue.button_b: + print("Button B pressed") + """ + return self._b + + def __update_button(self, button, value): + if button == "button_a": + if value: + self.__pressed_buttons.add("A") + self._a = value + elif button == "button_b": + if value: + self.__pressed_buttons.add("B") + self._b = value + + @property + def were_pressed(self): + """Returns a set of the buttons that have been pressed. + To use with the CLUE: + .. code-block:: python + from adafruit_clue import clue + while True: + print(clue.were_pressed) + """ + ret = self.__pressed_buttons.copy() + self.__pressed_buttons.clear() + return ret + @property def pixel(self): """The NeoPixel RGB LED. - .. image :: ../docs/_static/neopixel.jpg - :alt: NeoPixel This example turns the NeoPixel purple. To use with the CLUE: .. code-block:: python @@ -284,6 +334,15 @@ def simple_text_display( colors=colors, ) + def update_state(self, new_state): + self.__update_buttons(new_state) + + # helpers + def __update_buttons(self, new_state): + # get button pushes + for button_name in CONSTANTS.EXPECTED_INPUT_BUTTONS: + self.__update_button(button_name, new_state.get(button_name)) + clue = Clue() # pylint: disable=invalid-name """Object that is automatically created on import. diff --git a/src/clue/test/test_adafruit_clue.py b/src/clue/test/test_adafruit_clue.py index 9f516eac7..f634e5ae3 100644 --- a/src/clue/test/test_adafruit_clue.py +++ b/src/clue/test/test_adafruit_clue.py @@ -53,3 +53,20 @@ def test_clue_display_text(self): clue_data.show() helper._Helper__test_image_equality(displayio.bmp_img, expected) + + def test_buttons(self): + BUTTON_A = "button_a" + BUTTON_B = "button_b" + + clue._Clue__update_button(BUTTON_A, True) + assert clue.button_a + clue._Clue__update_button(BUTTON_A, False) + assert not clue.button_a + + clue._Clue__update_button(BUTTON_B, True) + assert clue.button_b + clue._Clue__update_button(BUTTON_B, False) + assert not clue.button_b + + assert set(["A", "B"]) == clue.were_pressed + assert set() == clue.were_pressed diff --git a/src/constants.ts b/src/constants.ts index 049484a5a..792735372 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -202,6 +202,14 @@ export const CONSTANTS = { "info.noDeviceChosenToDeployTo", "\n[INFO] No device was selected to deploy to.\n" ), + NO_DEVICE_CHOSEN_TO_SIMULATE_TO: localize( + "info.noDeviceChosenToSimulateTo", + "\n[INFO] No device was selected to simulate.\n" + ), + NO_DEVICE_CHOSEN_FOR_NEW_FILE: localize( + "info.noDeviceChosenForNewFile", + "\n[INFO] No device was selected to open a template file for.\n" + ), OPENED_SERIAL_PORT: (port: string) => { return localize( "info.openedSerialPort", @@ -283,6 +291,7 @@ export const CONSTANTS = { TEMPLATE: { CPX: "cpx_template.py", MICROBIT: "microbit_template.py", + CLUE: "clue_template.py", }, WARNING: { ACCEPT_AND_RUN: localize( @@ -350,6 +359,8 @@ export enum TelemetryEventName { MICROBIT_COMMAND_OPEN_SIMULATOR = "MICROBIT.COMMAND.OPEN.SIMULATOR", CLUE_COMMAND_DEPLOY_DEVICE = "CLUE.COMMAND.DEPLOY.DEVICE", + CLUE_COMMAND_NEW_FILE = "CLUE.COMMAND.NEW.FILE.CPX", + CLUE_COMMAND_OPEN_SIMULATOR = "CLUE.COMMAND.OPEN.SIMULATOR", // Simulator interaction CPX_SIMULATOR_BUTTON_A = "CPX.SIMULATOR.BUTTON.A", @@ -405,6 +416,8 @@ export enum TelemetryEventName { MICROBIT_PERFORMANCE_OPEN_SIMULATOR = "MICROBIT.PERFORMANCE.OPEN.SIMULATOR", CLUE_PERFORMANCE_DEPLOY_DEVICE = "CLUE.PERFORMANCE.DEPLOY.DEVICE", + CLUE_PERFORMANCE_NEW_FILE = "CLUE.PERFORMANCE.NEW.FILE", + CLUE_PERFORMANCE_OPEN_SIMULATOR = "CLUE.PERFORMANCE.OPEN.SIMULATOR", // Venv options SETUP_VENV_CREATION_ERR = "SETUP.VENV.CREATION.ERR", diff --git a/src/extension.ts b/src/extension.ts index dc677cefc..f691b7e5d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -77,6 +77,11 @@ export async function activate(context: vscode.ExtensionContext) { telemetryAI, deviceSelectionService ); + const formalNameToNickNameMapping = { + [CONSTANTS.DEVICE_NAME_FORMAL.CPX]: CONSTANTS.DEVICE_NAME.CPX, + [CONSTANTS.DEVICE_NAME_FORMAL.MICROBIT]: CONSTANTS.DEVICE_NAME.MICROBIT, + [CONSTANTS.DEVICE_NAME_FORMAL.CLUE]: CONSTANTS.DEVICE_NAME.CLUE, + }; // Add our library path to settings.json for autocomplete functionality updatePythonExtraPaths(); @@ -296,88 +301,47 @@ export async function activate(context: vscode.ExtensionContext) { sendCurrentDeviceMessage(currentPanel); }; - const openCPXWebview = () => { - deviceSelectionService.setCurrentActiveDevice( - CONSTANTS.DEVICE_NAME.CPX - ); - openWebview(); - }; + // Open Simulator on the webview + const openSimulator: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.common.openSimulator", + async () => { + const chosen_device = await vscode.window.showQuickPick( + Object.values(CONSTANTS.DEVICE_NAME_FORMAL) + ); - const openMicrobitWebview = () => { - deviceSelectionService.setCurrentActiveDevice( - CONSTANTS.DEVICE_NAME.MICROBIT - ); - openWebview(); - }; - const openClueWebview = () => { - deviceSelectionService.setCurrentActiveDevice( - CONSTANTS.DEVICE_NAME.CLUE - ); - openWebview(); - }; + if (!chosen_device) { + utils.logToOutputChannel( + outChannel, + CONSTANTS.INFO.NO_DEVICE_CHOSEN_TO_SIMULATE_TO, + true + ); + return; + } - const gettingStartedOpen: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.common.gettingStarted", - () => { - telemetryAI.trackFeatureUsage( - TelemetryEventName.COMMAND_GETTING_STARTED + const device = formalNameToNickNameMapping[chosen_device]; + deviceSelectionService.setCurrentActiveDevice(device); + const telemetryEvents = telemetryHandlerService.getTelemetryEventsForOpenSimulator( + device ); - webviewService.openTutorialPanel(); - } - ); - - // Open Simulator on the webview - const cpxOpenSimulator: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.cpx.openSimulator", - () => { telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_COMMAND_OPEN_SIMULATOR + telemetryEvents.openSimulatorTelemetryEvent ); telemetryAI.runWithLatencyMeasure( - openCPXWebview, - TelemetryEventName.CPX_PERFORMANCE_OPEN_SIMULATOR + openWebview, + telemetryEvents.openSimulatorPerformanceTelemetryEvent ); } ); - const microbitOpenSimulator: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.microbit.openSimulator", + const gettingStartedOpen: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.common.gettingStarted", () => { telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_COMMAND_OPEN_SIMULATOR - ); - telemetryAI.runWithLatencyMeasure( - openMicrobitWebview, - TelemetryEventName.MICROBIT_PERFORMANCE_OPEN_SIMULATOR + TelemetryEventName.COMMAND_GETTING_STARTED ); + webviewService.openTutorialPanel(); } ); - const clueOpenSimulator: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.clue.openSimulator", - () => { - telemetryAI.runWithLatencyMeasure(openClueWebview, ""); - } - ); - - const openCPXTemplateFile = () => { - deviceSelectionService.setCurrentActiveDevice( - CONSTANTS.DEVICE_NAME.CPX - ); - openTemplateFile(CONSTANTS.TEMPLATE.CPX); - }; - - const openMicrobitTemplateFile = () => { - deviceSelectionService.setCurrentActiveDevice( - CONSTANTS.DEVICE_NAME.MICROBIT - ); - openTemplateFile(CONSTANTS.TEMPLATE.MICROBIT); - }; - const openClueTemplateFile = () => { - deviceSelectionService.setCurrentActiveDevice( - CONSTANTS.DEVICE_NAME.CLUE - ); - openTemplateFile(CONSTANTS.TEMPLATE.MICROBIT); - }; const openTemplateFile = (template: string) => { const fileName = template; @@ -442,39 +406,46 @@ export async function activate(context: vscode.ExtensionContext) { }; }; - const cpxNewFile: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.cpx.newFile", - () => { - telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_COMMAND_NEW_FILE + const newFile: vscode.Disposable = vscode.commands.registerCommand( + "deviceSimulatorExpress.common.newFile", + async () => { + const chosen_device = await vscode.window.showQuickPick( + Object.values(CONSTANTS.DEVICE_NAME_FORMAL) ); - telemetryAI.runWithLatencyMeasure( - openCPXTemplateFile, - TelemetryEventName.CPX_PERFORMANCE_NEW_FILE + + if (!chosen_device) { + utils.logToOutputChannel( + outChannel, + CONSTANTS.INFO.NO_DEVICE_CHOSEN_FOR_NEW_FILE, + true + ); + return; + } + + const device = formalNameToNickNameMapping[chosen_device]; + deviceSelectionService.setCurrentActiveDevice(device); + + const deviceToTemplateMapping = { + [CONSTANTS.DEVICE_NAME.CPX]: CONSTANTS.TEMPLATE.CPX, + [CONSTANTS.DEVICE_NAME.MICROBIT]: CONSTANTS.TEMPLATE.MICROBIT, + [CONSTANTS.DEVICE_NAME.CLUE]: CONSTANTS.TEMPLATE.CLUE, + }; + const templateFile = deviceToTemplateMapping[device]; + + const telemetryEvents = telemetryHandlerService.getTelemetryEventsForNewFile( + device ); - } - ); - const microbitNewFile: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.microbit.newFile", - () => { telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_COMMAND_NEW_FILE + telemetryEvents.newFileTelemetryEvent ); telemetryAI.runWithLatencyMeasure( - openMicrobitTemplateFile, - TelemetryEventName.MICROBIT_PERFORMANCE_NEW_FILE + () => openTemplateFile(templateFile), + telemetryEvents.newFilePerformanceTelemetryEvent ); } ); - const clueNewFile: vscode.Disposable = vscode.commands.registerCommand( - "deviceSimulatorExpress.clue.newFile", - () => { - telemetryAI.runWithLatencyMeasure(openClueTemplateFile, ""); - } - ); - const installDependencies: vscode.Disposable = vscode.commands.registerCommand( "deviceSimulatorExpress.common.installDependencies", async () => { @@ -796,13 +767,6 @@ export async function activate(context: vscode.ExtensionContext) { Object.values(CONSTANTS.DEVICE_NAME_FORMAL) ); - const formalNameToNickNameMapping = { - [CONSTANTS.DEVICE_NAME_FORMAL.CPX]: CONSTANTS.DEVICE_NAME.CPX, - [CONSTANTS.DEVICE_NAME_FORMAL.MICROBIT]: - CONSTANTS.DEVICE_NAME.MICROBIT, - [CONSTANTS.DEVICE_NAME_FORMAL.CLUE]: CONSTANTS.DEVICE_NAME.CLUE, - }; - if (!chosen_device) { utils.logToOutputChannel( outChannel, @@ -1026,14 +990,10 @@ export async function activate(context: vscode.ExtensionContext) { changeBaudRate, closeSerialMonitor, deployToDevice, - cpxNewFile, + newFile, + openSimulator, openSerialMonitor, - cpxOpenSimulator, selectSerialPort, - microbitOpenSimulator, - microbitNewFile, - clueOpenSimulator, - clueNewFile, gettingStartedOpen, vscode.debug.registerDebugConfigurationProvider( CONSTANTS.DEBUG_CONFIGURATION_TYPE, diff --git a/src/service/telemetryHandlerService.ts b/src/service/telemetryHandlerService.ts index b0ecc9a53..c48f3a2de 100644 --- a/src/service/telemetryHandlerService.ts +++ b/src/service/telemetryHandlerService.ts @@ -1,361 +1,419 @@ -import * as open from "open"; -import * as vscode from "vscode"; -import { CONSTANTS, DialogResponses, TelemetryEventName } from "../constants"; -import * as utils from "../extension_utils/utils"; -import { DeviceSelectionService } from "./deviceSelectionService"; -import TelemetryAI from "../telemetry/telemetryAI"; - -export class TelemetryHandlerService { - private telemetryAI: TelemetryAI; - private deviceSelectionService: DeviceSelectionService; - - constructor( - telemetryAI: TelemetryAI, - deviceSelectionService: DeviceSelectionService - ) { - this.telemetryAI = telemetryAI; - this.deviceSelectionService = deviceSelectionService; - } - - public handleDebuggerTelemetry = () => { - switch (this.deviceSelectionService.getCurrentActiveDevice()) { - case CONSTANTS.DEVICE_NAME.CPX: - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_DEBUGGER_INIT_SUCCESS - ); - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_DEBUGGER_INIT_SUCCESS - ); - break; - default: - break; - } - }; - - public handleDebuggerFailTelemetry = () => { - switch (this.deviceSelectionService.getCurrentActiveDevice()) { - case CONSTANTS.DEVICE_NAME.CPX: - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_DEBUGGER_INIT_FAIL - ); - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_DEBUGGER_INIT_FAIL - ); - break; - default: - break; - } - }; - - public handleButtonPressTelemetry = (buttonState: any) => { - switch (this.deviceSelectionService.getCurrentActiveDevice()) { - case CONSTANTS.DEVICE_NAME.CPX: - this.handleCPXButtonPressTelemetry(buttonState); - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - this.handleMicrobitButtonPressTelemetry(buttonState); - break; - default: - break; - } - }; - - public handleGestureTelemetry = (sensorState: any) => { - switch (this.deviceSelectionService.getCurrentActiveDevice()) { - case CONSTANTS.DEVICE_NAME.CPX: - this.handleCPXGestureTelemetry(sensorState); - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - break; - default: - break; - } - }; - - public handleSensorTelemetry = (sensor: string) => { - switch (this.deviceSelectionService.getCurrentActiveDevice()) { - case CONSTANTS.DEVICE_NAME.CPX: - this.handleCPXSensorTelemetry(sensor); - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - this.handleMicrobitSensorTelemetry(sensor); - break; - default: - break; - } - }; - - public handleCPXButtonPressTelemetry = (buttonState: any) => { - if (buttonState.button_a && buttonState.button_b) { - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_BUTTON_AB - ); - } else if (buttonState.button_a) { - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_BUTTON_A - ); - } else if (buttonState.button_b) { - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_BUTTON_B - ); - } else if (buttonState.switch) { - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_SWITCH - ); - } - }; - - public handleCPXGestureTelemetry = (sensorState: any) => { - if (sensorState.shake) { - this.handleCPXSensorTelemetry("shake"); - } else if (sensorState.touch) { - this.handleCPXSensorTelemetry("touch"); - } - }; - - public handleCPXSensorTelemetry = (sensor: string) => { - switch (sensor) { - case "temperature": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_TEMPERATURE_SENSOR - ); - break; - case "light": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_LIGHT_SENSOR - ); - break; - case "motion_x": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_MOTION_SENSOR - ); - break; - case "motion_y": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_MOTION_SENSOR - ); - break; - case "motion_z": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_MOTION_SENSOR - ); - break; - case "shake": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_SHAKE - ); - break; - case "touch": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_SIMULATOR_CAPACITIVE_TOUCH - ); - break; - } - }; - - public handleMicrobitButtonPressTelemetry = (buttonState: any) => { - if (buttonState.button_a && buttonState.button_b) { - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_SIMULATOR_BUTTON_AB - ); - } else if (buttonState.button_a) { - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_SIMULATOR_BUTTON_A - ); - } else if (buttonState.button_b) { - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_SIMULATOR_BUTTON_B - ); - } - }; - - public handleMicrobitSensorTelemetry = (sensor: string) => { - switch (sensor) { - case "temperature": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_SIMULATOR_TEMPERATURE_SENSOR - ); - break; - case "light": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_SIMULATOR_LIGHT_SENSOR - ); - break; - case "motion_x": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_SIMULATOR_MOTION_SENSOR - ); - break; - case "motion_y": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_SIMULATOR_MOTION_SENSOR - ); - break; - case "motion_z": - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_SIMULATOR_MOTION_SENSOR - ); - break; - } - }; - - public handleNewFileErrorTelemetry = () => { - switch (this.deviceSelectionService.getCurrentActiveDevice()) { - case CONSTANTS.DEVICE_NAME.CPX: - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.CPX_ERROR_COMMAND_NEW_FILE - ); - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - this.telemetryAI.trackFeatureUsage( - TelemetryEventName.MICROBIT_ERROR_COMMAND_NEW_FILE - ); - break; - default: - break; - } - }; - - public getTelemetryEventsForStartingDeployToDevice = (device: string) => { - let deployTelemetryEvent: string; - let deployPerformanceTelemetryEvent: string; - switch (device) { - case CONSTANTS.DEVICE_NAME.CPX: - deployTelemetryEvent = - TelemetryEventName.CPX_COMMAND_DEPLOY_DEVICE; - deployPerformanceTelemetryEvent = - TelemetryEventName.CPX_COMMAND_DEPLOY_DEVICE; - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - deployTelemetryEvent = - TelemetryEventName.MICROBIT_COMMAND_DEPLOY_DEVICE; - deployPerformanceTelemetryEvent = - TelemetryEventName.MICROBIT_COMMAND_DEPLOY_DEVICE; - break; - case CONSTANTS.DEVICE_NAME.CLUE: - deployTelemetryEvent = - TelemetryEventName.CLUE_COMMAND_DEPLOY_DEVICE; - deployPerformanceTelemetryEvent = - TelemetryEventName.CLUE_COMMAND_DEPLOY_DEVICE; - break; - } - return { - deployTelemetryEvent: deployTelemetryEvent, - deployPerformanceTelemetryEvent: deployPerformanceTelemetryEvent, - }; - }; - - public handleDeployToDeviceErrorTelemetry = ( - data: string, - device: string - ) => { - let telemetryErrorName: string; - switch (device) { - case CONSTANTS.DEVICE_NAME.CPX: - telemetryErrorName = - TelemetryEventName.CPX_ERROR_PYTHON_DEVICE_PROCESS; - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - telemetryErrorName = - TelemetryEventName.MICROBIT_ERROR_PYTHON_DEVICE_PROCESS; - case CONSTANTS.DEVICE_NAME.CLUE: - telemetryErrorName = - TelemetryEventName.CLUE_ERROR_PYTHON_DEVICE_PROCESS; - } - this.telemetryAI.trackFeatureUsage(telemetryErrorName, { - error: `${data}`, - }); - }; - - public handleDeployToDeviceFinishedTelemetry = ( - message: any, - device: string - ) => { - let successCommandDeployDevice: string; - let errorCommandDeployWithoutDevice: string; - switch (device) { - case CONSTANTS.DEVICE_NAME.CPX: - successCommandDeployDevice = - TelemetryEventName.CPX_SUCCESS_COMMAND_DEPLOY_DEVICE; - errorCommandDeployWithoutDevice = - TelemetryEventName.CPX_ERROR_DEPLOY_WITHOUT_DEVICE; - break; - case CONSTANTS.DEVICE_NAME.MICROBIT: - successCommandDeployDevice = - TelemetryEventName.MICROBIT_SUCCESS_COMMAND_DEPLOY_DEVICE; - errorCommandDeployWithoutDevice = - TelemetryEventName.MICROBIT_ERROR_DEPLOY_WITHOUT_DEVICE; - break; - case CONSTANTS.DEVICE_NAME.CLUE: - successCommandDeployDevice = - TelemetryEventName.CLUE_SUCCESS_COMMAND_DEPLOY_DEVICE; - errorCommandDeployWithoutDevice = - TelemetryEventName.CLUE_ERROR_DEPLOY_WITHOUT_DEVICE; - break; - } - - switch (message.type) { - case "complete": - this.telemetryAI.trackFeatureUsage(successCommandDeployDevice); - break; - case "no-device": - this.telemetryAI.trackFeatureUsage( - errorCommandDeployWithoutDevice - ); - if ( - device === CONSTANTS.DEVICE_NAME.CPX || - device === CONSTANTS.DEVICE_NAME.CLUE - ) { - vscode.window - .showErrorMessage( - CONSTANTS.ERROR.NO_DEVICE, - DialogResponses.HELP - ) - .then((selection: vscode.MessageItem | undefined) => { - if (selection === DialogResponses.HELP) { - const okAction = () => { - let helpLink: string; - let helpTelemetryEvent: string; - if (device === CONSTANTS.DEVICE_NAME.CPX) { - helpLink = CONSTANTS.LINKS.CPX_HELP; - helpTelemetryEvent = - TelemetryEventName.CPX_CLICK_DIALOG_HELP_DEPLOY_TO_DEVICE; - } else if ( - device === CONSTANTS.DEVICE_NAME.CLUE - ) { - helpLink = CONSTANTS.LINKS.CLUE_HELP; - helpTelemetryEvent = - TelemetryEventName.CLUE_CLICK_DIALOG_HELP_DEPLOY_TO_DEVICE; - } - open(helpLink); - this.telemetryAI.trackFeatureUsage( - helpTelemetryEvent - ); - }; - utils.showPrivacyModal( - okAction, - CONSTANTS.INFO.THIRD_PARTY_WEBSITE_ADAFRUIT - ); - } - }); - } else if (device === CONSTANTS.DEVICE_NAME.MICROBIT) { - vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_DEVICE); - } - break; - case "low-python-version": - vscode.window.showErrorMessage( - CONSTANTS.ERROR.LOW_PYTHON_VERSION_FOR_MICROBIT_DEPLOYMENT - ); - break; - default: - console.log( - `Non-state JSON output from the process : ${message}` - ); - break; - } - }; -} +import * as open from "open"; +import * as vscode from "vscode"; +import { CONSTANTS, DialogResponses, TelemetryEventName } from "../constants"; +import * as utils from "../extension_utils/utils"; +import { DeviceSelectionService } from "./deviceSelectionService"; +import TelemetryAI from "../telemetry/telemetryAI"; + +export class TelemetryHandlerService { + private telemetryAI: TelemetryAI; + private deviceSelectionService: DeviceSelectionService; + + constructor( + telemetryAI: TelemetryAI, + deviceSelectionService: DeviceSelectionService + ) { + this.telemetryAI = telemetryAI; + this.deviceSelectionService = deviceSelectionService; + } + + public handleDebuggerTelemetry = () => { + switch (this.deviceSelectionService.getCurrentActiveDevice()) { + case CONSTANTS.DEVICE_NAME.CPX: + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_DEBUGGER_INIT_SUCCESS + ); + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_DEBUGGER_INIT_SUCCESS + ); + break; + default: + break; + } + }; + + public handleDebuggerFailTelemetry = () => { + switch (this.deviceSelectionService.getCurrentActiveDevice()) { + case CONSTANTS.DEVICE_NAME.CPX: + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_DEBUGGER_INIT_FAIL + ); + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_DEBUGGER_INIT_FAIL + ); + break; + default: + break; + } + }; + + public handleButtonPressTelemetry = (buttonState: any) => { + switch (this.deviceSelectionService.getCurrentActiveDevice()) { + case CONSTANTS.DEVICE_NAME.CPX: + this.handleCPXButtonPressTelemetry(buttonState); + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + this.handleMicrobitButtonPressTelemetry(buttonState); + break; + default: + break; + } + }; + + public handleGestureTelemetry = (sensorState: any) => { + switch (this.deviceSelectionService.getCurrentActiveDevice()) { + case CONSTANTS.DEVICE_NAME.CPX: + this.handleCPXGestureTelemetry(sensorState); + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + break; + default: + break; + } + }; + + public handleSensorTelemetry = (sensor: string) => { + switch (this.deviceSelectionService.getCurrentActiveDevice()) { + case CONSTANTS.DEVICE_NAME.CPX: + this.handleCPXSensorTelemetry(sensor); + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + this.handleMicrobitSensorTelemetry(sensor); + break; + default: + break; + } + }; + + public handleCPXButtonPressTelemetry = (buttonState: any) => { + if (buttonState.button_a && buttonState.button_b) { + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_BUTTON_AB + ); + } else if (buttonState.button_a) { + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_BUTTON_A + ); + } else if (buttonState.button_b) { + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_BUTTON_B + ); + } else if (buttonState.switch) { + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_SWITCH + ); + } + }; + + public handleCPXGestureTelemetry = (sensorState: any) => { + if (sensorState.shake) { + this.handleCPXSensorTelemetry("shake"); + } else if (sensorState.touch) { + this.handleCPXSensorTelemetry("touch"); + } + }; + + public handleCPXSensorTelemetry = (sensor: string) => { + switch (sensor) { + case "temperature": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_TEMPERATURE_SENSOR + ); + break; + case "light": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_LIGHT_SENSOR + ); + break; + case "motion_x": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_MOTION_SENSOR + ); + break; + case "motion_y": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_MOTION_SENSOR + ); + break; + case "motion_z": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_MOTION_SENSOR + ); + break; + case "shake": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_SHAKE + ); + break; + case "touch": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_SIMULATOR_CAPACITIVE_TOUCH + ); + break; + } + }; + + public handleMicrobitButtonPressTelemetry = (buttonState: any) => { + if (buttonState.button_a && buttonState.button_b) { + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_SIMULATOR_BUTTON_AB + ); + } else if (buttonState.button_a) { + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_SIMULATOR_BUTTON_A + ); + } else if (buttonState.button_b) { + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_SIMULATOR_BUTTON_B + ); + } + }; + + public handleMicrobitSensorTelemetry = (sensor: string) => { + switch (sensor) { + case "temperature": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_SIMULATOR_TEMPERATURE_SENSOR + ); + break; + case "light": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_SIMULATOR_LIGHT_SENSOR + ); + break; + case "motion_x": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_SIMULATOR_MOTION_SENSOR + ); + break; + case "motion_y": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_SIMULATOR_MOTION_SENSOR + ); + break; + case "motion_z": + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_SIMULATOR_MOTION_SENSOR + ); + break; + } + }; + + public handleNewFileErrorTelemetry = () => { + switch (this.deviceSelectionService.getCurrentActiveDevice()) { + case CONSTANTS.DEVICE_NAME.CPX: + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.CPX_ERROR_COMMAND_NEW_FILE + ); + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + this.telemetryAI.trackFeatureUsage( + TelemetryEventName.MICROBIT_ERROR_COMMAND_NEW_FILE + ); + break; + default: + break; + } + }; + + public getTelemetryEventsForStartingDeployToDevice = (device: string) => { + let deployTelemetryEvent: string; + let deployPerformanceTelemetryEvent: string; + switch (device) { + case CONSTANTS.DEVICE_NAME.CPX: + deployTelemetryEvent = + TelemetryEventName.CPX_COMMAND_DEPLOY_DEVICE; + deployPerformanceTelemetryEvent = + TelemetryEventName.CPX_COMMAND_DEPLOY_DEVICE; + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + deployTelemetryEvent = + TelemetryEventName.MICROBIT_COMMAND_DEPLOY_DEVICE; + deployPerformanceTelemetryEvent = + TelemetryEventName.MICROBIT_COMMAND_DEPLOY_DEVICE; + break; + case CONSTANTS.DEVICE_NAME.CLUE: + deployTelemetryEvent = + TelemetryEventName.CLUE_COMMAND_DEPLOY_DEVICE; + deployPerformanceTelemetryEvent = + TelemetryEventName.CLUE_COMMAND_DEPLOY_DEVICE; + break; + } + return { + deployTelemetryEvent: deployTelemetryEvent, + deployPerformanceTelemetryEvent: deployPerformanceTelemetryEvent, + }; + }; + + public handleDeployToDeviceErrorTelemetry = ( + data: string, + device: string + ) => { + let telemetryErrorName: string; + switch (device) { + case CONSTANTS.DEVICE_NAME.CPX: + telemetryErrorName = + TelemetryEventName.CPX_ERROR_PYTHON_DEVICE_PROCESS; + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + telemetryErrorName = + TelemetryEventName.MICROBIT_ERROR_PYTHON_DEVICE_PROCESS; + case CONSTANTS.DEVICE_NAME.CLUE: + telemetryErrorName = + TelemetryEventName.CLUE_ERROR_PYTHON_DEVICE_PROCESS; + } + this.telemetryAI.trackFeatureUsage(telemetryErrorName, { + error: `${data}`, + }); + }; + + public handleDeployToDeviceFinishedTelemetry = ( + message: any, + device: string + ) => { + let successCommandDeployDevice: string; + let errorCommandDeployWithoutDevice: string; + switch (device) { + case CONSTANTS.DEVICE_NAME.CPX: + successCommandDeployDevice = + TelemetryEventName.CPX_SUCCESS_COMMAND_DEPLOY_DEVICE; + errorCommandDeployWithoutDevice = + TelemetryEventName.CPX_ERROR_DEPLOY_WITHOUT_DEVICE; + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + successCommandDeployDevice = + TelemetryEventName.MICROBIT_SUCCESS_COMMAND_DEPLOY_DEVICE; + errorCommandDeployWithoutDevice = + TelemetryEventName.MICROBIT_ERROR_DEPLOY_WITHOUT_DEVICE; + break; + case CONSTANTS.DEVICE_NAME.CLUE: + successCommandDeployDevice = + TelemetryEventName.CLUE_SUCCESS_COMMAND_DEPLOY_DEVICE; + errorCommandDeployWithoutDevice = + TelemetryEventName.CLUE_ERROR_DEPLOY_WITHOUT_DEVICE; + break; + } + + switch (message.type) { + case "complete": + this.telemetryAI.trackFeatureUsage(successCommandDeployDevice); + break; + case "no-device": + this.telemetryAI.trackFeatureUsage( + errorCommandDeployWithoutDevice + ); + if ( + device === CONSTANTS.DEVICE_NAME.CPX || + device === CONSTANTS.DEVICE_NAME.CLUE + ) { + vscode.window + .showErrorMessage( + CONSTANTS.ERROR.NO_DEVICE, + DialogResponses.HELP + ) + .then((selection: vscode.MessageItem | undefined) => { + if (selection === DialogResponses.HELP) { + const okAction = () => { + let helpLink: string; + let helpTelemetryEvent: string; + if (device === CONSTANTS.DEVICE_NAME.CPX) { + helpLink = CONSTANTS.LINKS.CPX_HELP; + helpTelemetryEvent = + TelemetryEventName.CPX_CLICK_DIALOG_HELP_DEPLOY_TO_DEVICE; + } else if ( + device === CONSTANTS.DEVICE_NAME.CLUE + ) { + helpLink = CONSTANTS.LINKS.CLUE_HELP; + helpTelemetryEvent = + TelemetryEventName.CLUE_CLICK_DIALOG_HELP_DEPLOY_TO_DEVICE; + } + open(helpLink); + this.telemetryAI.trackFeatureUsage( + helpTelemetryEvent + ); + }; + utils.showPrivacyModal( + okAction, + CONSTANTS.INFO.THIRD_PARTY_WEBSITE_ADAFRUIT + ); + } + }); + } else if (device === CONSTANTS.DEVICE_NAME.MICROBIT) { + vscode.window.showErrorMessage(CONSTANTS.ERROR.NO_DEVICE); + } + break; + case "low-python-version": + vscode.window.showErrorMessage( + CONSTANTS.ERROR.LOW_PYTHON_VERSION_FOR_MICROBIT_DEPLOYMENT + ); + break; + default: + console.log( + `Non-state JSON output from the process : ${message}` + ); + break; + } + }; + + public getTelemetryEventsForOpenSimulator = (device: string) => { + let openSimulatorTelemetryEvent: string; + let openSimulatorPerformanceTelemetryEvent: string; + switch (device) { + case CONSTANTS.DEVICE_NAME.CPX: + openSimulatorTelemetryEvent = + TelemetryEventName.CPX_COMMAND_OPEN_SIMULATOR; + openSimulatorPerformanceTelemetryEvent = + TelemetryEventName.CPX_PERFORMANCE_OPEN_SIMULATOR; + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + openSimulatorTelemetryEvent = + TelemetryEventName.MICROBIT_COMMAND_OPEN_SIMULATOR; + openSimulatorPerformanceTelemetryEvent = + TelemetryEventName.MICROBIT_PERFORMANCE_OPEN_SIMULATOR; + break; + case CONSTANTS.DEVICE_NAME.CLUE: + openSimulatorTelemetryEvent = + TelemetryEventName.CLUE_COMMAND_OPEN_SIMULATOR; + openSimulatorPerformanceTelemetryEvent = + TelemetryEventName.CLUE_PERFORMANCE_OPEN_SIMULATOR; + break; + } + return { + openSimulatorTelemetryEvent: openSimulatorTelemetryEvent, + openSimulatorPerformanceTelemetryEvent: openSimulatorPerformanceTelemetryEvent, + }; + }; + + public getTelemetryEventsForNewFile = (device: string) => { + let newFileTelemetryEvent: string; + let newFilePerformanceTelemetryEvent: string; + switch (device) { + case CONSTANTS.DEVICE_NAME.CPX: + newFileTelemetryEvent = + TelemetryEventName.CPX_COMMAND_OPEN_SIMULATOR; + newFilePerformanceTelemetryEvent = + TelemetryEventName.CPX_PERFORMANCE_OPEN_SIMULATOR; + break; + case CONSTANTS.DEVICE_NAME.MICROBIT: + newFileTelemetryEvent = + TelemetryEventName.MICROBIT_COMMAND_OPEN_SIMULATOR; + newFilePerformanceTelemetryEvent = + TelemetryEventName.MICROBIT_PERFORMANCE_OPEN_SIMULATOR; + break; + case CONSTANTS.DEVICE_NAME.CLUE: + newFileTelemetryEvent = + TelemetryEventName.CLUE_COMMAND_OPEN_SIMULATOR; + newFilePerformanceTelemetryEvent = + TelemetryEventName.CLUE_PERFORMANCE_OPEN_SIMULATOR; + break; + } + return { + newFileTelemetryEvent: newFileTelemetryEvent, + newFilePerformanceTelemetryEvent: newFilePerformanceTelemetryEvent, + }; + }; +} diff --git a/src/templates/clue_template.py b/src/templates/clue_template.py new file mode 100644 index 000000000..5f255c0b1 --- /dev/null +++ b/src/templates/clue_template.py @@ -0,0 +1,14 @@ +""" +To learn more about the CLUE and CircuitPython, check this link out: +https://learn.adafruit.com/adafruit-clue/circuitpython + +Find example code for CPX on: +https://blog.adafruit.com/2020/02/12/three-fun-sensor-packed-projects-to-try-on-your-clue-adafruitlearningsystem-adafruit-circuitpython-adafruit/ +""" + +from adafruit_clue import clue + +clue_data = clue.simple_text_display(title="Hello World", title_scale=3) + +while True: + clue_data.show() diff --git a/src/view/components/clue/Clue.spec.tsx b/src/view/components/clue/Clue.spec.tsx new file mode 100644 index 000000000..c69b4ebe7 --- /dev/null +++ b/src/view/components/clue/Clue.spec.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { IntlProvider } from "react-intl"; +import * as testRenderer from "react-test-renderer"; +import { Clue } from "./Clue"; + +describe("Clue component", () => { + it("should render correctly", () => { + const component = testRenderer + .create( + + + + ) + .toJSON(); + expect(component).toMatchSnapshot(); + }); + + it("should render without crashing", () => { + const div = document.createElement("div"); + ReactDOM.render( + + + , + div + ); + ReactDOM.unmountComponentAtNode(div); + }); +}); diff --git a/src/view/components/clue/ClueImage.tsx b/src/view/components/clue/ClueImage.tsx index 00e0d9a3b..402572c95 100644 --- a/src/view/components/clue/ClueImage.tsx +++ b/src/view/components/clue/ClueImage.tsx @@ -5,7 +5,6 @@ import * as React from "react"; import { VIEW_STATE } from "../../constants"; import CONSTANTS, { BUTTON_STYLING_CLASSES } from "../../constants"; import { ViewStateContext } from "../../context"; -import "../../styles/Microbit.css"; import { ClueSvg, IRefObject } from "./Clue_svg"; interface EventTriggers { @@ -17,6 +16,7 @@ interface EventTriggers { interface IProps { eventTriggers: EventTriggers; displayMessage: string; + neopixel: number[]; } const BUTTON_CLASSNAME = { @@ -85,6 +85,7 @@ export class ClueImage extends React.Component { ); } diff --git a/src/view/components/clue/ClueSimulator.tsx b/src/view/components/clue/ClueSimulator.tsx index 9e1c87840..3fd03cfc2 100644 --- a/src/view/components/clue/ClueSimulator.tsx +++ b/src/view/components/clue/ClueSimulator.tsx @@ -3,8 +3,9 @@ import { AB_BUTTONS_KEYS, // DEVICE_LIST_KEY, CONSTANTS, - WEBVIEW_MESSAGES, DEFAULT_IMG_CLUE, + DEVICE_LIST_KEY, + WEBVIEW_MESSAGES, } from "../../constants"; import PlayLogo from "../../svgs/play_svg"; import StopLogo from "../../svgs/stop_svg"; @@ -12,9 +13,10 @@ import { sendMessage } from "../../utils/MessageUtils"; import ActionBar from "../simulator/ActionBar"; import { BUTTONS_KEYS, ClueImage } from "./ClueImage"; -const DEFAULT_CLUE_STATE: IClueState = { +export const DEFAULT_CLUE_STATE: IClueState = { buttons: { button_a: false, button_b: false }, displayMessage: DEFAULT_IMG_CLUE, + neopixel: [0, 0, 0], }; interface IState { @@ -29,6 +31,7 @@ interface IState { interface IClueState { buttons: { button_a: boolean; button_b: boolean }; displayMessage: string; + neopixel: number[]; } export class ClueSimulator extends React.Component { private imageRef: React.RefObject = React.createRef(); @@ -46,7 +49,9 @@ export class ClueSimulator extends React.Component { } handleMessage = (event: any): void => { const message = event.data; - + if (message.active_device !== DEVICE_LIST_KEY.CLUE) { + return; + } switch (message.command) { case "reset-state": this.setState({ @@ -55,12 +60,25 @@ export class ClueSimulator extends React.Component { }); break; case "set-state": - this.setState({ - clue: { - ...this.state.clue, - displayMessage: message.state.display_base64, - }, - }); + console.log( + `message received ${JSON.stringify(message.state)}` + ); + if (message.state.display_base64) { + this.setState({ + clue: { + ...this.state.clue, + displayMessage: message.state.display_base64, + }, + }); + } else if (message.state.pixels) { + this.setState({ + clue: { + ...this.state.clue, + neopixel: message.state.pixels, + }, + }); + } + break; case "activate-play": const newRunningFile = this.state.currently_selected_file; @@ -121,6 +139,7 @@ export class ClueSimulator extends React.Component { onKeyEvent: this.onKeyEvent, }} displayMessage={this.state.clue.displayMessage} + neopixel={this.state.clue.neopixel} /> ; } interface IProps { displayImage: string; + neopixel: number[]; } - export class ClueSvg extends React.Component { private svgRef: React.RefObject = React.createRef(); + private neopixel: React.RefObject = React.createRef(); + private pixelStopGradient: React.RefObject< + SVGStopElement + > = React.createRef(); private buttonRefs: IRefObject = { BTN_A: React.createRef(), @@ -32,9 +38,11 @@ export class ClueSvg extends React.Component { } componentDidMount() { this.updateDisplay(); + this.updateNeopixel(); } componentDidUpdate() { this.updateDisplay(); + this.updateNeopixel(); } render() { @@ -42,14 +50,31 @@ export class ClueSvg extends React.Component {
- + + + + + + { - - + + { transform="translate(-49.27 -48.48)" /> - + + + + + + + + + { rx="1.79" /> { cy="130.74" r="3.23" /> + + + + A+B + { y={28} width={176} height={152} - href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQICAQEBAQMCAgICAwMEBAMDAwMEBAYFBAQFBAMDBQcFBQYGBgYGBAUHBwcGBwYGBgb/2wBDAQEBAQEBAQMCAgMGBAMEBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgb/wAARCAJYAlgDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD/AD/6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP//Z" /> { rx="18.28" /> + + Neopixel + + +
); } + private updateDisplay() { if (this.displayRef.current && this.props.displayImage) { this.displayRef.current.setAttribute( @@ -949,4 +1049,31 @@ export class ClueSvg extends React.Component { ); } } + + private updateNeopixel() { + const { neopixel } = this.props; + const rgbColor = `rgb(${neopixel[0] + + (255 - neopixel[0]) * CONSTANTS.LED_TINT_FACTOR}, + ${neopixel[1] + + (255 - neopixel[1]) * CONSTANTS.LED_TINT_FACTOR},${neopixel[2] + + (255 - neopixel[2]) * CONSTANTS.LED_TINT_FACTOR})`; + + if (this.neopixel.current) { + this.neopixel.current.setAttribute("fill", rgbColor); + } + if (this.pixelStopGradient.current) { + if (neopixel === DEFAULT_CLUE_STATE.neopixel) { + this.pixelStopGradient.current.setAttribute( + "stop-opacity", + "0" + ); + } else { + this.pixelStopGradient.current.setAttribute( + "stop-opacity", + "1" + ); + } + this.pixelStopGradient.current.setAttribute("stop-color", rgbColor); + } + } } diff --git a/src/view/components/clue/__snapshots__/Clue.spec.tsx.snap b/src/view/components/clue/__snapshots__/Clue.spec.tsx.snap new file mode 100644 index 000000000..099a3de7a --- /dev/null +++ b/src/view/components/clue/__snapshots__/Clue.spec.tsx.snap @@ -0,0 +1,1160 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Clue component should render correctly 1`] = ` +Array [ +
+
+ The simulator will run the .py file you have focused on. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A+B + + + + + + + + LIGHT + + + GESTURE + + + P + + R + + + O + + + XIMI + + + T + + + Y + + + + + + + + + + + + + + Neopixel + + + + +
+
+
+ + +
+
, +
+
+
+
+
, +] +`; diff --git a/src/view/components/cpx/Cpx.spec.tsx b/src/view/components/cpx/Cpx.spec.tsx index deb6da57d..24c53d551 100644 --- a/src/view/components/cpx/Cpx.spec.tsx +++ b/src/view/components/cpx/Cpx.spec.tsx @@ -4,7 +4,7 @@ import { IntlProvider } from "react-intl"; import * as testRenderer from "react-test-renderer"; import { Cpx } from "./Cpx"; -describe("Device component", () => { +describe("CPX component", () => { it("should render correctly", () => { const component = testRenderer .create( diff --git a/src/view/components/cpx/__snapshots__/Cpx.spec.tsx.snap b/src/view/components/cpx/__snapshots__/Cpx.spec.tsx.snap index 5120fb0a4..3a41b60f6 100644 --- a/src/view/components/cpx/__snapshots__/Cpx.spec.tsx.snap +++ b/src/view/components/cpx/__snapshots__/Cpx.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Device component should render correctly 1`] = ` +exports[`CPX component should render correctly 1`] = ` Array [
; } diff --git a/src/view/constants.ts b/src/view/constants.ts index 1a4351b43..9358452ee 100644 --- a/src/view/constants.ts +++ b/src/view/constants.ts @@ -46,6 +46,7 @@ export const CONSTANTS = { "The simulator will run the .py file you have focused on.", SIMULATOR_BUTTON_WIDTH: 60, TOOLBAR_INFO: `Explore what's on the board:`, + LED_TINT_FACTOR: 0.5, }; export const AB_BUTTONS_KEYS = { BTN_A: "BTN_A", diff --git a/src/view/styles/Clue.css b/src/view/styles/Clue.css deleted file mode 100644 index 45a9e3d92..000000000 --- a/src/view/styles/Clue.css +++ /dev/null @@ -1,131 +0,0 @@ -.cls-1 { - fill: #097054; -} -.cls-1, -.cls-13, -.cls-14, -.cls-15, -.cls-23, -.cls-9 { - stroke: #000; -} -.cls-1, -.cls-11, -.cls-12, -.cls-13, -.cls-14, -.cls-15, -.cls-17, -.cls-22, -.cls-23, -.cls-3, -.cls-4, -.cls-5, -.cls-6, -.cls-9 { - stroke-miterlimit: 10; -} -.cls-1, -.cls-23 { - stroke-width: 1.99px; -} -.cls-18, -.cls-2 { - fill: #fff; -} -.cls-11, -.cls-12, -.cls-17, -.cls-23, -.cls-3, -.cls-4, -.cls-5, -.cls-6, -.cls-7, -.cls-8 { - fill: none; -} -.cls-11, -.cls-12, -.cls-17, -.cls-3, -.cls-4, -.cls-5, -.cls-6, -.cls-7, -.cls-8 { - stroke: #fff; -} -.cls-3 { - stroke-width: 2px; -} -.cls-12, -.cls-17, -.cls-4, -.cls-5, -.cls-6, -.cls-7, -.cls-8 { - stroke-linecap: round; -} -.cls-4, -.cls-7 { - stroke-width: 1.63px; -} -.cls-5 { - stroke-width: 1.41px; -} -.cls-6 { - stroke-width: 1.6px; -} -.cls-7, -.cls-8 { - stroke-linejoin: round; -} -.cls-8 { - stroke-width: 1.63px; -} -.cls-10, -.cls-15, -.cls-9 { - fill: #7e7272; -} -.cls-22, -.cls-9 { - stroke-width: 1.5px; -} -.cls-12 { - stroke-width: 1.71px; -} -.cls-13 { - fill: #6599ff; -} -.cls-13, -.cls-14, -.cls-15 { - stroke-width: 0.25px; -} -.cls-16 { - fill: #ffde00; -} -.cls-17 { - stroke-width: 2.02px; -} -.cls-18 { - font-size: 7px; - font-family: SegoeUI-Bold, Segoe UI; - font-weight: 700; - letter-spacing: -0.02em; -} -.cls-19 { - letter-spacing: -0.03em; -} -.cls-20 { - letter-spacing: -0.05em; -} -.cls-21 { - letter-spacing: 0em; -} -.cls-22 { - stroke: #7e7272; -} diff --git a/src/view/styles/Microbit.css b/src/view/styles/SimulatorSvg.css similarity index 61% rename from src/view/styles/Microbit.css rename to src/view/styles/SimulatorSvg.css index f96655fa0..9e9deb4fb 100644 --- a/src/view/styles/Microbit.css +++ b/src/view/styles/SimulatorSvg.css @@ -24,7 +24,7 @@ svg.sim.grayscale { } .sim-text-outside { font-size: 25px; - fill: var(--vscode-descriptionForeground); + fill: var(--vscode-foreground); } .sim-board, .sim-display, @@ -38,6 +38,9 @@ sim-button { .sim-button-outer:active { fill: orange; } +.sim-button-outer { + fill: #7e7272; +} .sim-button-key-press { fill: orange; @@ -174,3 +177,135 @@ sim-button { -webkit-user-select: none; -ms-user-select: none; } +.sim-button { + stroke: none; +} +.sim-button:active { + stroke: none; +} +.cls-1 { + fill: #097054; +} +.cls-1, +.cls-14, +.cls-23, +.cls-9 { + stroke: #000; +} +.cls-1, +.cls-11, +.cls-12, +.cls-14, +.cls-17, +.cls-22, +.cls-23, +.cls-3, +.cls-4, +.cls-5, +.cls-6, +.cls-9 { + stroke-miterlimit: 10; +} +.cls-1, +.cls-23 { + stroke-width: 1.99px; +} +.cls-18, +.cls-2 { + fill: #fff; +} +.cls-11, +.cls-12, +.cls-17, +.cls-23, +.cls-3, +.cls-4, +.cls-5, +.cls-6, +.cls-7, +.cls-8 { + fill: none; +} +.cls-11, +.cls-12, +.cls-17, +.cls-3, +.cls-4, +.cls-5, +.cls-6, +.cls-7, +.cls-8 { + stroke: #fff; +} +.cls-3 { + stroke-width: 2px; +} +.cls-12, +.cls-17, +.cls-4, +.cls-5, +.cls-6, +.cls-7, +.cls-8 { + stroke-linecap: round; +} +.cls-4, +.cls-7 { + stroke-width: 1.63px; +} +.cls-5 { + stroke-width: 1.41px; +} +.cls-6 { + stroke-width: 1.6px; +} +.cls-7, +.cls-8 { + stroke-linejoin: round; +} +.cls-8 { + stroke-width: 1.63px; +} +.cls-10, +.cls-9 { + fill: #7e7272; +} +.cls-22, +.cls-9 { + stroke-width: 1.5px; +} +.cls-12 { + stroke-width: 1.71px; +} + +.cls-14 { + stroke-width: 0.25px; +} +.cls-16 { + fill: #ffde00; +} +.cls-17 { + stroke-width: 2.02px; +} +.cls-18 { + font-size: 7px; + font-family: SegoeUI-Bold, Segoe UI; + font-weight: 700; + letter-spacing: -0.02em; +} +.cls-19 { + letter-spacing: -0.03em; +} +.cls-20 { + letter-spacing: -0.05em; +} +.cls-21 { + letter-spacing: 0em; +} +.cls-22 { + stroke: #7e7272; +} +.sim-text-outside-clue { + font-size: 14px; + fill: var(--vscode-foreground); +}