From 333b5964fcf528b029694d16ace12ca5115cf2d5 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Tue, 28 Jan 2025 13:01:06 -0600 Subject: [PATCH 01/25] Added Sphero Functionality --- README.md | 112 ++++++++++++++++++++++++++++++++++++++++++++-- SpheroServer.py | 76 +++++++++++++++++++++++++++++++ src/main/index.js | 41 ++++++++++++++--- 3 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 SpheroServer.py diff --git a/README.md b/README.md index e05930a..196f2d0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,111 @@ -# Readme +# Neuroscope -TODO +This project builds on [this boiler plate](https://github.com/a133xz/electron-vuejs-parcel-boilerplate) and adds functionality to control a Sphero Bolt using WebSocket commands. -Project builds on [this boiler plate](https://github.com/a133xz/electron-vuejs-parcel-boilerplate). +## Getting Started + +### Prerequisites + +- Node.js +- npm or yarn +- Python 3.x +- `websockets` Python package + +### Installation + +1. Clone the repository: + ```sh + git clone https://github.com/yourusername/neuroscope.git + cd neuroscope + ``` + +2. Install the dependencies: + ```sh + yarn install + ``` + +3. Start the Sphero server: + ```sh + python SpheroServer.py + ``` + +4. Start the Electron application: + ```sh + yarn serve + ``` + +## Usage + +### Controlling the Sphero + +The application sends WebSocket commands to control the Sphero Bolt. The following commands are available: + +- **drone-up**: Moves the Sphero up. +- **drone-down**: Moves the Sphero down. +- **drone-forward**: Moves the Sphero forward. + +### Code Changes + +The main changes are in the `index.js` file: + +1. **WebSocket Setup**: + ```javascript + const WebSocket = require('ws'); + const ws = new WebSocket('ws://localhost:8765'); + + ws.on('open', function open() { + console.log('WebSocket connection opened'); + }); + + ws.on('error', function error(err) { + console.error('WebSocket error:', err); + }); + ``` + +2. **Command Sending with Debounce**: + ```javascript + let lastCommandTime = 0; + const commandInterval = 3000; // 3 seconds + + function sendCommand(command) { + const currentTime = Date.now(); + if (currentTime - lastCommandTime >= commandInterval) { + ws.send(JSON.stringify(command)); + console.log("Command sent:", command); + lastCommandTime = currentTime; + } else { + console.log("Command skipped to avoid spamming:", command); + } + } + ``` + +3. **IPC Event Handlers**: + ```javascript + ipcMain.on("drone-up", (event, response) => { + let recent_val = parseInt(response); + let upVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; + console.log("drone up", upVal, "sent", response); + const moveCommand = { action: "move", heading: 0, speed: upVal, duration: 1 }; + sendCommand(moveCommand); + }); + + ipcMain.on("drone-down", (event, response) => { + let recent_val = parseInt(response); + let downVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; + console.log("drone down", downVal, "sent", response); + const moveCommand = { action: "move", heading: 180, speed: downVal, duration: 1 }; + sendCommand(moveCommand); + }); + + ipcMain.on("drone-forward", (event, response) => { + let recent_val = parseInt(response); + let forwardVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; + console.log("drone forward", forwardVal, "sent", response); + const moveCommand = { action: "move", heading: 90, speed: forwardVal, duration: 1 }; + sendCommand(moveCommand); + }); + ``` + +## Work in Progress + +This project is a work in progress. The current implementation allows basic control of the Sphero Bolt using WebSocket commands. Further improvements and features are planned for future updates. diff --git a/SpheroServer.py b/SpheroServer.py new file mode 100644 index 0000000..70ba4e2 --- /dev/null +++ b/SpheroServer.py @@ -0,0 +1,76 @@ +import asyncio +import websockets +import json +from spherov2 import scanner +from spherov2.sphero_edu import SpheroEduAPI +from spherov2.types import Color + +# Function to find the Sphero BOLT synchronously +async def find_toy(): + try: + print("Scanning for Sphero BOLT...") + # Wrapping the synchronous `find_toy` method in a coroutine + loop = asyncio.get_event_loop() + toy = await loop.run_in_executor(None, scanner.find_toy) + if not toy: + print("No Sphero BOLT found.") + return None + print("Sphero BOLT found!") + return toy + except Exception as e: + print(f"Error during toy scanning: {e}") + return None + +# Command handler for Sphero +async def handle_command(droid, command): + try: + if command["action"] == "led_on": + color = command.get("color", {"r": 0, "g": 255, "b": 0}) # Default green + print(f"Turning LED on with color: {color}") + droid.set_main_led(Color(r=color["r"], g=color["g"], b=color["b"])) + elif command["action"] == "led_off": + print("Turning LED off") + droid.set_main_led(Color(r=0, g=0, b=0)) # Turn off LED + elif command["action"] == "move": + print("Moving Sphero BOLT") + heading = command.get("heading", 0) # Default to up + speed = command.get("speed", 60) + duration = command.get("duration", 2) + droid.roll(heading, speed, duration) + else: + print(f"Unknown command: {command}") + except Exception as e: + print(f"Error handling command {command}: {e}") + raise e # Propagate the exception for better debugging + +async def handle_connection(websocket, path): + print("Client connected") + toy = await find_toy() # Find the Sphero BOLT asynchronously + if not toy: + print("Sphero BOLT not found!") + await websocket.send(json.dumps({"error": "Sphero BOLT not found!"})) + return + + with SpheroEduAPI(toy) as droid: + droid.set_main_led(Color(r=0, g=0, b=255)) # Set LED to blue for idle + try: + async for message in websocket: + print(f"Received message: {message}") + try: + command = json.loads(message) + await handle_command(droid, command) + except json.JSONDecodeError: + print(f"Invalid JSON received: {message}") + await websocket.send(json.dumps({"error": "Invalid JSON format"})) + except websockets.exceptions.ConnectionClosed: + print("Client disconnected") + except Exception as e: + print(f"Unexpected server error: {e}") + +async def main(): + print("Starting WebSocket server on ws://localhost:8765") + async with websockets.serve(handle_connection, "localhost", 8765): + await asyncio.Future() # Keep the server running indefinitely + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/main/index.js b/src/main/index.js index c2a3429..ed33d15 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,6 +1,7 @@ const { app, BrowserWindow, ipcMain, dialog } = require("electron"); const path = require("path"); const tello = require("./tello.js"); +const WebSocket = require('ws'); const isProduction = process.env.NODE_ENV === "production" || !process || !process.env || !process.env.NODE_ENV; @@ -56,7 +57,7 @@ async function createWindow() { // Reload try { require("electron-reloader")(module); - } catch (_) {} + } catch (_) { } // Errors are thrown if the dev tools are opened // before the DOM is ready win.webContents.once("dom-ready", async () => { @@ -125,26 +126,52 @@ async function createWindow() { } }); + const ws = new WebSocket('ws://localhost:8765'); + + ws.on('open', function open() { + console.log('WebSocket connection opened'); + }); + + ws.on('error', function error(err) { + console.error('WebSocket error:', err); + }); + + let lastCommandTime = 0; + const commandInterval = 3000; // 3 seconds + + function sendCommand(command) { + const currentTime = Date.now(); + if (currentTime - lastCommandTime >= commandInterval) { + ws.send(JSON.stringify(command)); + console.log("Command sent:", command); + lastCommandTime = currentTime; + } else { + console.log("Command skipped to avoid spamming:", command); + } + } + ipcMain.on("drone-up", (event, response) => { let recent_val = parseInt(response); let upVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("drone up", upVal, "sent", response); - tello.up(upVal); + const moveCommand = { action: "move", heading: 0, speed: upVal, duration: 1 }; + sendCommand(moveCommand); }); ipcMain.on("drone-down", (event, response) => { let recent_val = parseInt(response); let downVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("drone down", downVal, "sent", response); - tello.down(downVal); + const moveCommand = { action: "move", heading: 180, speed: downVal, duration: 1 }; + sendCommand(moveCommand); }); ipcMain.on("drone-forward", (event, response) => { let recent_val = parseInt(response); - _maxSpeed = 80; - let val = recent_val > _maxSpeed ? _maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; - console.log("drone forward", val, "sent", response); - tello.forward(val); + let forwardVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; + console.log("drone forward", forwardVal, "sent", response); + const moveCommand = { action: "move", heading: 90, speed: forwardVal, duration: 1 }; + sendCommand(moveCommand); }); ipcMain.on("drone-back", (event, response) => { From 084a74c5e09052636c892830f50b2a36adfc5440 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Tue, 25 Feb 2025 16:57:51 -0600 Subject: [PATCH 02/25] More Controls --- src/main/index.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/index.js b/src/main/index.js index ed33d15..e6c6af5 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -154,7 +154,7 @@ async function createWindow() { let recent_val = parseInt(response); let upVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("drone up", upVal, "sent", response); - const moveCommand = { action: "move", heading: 0, speed: upVal, duration: 1 }; + const moveCommand = { action: "move", heading: 90, speed: upVal, duration: 3 }; sendCommand(moveCommand); }); @@ -162,7 +162,7 @@ async function createWindow() { let recent_val = parseInt(response); let downVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("drone down", downVal, "sent", response); - const moveCommand = { action: "move", heading: 180, speed: downVal, duration: 1 }; + const moveCommand = { action: "move", heading: 270, speed: downVal, duration: 3 }; sendCommand(moveCommand); }); @@ -170,15 +170,19 @@ async function createWindow() { let recent_val = parseInt(response); let forwardVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("drone forward", forwardVal, "sent", response); - const moveCommand = { action: "move", heading: 90, speed: forwardVal, duration: 1 }; + const moveCommand = { action: "move", heading: 0, speed: forwardVal, duration: 1 }; sendCommand(moveCommand); }); ipcMain.on("drone-back", (event, response) => { let recent_val = parseInt(response); - let val = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; - console.log("drone back", val, "sent", response); - tello.back(val); + let backVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; + console.log("drone back", backVal, "sent", response); + // let val = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; + // console.log("drone back", val, "sent", response); + const moveCommand = { action: "move", heading: 180, speed: backVal, duration: 1 }; + sendCommand(moveCommand); + // tello.back(val); }); ipcMain.on("cw", (event, response) => { From 6da0b3e5d83b8659d368358894937ed47609e5ad Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Mon, 31 Mar 2025 11:33:39 -0500 Subject: [PATCH 03/25] changed block names to fit project forward = Right down = Left --- SpheroServer.py | 2 +- SpheroTest.py | 31 +++++++++++++++++++++++++++++++ src/main/index.js | 8 ++++---- src/renderer/js/customblock.js | 4 ++-- 4 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 SpheroTest.py diff --git a/SpheroServer.py b/SpheroServer.py index 70ba4e2..032c0c3 100644 --- a/SpheroServer.py +++ b/SpheroServer.py @@ -43,7 +43,7 @@ async def handle_command(droid, command): print(f"Error handling command {command}: {e}") raise e # Propagate the exception for better debugging -async def handle_connection(websocket, path): +async def handle_connection(websocket): print("Client connected") toy = await find_toy() # Find the Sphero BOLT asynchronously if not toy: diff --git a/SpheroTest.py b/SpheroTest.py new file mode 100644 index 0000000..5d29f06 --- /dev/null +++ b/SpheroTest.py @@ -0,0 +1,31 @@ +from spherov2 import scanner +from spherov2.sphero_edu import SpheroEduAPI +from spherov2.types import Color + +def main(): + print("Scanning for Sphero BOLT...") + toy = scanner.find_toy() # Synchronously find the Sphero BOLT + if not toy: + print("No Sphero BOLT found!") + return + + print("Sphero BOLT found! Connecting...") + with SpheroEduAPI(toy) as droid: + print("Connected to Sphero BOLT!") + + # Set LED color to green + print("Setting LED color to green...") + droid.set_main_led(Color(r=0, g=255, b=0)) + + # Move forward with heading 0, speed 60, for 2 seconds + print("Moving forward...") + droid.roll(heading=0, speed=60, duration=2) + + # Set LED color to red after moving + print("Setting LED color to red...") + droid.set_main_led(Color(r=255, g=0, b=0)) + + print("Done!") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/main/index.js b/src/main/index.js index e6c6af5..84ca2b2 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -152,16 +152,16 @@ async function createWindow() { ipcMain.on("drone-up", (event, response) => { let recent_val = parseInt(response); - let upVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; - console.log("drone up", upVal, "sent", response); - const moveCommand = { action: "move", heading: 90, speed: upVal, duration: 3 }; + let rightVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; + console.log("Sphero right", rightVal, "sent", response); + const moveCommand = { action: "move", heading: 90, speed: rightVal, duration: 3 }; sendCommand(moveCommand); }); ipcMain.on("drone-down", (event, response) => { let recent_val = parseInt(response); let downVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; - console.log("drone down", downVal, "sent", response); + console.log("Sphero Left", downVal, "sent", response); const moveCommand = { action: "move", heading: 270, speed: downVal, duration: 3 }; sendCommand(moveCommand); }); diff --git a/src/renderer/js/customblock.js b/src/renderer/js/customblock.js index ab26ff1..9d4b98c 100644 --- a/src/renderer/js/customblock.js +++ b/src/renderer/js/customblock.js @@ -212,7 +212,7 @@ javascriptGenerator.forBlock["wait_seconds"] = function (block) { /* droneUp() */ var droneUp = { type: "drone_up", - message0: "up %1 cm", + message0: "Right %1 cm", args0: [{ type: "input_value", name: "value", check: "Number" }], previousStatement: null, nextStatement: null, @@ -236,7 +236,7 @@ javascriptGenerator.forBlock["drone_up"] = function (block, generator) { /* droneDown() */ var droneDown = { type: "drone_down", - message0: "down %1 cm", + message0: "Left %1 cm", args0: [{ type: "input_value", name: "value", check: "Number" }], previousStatement: null, nextStatement: null, From f96cd4a0b4251a7df6aa672cecf1ba5c97234889 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Sun, 25 May 2025 11:01:08 -0400 Subject: [PATCH 04/25] Start of Vex Branch Added new blocks (Move and Color) Only Forward command is working with vex WORK IN PROGRESS --- SpheroServer.py | 76 --------------------------------- SpheroTest.py | 31 -------------- VEXServer.py | 68 +++++++++++++++++++++++++++++ VexTest.py | 50 ++++++++++++++++++++++ requirements.txt | 3 ++ src/main/index copy.js | 2 +- src/main/index.js | 63 +++++++++++++++++---------- src/main/preload.js | 3 +- src/renderer/js/blockly-main.js | 18 +++++++- src/renderer/js/customblock.js | 53 +++++++++++++++++++++++ src/renderer/js/main.js | 20 +++++++++ 11 files changed, 254 insertions(+), 133 deletions(-) delete mode 100644 SpheroServer.py delete mode 100644 SpheroTest.py create mode 100644 VEXServer.py create mode 100644 VexTest.py create mode 100644 requirements.txt diff --git a/SpheroServer.py b/SpheroServer.py deleted file mode 100644 index 032c0c3..0000000 --- a/SpheroServer.py +++ /dev/null @@ -1,76 +0,0 @@ -import asyncio -import websockets -import json -from spherov2 import scanner -from spherov2.sphero_edu import SpheroEduAPI -from spherov2.types import Color - -# Function to find the Sphero BOLT synchronously -async def find_toy(): - try: - print("Scanning for Sphero BOLT...") - # Wrapping the synchronous `find_toy` method in a coroutine - loop = asyncio.get_event_loop() - toy = await loop.run_in_executor(None, scanner.find_toy) - if not toy: - print("No Sphero BOLT found.") - return None - print("Sphero BOLT found!") - return toy - except Exception as e: - print(f"Error during toy scanning: {e}") - return None - -# Command handler for Sphero -async def handle_command(droid, command): - try: - if command["action"] == "led_on": - color = command.get("color", {"r": 0, "g": 255, "b": 0}) # Default green - print(f"Turning LED on with color: {color}") - droid.set_main_led(Color(r=color["r"], g=color["g"], b=color["b"])) - elif command["action"] == "led_off": - print("Turning LED off") - droid.set_main_led(Color(r=0, g=0, b=0)) # Turn off LED - elif command["action"] == "move": - print("Moving Sphero BOLT") - heading = command.get("heading", 0) # Default to up - speed = command.get("speed", 60) - duration = command.get("duration", 2) - droid.roll(heading, speed, duration) - else: - print(f"Unknown command: {command}") - except Exception as e: - print(f"Error handling command {command}: {e}") - raise e # Propagate the exception for better debugging - -async def handle_connection(websocket): - print("Client connected") - toy = await find_toy() # Find the Sphero BOLT asynchronously - if not toy: - print("Sphero BOLT not found!") - await websocket.send(json.dumps({"error": "Sphero BOLT not found!"})) - return - - with SpheroEduAPI(toy) as droid: - droid.set_main_led(Color(r=0, g=0, b=255)) # Set LED to blue for idle - try: - async for message in websocket: - print(f"Received message: {message}") - try: - command = json.loads(message) - await handle_command(droid, command) - except json.JSONDecodeError: - print(f"Invalid JSON received: {message}") - await websocket.send(json.dumps({"error": "Invalid JSON format"})) - except websockets.exceptions.ConnectionClosed: - print("Client disconnected") - except Exception as e: - print(f"Unexpected server error: {e}") - -async def main(): - print("Starting WebSocket server on ws://localhost:8765") - async with websockets.serve(handle_connection, "localhost", 8765): - await asyncio.Future() # Keep the server running indefinitely - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/SpheroTest.py b/SpheroTest.py deleted file mode 100644 index 5d29f06..0000000 --- a/SpheroTest.py +++ /dev/null @@ -1,31 +0,0 @@ -from spherov2 import scanner -from spherov2.sphero_edu import SpheroEduAPI -from spherov2.types import Color - -def main(): - print("Scanning for Sphero BOLT...") - toy = scanner.find_toy() # Synchronously find the Sphero BOLT - if not toy: - print("No Sphero BOLT found!") - return - - print("Sphero BOLT found! Connecting...") - with SpheroEduAPI(toy) as droid: - print("Connected to Sphero BOLT!") - - # Set LED color to green - print("Setting LED color to green...") - droid.set_main_led(Color(r=0, g=255, b=0)) - - # Move forward with heading 0, speed 60, for 2 seconds - print("Moving forward...") - droid.roll(heading=0, speed=60, duration=2) - - # Set LED color to red after moving - print("Setting LED color to red...") - droid.set_main_led(Color(r=255, g=0, b=0)) - - print("Done!") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/VEXServer.py b/VEXServer.py new file mode 100644 index 0000000..fba0b5c --- /dev/null +++ b/VEXServer.py @@ -0,0 +1,68 @@ +import asyncio +import websockets +import json +from vex import * +from vex.vex_globals import * + +# Robot initialization for AIM platform +robot = Robot() + +color_list = [ + RED, GREEN, BLUE, WHITE, YELLOW, ORANGE, PURPLE, CYAN +] + +for color in color_list: + robot.led.on(ALL_LEDS, color) + wait(1, SECONDS) + +robot.led.off(ALL_LEDS) + +# Command handler for VEX AIM +# Made 'path' optional so it works with the current websockets API +async def handle_command(websocket, path=None): + try: + async for message in websocket: + command = json.loads(message) + action = command.get("action", "") + if action == "led_on": + color_name = command.get("color", "BLUE") + # Map string color names to vex.Color constants + color_map = { + "RED": RED, + "GREEN": GREEN, + "BLUE": BLUE, + "WHITE": WHITE, + "YELLOW": YELLOW, + "ORANGE": ORANGE, + "PURPLE": PURPLE, + "CYAN": CYAN, + } + color = color_map.get(color_name.upper(), BLUE) # Default to BLUE if not found + print(f"Turning LED on with color: {color_name}") + robot.led.on(ALL_LEDS, color) + # Send a response back to the client + await websocket.send(json.dumps({"status": "success", "action": "led_on", "color": color_name})) + elif action == "move": + distance = command.get("distance", 100) + heading = command.get("heading", 0) + print(f"Received move command: {command}") + print(f"Moving robot: Distance={distance}, Heading={heading}") + robot.move_for(distance, heading) + # Send a response back to the client + await websocket.send(json.dumps({"status": "success", "action": "move", "distance": distance, "heading": heading})) + else: + print(f"Unknown command: {command}") + # Send an error response back to the client + await websocket.send(json.dumps({"status": "error", "message": "Unknown command"})) + except Exception as e: + print(f"Error handling command: {e}") + await websocket.send(json.dumps({"status": "error", "message": str(e)})) + +async def main(): + port = 8765 + print(f"Starting WebSocket server on ws://127.0.0.1:{port}") + async with websockets.serve(handle_command, "127.0.0.1", port): + await asyncio.Future() # run forever + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/VexTest.py b/VexTest.py new file mode 100644 index 0000000..4eba56c --- /dev/null +++ b/VexTest.py @@ -0,0 +1,50 @@ +import asyncio +import websockets +import json + +async def send_test_commands(): + uri = "ws://localhost:8765" # WebSocket server address + try: + print(f"Connecting to WebSocket server at {uri}...") + async with websockets.connect(uri) as websocket: + print("Connected to WebSocket server!") + + # Test 1: Turn LED on with a specific color + led_command = { + "action": "led_on", + "color": "GREEN" # Change to any color: "RED", "BLUE", etc. + } + print(f"Sending LED command: {led_command}") + await websocket.send(json.dumps(led_command)) + response = await websocket.recv() + print(f"Response from server: {response}") + + # Test 2: Move the robot forward + move_command = { + "action": "move", + "distance": 100, # Distance in cm + "heading": 0 # Heading in degrees + } + print(f"Sending move command: {move_command}") + await websocket.send(json.dumps(move_command)) + response = await websocket.recv() + print(f"Response from server: {response}") + + # Test 3: Turn LED off + led_off_command = { + "action": "led_on", + "color": "OFF" # Assuming "OFF" turns off the LEDs + } + print(f"Sending LED off command: {led_off_command}") + await websocket.send(json.dumps(led_off_command)) + response = await websocket.recv() + print(f"Response from server: {response}") + + except Exception as e: + print(f"Error: {e}") + +def main(): + asyncio.run(send_test_commands()) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..946f5a5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +-e . +opencv-python +pyaudio \ No newline at end of file diff --git a/src/main/index copy.js b/src/main/index copy.js index 4fc91a8..d7ba9c1 100644 --- a/src/main/index copy.js +++ b/src/main/index copy.js @@ -52,7 +52,7 @@ async function createWindow() { // Reload try { require("electron-reloader")(module); - } catch (_) {} + } catch (_) { } // Errors are thrown if the dev tools are opened // before the DOM is ready win.webContents.once("dom-ready", async () => { diff --git a/src/main/index.js b/src/main/index.js index 84ca2b2..7fec29b 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -126,7 +126,7 @@ async function createWindow() { } }); - const ws = new WebSocket('ws://localhost:8765'); + const ws = new WebSocket('ws://127.0.0.1:8765'); ws.on('open', function open() { console.log('WebSocket connection opened'); @@ -170,6 +170,7 @@ async function createWindow() { let recent_val = parseInt(response); let forwardVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("drone forward", forwardVal, "sent", response); + var code = `window.sendCommand({ action: "move", distance: 50, heading: 0 });\n`;//Vex Control const moveCommand = { action: "move", heading: 0, speed: forwardVal, duration: 1 }; sendCommand(moveCommand); }); @@ -201,27 +202,27 @@ async function createWindow() { let isUp = false; - ipcMain.on("manual-control", (event, response) => { - //console.log("index", response); - switch (response) { - case "takeoff": - isUp = true; - tello.takeoff(); - break; - case "land": - isUp = true; - tello.land(); - break; - case "up": - tello.up(20); - break; - case "down": - tello.down(20); - break; - default: - break; - } - }); + // ipcMain.on("manual-control", (event, response) => { + // //console.log("index", response); + // switch (response) { + // case "takeoff": + // isUp = true; + // tello.takeoff(); + // break; + // case "land": + // isUp = true; + // tello.land(); + // break; + // case "up": + // tello.up(20); + // break; + // case "down": + // tello.down(20); + // break; + // default: + // break; + // } + // }); ipcMain.on("control-signal", (event, response) => { /* @@ -240,6 +241,24 @@ async function createWindow() { } */ }); + + ipcMain.on("send-command", (event, command) => { + sendCommand(command); + }); + + javascriptGenerator.forBlock["move"] = function (block) { + var distance = block.getFieldValue("DISTANCE"); + var heading = block.getFieldValue("HEADING"); + var code = `window.sendCommand({ action: "move", distance: 50, heading: 0 });\n`;//Vex Control + // var code = `window.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; + return code; + }; + + javascriptGenerator.forBlock["led_control"] = function (block) { + var color = block.getFieldValue("COLOR"); + var code = `window.sendCommand({ action: "led_on", color: "${color}" });\n`; + return code; + }; } // This method will be called when Electron has finished diff --git a/src/main/preload.js b/src/main/preload.js index 24737c1..f972146 100644 --- a/src/main/preload.js +++ b/src/main/preload.js @@ -32,6 +32,7 @@ process.once("loaded", () => { selectBluetoothDevice: (deviceID) => ipcRenderer.send("select-ble-device", deviceID), cancelBluetoothRequest: (callback) => ipcRenderer.send("cancel-bluetooth-request", callback), bluetoothPairingRequest: (callback) => ipcRenderer.on("bluetooth-pairing-request", callback), - bluetoothPairingResponse: (response) => ipcRenderer.send("bluetooth-pairing-response", response) + bluetoothPairingResponse: (response) => ipcRenderer.send("bluetooth-pairing-response", response), + sendCommand: (command) => ipcRenderer.send("send-command", command) }); }); diff --git a/src/renderer/js/blockly-main.js b/src/renderer/js/blockly-main.js index 90f4ddf..b28870c 100644 --- a/src/renderer/js/blockly-main.js +++ b/src/renderer/js/blockly-main.js @@ -18,10 +18,24 @@ export const BlocklyMain = class { this.runner = null; // may need to use window here this.latestCode = ""; - let _toolbox = new Toolbox([cat_logic, cat_loops, cat_math, cat_sep, cat_data, cat_drone]); + let cat_robot = { + name: "Robot Controls", + colour: 160, + modules: ["move", "led_control"] + }; + + let _toolbox = new Toolbox([ + cat_logic, + cat_loops, + cat_math, + cat_sep, + cat_data, + cat_drone, + cat_robot // Add the new category here + ]); this.workspace = Blockly.inject("blocklyDiv", { - toolbox: _toolbox.toString() + toolbox: _toolbox.toString(), }); this.registerCustomToolbox(); diff --git a/src/renderer/js/customblock.js b/src/renderer/js/customblock.js index 9d4b98c..e557485 100644 --- a/src/renderer/js/customblock.js +++ b/src/renderer/js/customblock.js @@ -389,3 +389,56 @@ javascriptGenerator.forBlock["land"] = function (block, generator) { var code = `land();\n`; return code; }; + +var moveBlock = { + type: "move", + message0: "move %1 cm at heading %2°", + args0: [ + { type: "field_number", name: "DISTANCE", value: 100, min: 0 }, + { type: "field_number", name: "HEADING", value: 0, min: 0, max: 360 } + ], + previousStatement: null, + nextStatement: null, + colour: 160 +}; + +Blockly.Blocks["move"] = { + init: function () { + this.jsonInit(moveBlock); + } +}; + +javascriptGenerator.forBlock["move"] = function (block) { + var distance = block.getFieldValue("DISTANCE"); + var heading = block.getFieldValue("HEADING"); + var code = `window.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; + console.log("Generated code for move block:", code); + return code; +}; + +var ledBlock = { + type: "led_control", + message0: "set LED color to %1", + args0: [ + { + type: "field_dropdown", + name: "COLOR", + options: [["BLUE", "BLUE"], ["RED", "RED"], ["GREEN", "GREEN"], ["ORANGE", "ORANGE"]] + } + ], + previousStatement: null, + nextStatement: null, + colour: 160 +}; + +Blockly.Blocks["led_control"] = { + init: function () { + this.jsonInit(ledBlock); + } +}; + +javascriptGenerator.forBlock["led_control"] = function (block) { + var color = block.getFieldValue("COLOR"); + var code = `window.sendCommand({ action: "led_on", color: "${color}" });\n`; + return code; +}; diff --git a/src/renderer/js/main.js b/src/renderer/js/main.js index 65d5848..0aec725 100644 --- a/src/renderer/js/main.js +++ b/src/renderer/js/main.js @@ -15,6 +15,26 @@ import { ChannelVis } from "./channel_vis.js"; import { BlocklyMain } from "./blockly-main.js"; import { BandPowerVis } from "./band-power-vis.js"; +const ws = new WebSocket("ws://127.0.0.1:8765"); + +ws.onopen = () => { + console.log("WebSocket connection established"); +}; + +ws.onerror = (error) => { + console.error("WebSocket error:", error); +}; + +ws.onmessage = (event) => { + console.log("Message from server:", event.data); +}; + +function sendCommand(command) { + window.electronAPI.sendCommand(command); +} + +window.sendCommand = sendCommand; + export const NeuroScope = class { constructor() { this.blocklyMain = new BlocklyMain(); From 94021c8ec0cac301c9396c16937670f0187d090b Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Sun, 1 Jun 2025 18:40:07 -0500 Subject: [PATCH 05/25] added block functionality Previous block functionality still working on new block functions --- src/main/index.js | 19 ++++++++++--------- src/main/preload.js | 3 ++- src/renderer/js/customblock.js | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/index.js b/src/main/index.js index 7fec29b..3f678cd 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -154,7 +154,7 @@ async function createWindow() { let recent_val = parseInt(response); let rightVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("Sphero right", rightVal, "sent", response); - const moveCommand = { action: "move", heading: 90, speed: rightVal, duration: 3 }; + const moveCommand = { action: "move", distance: response, heading: 90 };//For Vex sendCommand(moveCommand); }); @@ -162,7 +162,7 @@ async function createWindow() { let recent_val = parseInt(response); let downVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("Sphero Left", downVal, "sent", response); - const moveCommand = { action: "move", heading: 270, speed: downVal, duration: 3 }; + const moveCommand = { action: "move", distance: response, heading: 270 };//For Vex sendCommand(moveCommand); }); @@ -170,8 +170,7 @@ async function createWindow() { let recent_val = parseInt(response); let forwardVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; console.log("drone forward", forwardVal, "sent", response); - var code = `window.sendCommand({ action: "move", distance: 50, heading: 0 });\n`;//Vex Control - const moveCommand = { action: "move", heading: 0, speed: forwardVal, duration: 1 }; + const moveCommand = { action: "move", distance: response, heading: 0 };//For Vex sendCommand(moveCommand); }); @@ -181,7 +180,7 @@ async function createWindow() { console.log("drone back", backVal, "sent", response); // let val = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; // console.log("drone back", val, "sent", response); - const moveCommand = { action: "move", heading: 180, speed: backVal, duration: 1 }; + const moveCommand = { action: "move", distance: response, heading: 180 };//For Vex sendCommand(moveCommand); // tello.back(val); }); @@ -243,20 +242,22 @@ async function createWindow() { }); ipcMain.on("send-command", (event, command) => { - sendCommand(command); + console.log("Received command from renderer:", command); + sendCommand(command); // Use the existing sendCommand function }); javascriptGenerator.forBlock["move"] = function (block) { var distance = block.getFieldValue("DISTANCE"); var heading = block.getFieldValue("HEADING"); - var code = `window.sendCommand({ action: "move", distance: 50, heading: 0 });\n`;//Vex Control - // var code = `window.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; + var code = `electronAPI.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; + console.log("Generated code for move block:", code); return code; }; javascriptGenerator.forBlock["led_control"] = function (block) { var color = block.getFieldValue("COLOR"); - var code = `window.sendCommand({ action: "led_on", color: "${color}" });\n`; + var code = `electronAPI.sendCommand({ action: "led_on", color: "${color}" });\n`; + console.log("Generated code for LED block:", code); return code; }; } diff --git a/src/main/preload.js b/src/main/preload.js index f972146..b7b34bb 100644 --- a/src/main/preload.js +++ b/src/main/preload.js @@ -33,6 +33,7 @@ process.once("loaded", () => { cancelBluetoothRequest: (callback) => ipcRenderer.send("cancel-bluetooth-request", callback), bluetoothPairingRequest: (callback) => ipcRenderer.on("bluetooth-pairing-request", callback), bluetoothPairingResponse: (response) => ipcRenderer.send("bluetooth-pairing-response", response), - sendCommand: (command) => ipcRenderer.send("send-command", command) + // Expose sendCommand to the renderer process + sendCommand: (command) => ipcRenderer.send("send-command", command), }); }); diff --git a/src/renderer/js/customblock.js b/src/renderer/js/customblock.js index e557485..1a1f067 100644 --- a/src/renderer/js/customblock.js +++ b/src/renderer/js/customblock.js @@ -411,7 +411,7 @@ Blockly.Blocks["move"] = { javascriptGenerator.forBlock["move"] = function (block) { var distance = block.getFieldValue("DISTANCE"); var heading = block.getFieldValue("HEADING"); - var code = `window.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; + var code = `electronAPI.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; console.log("Generated code for move block:", code); return code; }; From b031569cad9dbe00aedcf84ff20f0fe8f4af10ea Mon Sep 17 00:00:00 2001 From: Chris Crawford Date: Sun, 15 Jun 2025 14:49:44 -0500 Subject: [PATCH 06/25] Add support for Gaglion --- package-lock.json | 665 +++++++++++++++-- package.json | 3 +- src/main/index.js | 22 +- src/renderer/index.html | 3 + src/renderer/js/ble.js | 22 +- src/renderer/js/channel_vis.js | 4 +- src/renderer/js/ganglion-client.js | 157 ++++ src/renderer/js/main.js | 6 +- src/renderer/js/signal.js | 49 +- yarn.lock | 1116 ++++++++++++++++------------ 10 files changed, 1495 insertions(+), 552 deletions(-) create mode 100644 src/renderer/js/ganglion-client.js diff --git a/package-lock.json b/package-lock.json index 9fc72ba..7e42d0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,18 @@ { "name": "electron-vuejs-parcel", - "version": "1.0.11", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "electron-vuejs-parcel", - "version": "1.0.11", + "version": "1.3.0", "license": "MIT", "dependencies": { + "@openbci/utilities": "^1.0.0", + "blockly": "^10.4.3", "d3": "^7.9.0", + "js-interpreter": "^5.1.1", "rxjs": "^7.8.1" }, "devDependencies": { @@ -758,6 +761,23 @@ "node": ">= 8" } }, + "node_modules/@openbci/utilities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@openbci/utilities/-/utilities-1.0.0.tgz", + "integrity": "sha512-fhQ4lqL2qG9TDpWnLtWPG9zbYMBNNyPF/CdJY29zpSfu6E407luYr8weuViWsGcq24PZSfC9ubaNCYa8pL4j+g==", + "dependencies": { + "buffer": "^5.0.8", + "buffer-equal": "^1.0.0", + "clone": "^2.0.0", + "gaussian": "^1.0.0", + "mathjs": "^4.0.0", + "performance-now": "^2.1.0", + "streamsearch": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@parcel/babel-ast-utils": { "version": "2.0.0-beta.3.1", "resolved": "https://registry.npmjs.org/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.0-beta.3.1.tgz", @@ -2230,6 +2250,14 @@ "node": ">=10" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -2618,11 +2646,10 @@ "license": "MIT" }, "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true, - "license": "BSD-3-Clause" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead" }, "node_modules/abortcontroller-polyfill": { "version": "1.7.3", @@ -2665,6 +2692,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3112,7 +3150,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -3231,7 +3268,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -3290,6 +3326,261 @@ "readable-stream": "^3.4.0" } }, + "node_modules/blockly": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-10.4.3.tgz", + "integrity": "sha512-+opfBmQnSiv7vTiY/TkDEBOslxUyfj8luS3S+qs1NnQKjInC+Waf2l9cNsMh9J8BMkmiCIT+Ed/3mmjIaL9wug==", + "dependencies": { + "jsdom": "22.1.0" + } + }, + "node_modules/blockly/node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/blockly/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/blockly/node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blockly/node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/blockly/node_modules/jsdom": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", + "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", + "dependencies": { + "abab": "^2.0.6", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.4", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/blockly/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/blockly/node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/blockly/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/blockly/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/blockly/node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/blockly/node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/blockly/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/blockly/node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/blockly/node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "engines": { + "node": ">=12" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -3524,7 +3815,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -3559,7 +3849,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3733,6 +4022,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -4051,7 +4352,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -4204,7 +4504,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -4227,6 +4526,14 @@ "dev": true, "license": "MIT" }, + "node_modules/complex.js": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.10.tgz", + "integrity": "sha512-PsT3WqpnTjS2ijoMM8XodCi/BYO04vkS8kBg1YXcqf5KcnKVV6uXUc1eeLHhBksj8i7Vu9iQF2/6ZG9gqI6CPQ==", + "engines": { + "node": "*" + } + }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -5456,7 +5763,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -5480,6 +5786,11 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==" + }, "node_modules/decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -5681,7 +5992,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -5935,6 +6245,19 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -6290,6 +6613,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -6350,6 +6714,11 @@ "dev": true, "license": "MIT" }, + "node_modules/escape-latex": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.0.3.tgz", + "integrity": "sha512-GfKaG/7FOKdIdciylIzgaShBTPjdGQ5LJ2EcKLKXPLpcMO1MvCEVotkhydEShwCINRacZr2r3fk5A1PwZ4e5sA==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -6833,6 +7202,14 @@ "node": ">=0.4.x" } }, + "node_modules/fraction.js": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.8.tgz", + "integrity": "sha512-8Jx2AkFIFQtFaF8wP7yUIW+lnCgzPbxsholryMZ+oPK6kKjY/nUrvMKtq1+A8aSAeFau7+G/zfO8aGk2Aw1wCA==", + "engines": { + "node": "*" + } + }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -6869,11 +7246,20 @@ "license": "ISC" }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true, - "license": "MIT" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaussian": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/gaussian/-/gaussian-1.3.0.tgz", + "integrity": "sha512-rYQ0ESfB+z0t7G95nHH80Zh7Pgg9A0FUYoZqV0yPec5WJZWKIHV2MPYpiJNy8oZAeVqyKwC10WXKSCnUQ5iDVg==", + "engines": { + "node": ">= 0.6.0" + } }, "node_modules/generic-names": { "version": "2.0.1", @@ -6906,15 +7292,23 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "license": "MIT", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6930,6 +7324,18 @@ "node": ">=6" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -7124,6 +7530,17 @@ "node": ">=8" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/got": { "version": "11.8.6", "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", @@ -7235,11 +7652,23 @@ } }, "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true, - "license": "MIT", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -7332,6 +7761,17 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hex-color-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", @@ -7480,6 +7920,19 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/http-proxy-middleware": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz", @@ -7534,6 +7987,18 @@ "dev": true, "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -7570,7 +8035,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -8138,6 +8602,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, "node_modules/is-regex": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", @@ -8372,6 +8841,11 @@ "node": ">=4" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, "node_modules/joi": { "version": "17.4.0", "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", @@ -8386,6 +8860,17 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-interpreter": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/js-interpreter/-/js-interpreter-5.2.1.tgz", + "integrity": "sha512-offKOHFrtvQckRY0g4Bp6l4QbRUap/pFQ6HgwMgOaqqGmG7hNh21FZpHUJa99XWRyoWUlj7J/P70jHznRB4kUg==", + "dependencies": { + "minimist": "^1.2.8" + }, + "bin": { + "js-interpreter": "lib/cli.min.js" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8846,6 +9331,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mathjs": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-4.4.2.tgz", + "integrity": "sha512-T5zGIbDT/JGmzIu2Bocq4U8gbcmQVCyZaJbBCHKmJkLMQoWuh1SOuFH98doj1JEQwjpKkq3rqdUCuy3vLlBZOA==", + "dependencies": { + "complex.js": "2.0.10", + "decimal.js": "9.0.1", + "escape-latex": "1.0.3", + "fraction.js": "4.0.8", + "javascript-natural-sort": "0.7.1", + "seed-random": "2.2.0", + "tiny-emitter": "2.0.2", + "typed-function": "1.0.3" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/mathjs/node_modules/decimal.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-9.0.1.tgz", + "integrity": "sha512-2h0iKbJwnImBk4TGk7CG1xadoA0g3LDPlQhQzbZ221zvG0p2YVUedbKIPsOZXKZGx6YmZMJKYOalpCMxSdDqTQ==" + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -8930,7 +9449,6 @@ "version": "1.48.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -8940,7 +9458,6 @@ "version": "2.1.31", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.48.0" @@ -8997,11 +9514,12 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true, - "license": "MIT" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -9047,7 +9565,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -9251,11 +9768,9 @@ "license": "MIT" }, "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true, - "license": "MIT" + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==" }, "node_modules/oauth-sign": { "version": "0.9.0", @@ -9965,7 +10480,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true, "license": "MIT" }, "node_modules/picomatch": { @@ -11034,7 +11548,6 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true, "license": "MIT" }, "node_modules/public-encrypt": { @@ -11064,11 +11577,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "license": "MIT", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } @@ -11212,6 +11723,11 @@ "node": ">=0.4.x" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11580,7 +12096,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true, "license": "MIT" }, "node_modules/resolve": { @@ -11734,6 +12249,11 @@ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -11855,6 +12375,11 @@ "node": ">=8" } }, + "node_modules/seed-random": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "integrity": "sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==" + }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -12447,6 +12972,14 @@ "xtend": "^4.0.2" } }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -12710,7 +13243,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, "license": "MIT" }, "node_modules/temp-file": { @@ -12795,6 +13327,11 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-emitter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", + "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -13034,6 +13571,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-function": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.0.3.tgz", + "integrity": "sha512-sVC/1pm70oELDFMdYtFXMFqyawenLoaDiAXA3QvOAwKF/WvFNTSJN23cY2lFNL8iP0kh3T0PPKewrboO8XUVGQ==", + "engines": { + "node": ">= 6" + } + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -13335,6 +13880,15 @@ "querystring": "0.2.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -13789,7 +14343,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, "license": "MIT" }, "node_modules/xtend": { diff --git a/package.json b/package.json index 47b967a..cf07e26 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "identity": null }, "win": { - "icon": "src/renderer/icons/icon.png" + "icon": "src/renderer/icons/icon.png" }, "dmg": { "contents": [ @@ -91,6 +91,7 @@ }, "homepage": "./", "dependencies": { + "@openbci/utilities": "^1.0.0", "blockly": "^10.4.3", "d3": "^7.9.0", "js-interpreter": "^5.1.1", diff --git a/src/main/index.js b/src/main/index.js index c2a3429..f41bcd4 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -80,26 +80,16 @@ async function createWindow() { /* Code to integrate BLE and Tello Drone */ + // Electron native event listener for selecting a bluetooth device + // Purpose of this function is to get the callback function and send the device list to the renderer win.webContents.on("select-bluetooth-device", (event, deviceList, callback) => { + console.log("select-bluetooth-device"); bleCallback = callback; event.preventDefault(); - //console.log(deviceList); + console.log(deviceList); win.webContents.send("device_list", deviceList); - /* - deviceList.map((x) => { - console.log(x.deviceName); - }); - */ - let result = null; - //selectBluetoothCallback = callback - /* - const result = deviceList.find((device) => { - return device.deviceName === MUSE_DEVICE_NAME; - }); - */ - - //console.log(MuseClient) + let result = null; if (result) { callback(result.deviceId); @@ -113,11 +103,11 @@ async function createWindow() { }); setInterval(() => { - //console.log(tello.getState()); let drone_state = tello.getState(); win.webContents.send("drone_state", drone_state); }, 5000); + // This function triggers the connection to the selected BLE device ipcMain.on("select-ble-device", (event, selected_ble_device) => { console.log("device selected: ", selected_ble_device); if (bleCallback) { diff --git a/src/renderer/index.html b/src/renderer/index.html index 627e61b..14f98b7 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -24,6 +24,9 @@ class="ui raised card centered" style="width: 100%; height: 40vh" > +

+ Muscle Energy 0 +

diff --git a/src/renderer/js/ble.js b/src/renderer/js/ble.js index 993bd98..cbbf7ab 100644 --- a/src/renderer/js/ble.js +++ b/src/renderer/js/ble.js @@ -1,9 +1,13 @@ import { MuseElectronClient } from "./muse-client.js"; +import { GanglionClient } from "./ganglion-client.js"; export const BLE = class { constructor(callback, connect_button_id = "bluetooth") { - this.device = new MuseElectronClient(); + //this.device = new MuseElectronClient(); + this.device = new GanglionClient(); this.callback = callback; + this.device_name = ""; + this.ble_device_name = ""; // Connect Events document.getElementById(connect_button_id).onclick = function (e) { @@ -12,6 +16,7 @@ export const BLE = class { this.connect(); }.bind(this); + // This is a listener for the BLE list window.electronAPI.getBLEList((event, list) => { let ble_device_list = []; for (let device in list) { @@ -31,6 +36,7 @@ export const BLE = class { return; } let ble_device = event.target.getAttribute("data"); + this.ble_device_name = event.target.getAttribute("name"); window.electronAPI.selectBluetoothDevice(ble_device); $(".ui.modal").modal("hide"); //console.log(); @@ -49,7 +55,7 @@ export const BLE = class { >

${device_name}

- +
`; @@ -68,8 +74,16 @@ export const BLE = class { async connect() { await this.device.connect(); - // EEG DATA - this.device.eegReadings.subscribe(this.callback); + // console.log(this.ble_device_name); + + // EMG DATA (Ganglion) + if (this.ble_device_name.includes("Ganglion-")) { + // console.log("Ganglion-"); + // EEG Data (Muse) + this.device.stream.subscribe(this.callback); + } else if (this.ble_device_name.includes("Muse-")) { + this.device.eegReadings.subscribe(this.callback); + } } get_device() { diff --git a/src/renderer/js/channel_vis.js b/src/renderer/js/channel_vis.js index 77532ff..837b20d 100644 --- a/src/renderer/js/channel_vis.js +++ b/src/renderer/js/channel_vis.js @@ -1,9 +1,9 @@ import * as d3 from "d3"; export const ChannelVis = class { - constructor() { + constructor(signal_div_height) { this.width = window.innerWidth * 0.4; - this.height = window.innerHeight * 0.09; + this.height = window.innerHeight * signal_div_height; const signal_amplitude = 300; this.svgs = {}; diff --git a/src/renderer/js/ganglion-client.js b/src/renderer/js/ganglion-client.js new file mode 100644 index 0000000..eaad3cf --- /dev/null +++ b/src/renderer/js/ganglion-client.js @@ -0,0 +1,157 @@ +import { constants, debug, utilities } from "@openbci/utilities"; +// import { fromEvent, merge, Subject, BehaviorSubject } from "rxjs"; +import { + fromEvent, + merge, + Subject, + BehaviorSubject, + tap, + first, + map, + takeUntil, + scan, + concatMap, + filter, + share, + mergeMap, + fromEvent +} from "rxjs"; + +let parseGanglion = utilities.parseGanglion; +let numberOfChannelsForBoardType = constants.numberOfChannelsForBoardType; +let rawDataToSampleObjectDefault = constants.rawDataToSampleObjectDefault; + +const serviceId = 0xfe84; +const boardName = "ganglion"; +const characteristicsByType = { + reader: "2d30c082-f39f-4ce6-923f-3484ea480596", + writer: "2d30c083-f39f-4ce6-923f-3484ea480596", + connection: "2d30c084-f39f-4ce6-923f-3484ea480596" +}; +const onCharacteristic = "characteristicvaluechanged"; +const onDisconnected = "gattserverdisconnected"; +const deviceOptions = { + filters: [{ namePrefix: "Ganglion-" }], + optionalServices: [serviceId] +}; + +const commandStrings = { + start: "b", + accelData: "n" +}; + +const renameDataProp = ({ channelData, ...sample }) => ({ + ...sample, + data: channelData +}); + +export const GanglionClient = class { + constructor(options = {}) { + this.GANGLION_SERVICE = serviceId; + // this.MUSE_SERVICE = 0xfe8d; + this.options = deviceOptions; + this.signalMultiplier = 10000; + this.gatt = null; + this.device = null; + this.deviceName = null; + this.service = null; + this.characteristics = null; + this.onDisconnect$ = new Subject(); + this.boardName = boardName; + this.channelSize = numberOfChannelsForBoardType(boardName); + this.rawDataPacketToSample = rawDataToSampleObjectDefault(this.channelSize); + this.connectionStatus = new BehaviorSubject(false); + this.stream = new Subject().pipe( + map((event) => this.eventToBufferMapper(event)), + tap((buffer) => this.setRawDataPacket(buffer)), + map(() => parseGanglion(this.rawDataPacketToSample)), + mergeMap((x) => x), + map(renameDataProp), + takeUntil(this.onDisconnect$) + ); + this.accelData = this.stream.pipe(filter((sample) => sample.accelData.length)); + } + + eventToBufferMapper(event) { + return new Uint8Array(event.target.value.buffer); + } + + setRawDataPacket(buffer) { + this.rawDataPacketToSample.rawDataPacket = buffer; + } + + async connect() { + this.device = await navigator.bluetooth.requestDevice(deviceOptions); + this.addDisconnectedEvent(); + this.gatt = await this.device.gatt.connect(); + this.deviceName = this.gatt.device.name; + this.service = await this.gatt.getPrimaryService(serviceId); + this.setCharacteristics(await this.service.getCharacteristics()); + this.connectionStatus.next(true); + + await this.start(); + + // this.stream.subscribe((sample) => { + // //console.log(sample.data); + // let new_sample = sample.data[0] * this.signalMultiplier; + // console.log(new_sample); + // }); + + //console.log(this.device); + //console.log(utilities.parseGanglion); + } + + setCharacteristics(characteristics) { + this.characteristics = Object.entries(characteristicsByType).reduce( + (map, [name, uuid]) => ({ + ...map, + [name]: characteristics.find((c) => c.uuid === uuid) + }), + {} + ); + } + + async start() { + const { reader, writer } = this.characteristics; + const commands = Object.entries(commandStrings).reduce( + (acc, [key, command]) => ({ + ...acc, + [key]: new TextEncoder().encode(command) + }), + {} + ); + + reader.startNotifications(); + reader.addEventListener(onCharacteristic, (event) => { + this.stream.next(event); + }); + + if (this.options.accelData) { + await writer.writeValue(commands.accelData); + reader.readValue(); + } + await writer.writeValue(commands.start); + reader.readValue(); + } + + addDisconnectedEvent() { + fromEvent(this.device, onDisconnected) + .pipe(first()) + .subscribe(() => { + this.gatt = null; + this.device = null; + this.deviceName = null; + this.service = null; + this.characteristics = null; + this.connectionStatus.next(false); + }); + } + + disconnect() { + if (!this.gatt) { + return; + } + this.onDisconnect$.next(); + this.gatt.disconnect(); + } +}; diff --git a/src/renderer/js/main.js b/src/renderer/js/main.js index 65d5848..34c8ccf 100644 --- a/src/renderer/js/main.js +++ b/src/renderer/js/main.js @@ -18,10 +18,12 @@ import { BandPowerVis } from "./band-power-vis.js"; export const NeuroScope = class { constructor() { this.blocklyMain = new BlocklyMain(); - this.signal_handler = new Signal(512); + this.signal_handler = new Signal(512, "ganglion"); this.bpBis = new BandPowerVis(); this.events = new Events(this.blocklyMain); - this.ble = new BLE(this.signal_handler.add_data.bind(this.signal_handler)); + //this.ble = new BLE(this.signal_handler.add_data.bind(this.signal_handler)); + this.ble = new BLE(this.signal_handler.add_data_ganglion.bind(this.signal_handler)); + this.feature_extractor = new FeatureExtractor(256); this.blocklyMain.start(); setTimeout(() => { diff --git a/src/renderer/js/signal.js b/src/renderer/js/signal.js index 3fe28a7..dad35ce 100644 --- a/src/renderer/js/signal.js +++ b/src/renderer/js/signal.js @@ -2,11 +2,31 @@ import { ChannelVis } from "./channel_vis.js"; export const Signal = class { - constructor(buffer_size = 256) { + constructor(buffer_size = 256, device = "muse") { this.channels = {}; this.channels_d3_plot = {}; this.BUFFER_SIZE = buffer_size; - this.channel_vis = new ChannelVis(); + let signal_div_height = device === "ganglion" ? 0.5 : 0.09; // For now if device is muse, we use 0.09, if device is ganglion, we use 0.2 + this.x_top_padding = device === "ganglion" ? window.innerHeight * 3 : 0; + console.log("top padding", this.x_top_padding); + this.channel_vis = new ChannelVis(signal_div_height); + this.signal_value_dom = document.querySelector("#signal_value"); + this.last_signal_update = Date.now(); + this.value_refresh_delay_ms = 100; + this.EMG_SIGNAL_MULTIPLIER = 10000000; + let Fili = window.fili; + this.sampleRate = 250; + // this.lowFreq = lowFreq; + // this.highFreq = highFreq; + this.filterOrder = 100; + this.firCalculator = new Fili.FirCoeffs(); + this.coeffs = this.firCalculator.lowpass({ + order: this.filterOrder, + Fs: this.sampleRate, + Fc: 3 + }); + + this.filter = new Fili.FirFilter(this.coeffs); //this.tensor = new TensorDSP("muse"); /* @@ -20,6 +40,31 @@ export const Signal = class { // This will come with a computational cost. } + add_data_ganglion(sample, electrode = 0) { + let new_sample = Math.abs(sample.data[0] * this.EMG_SIGNAL_MULTIPLIER); + let filtered_data = this.filter.singleStep(new_sample); + //console.log("my sample", filtered_data); + window.filteredSample = filtered_data; + + if (Date.now() - this.last_signal_update > this.value_refresh_delay_ms) { + this.signal_value_dom.innerHTML = Math.abs(sample.data[0] * 100000).toFixed(2); // easier for students to interpret + this.last_signal_update = Date.now(); + } + + if (!this.channels[electrode]) { + this.channels[electrode] = []; + this.channels_d3_plot[electrode] = []; + } + + if (this.channels[electrode].length > this.BUFFER_SIZE - 1) { + this.channels[electrode].shift(); + } + + let formatted_data = filtered_data - this.x_top_padding; + this.channels[electrode].push(formatted_data); + //console.log(this.channels[electrode]); + } + add_data(sample) { //console.log(sample); let { electrode, samples } = sample; diff --git a/yarn.lock b/yarn.lock index 4ed2ea9..ebb89d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"7zip-bin@~5.1.1": - version "5.1.1" - resolved "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz" - integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== - "@babel/code-frame@^7.14.5": version "7.14.5" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz" @@ -19,7 +14,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz" integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== -"@babel/core@^7.12.0": +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.12.0": version "7.14.6" resolved "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz" integrity sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA== @@ -302,7 +297,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -315,6 +310,19 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openbci/utilities@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@openbci/utilities/-/utilities-1.0.0.tgz" + integrity sha512-fhQ4lqL2qG9TDpWnLtWPG9zbYMBNNyPF/CdJY29zpSfu6E407luYr8weuViWsGcq24PZSfC9ubaNCYa8pL4j+g== + dependencies: + buffer "^5.0.8" + buffer-equal "^1.0.0" + clone "^2.0.0" + gaussian "^1.0.0" + mathjs "^4.0.0" + performance-now "^2.1.0" + streamsearch "^0.1.2" + "@parcel/babel-ast-utils@2.0.0-beta.3.1": version "2.0.0-beta.3.1" resolved "https://registry.npmjs.org/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.0-beta.3.1.tgz" @@ -382,7 +390,7 @@ "@parcel/transformer-raw" "2.0.0-beta.3.1" "@parcel/transformer-react-refresh-wrap" "2.0.0-beta.3.1" -"@parcel/core@2.0.0-beta.3.1": +"@parcel/core@^2.0.0-alpha.3.1", "@parcel/core@2.0.0-beta.3.1": version "2.0.0-beta.3.1" resolved "https://registry.npmjs.org/@parcel/core/-/core-2.0.0-beta.3.1.tgz" integrity sha512-Rkp92SBcVyr5qlPEoV7gVzNRYstHxo6ah4NadSdNN0QzXtcLUGkrAPXBGJpjlifXqXogyz0D3QjAaPBpk6p/8Q== @@ -921,7 +929,7 @@ "@tootallnate/once@2": version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@types/cacheable-request@^6.0.1": @@ -995,14 +1003,6 @@ dependencies: undici-types "~5.26.4" -"@types/plist@^3.0.1": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/plist/-/plist-3.0.5.tgz#9a0c49c0f9886c8c8696a7904dd703f6284036e0" - integrity sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA== - dependencies: - "@types/node" "*" - xmlbuilder ">=11.0.1" - "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz" @@ -1015,11 +1015,6 @@ dependencies: "@types/node" "*" -"@types/verror@^1.10.3": - version "1.10.10" - resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.10.tgz#d5a4b56abac169bfbc8b23d291363a682e6fa087" - integrity sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg== - "@types/yargs-parser@*": version "20.2.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz" @@ -1118,19 +1113,14 @@ resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.1.2.tgz" integrity sha512-EmH/poaDWBPJaPILXNI/1fvUbArJQmmTyVCwvvyDYDFnkPoTclAbHRAtyIvqfez7jybTDn077HTNILpxlsoWhg== -"@xmldom/xmldom@^0.8.8": - version "0.8.10" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" - integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== - -abab@^2.0.0: - version "2.0.5" - resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz" - integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== +"7zip-bin@~5.1.1": + version "5.1.1" + resolved "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz" + integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== -abab@^2.0.6: +abab@^2.0.0, abab@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== abortcontroller-polyfill@^1.1.9: @@ -1158,7 +1148,7 @@ acorn@^6.0.1, acorn@^6.0.4: agent-base@6: version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" @@ -1168,7 +1158,7 @@ ajv-keywords@^3.4.1: resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.3: +ajv@^6.12.0, ajv@^6.12.3, ajv@^6.9.1: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1215,7 +1205,14 @@ ansi-styles@^2.2.1: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.0: + version "3.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -1247,10 +1244,10 @@ app-builder-lib@22.11.7: resolved "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.11.7.tgz" integrity sha512-pS9/cR4/TnNZVAHZECiSvvwTBzbwblj7KBBZkMKDG57nibq0I1XY8zAaYeHFdlYTyrRcz9JUXbAqJKezya7UFQ== dependencies: - "7zip-bin" "~5.1.1" "@develar/schema-utils" "~2.6.5" "@electron/universal" "1.0.5" "@malept/flatpak-bundler" "^0.4.0" + "7zip-bin" "~5.1.1" async-exit-hook "^2.0.1" bluebird-lst "^1.0.9" builder-util "22.11.7" @@ -1342,7 +1339,7 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" -assert-plus@1.0.0, assert-plus@^1.0.0: +assert-plus@^1.0.0, assert-plus@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= @@ -1436,11 +1433,6 @@ base-x@^3.0.8: dependencies: safe-buffer "^5.0.1" -base64-js@^1.3.1, base64-js@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - base@^0.11.1: version "0.11.2" resolved "https://registry.npmjs.org/base/-/base-0.11.2.tgz" @@ -1454,6 +1446,11 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" @@ -1482,7 +1479,7 @@ bl@^4.1.0: blockly@^10.4.3: version "10.4.3" - resolved "https://registry.yarnpkg.com/blockly/-/blockly-10.4.3.tgz#a3ca155e2ba7bae59883a143b8b901c781bfac0d" + resolved "https://registry.npmjs.org/blockly/-/blockly-10.4.3.tgz" integrity sha512-+opfBmQnSiv7vTiY/TkDEBOslxUyfj8luS3S+qs1NnQKjInC+Waf2l9cNsMh9J8BMkmiCIT+Ed/3mmjIaL9wug== dependencies: jsdom "22.1.0" @@ -1504,7 +1501,12 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1: +bn.js@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== + +bn.js@^5.1.1: version "5.2.0" resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== @@ -1651,7 +1653,7 @@ buffer-crc32@~0.2.3: resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-equal@1.0.0: +buffer-equal@^1.0.0, buffer-equal@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz" integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= @@ -1666,9 +1668,9 @@ buffer-xor@^1.0.3: resolved "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= -buffer@^5.1.0, buffer@^5.5.0: +buffer@^5.0.8, buffer@^5.5.0: version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -1695,9 +1697,9 @@ builder-util@22.11.7: resolved "https://registry.npmjs.org/builder-util/-/builder-util-22.11.7.tgz" integrity sha512-ihqUe5ey82LM9qqQe0/oIcaSm9w+B9UjcsWJZxJliTBsbU+sErOpDFpHW+sim0veiTF/EIcGUh9HoduWw+l9FA== dependencies: - "7zip-bin" "~5.1.1" "@types/debug" "^4.1.5" "@types/fs-extra" "^9.0.11" + "7zip-bin" "~5.1.1" app-builder-bin "3.5.13" bluebird-lst "^1.0.9" builder-util-runtime "8.7.7" @@ -1766,6 +1768,14 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" @@ -1834,7 +1844,25 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^2.4.1: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1851,7 +1879,7 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.0: +chokidar@^3.5.0, "chokidar@>=3.0.0 <4.0.0": version "3.5.2" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -1921,14 +1949,6 @@ cli-spinners@^2.5.0: resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz" integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== -cli-truncate@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" - integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== - dependencies: - slice-ansi "^3.0.0" - string-width "^4.2.0" - cliui@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" @@ -1959,7 +1979,7 @@ clone@^1.0.2: resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -clone@^2.1.1: +clone@^2.0.0, clone@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= @@ -1995,16 +2015,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + color-string@^1.5.4: version "1.5.5" resolved "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz" @@ -2043,6 +2063,21 @@ command-exists@^1.2.6: resolved "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@2.9.0: version "2.9.0" resolved "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" @@ -2050,20 +2085,15 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" -commander@7, commander@^7.0.0: +commander@7: version "7.2.0" resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +complex.js@2.0.10: + version "2.0.10" + resolved "https://registry.npmjs.org/complex.js/-/complex.js-2.0.10.tgz" + integrity sha512-PsT3WqpnTjS2ijoMM8XodCi/BYO04vkS8kBg1YXcqf5KcnKVV6uXUc1eeLHhBksj8i7Vu9iQF2/6ZG9gqI6CPQ== component-emitter@^1.2.1: version "1.3.0" @@ -2151,7 +2181,7 @@ core-js@^3.2.1: resolved "https://registry.npmjs.org/core-js/-/core-js-3.15.1.tgz" integrity sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg== -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@~1.0.0, core-util-is@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -2166,13 +2196,6 @@ cosmiconfig@^5.0.0: js-yaml "^3.13.1" parse-json "^4.0.0" -crc@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" - integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== - dependencies: - buffer "^5.1.0" - create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz" @@ -2253,7 +2276,7 @@ crypto-random-string@^2.0.0: resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css-color-names@0.0.4, css-color-names@^0.0.4: +css-color-names@^0.0.4, css-color-names@0.0.4: version "0.0.4" resolved "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz" integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= @@ -2301,14 +2324,6 @@ css-selector-tokenizer@^0.7.0: cssesc "^3.0.0" fastparse "^1.1.2" -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - css-tree@^1.1.2: version "1.1.3" resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" @@ -2317,6 +2332,14 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz" @@ -2402,7 +2425,7 @@ csso@^4.0.2: dependencies: css-tree "^1.1.2" -cssom@0.3.x, cssom@^0.3.4: +cssom@^0.3.4, cssom@0.3.x: version "0.3.8" resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== @@ -2416,7 +2439,7 @@ cssstyle@^1.1.1: cssstyle@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz" integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg== dependencies: rrweb-cssom "^0.6.0" @@ -2426,21 +2449,21 @@ csstype@^2.6.8: resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz" integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A== -"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: +d3-array@^3.2.0, "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: version "3.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== dependencies: internmap "1 - 2" d3-axis@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + resolved "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz" integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== d3-brush@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + resolved "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz" integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== dependencies: d3-dispatch "1 - 3" @@ -2451,38 +2474,38 @@ d3-brush@3: d3-chord@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + resolved "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz" integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== dependencies: d3-path "1 - 3" "d3-color@1 - 3", d3-color@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== d3-contour@4: version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" + resolved "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz" integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== dependencies: d3-array "^3.2.0" d3-delaunay@6: version "6.0.4" - resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + resolved "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz" integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== dependencies: delaunator "5" "d3-dispatch@1 - 3", d3-dispatch@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz" integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== "d3-drag@2 - 3", d3-drag@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz" integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== dependencies: d3-dispatch "1 - 3" @@ -2490,7 +2513,7 @@ d3-delaunay@6: "d3-dsv@1 - 3", d3-dsv@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + resolved "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz" integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== dependencies: commander "7" @@ -2499,19 +2522,19 @@ d3-delaunay@6: "d3-ease@1 - 3", d3-ease@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== d3-fetch@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + resolved "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz" integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== dependencies: d3-dsv "1 - 3" d3-force@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + resolved "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz" integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== dependencies: d3-dispatch "1 - 3" @@ -2520,51 +2543,51 @@ d3-force@3: "d3-format@1 - 3", d3-format@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== d3-geo@3: version "3.1.1" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" + resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz" integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== dependencies: d3-array "2.5.0 - 3" d3-hierarchy@3: version "3.1.2" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz" integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== "d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== dependencies: d3-color "1 - 3" -"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: +d3-path@^3.1.0, "d3-path@1 - 3", d3-path@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== d3-polygon@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + resolved "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz" integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== "d3-quadtree@1 - 3", d3-quadtree@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz" integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== d3-random@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + resolved "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz" integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== d3-scale-chromatic@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + resolved "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz" integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== dependencies: d3-color "1 - 3" @@ -2572,7 +2595,7 @@ d3-scale-chromatic@3: d3-scale@4: version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== dependencies: d3-array "2.10.0 - 3" @@ -2583,38 +2606,38 @@ d3-scale@4: "d3-selection@2 - 3", d3-selection@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz" integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== d3-shape@3: version "3.2.0" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== dependencies: d3-path "^3.1.0" "d3-time-format@2 - 4", d3-time-format@4: version "4.1.0" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== dependencies: d3-time "1 - 3" "d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== dependencies: d3-array "2 - 3" "d3-timer@1 - 3", d3-timer@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== "d3-transition@2 - 3", d3-transition@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz" integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== dependencies: d3-color "1 - 3" @@ -2625,7 +2648,7 @@ d3-shape@3: d3-zoom@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz" integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== dependencies: d3-dispatch "1 - 3" @@ -2636,7 +2659,7 @@ d3-zoom@3: d3@^7.9.0: version "7.9.0" - resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + resolved "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz" integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== dependencies: d3-array "3" @@ -2688,7 +2711,7 @@ data-urls@^1.1.0: data-urls@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz" integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g== dependencies: abab "^2.0.6" @@ -2707,21 +2730,21 @@ date-time@^3.1.0: dependencies: time-zone "^1.0.0" -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@^2.2.0: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@^2.3.3: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: - ms "2.1.2" + ms "2.0.0" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@4: version "4.3.1" resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -2735,15 +2758,27 @@ debug@^4.3.2: dependencies: ms "2.1.2" +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decimal.js@^10.4.3: - version "10.4.3" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" - integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + version "10.5.0" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz" + integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== + +decimal.js@9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-9.0.1.tgz" + integrity sha512-2h0iKbJwnImBk4TGk7CG1xadoA0g3LDPlQhQzbZ221zvG0p2YVUedbKIPsOZXKZGx6YmZMJKYOalpCMxSdDqTQ== decode-uri-component@^0.2.0: version "0.2.0" @@ -2822,7 +2857,7 @@ define-property@^2.0.2: delaunator@5: version "5.0.1" - resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" + resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz" integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== dependencies: robust-predicates "^3.0.2" @@ -2890,28 +2925,6 @@ dmg-builder@22.11.7: optionalDependencies: dmg-license "^1.0.9" -dmg-license@^1.0.9: - version "1.0.11" - resolved "https://registry.yarnpkg.com/dmg-license/-/dmg-license-1.0.11.tgz#7b3bc3745d1b52be7506b4ee80cb61df6e4cd79a" - integrity sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q== - dependencies: - "@types/plist" "^3.0.1" - "@types/verror" "^1.10.3" - ajv "^6.10.0" - crc "^3.8.0" - iconv-corefoundation "^1.1.7" - plist "^3.0.4" - smart-buffer "^4.0.2" - verror "^1.10.0" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - dom-serializer@^1.0.1: version "1.3.2" resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz" @@ -2921,21 +2934,29 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@0: + version "0.2.2" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + domain-browser@^3.5.0: version "3.5.0" resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-3.5.0.tgz" integrity sha512-zrzUu6auyZWRexjCEPJnfWc30Hupxh2lJZOJAF3qa2bCuD4O/55t0FvQt3ZMhEw++gjNkwdkOVZh8yA32w/Vfw== -domelementtype@1: - version "1.3.1" - resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - domelementtype@^2.0.1, domelementtype@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== +domelementtype@1: + version "1.3.1" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + domexception@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz" @@ -2945,7 +2966,7 @@ domexception@^1.0.1: domexception@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + resolved "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz" integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== dependencies: webidl-conversions "^7.0.0" @@ -3003,6 +3024,15 @@ dotenv@^9.0.2: resolved "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz" integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" @@ -3171,10 +3201,10 @@ entities@^2.0.0: resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== env-paths@^2.2.0: version "2.2.1" @@ -3210,6 +3240,33 @@ es-abstract@^1.17.2, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es- string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" @@ -3244,6 +3301,11 @@ escape-html@~1.0.3: resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= +escape-latex@1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-latex/-/escape-latex-1.0.3.tgz" + integrity sha512-GfKaG/7FOKdIdciylIzgaShBTPjdGQ5LJ2EcKLKXPLpcMO1MvCEVotkhydEShwCINRacZr2r3fk5A1PwZ4e5sA== + escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" @@ -3324,7 +3386,15 @@ extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" -extend-shallow@^3.0.0, extend-shallow@^3.0.2: +extend-shallow@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz" integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= @@ -3362,43 +3432,43 @@ extract-zip@^2.0.1: optionalDependencies: "@types/yauzl" "^2.9.1" -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - extsprintf@^1.2.0: version "1.4.0" resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz" - integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== +fast-glob@^3.1.1: + version "3.2.5" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.0" merge2 "^1.3.0" micromatch "^4.0.2" + picomatch "^2.2.1" -fast-glob@^3.1.1: - version "3.2.5" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== +fast-glob@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz" + integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.0" merge2 "^1.3.0" micromatch "^4.0.2" - picomatch "^2.2.1" fast-json-stable-stringify@^2.0.0: version "2.1.0" @@ -3526,12 +3596,14 @@ forever-agent@~0.6.1: integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + version "4.0.3" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz" + integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" mime-types "^2.1.12" form-data@~2.3.2: @@ -3548,6 +3620,11 @@ format@^0.2.0: resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz" integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= +fraction.js@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.8.tgz" + integrity sha512-8Jx2AkFIFQtFaF8wP7yUIW+lnCgzPbxsholryMZ+oPK6kKjY/nUrvMKtq1+A8aSAeFau7+G/zfO8aGk2Aw1wCA== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz" @@ -3573,7 +3650,17 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -3588,15 +3675,15 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +function-bind@^1.1.1, function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +gaussian@^1.0.0: + version "1.3.0" + resolved "https://registry.npmjs.org/gaussian/-/gaussian-1.3.0.tgz" + integrity sha512-rYQ0ESfB+z0t7G95nHH80Zh7Pgg9A0FUYoZqV0yPec5WJZWKIHV2MPYpiJNy8oZAeVqyKwC10WXKSCnUQ5iDVg== generic-names@^2.0.1: version "2.0.1" @@ -3615,20 +3702,35 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" get-port@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz" integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" @@ -3724,6 +3826,11 @@ globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + got@^11.8.5: version "11.8.6" resolved "https://registry.npmjs.org/got/-/got-11.8.6.tgz" @@ -3808,10 +3915,17 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1, has-symbols@^1.0.2: +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" has-value@^0.3.1: version "0.3.1" @@ -3878,6 +3992,13 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz" @@ -3928,7 +4049,7 @@ html-encoding-sniffer@^1.0.2: html-encoding-sniffer@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz" integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== dependencies: whatwg-encoding "^2.0.0" @@ -3980,7 +4101,7 @@ http-cache-semantics@^4.0.0: http-proxy-agent@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== dependencies: "@tootallnate/once" "2" @@ -4031,19 +4152,18 @@ https-browserify@^1.0.0: https-proxy-agent@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" -iconv-corefoundation@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz#31065e6ab2c9272154c8b0821151e2c88f1b002a" - integrity sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ== +iconv-lite@^0.6.2, iconv-lite@0.6, iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: - cli-truncate "^2.1.0" - node-addon-api "^1.6.3" + safer-buffer ">= 2.1.2 < 3.0.0" iconv-lite@0.4.24: version "0.4.24" @@ -4052,14 +4172,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -icss-replace-symbols@1.1.0, icss-replace-symbols@^1.1.0: +icss-replace-symbols@^1.1.0, icss-replace-symbols@1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz" integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= @@ -4122,24 +4235,24 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - ini@~1.3.0: version "1.3.8" resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + "internmap@1 - 2": version "2.0.3" - resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== is-absolute-url@^2.0.0: @@ -4410,7 +4523,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: is-potential-custom-element-name@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== is-regex@^1.1.3: @@ -4481,7 +4594,7 @@ is-yarn-global@^0.3.0: resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== -isarray@1.0.0, isarray@~1.0.0: +isarray@~1.0.0, isarray@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -4523,6 +4636,11 @@ jake@^10.6.1: filelist "^1.0.1" minimatch "^3.0.4" +javascript-natural-sort@0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz" + integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== + joi@^17.3.0: version "17.4.0" resolved "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz" @@ -4535,9 +4653,9 @@ joi@^17.3.0: "@sideway/pinpoint" "^2.0.0" js-interpreter@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/js-interpreter/-/js-interpreter-5.1.1.tgz#dbe0c01c4b986c1b6c68ed90e39b9b9fb62b285b" - integrity sha512-LsN3y0ja1UUUOmxxaK2ikEVLqdR1hZjixNg5UDWtFao2BxjM6AWVah45SpRtntPiQdhWmpfl/rHKA32JqZ7RjA== + version "5.2.1" + resolved "https://registry.npmjs.org/js-interpreter/-/js-interpreter-5.2.1.tgz" + integrity sha512-offKOHFrtvQckRY0g4Bp6l4QbRUap/pFQ6HgwMgOaqqGmG7hNh21FZpHUJa99XWRyoWUlj7J/P70jHznRB4kUg== dependencies: minimist "^1.2.8" @@ -4566,35 +4684,6 @@ jsbn@~0.1.0: resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@22.1.0: - version "22.1.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8" - integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== - dependencies: - abab "^2.0.6" - cssstyle "^3.0.0" - data-urls "^4.0.0" - decimal.js "^10.4.3" - domexception "^4.0.0" - form-data "^4.0.0" - html-encoding-sniffer "^3.0.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.1" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.4" - parse5 "^7.1.2" - rrweb-cssom "^0.6.0" - saxes "^6.0.0" - symbol-tree "^3.2.4" - tough-cookie "^4.1.2" - w3c-xmlserializer "^4.0.0" - webidl-conversions "^7.0.0" - whatwg-encoding "^2.0.0" - whatwg-mimetype "^3.0.0" - whatwg-url "^12.0.1" - ws "^8.13.0" - xml-name-validator "^4.0.0" - jsdom@^14.1.0: version "14.1.0" resolved "https://registry.npmjs.org/jsdom/-/jsdom-14.1.0.tgz" @@ -4627,6 +4716,35 @@ jsdom@^14.1.0: ws "^6.1.2" xml-name-validator "^3.0.0" +jsdom@22.1.0: + version "22.1.0" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz" + integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== + dependencies: + abab "^2.0.6" + cssstyle "^3.0.0" + data-urls "^4.0.0" + decimal.js "^10.4.3" + domexception "^4.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.4" + parse5 "^7.1.2" + rrweb-cssom "^0.6.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^12.0.1" + ws "^8.13.0" + xml-name-validator "^4.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -4674,7 +4792,21 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.0, json5@^2.1.2, json5@^2.2.0: +json5@^2.1.0: + version "2.2.0" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +json5@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -4750,7 +4882,12 @@ kind-of@^5.0.0: resolved "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== -kind-of@^6.0.0, kind-of@^6.0.2: +kind-of@^6.0.0: + version "6.0.3" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kind-of@^6.0.2: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -4897,6 +5034,25 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mathjs@^4.0.0: + version "4.4.2" + resolved "https://registry.npmjs.org/mathjs/-/mathjs-4.4.2.tgz" + integrity sha512-T5zGIbDT/JGmzIu2Bocq4U8gbcmQVCyZaJbBCHKmJkLMQoWuh1SOuFH98doj1JEQwjpKkq3rqdUCuy3vLlBZOA== + dependencies: + complex.js "2.0.10" + decimal.js "9.0.1" + escape-latex "1.0.3" + fraction.js "4.0.8" + javascript-natural-sort "0.7.1" + seed-random "2.2.0" + tiny-emitter "2.0.2" + typed-function "1.0.3" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz" @@ -4963,22 +5119,15 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.48.0: - version "1.48.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz" - integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== - mime-db@~1.33.0: version "1.33.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz" integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== -mime-types@2.1.18: - version "2.1.18" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz" - integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== - dependencies: - mime-db "~1.33.0" +mime-db@1.48.0: + version "1.48.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz" + integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.31" @@ -4987,6 +5136,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.48.0" +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + mime@^2.5.2: version "2.5.2" resolved "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz" @@ -5017,21 +5173,16 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@^3.0.4, minimatch@3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -minimist@^1.2.8: +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.8: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== mixin-deep@^1.2.0: @@ -5091,11 +5242,6 @@ nice-try@^1.0.4: resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-addon-api@^1.6.3: - version "1.7.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" - integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== - node-addon-api@^3.0.2: version "3.2.1" resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz" @@ -5158,15 +5304,10 @@ nullthrows@^1.1.1: resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== -nwsapi@^2.1.3: - version "2.2.0" - resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== - -nwsapi@^2.2.4: - version "2.2.8" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.8.tgz#a3552e65b74bf8cc89d0480c4132b61dbe54eccf" - integrity sha512-GU/I3lTEFQ9mkEm07Q7HvdRajss8E1wVMGOk3/lHl60QPseG+B3BIQY+JUjYWw7gF8cCeoQCXd4N7DB7avw0Rg== +nwsapi@^2.1.3, nwsapi@^2.2.4: + version "2.2.20" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz" + integrity sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA== oauth-sign@~0.9.0: version "0.9.0" @@ -5400,18 +5541,18 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse5@^7.1.2: + version "7.3.0" + resolved "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== + dependencies: + entities "^6.0.0" + parse5@5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz" integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== -parse5@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== - dependencies: - entities "^4.4.0" - parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" @@ -5503,15 +5644,6 @@ pify@^3.0.0: resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -plist@^3.0.4: - version "3.1.0" - resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" - integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== - dependencies: - "@xmldom/xmldom" "^0.8.8" - base64-js "^1.5.1" - xmlbuilder "^15.1.1" - pn@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz" @@ -5640,13 +5772,6 @@ postcss-minify-selectors@^4.0.2: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -postcss-modules-extract-imports@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz" - integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= - dependencies: - postcss "^6.0.1" - postcss-modules-extract-imports@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz" @@ -5659,12 +5784,11 @@ postcss-modules-extract-imports@^3.0.0: resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== -postcss-modules-local-by-default@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz" - integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= +postcss-modules-extract-imports@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz" + integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= dependencies: - css-selector-tokenizer "^0.7.0" postcss "^6.0.1" postcss-modules-local-by-default@^3.0.2: @@ -5686,10 +5810,10 @@ postcss-modules-local-by-default@^4.0.0: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-scope@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz" - integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= +postcss-modules-local-by-default@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= dependencies: css-selector-tokenizer "^0.7.0" postcss "^6.0.1" @@ -5709,12 +5833,12 @@ postcss-modules-scope@^3.0.0: dependencies: postcss-selector-parser "^6.0.4" -postcss-modules-values@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz" - integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= +postcss-modules-scope@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= dependencies: - icss-replace-symbols "^1.1.0" + css-selector-tokenizer "^0.7.0" postcss "^6.0.1" postcss-modules-values@^3.0.0: @@ -5732,6 +5856,14 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" +postcss-modules-values@1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz" + integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + postcss-modules@^3.2.2: version "3.2.2" resolved "https://registry.npmjs.org/postcss-modules/-/postcss-modules-3.2.2.tgz" @@ -5871,15 +6003,6 @@ postcss-reduce-transforms@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-selector-parser@6.0.2: - version "6.0.2" - resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - postcss-selector-parser@^3.0.0: version "3.1.2" resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz" @@ -5897,6 +6020,15 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + postcss-svgo@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz" @@ -5920,28 +6052,15 @@ postcss-value-parser@^3.0.0: resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.2: version "4.1.0" resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz" - integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= - dependencies: - chalk "^1.1.3" - source-map "^0.5.6" - supports-color "^3.2.3" - -postcss@7.0.32: - version "7.0.32" - resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz" - integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" +postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== postcss@^6.0.1: version "6.0.23" @@ -5961,7 +6080,7 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.0.5, postcss@^8.1.10, postcss@^8.2.1: +postcss@^8.0.0, postcss@^8.1.0, postcss@^8.1.10: version "8.3.5" resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz" integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== @@ -5970,6 +6089,42 @@ postcss@^8.0.5, postcss@^8.1.10, postcss@^8.2.1: nanoid "^3.1.23" source-map-js "^0.6.2" +postcss@^8.0.5: + version "8.3.5" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz" + integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== + dependencies: + colorette "^1.2.2" + nanoid "^3.1.23" + source-map-js "^0.6.2" + +postcss@^8.2.1: + version "8.3.5" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz" + integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== + dependencies: + colorette "^1.2.2" + nanoid "^3.1.23" + source-map-js "^0.6.2" + +postcss@6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz" + integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@7.0.32: + version "7.0.32" + resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz" + integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + posthtml-parser@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.6.0.tgz" @@ -6027,16 +6182,11 @@ progress@^2.0.3: resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -psl@^1.1.28: +psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== -psl@^1.1.33: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz" @@ -6057,26 +6207,26 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.3.2, punycode@^1.4.1: +punycode@^1.3.2: version "1.4.1" resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.3.0: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + pupa@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz" @@ -6109,19 +6259,19 @@ querystring-es3@^0.2.1: resolved "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - querystring@^0.2.0: version "0.2.1" resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz" integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + querystringify@^2.1.1: version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== queue-microtask@^1.2.2: @@ -6189,6 +6339,15 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" +readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + "readable-stream@1 || 2": version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" @@ -6202,15 +6361,6 @@ read-pkg@^4.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" @@ -6271,7 +6421,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.88.0: +request@^2.34, request@^2.88.0: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -6406,12 +6556,12 @@ roarr@^2.15.3: robust-predicates@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" + resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz" integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== rrweb-cssom@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" + resolved "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz" integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== run-parallel@^1.1.9: @@ -6423,10 +6573,17 @@ run-parallel@^1.1.9: rw@1: version "1.3.3" - resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz" integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== -rxjs@^6.5.2, rxjs@^6.6.3: +rxjs@^6.5.2: + version "6.6.7" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +rxjs@^6.6.3: version "6.6.7" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -6457,7 +6614,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +safer-buffer@^2.0.2, safer-buffer@^2.1.0, "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -6490,11 +6647,16 @@ saxes@^3.1.9: saxes@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + resolved "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz" integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== dependencies: xmlchars "^2.2.0" +seed-random@2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz" + integrity sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ== + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" @@ -6507,17 +6669,41 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.7.0: +semver@^5.4.1, semver@^5.5.0, semver@^5.7.0, "semver@2 || 3 || 4 || 5": version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@^7.3.2: + version "7.3.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.4: + version "7.3.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.5: version "7.3.5" resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -6614,15 +6800,6 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" - integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" @@ -6632,11 +6809,6 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -smart-buffer@^4.0.2: - version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz" @@ -6696,7 +6868,12 @@ source-map-url@^0.4.0: resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== -source-map@^0.5.0, source-map@^0.5.6: +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.5.6: version "0.5.7" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -6829,6 +7006,25 @@ stream-http@^3.1.0: readable-stream "^3.6.0" xtend "^4.0.2" +streamsearch@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz" + integrity sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA== + +string_decoder@^1.1.1, string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + string-hash@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz" @@ -6868,20 +7064,6 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string_decoder@^1.1.1, string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" @@ -7020,6 +7202,11 @@ timsort@^0.3.0: resolved "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-emitter@2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz" + integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== + tmp-promise@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.2.tgz" @@ -7085,9 +7272,9 @@ tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: punycode "^2.1.1" tough-cookie@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" - integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== + version "4.1.4" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== dependencies: psl "^1.1.33" punycode "^2.1.1" @@ -7103,7 +7290,7 @@ tr46@^1.0.1: tr46@^4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" + resolved "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz" integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== dependencies: punycode "^2.3.0" @@ -7164,6 +7351,11 @@ type-fest@^0.20.2: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +typed-function@1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/typed-function/-/typed-function-1.0.3.tgz" + integrity sha512-sVC/1pm70oELDFMdYtFXMFqyawenLoaDiAXA3QvOAwKF/WvFNTSJN23cY2lFNL8iP0kh3T0PPKewrboO8XUVGQ== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" @@ -7240,7 +7432,7 @@ universalify@^0.1.0: universalify@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== universalify@^2.0.0: @@ -7307,7 +7499,7 @@ url-parse-lax@^3.0.0: url-parse@^1.5.3: version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz" integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" @@ -7395,21 +7587,12 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -verror@^1.10.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb" - integrity sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vm-browserify@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vue@^3.0.0: +vue@^3.0.0, vue@3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/vue/-/vue-3.1.2.tgz" integrity sha512-q/rbKpb7aofax4ugqu2k/uj7BYuNPcd6Z5/qJtfkJQsE0NkwVoCyeSh7IZGH61hChwYn3CEkh4bHolvUPxlQ+w== @@ -7436,7 +7619,7 @@ w3c-xmlserializer@^1.1.2: w3c-xmlserializer@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz" integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== dependencies: xml-name-validator "^4.0.0" @@ -7466,7 +7649,7 @@ webidl-conversions@^4.0.2: webidl-conversions@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: @@ -7478,7 +7661,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: whatwg-encoding@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz" integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== dependencies: iconv-lite "0.6.3" @@ -7490,12 +7673,12 @@ whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: whatwg-mimetype@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz" integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== whatwg-url@^12.0.0, whatwg-url@^12.0.1: version "12.0.1" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz" integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ== dependencies: tr46 "^4.1.1" @@ -7611,9 +7794,9 @@ ws@^7.0.0: integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== ws@^8.13.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" - integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== + version "8.18.2" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz" + integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== xdg-basedir@^4.0.0: version "4.0.0" @@ -7627,14 +7810,9 @@ xml-name-validator@^3.0.0: xml-name-validator@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz" integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== -xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: - version "15.1.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" - integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== - xmlchars@^2.1.1, xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" From 46af2841f09b6d3df912b0f0769fe64cd02b1d5b Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Tue, 17 Jun 2025 12:52:44 -0500 Subject: [PATCH 07/25] Added muscle energy block Found problem: the number for the muscle energy window and the raw data doesn't match up --- src/renderer/js/blockly-main.js | 5 ++++- src/renderer/js/categories.js | 2 +- src/renderer/js/customblock.js | 26 ++++++++++++++++++++++++++ src/renderer/js/interpreter-api.js | 22 +++++++++++++++------- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/renderer/js/blockly-main.js b/src/renderer/js/blockly-main.js index 90f4ddf..eb2cb0c 100644 --- a/src/renderer/js/blockly-main.js +++ b/src/renderer/js/blockly-main.js @@ -114,7 +114,10 @@ export const BlocklyMain = class { registerCustomToolbox = () => { // Triggers everytime category opens this.workspace.registerToolboxCategoryCallback("DATA", (ws) => { - return this.createCustomToolBox(["filter_signal"]); + return this.createCustomToolBox([ + "filter_signal", + "muscle_energy", + ]); }); }; diff --git a/src/renderer/js/categories.js b/src/renderer/js/categories.js index c0c7017..2d996ab 100644 --- a/src/renderer/js/categories.js +++ b/src/renderer/js/categories.js @@ -24,7 +24,7 @@ export const Categories = { cat_data: { name: "Data", colour: 330, - modules: ["delta", "theta", "alpha", "beta", "gamma", "print"] + modules: ["delta", "theta", "alpha", "beta", "gamma", "print", "muscle_energy"] }, cat_drone: { diff --git a/src/renderer/js/customblock.js b/src/renderer/js/customblock.js index ab26ff1..1f50590 100644 --- a/src/renderer/js/customblock.js +++ b/src/renderer/js/customblock.js @@ -175,6 +175,32 @@ export const createCustomBlocks = function () { var code = `blockly_print(${text});\n`; return code; }; + + // 1. Define the block’s JSON + const muscleEnergyJson = { + type: "muscle_energy", + message0: "muscle energy", + output: "Number", + colour: 230, + tooltip: "Current EMG muscle‐energy value", + helpUrl: "" + }; + + // 2. Tell Blockly about the block + Blockly.Blocks["muscle_energy"] = { + init: function () { + this.jsonInit(muscleEnergyJson); + } + }; + + // 3. Generate JS for the block: read from window.filteredSample + javascriptGenerator.forBlock["muscle_energy"] = function (block) { + // Call the host-registered function: + return ["getMuscleEnergy()", Order.NONE]; + }; + + + }; /// diff --git a/src/renderer/js/interpreter-api.js b/src/renderer/js/interpreter-api.js index 7c3238d..166981c 100644 --- a/src/renderer/js/interpreter-api.js +++ b/src/renderer/js/interpreter-api.js @@ -3,6 +3,8 @@ import { WrapperFunctions } from "./wrapper-functions"; export const InterpreterAPI = class { constructor(workspace) { this.wrapperFunctions = new WrapperFunctions(workspace); + + // Expose all the functions you need inside Blockly’s interpreter this.nativeFunctions = { highlightBlock: this.wrapperFunctions.highlightWrapper, getDelta: this.wrapperFunctions.getDelta, @@ -18,8 +20,12 @@ export const InterpreterAPI = class { ccw: this.wrapperFunctions.ccw, cw: this.wrapperFunctions.cw, takeoff: this.wrapperFunctions.takeoff, - land: this.wrapperFunctions.land + land: this.wrapperFunctions.land, + + // ← NEW: expose muscle energy from window.filteredSample + getMuscleEnergy: () => window.filteredSample }; + this.asyncFunctions = { filterSignal: this.wrapperFunctions.filterSignalWrapper, wait_seconds: this.wrapperFunctions.wait_seconds @@ -27,19 +33,21 @@ export const InterpreterAPI = class { } init(interpreter, globalObject) { - for (let [key, value] of Object.entries(this.nativeFunctions)) { + // Bind native (synchronous) functions + for (const [name, fn] of Object.entries(this.nativeFunctions)) { interpreter.setProperty( globalObject, - key, - interpreter.createNativeFunction(value.bind(this.wrapperFunctions)) + name, + interpreter.createNativeFunction(fn.bind(this.wrapperFunctions)) ); } - for (let [key, value] of Object.entries(this.asyncFunctions)) { + // Bind async functions (returning promises) + for (const [name, fn] of Object.entries(this.asyncFunctions)) { interpreter.setProperty( globalObject, - key, - interpreter.createAsyncFunction(value.bind(this.wrapperFunctions)) + name, + interpreter.createAsyncFunction(fn.bind(this.wrapperFunctions)) ); } } From 1950b3366803ba7990a230aff0c469cefb35f620 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Mon, 23 Jun 2025 10:36:02 -0500 Subject: [PATCH 08/25] Python server starts automatically - need to connect the vex through AP before starting the application --- README.md | 144 +++++++++--------- package-lock.json | 72 ++++----- package.json | 17 +-- VEXServer.py => resources/python/VEXServer.py | 14 +- VexTest.py => resources/python/VexTest.py | 0 src/main/index.js | 32 +++- yarn.lock | 10 +- 7 files changed, 154 insertions(+), 135 deletions(-) rename VEXServer.py => resources/python/VEXServer.py (92%) rename VexTest.py => resources/python/VexTest.py (100%) diff --git a/README.md b/README.md index 196f2d0..48cdb5c 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,113 @@ # Neuroscope -This project builds on [this boiler plate](https://github.com/a133xz/electron-vuejs-parcel-boilerplate) and adds functionality to control a Sphero Bolt using WebSocket commands. +Neuroscope is an Electron-based application for real-time EEG/BCI signal visualization, feature extraction, and device connectivity using Bluetooth-enabled headsets like **OpenBCI Ganglion**. It features a Blockly-based visual programming interface for custom workflows and supports VEX and Ganglion devices. + +--- + +## Features + +- **EEG Device Support:** Connect to OpenBCI Ganglion headsets via Bluetooth. +- **Real-Time Visualization:** View EEG and telemetry data live. +- **Blockly Programming:** Drag-and-drop blocks for custom signal processing and logic. +- **Extensible:** Easily add new blocks or device integrations. + +--- ## Getting Started ### Prerequisites -- Node.js +- [Node.js](https://nodejs.org/) (v14+ recommended) - npm or yarn -- Python 3.x -- `websockets` Python package +- Python 3.x (for some optional features) +- Python package: `websockets` (optional, only if using WebSocket features) +- A supported EEG device (OpenBCI Ganglion) ### Installation -1. Clone the repository: +1. **Clone the repository:** ```sh git clone https://github.com/yourusername/neuroscope.git cd neuroscope ``` -2. Install the dependencies: +2. **Install Node dependencies:** ```sh yarn install + # or + npm install ``` -3. Start the Sphero server: +3. *(Optional, only if using Python WebSocket features)* + **Install Python dependencies:** ```sh - python SpheroServer.py + pip install websockets ``` -4. Start the Electron application: +--- + +## Running the Application + +1. **Start the Electron Application** ```sh yarn serve + # or + npm run serve ``` + This will launch the Electron app. In development mode, it will open with hot-reloading and developer tools enabled. + +2. **Connect Your EEG Device** + - Click the **Bluetooth** button in the UI. + - Select your **Ganglion** device from the list. + - Wait for the connection confirmation. + +--- + ## Usage -### Controlling the Sphero +- **Signal Visualization:** + EEG channels are displayed in real time. You can view raw and filtered signals, as well as extracted features (alpha, beta, etc.). -The application sends WebSocket commands to control the Sphero Bolt. The following commands are available: +- **Blockly Programming:** + Use the Blockly interface to create custom workflows. Drag and drop blocks for signal processing and feature extraction. -- **drone-up**: Moves the Sphero up. -- **drone-down**: Moves the Sphero down. -- **drone-forward**: Moves the Sphero forward. +--- -### Code Changes +## Troubleshooting -The main changes are in the `index.js` file: +- **Bluetooth Issues:** + Make sure your Ganglion device is powered on and not paired with another app. On Windows, you may need to grant Bluetooth permissions. +- **Missing Dependencies:** + If you see errors about missing modules, re-run `yarn install` or `npm install`. +- **Electron Fails to Start:** + Make sure you are using a compatible Node.js version and have all dependencies installed. -1. **WebSocket Setup**: - ```javascript - const WebSocket = require('ws'); - const ws = new WebSocket('ws://localhost:8765'); +--- - ws.on('open', function open() { - console.log('WebSocket connection opened'); - }); +## Development - ws.on('error', function error(err) { - console.error('WebSocket error:', err); - }); - ``` +- **Hot Reload:** + The app reloads automatically in development mode. +- **Main Process:** + See `src/main/index.js` for Electron main process logic. +- **Renderer Process:** + See `src/renderer/js/` for UI and signal processing code. +- **Blockly Blocks:** + Custom blocks are defined in `src/renderer/js/customblock.js`. -2. **Command Sending with Debounce**: - ```javascript - let lastCommandTime = 0; - const commandInterval = 3000; // 3 seconds - - function sendCommand(command) { - const currentTime = Date.now(); - if (currentTime - lastCommandTime >= commandInterval) { - ws.send(JSON.stringify(command)); - console.log("Command sent:", command); - lastCommandTime = currentTime; - } else { - console.log("Command skipped to avoid spamming:", command); - } - } - ``` +--- -3. **IPC Event Handlers**: - ```javascript - ipcMain.on("drone-up", (event, response) => { - let recent_val = parseInt(response); - let upVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; - console.log("drone up", upVal, "sent", response); - const moveCommand = { action: "move", heading: 0, speed: upVal, duration: 1 }; - sendCommand(moveCommand); - }); - - ipcMain.on("drone-down", (event, response) => { - let recent_val = parseInt(response); - let downVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; - console.log("drone down", downVal, "sent", response); - const moveCommand = { action: "move", heading: 180, speed: downVal, duration: 1 }; - sendCommand(moveCommand); - }); - - ipcMain.on("drone-forward", (event, response) => { - let recent_val = parseInt(response); - let forwardVal = recent_val > maxSpeed ? maxSpeed : recent_val < minSpeed ? minSpeed : recent_val; - console.log("drone forward", forwardVal, "sent", response); - const moveCommand = { action: "move", heading: 90, speed: forwardVal, duration: 1 }; - sendCommand(moveCommand); - }); - ``` +## License + +MIT License. See [LICENSE](LICENSE) for details. + +--- + +## Acknowledgements -## Work in Progress +- [OpenBCI Ganglion](https://shop.openbci.com/products/ganglion-board) +- [Blockly](https://developers.google.com/blockly) +- [Electron](https://www.electronjs.org/) -This project is a work in progress. The current implementation allows basic control of the Sphero Bolt using WebSocket commands. Further improvements and features are planned for future updates. +--- \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7e42d0a..de32e1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,9 @@ "blockly": "^10.4.3", "d3": "^7.9.0", "js-interpreter": "^5.1.1", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "wait-on": "^5.3.0", + "ws": "^8.18.2" }, "devDependencies": { "@parcel/transformer-sass": "2.0.0-beta.3.1", @@ -29,8 +31,7 @@ "prettier": "^2.3.1", "sass": "^1.35.1", "typescript": "^4.3.2", - "vue": "^3.0.0", - "wait-on": "^5.0.0" + "vue": "^3.0.0" } }, "node_modules/@babel/code-frame": { @@ -648,14 +649,12 @@ "version": "9.2.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@hapi/topo": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" @@ -1712,6 +1711,28 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@parcel/reporter-dev-server/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@parcel/resolver-default": { "version": "2.0.0-beta.3.1", "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.0.0-beta.3.1.tgz", @@ -2197,7 +2218,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.2.tgz", "integrity": "sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0" @@ -2207,14 +2227,12 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@sindresorhus/is": { @@ -3209,7 +3227,6 @@ "version": "0.21.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.10.0" @@ -3553,26 +3570,6 @@ "node": ">=14" } }, - "node_modules/blockly/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/blockly/node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -7134,7 +7131,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", - "dev": true, "funding": [ { "type": "individual", @@ -8850,7 +8846,6 @@ "version": "17.4.0", "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", "integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@hapi/hoek": "^9.0.0", @@ -9158,7 +9153,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.camelcase": { @@ -14082,7 +14076,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz", "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==", - "dev": true, "license": "MIT", "dependencies": { "axios": "^0.21.1", @@ -14102,7 +14095,6 @@ "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^1.9.0" @@ -14115,7 +14107,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, "license": "0BSD" }, "node_modules/wcwidth": { @@ -14301,17 +14292,16 @@ } }, "node_modules/ws": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz", - "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==", - "dev": true, + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index cf07e26..4da481b 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,7 @@ "prettier": "^2.3.1", "sass": "^1.35.1", "typescript": "^4.3.2", - "vue": "^3.0.0", - "wait-on": "^5.0.0" + "vue": "^3.0.0" }, "main": "./src/main/index.js", "build": { @@ -60,11 +59,9 @@ "compression": "maximum", "files": [ "src/main/**/*", - "node_modules/**/*", - { - "from": "build/renderer", - "to": "src/renderer" - } + "src/renderer/**/*", + "resources/python/**/*", + "node_modules/**/*" ], "mac": { "icon": "src/renderer/icons/icon.icns", @@ -95,6 +92,8 @@ "blockly": "^10.4.3", "d3": "^7.9.0", "js-interpreter": "^5.1.1", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "ws": "^8.18.2", + "wait-on": "^5.3.0" } -} +} \ No newline at end of file diff --git a/VEXServer.py b/resources/python/VEXServer.py similarity index 92% rename from VEXServer.py rename to resources/python/VEXServer.py index fba0b5c..585255c 100644 --- a/VEXServer.py +++ b/resources/python/VEXServer.py @@ -7,15 +7,15 @@ # Robot initialization for AIM platform robot = Robot() -color_list = [ - RED, GREEN, BLUE, WHITE, YELLOW, ORANGE, PURPLE, CYAN -] +# color_list = [ +# RED, GREEN, BLUE, WHITE, YELLOW, ORANGE, PURPLE, CYAN +# ] -for color in color_list: - robot.led.on(ALL_LEDS, color) - wait(1, SECONDS) +# for color in color_list: +# robot.led.on(ALL_LEDS, color) +# wait(1, SECONDS) -robot.led.off(ALL_LEDS) +# robot.led.off(ALL_LEDS) # Command handler for VEX AIM # Made 'path' optional so it works with the current websockets API diff --git a/VexTest.py b/resources/python/VexTest.py similarity index 100% rename from VexTest.py rename to resources/python/VexTest.py diff --git a/src/main/index.js b/src/main/index.js index 272a0ed..ac1fdae 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,7 +1,9 @@ const { app, BrowserWindow, ipcMain, dialog } = require("electron"); +const { spawn } = require('child_process'); const path = require("path"); const tello = require("./tello.js"); const WebSocket = require('ws'); +const waitOn = require('wait-on'); const isProduction = process.env.NODE_ENV === "production" || !process || !process.env || !process.env.NODE_ENV; @@ -19,7 +21,30 @@ let bleCallback = null; // be closed automatically when the JavaScript object is garbage collected. let win; +let pythonProcess; + async function createWindow() { + // ---- Start Python VEXServer ---- + const pythonExe = 'python'; // or full path to python if not in PATH + const script = path.join(__dirname, '..', '..', 'resources', 'python', 'VEXServer.py'); + pythonProcess = spawn(pythonExe, [script]); + + pythonProcess.stdout.on('data', (data) => { + console.log(`PYTHON: ${data}`); + }); + + pythonProcess.stderr.on('data', (data) => { + console.error(`PYTHON ERROR: ${data}`); + }); + + pythonProcess.on('close', (code) => { + console.log(`Python process exited with code ${code}`); + }); + // ---- End Python VEXServer ---- + + // Wait for the Python WebSocket server to be ready (up to 10 seconds) + await waitOn({ resources: ['tcp:127.0.0.1:8765'], timeout: 10000 }); + // If you'd like to set up auto-updating for your app, // I'd recommend looking at https://github.com/iffy/electron-updater-example // to use the method most suitable for you. @@ -48,8 +73,7 @@ async function createWindow() { if (isDevelopment) { win.loadURL(selfHost); } else { - //win.loadURL(`${Protocol.scheme}://rse/index.html`); - win.loadURL(`file://${path.join(__dirname, "../renderer/index.html")}`); + win.loadURL(`file://${path.join(__dirname, "../../build/renderer/index.html")}`); } // Only do these things when in development @@ -365,3 +389,7 @@ ipcMain.on("toMain", (event, { data }) => { event.reply("fromMain", reply); //win.webContents.send("fromMain", reply); }); + +win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { + console.error('Window failed to load:', errorDescription); +}); diff --git a/yarn.lock b/yarn.lock index ebb89d5..927f1cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7624,7 +7624,7 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" -wait-on@^5.0.0: +wait-on@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz" integrity sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg== @@ -7789,11 +7789,11 @@ ws@^6.1.2: async-limiter "~1.0.0" ws@^7.0.0: - version "7.5.0" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz" - integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== + version "7.5.10" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.13.0: +ws@^8.13.0, ws@^8.18.2: version "8.18.2" resolved "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz" integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== From ceb24df3602b59691a142b93306d7b0ae060095f Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Wed, 25 Jun 2025 17:59:56 -0500 Subject: [PATCH 09/25] [UNSTABLE] Exe python --- package.json | 6 ++ resources/python/VEXServer.py | 11 ++- resources/python/VEXServer.spec | 38 +++++++++ .../python/vex}/settings.json | 0 src/main/index.js | 85 +++++++++++-------- 5 files changed, 104 insertions(+), 36 deletions(-) create mode 100644 resources/python/VEXServer.spec rename {.vscode => resources/python/vex}/settings.json (100%) diff --git a/package.json b/package.json index 4da481b..6cdc42f 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,12 @@ "resources/python/**/*", "node_modules/**/*" ], + "extraResources": [ + { + "from": "resources/python/VEXServer.exe", + "to": "python/VEXServer.exe" + } + ], "mac": { "icon": "src/renderer/icons/icon.icns", "category": "public.app-category.productivity", diff --git a/resources/python/VEXServer.py b/resources/python/VEXServer.py index 585255c..a95ff9c 100644 --- a/resources/python/VEXServer.py +++ b/resources/python/VEXServer.py @@ -3,10 +3,19 @@ import json from vex import * from vex.vex_globals import * +import sys +import os # Robot initialization for AIM platform robot = Robot() +def resource_path(relative_path): + if hasattr(sys, '_MEIPASS'): + return os.path.join(sys._MEIPASS, relative_path) + return os.path.join(os.path.abspath("."), relative_path) + +settings_path = resource_path("vex/settings.json") + # color_list = [ # RED, GREEN, BLUE, WHITE, YELLOW, ORANGE, PURPLE, CYAN # ] @@ -59,7 +68,7 @@ async def handle_command(websocket, path=None): await websocket.send(json.dumps({"status": "error", "message": str(e)})) async def main(): - port = 8765 + port = 8777 print(f"Starting WebSocket server on ws://127.0.0.1:{port}") async with websockets.serve(handle_command, "127.0.0.1", port): await asyncio.Future() # run forever diff --git a/resources/python/VEXServer.spec b/resources/python/VEXServer.spec new file mode 100644 index 0000000..5ec8acb --- /dev/null +++ b/resources/python/VEXServer.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['VEXServer.py'], + pathex=[], + binaries=[], + datas=[('vex/settings.json', 'vex')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='VEXServer', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/.vscode/settings.json b/resources/python/vex/settings.json similarity index 100% rename from .vscode/settings.json rename to resources/python/vex/settings.json diff --git a/src/main/index.js b/src/main/index.js index ac1fdae..142a65b 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -23,27 +23,41 @@ let win; let pythonProcess; -async function createWindow() { - // ---- Start Python VEXServer ---- - const pythonExe = 'python'; // or full path to python if not in PATH - const script = path.join(__dirname, '..', '..', 'resources', 'python', 'VEXServer.py'); - pythonProcess = spawn(pythonExe, [script]); +const isPackaged = app.isPackaged; +let script; +let pythonExe; + +if (isPackaged) { + // Use the packaged EXE + script = path.join(process.resourcesPath, 'python', 'VEXServer.exe'); + pythonExe = script; // The exe is self-contained +} else { + // Use the script and system Python in development + script = path.join(__dirname, '..', '..', 'resources', 'python', 'VEXServer.py'); + pythonExe = 'python'; +} - pythonProcess.stdout.on('data', (data) => { - console.log(`PYTHON: ${data}`); - }); +console.log("Launching Python server at:", script); +pythonProcess = isPackaged + ? spawn(pythonExe, []) // exe, no args + : spawn(pythonExe, [script]); - pythonProcess.stderr.on('data', (data) => { - console.error(`PYTHON ERROR: ${data}`); - }); +pythonProcess.stdout.on('data', (data) => { + console.log(`PYTHON: ${data}`); +}); - pythonProcess.on('close', (code) => { - console.log(`Python process exited with code ${code}`); - }); - // ---- End Python VEXServer ---- +pythonProcess.stderr.on('data', (data) => { + console.error(`PYTHON ERROR: ${data}`); +}); +pythonProcess.on('close', (code) => { + console.log(`Python process exited with code ${code}`); +}); +// ---- End Python VEXServer ---- + +async function createWindow() { // Wait for the Python WebSocket server to be ready (up to 10 seconds) - await waitOn({ resources: ['tcp:127.0.0.1:8765'], timeout: 10000 }); + await waitOn({ resources: ['ws://127.0.0.1:8777'], timeout: 10000 }); // If you'd like to set up auto-updating for your app, // I'd recommend looking at https://github.com/iffy/electron-updater-example @@ -68,6 +82,11 @@ async function createWindow() { } }); + // Add this here, not outside! + win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { + console.error('Window failed to load:', errorDescription); + }); + // Load the url of the dev server if in development mode // Load the index.html when not in development if (isDevelopment) { @@ -140,7 +159,7 @@ async function createWindow() { } }); - const ws = new WebSocket('ws://127.0.0.1:8765'); + const ws = new WebSocket('ws://127.0.0.1:8777'); ws.on('open', function open() { console.log('WebSocket connection opened'); @@ -260,20 +279,20 @@ async function createWindow() { sendCommand(command); // Use the existing sendCommand function }); - javascriptGenerator.forBlock["move"] = function (block) { - var distance = block.getFieldValue("DISTANCE"); - var heading = block.getFieldValue("HEADING"); - var code = `electronAPI.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; - console.log("Generated code for move block:", code); - return code; - }; - - javascriptGenerator.forBlock["led_control"] = function (block) { - var color = block.getFieldValue("COLOR"); - var code = `electronAPI.sendCommand({ action: "led_on", color: "${color}" });\n`; - console.log("Generated code for LED block:", code); - return code; - }; + // javascriptGenerator.forBlock["move"] = function (block) { + // var distance = block.getFieldValue("DISTANCE"); + // var heading = block.getFieldValue("HEADING"); + // var code = `electronAPI.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; + // console.log("Generated code for move block:", code); + // return code; + // }; + + // javascriptGenerator.forBlock["led_control"] = function (block) { + // var color = block.getFieldValue("COLOR"); + // var code = `electronAPI.sendCommand({ action: "led_on", color: "${color}" });\n`; + // console.log("Generated code for LED block:", code); + // return code; + // }; } // This method will be called when Electron has finished @@ -389,7 +408,3 @@ ipcMain.on("toMain", (event, { data }) => { event.reply("fromMain", reply); //win.webContents.send("fromMain", reply); }); - -win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { - console.error('Window failed to load:', errorDescription); -}); From e44b1e1d1dabe30a5d15e4d2e264cb44b765c57f Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Tue, 8 Jul 2025 12:19:09 -0500 Subject: [PATCH 10/25] Revert "[UNSTABLE] Exe python" This reverts commit ceb24df3602b59691a142b93306d7b0ae060095f. --- .../python/vex => .vscode}/settings.json | 0 package.json | 6 -- resources/python/VEXServer.py | 11 +-- resources/python/VEXServer.spec | 38 --------- src/main/index.js | 85 ++++++++----------- 5 files changed, 36 insertions(+), 104 deletions(-) rename {resources/python/vex => .vscode}/settings.json (100%) delete mode 100644 resources/python/VEXServer.spec diff --git a/resources/python/vex/settings.json b/.vscode/settings.json similarity index 100% rename from resources/python/vex/settings.json rename to .vscode/settings.json diff --git a/package.json b/package.json index 6cdc42f..4da481b 100644 --- a/package.json +++ b/package.json @@ -63,12 +63,6 @@ "resources/python/**/*", "node_modules/**/*" ], - "extraResources": [ - { - "from": "resources/python/VEXServer.exe", - "to": "python/VEXServer.exe" - } - ], "mac": { "icon": "src/renderer/icons/icon.icns", "category": "public.app-category.productivity", diff --git a/resources/python/VEXServer.py b/resources/python/VEXServer.py index a95ff9c..585255c 100644 --- a/resources/python/VEXServer.py +++ b/resources/python/VEXServer.py @@ -3,19 +3,10 @@ import json from vex import * from vex.vex_globals import * -import sys -import os # Robot initialization for AIM platform robot = Robot() -def resource_path(relative_path): - if hasattr(sys, '_MEIPASS'): - return os.path.join(sys._MEIPASS, relative_path) - return os.path.join(os.path.abspath("."), relative_path) - -settings_path = resource_path("vex/settings.json") - # color_list = [ # RED, GREEN, BLUE, WHITE, YELLOW, ORANGE, PURPLE, CYAN # ] @@ -68,7 +59,7 @@ async def handle_command(websocket, path=None): await websocket.send(json.dumps({"status": "error", "message": str(e)})) async def main(): - port = 8777 + port = 8765 print(f"Starting WebSocket server on ws://127.0.0.1:{port}") async with websockets.serve(handle_command, "127.0.0.1", port): await asyncio.Future() # run forever diff --git a/resources/python/VEXServer.spec b/resources/python/VEXServer.spec deleted file mode 100644 index 5ec8acb..0000000 --- a/resources/python/VEXServer.spec +++ /dev/null @@ -1,38 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - - -a = Analysis( - ['VEXServer.py'], - pathex=[], - binaries=[], - datas=[('vex/settings.json', 'vex')], - hiddenimports=[], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - noarchive=False, - optimize=0, -) -pyz = PYZ(a.pure) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.datas, - [], - name='VEXServer', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, -) diff --git a/src/main/index.js b/src/main/index.js index 142a65b..ac1fdae 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -23,41 +23,27 @@ let win; let pythonProcess; -const isPackaged = app.isPackaged; -let script; -let pythonExe; - -if (isPackaged) { - // Use the packaged EXE - script = path.join(process.resourcesPath, 'python', 'VEXServer.exe'); - pythonExe = script; // The exe is self-contained -} else { - // Use the script and system Python in development - script = path.join(__dirname, '..', '..', 'resources', 'python', 'VEXServer.py'); - pythonExe = 'python'; -} - -console.log("Launching Python server at:", script); -pythonProcess = isPackaged - ? spawn(pythonExe, []) // exe, no args - : spawn(pythonExe, [script]); +async function createWindow() { + // ---- Start Python VEXServer ---- + const pythonExe = 'python'; // or full path to python if not in PATH + const script = path.join(__dirname, '..', '..', 'resources', 'python', 'VEXServer.py'); + pythonProcess = spawn(pythonExe, [script]); -pythonProcess.stdout.on('data', (data) => { - console.log(`PYTHON: ${data}`); -}); + pythonProcess.stdout.on('data', (data) => { + console.log(`PYTHON: ${data}`); + }); -pythonProcess.stderr.on('data', (data) => { - console.error(`PYTHON ERROR: ${data}`); -}); + pythonProcess.stderr.on('data', (data) => { + console.error(`PYTHON ERROR: ${data}`); + }); -pythonProcess.on('close', (code) => { - console.log(`Python process exited with code ${code}`); -}); -// ---- End Python VEXServer ---- + pythonProcess.on('close', (code) => { + console.log(`Python process exited with code ${code}`); + }); + // ---- End Python VEXServer ---- -async function createWindow() { // Wait for the Python WebSocket server to be ready (up to 10 seconds) - await waitOn({ resources: ['ws://127.0.0.1:8777'], timeout: 10000 }); + await waitOn({ resources: ['tcp:127.0.0.1:8765'], timeout: 10000 }); // If you'd like to set up auto-updating for your app, // I'd recommend looking at https://github.com/iffy/electron-updater-example @@ -82,11 +68,6 @@ async function createWindow() { } }); - // Add this here, not outside! - win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { - console.error('Window failed to load:', errorDescription); - }); - // Load the url of the dev server if in development mode // Load the index.html when not in development if (isDevelopment) { @@ -159,7 +140,7 @@ async function createWindow() { } }); - const ws = new WebSocket('ws://127.0.0.1:8777'); + const ws = new WebSocket('ws://127.0.0.1:8765'); ws.on('open', function open() { console.log('WebSocket connection opened'); @@ -279,20 +260,20 @@ async function createWindow() { sendCommand(command); // Use the existing sendCommand function }); - // javascriptGenerator.forBlock["move"] = function (block) { - // var distance = block.getFieldValue("DISTANCE"); - // var heading = block.getFieldValue("HEADING"); - // var code = `electronAPI.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; - // console.log("Generated code for move block:", code); - // return code; - // }; - - // javascriptGenerator.forBlock["led_control"] = function (block) { - // var color = block.getFieldValue("COLOR"); - // var code = `electronAPI.sendCommand({ action: "led_on", color: "${color}" });\n`; - // console.log("Generated code for LED block:", code); - // return code; - // }; + javascriptGenerator.forBlock["move"] = function (block) { + var distance = block.getFieldValue("DISTANCE"); + var heading = block.getFieldValue("HEADING"); + var code = `electronAPI.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; + console.log("Generated code for move block:", code); + return code; + }; + + javascriptGenerator.forBlock["led_control"] = function (block) { + var color = block.getFieldValue("COLOR"); + var code = `electronAPI.sendCommand({ action: "led_on", color: "${color}" });\n`; + console.log("Generated code for LED block:", code); + return code; + }; } // This method will be called when Electron has finished @@ -408,3 +389,7 @@ ipcMain.on("toMain", (event, { data }) => { event.reply("fromMain", reply); //win.webContents.send("fromMain", reply); }); + +win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { + console.error('Window failed to load:', errorDescription); +}); From c54884cfb969b5ae60eb2445e46c077f4e531602 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Tue, 8 Jul 2025 12:23:11 -0500 Subject: [PATCH 11/25] Added Vex Files --- resources/python/vex/__init__.py | 17 + .../vex/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 614 bytes .../vex/__pycache__/aim.cpython-313.pyc | Bin 0 -> 118347 bytes .../vex/__pycache__/settings.cpython-313.pyc | Bin 0 -> 2345 bytes .../__pycache__/vex_globals.cpython-313.pyc | Bin 0 -> 8285 bytes .../__pycache__/vex_messages.cpython-313.pyc | Bin 0 -> 34130 bytes .../vex/__pycache__/vex_types.cpython-313.pyc | Bin 0 -> 17008 bytes resources/python/vex/aim.py | 2325 +++++++++++++++++ resources/python/vex/settings.json | 5 + resources/python/vex/settings.py | 48 + resources/python/vex/vex_globals.py | 295 +++ resources/python/vex/vex_messages.py | 656 +++++ resources/python/vex/vex_types.py | 389 +++ 13 files changed, 3735 insertions(+) create mode 100644 resources/python/vex/__init__.py create mode 100644 resources/python/vex/__pycache__/__init__.cpython-313.pyc create mode 100644 resources/python/vex/__pycache__/aim.cpython-313.pyc create mode 100644 resources/python/vex/__pycache__/settings.cpython-313.pyc create mode 100644 resources/python/vex/__pycache__/vex_globals.cpython-313.pyc create mode 100644 resources/python/vex/__pycache__/vex_messages.cpython-313.pyc create mode 100644 resources/python/vex/__pycache__/vex_types.cpython-313.pyc create mode 100644 resources/python/vex/aim.py create mode 100644 resources/python/vex/settings.json create mode 100644 resources/python/vex/settings.py create mode 100644 resources/python/vex/vex_globals.py create mode 100644 resources/python/vex/vex_messages.py create mode 100644 resources/python/vex/vex_types.py diff --git a/resources/python/vex/__init__.py b/resources/python/vex/__init__.py new file mode 100644 index 0000000..1ecd4a0 --- /dev/null +++ b/resources/python/vex/__init__.py @@ -0,0 +1,17 @@ +# ================================================================================================= +# Copyright (c) Innovation First 2025. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# ================================================================================================= +""" +AIM WebSocket API + +This package provides modules for interacting with the VEX AIM Robot using WebSocket communication. + +Modules exposed: +- `aim`: Core functionality for robot control and WebSocket communication. +- `vex_types`: Definitions for various types and enums used in the API. +- `vex_globals`: Global constants and variables to match VEXcode AIM API. +""" + +from .aim import * +from .vex_types import * diff --git a/resources/python/vex/__pycache__/__init__.cpython-313.pyc b/resources/python/vex/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77be36a0d6e70b5938980a8bed45a4ced2bb8e59 GIT binary patch literal 614 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
  • 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 F{$K508b|;D literal 0 HcmV?d00001 diff --git a/resources/python/vex/__pycache__/settings.cpython-313.pyc b/resources/python/vex/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..030b32ddb3e691aed3f839e9af6f79c15bdcf33f GIT binary patch literal 2345 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 z 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 5: + self.current_status = self._empty_status + + # print("current_status: ", self.current_status) + time.sleep(0.05) + else: + # print ("trying to reconnect to ws_status") + try: + print(f"{self.ws_name} reconnecting") + self.ws.connect(self.uri) + except: + pass # we'll keep trying to reconnect + +class WSImageThread(WSThread): + """ Websocket thread for receiving image stream from the robot""" + def __init__(self, host): + super().__init__ (host, "ws_img") + self.current_image_index = 0 + self.image_list: list[bytes] = [bytes(1), bytes(1)] + + self._next_image_index = 1 + self._streaming = False + + def run(self): + while self.running: + if self._ws_needs_reset: + self.ws_close() + self._ws_needs_reset = False + + if self.ws.connected and self._streaming: + if self.current_image_index == 1: + self._next_image_index = 1 + self.current_image_index = 0 + else: + self._next_image_index = 0 + self.current_image_index = 1 + + try: + self.image_list[self._next_image_index] = cast(bytes, self.ws_receive()) # cast to narrow str | bytes down to bytes + if self.callback: + if callable(self.callback): + self.callback() + except ReceiveErrorException: + self.image_list[self._next_image_index] = (0).to_bytes(1, 'little') + + elif not self.ws.connected: + self._streaming = False + try: + print(f"{self.ws_name} reconnecting") + self.ws.connect(self.uri) + except: + pass # we'll keep trying to reconnect + else: + time.sleep(0.05) + + def stop_stream(self): + """ stop the image stream""" + self._streaming = False + self.ws_send((0).to_bytes(1, 'little'), websocket.ABNF.OPCODE_BINARY) + + def start_stream(self): + """ start the image stream""" + self._streaming = True + self.ws_send((1).to_bytes(1, 'little'), websocket.ABNF.OPCODE_BINARY) + +class WSCommandThread(WSThread): + """ Websocket thread for sending commands to the robot""" + def __init__(self, host): + super().__init__ (host, "ws_cmd") + + def run(self): + while self.running: + if self._ws_needs_reset: + self.ws_close() + self._ws_needs_reset = False + + if not self.ws.connected: + try: + print(f"{self.ws_name} reconnecting") + self.ws.connect(self.uri) + except: + pass # we'll keep on trying + + time.sleep(0.2) + +class WSAudioThread(WSThread): + """ Websocket thread for sending audio to the robot""" + def __init__(self, host): + super().__init__ (host, "ws_audio") + + def run(self): + while self.running: + if self._ws_needs_reset: + self.ws_close() + self._ws_needs_reset = False + + if not self.ws.connected: + try: + print(f"{self.ws_name} reconnecting") + self.ws.connect(self.uri) + except: + pass # we'll keep on trying + + time.sleep(0.2) + +class ColorRGB: + """ RGB color class""" + def __init__(self, r: int, g: int, b: int, t: bool=False): + self.r = r + self.g = g + self.b = b + self.t = t + +class Robot(): + """ + AIM Robot class. + When initializing, provide a host (IP address, hostname, or even domain name) + or leave at empty for default if AIM is in WiFi AP mode. + """ + #region private internal methods + def __init__(self, host=""): + """ + Initialize the Robot with default settings and WebSocket connections. + """ + # If host is not provided, read the host from the settings file + if host == "": + # Create an instance of the Settings class to read the host from the settings file + settings = Settings() + host = settings.host + + self.host = host + print( + f"Welcome to the AIM Websocket Python Client. " + f"Running version {VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}.{VERSION_BETA} " + f"and connecting to {self.host}" + ) + self.move_active_cmd_list = ["drive", "drive_for"] + self.turn_active_cmd_list = [ "turn", "turn_for", "turn_to"] + self.stopped_active_cmd_list = self.move_active_cmd_list + self.turn_active_cmd_list + + + self._ws_status_thread = WSStatusThread(self.host) + self._ws_status_thread.daemon = True + self._ws_status_thread.start() + + self._ws_img_thread = WSImageThread(self.host) + self._ws_img_thread.daemon = True + self._ws_img_thread.start() + + self._ws_cmd_thread = WSCommandThread(self.host) + self._ws_cmd_thread.daemon = True + self._ws_cmd_thread.start() + + self._ws_audio_thread = WSAudioThread(self.host) + self._ws_audio_thread.daemon = True + self._ws_audio_thread.start() + + atexit.register(self.exit_handler) + signal.signal(signal.SIGINT, self.kill_handler) + signal.signal(signal.SIGTERM, self.kill_handler) + + self._program_init() + + self.drive_speed = 100 # Millimeters per second + self.turn_speed = 75 # Degrees per second + # internal class instances to access through robot instance + self.timer = Timer() + self.screen = Screen(self) + self.inertial = Inertial(self) + self.kicker = Kicker(self) + self.sound = Sound(self) + self.led = Led(self) + self.vision = AiVision(self) + + # We don't want to execute certain things (like reset_heading) until we start getting status packets + while self._ws_status_thread.is_current_status_empty() is True: + # print("waiting for status") + time.sleep(0.05) + self.inertial.reset_heading() + + def exit_handler(self): + """upon system exit, either due to SIGINT/SIGTERM or uncaught exceptions""" + if hasattr(self, '_ws_cmd_thread'): #if connection were never established, this property wouldn't exist + pass + # print("program terminating, stopping robot") + # #not attempting to stop robot since upon program end or connection loss, robot stops itself and wouldn't respond anyways. + # try: + # self.stop_all_movement() + # except Exception as error: + # print("exceptions arose during stop_all_movement(), error:", error) + else: + print("program terminating (never connected to robot)") + + try: + self._ws_cmd_thread.running = False + except: + # print("ws_cmd_thread doesn't exist") + pass + try: + self._ws_status_thread.running = False + except: + # print("ws_status_thread doesn't exist") + pass + try: + self._ws_img_thread.running = False + except: + # print("ws_img_thread doesn't exist") + pass + try: + self._ws_img_thread.stop_stream() + except: + # print("error stopping ws_img stream") + pass + + def kill_handler(self, signum, frame): + """when kill signal is received, exit the program. Will result in exit_handler being run""" + signame = signal.Signals(signum).name + print(f'Received signal {signame} ({signum})') + sys.exit(0) + + def __getattribute__(self, name): + """ + This function gets called before any other Robot function.\n + If we are not connected to the robot (just looking at ws_cmd), then raise an exception + and terminate program unless the user handles the exception. + """ + method = object.__getattribute__(self, name) + if not method: + # raise Exception("Method %s not implemented" %name) + return + if callable(method): + if self._ws_cmd_thread.ws.connected is False: + raise DisconnectedException(f"error calling {name}: not connected to robot") + return method + + def _program_init(self): + """ + Sends a command indicating to robot that new program is starting. + To be called during __init__ + """ + message = commands.ProgramInit() + self.robot_send(message.to_json()) + + def _block_on_state(self, state_method): + time_start = time.time() + blocking = True + while True: + if state_method() is False: # debounce + time.sleep(0.05) + if state_method() is False: + break + # print("blocking") + time_elapsed = time.time() - time_start + time.sleep(0.1) + #if turning/moving took too long, we want to stop moving and stop blocking. + if time_elapsed > 10: + print(f"{state_method.__name__} wait timed out, stopping") + self.stop_all_movement() + return + + @property + def status(self): + """ returns the current status of the robot """ + return self._ws_status_thread.current_status + #endregion private internal methods + + #region Generic methods to send commands to the robot + def robot_send(self, json_cmd): + """send a command to the robot through websocket connection""" + disconnected_error = False + #print(json_cmd) + json_cmd_string = json.dumps(json_cmd, separators=(',',':')) + #print("sending: ", json_cmd_string) + try: + cmd_id = json_cmd["cmd_id"] + except: + print("robot_send did not receive a cmd_id") + return + + self._ws_cmd_thread.ws_send(str.encode(json_cmd_string), websocket.ABNF.OPCODE_BINARY) + try: + response_json = self._ws_cmd_thread.ws_receive() + except ReceiveErrorException: + disconnected_error = True + raise DisconnectedException(f"robot got disconnected after sending cmd_id: {cmd_id}") from None # disable exception chaining + # not trying to resend command because that would take too long, let user decide. + + try: + response = json.loads(response_json) + except Exception as error: + print(f"{cmd_id} Error: could not parse ws_cmd JSON response: '{error}'") + print("response_json", response_json) + return + + # print("response_json", response_json) + if response["cmd_id"] == "cmd_unknown": + print("robot: did not recognize command: ", cmd_id) + return + + if response["status"] == "error": + try: + error_info_string = response["error_info"] + except KeyError: + error_info_string = "no reason given" + print("robot: error processing command, reason: ", error_info_string) + return + + # trigger a local update to the robot status flags in ws_status_thread + if response["status"] in ["complete", "in_progress"]: + if response["cmd_id"] in self.move_active_cmd_list: + self._ws_status_thread.is_move_active_flag_needs_setting = True + if response["cmd_id"] in self.turn_active_cmd_list: + self._ws_status_thread.is_turn_active_flag_needs_setting = True + if response["cmd_id"] in self.stopped_active_cmd_list: + self._ws_status_thread.is_moving_flag_needs_setting = True + self._ws_status_thread.is_moving_flag_needs_clearing = False + if response["cmd_id"] == "imu_calibrate": + self._ws_status_thread.imu_cal_flag_needs_setting = True + + return + + def robot_send_audio(self, audio): + """ send audio to the robot through websocket audio thread""" + self._ws_audio_thread.ws_send(audio, websocket.ABNF.OPCODE_BINARY) + + def add_screen_pressed_callback(self, callback: Callable[..., None], arg: tuple=()): + """Adds a screen-pressed callback (delegate to _ws_status_thread).""" + self._ws_status_thread.add_screen_pressed_callback(callback, arg) + + def add_screen_released_callback(self, callback: Callable[..., None], arg: tuple=()): + """Adds a screen-released callback (delegate to _ws_status_thread).""" + self._ws_status_thread.add_screen_released_callback(callback, arg) + + def add_inertial_crash_callback(self, callback: Callable[..., None], arg: tuple=()): + """Adds a crash detected callback (delegate to _ws_status_thread).""" + self._ws_status_thread.add_inertial_crash_callback(callback, arg) + + #endregion Generic methods to send commands to the robot + #region Sensing Motion + def get_x_position(self): + """ returns the x position of the robot""" + origin_x = float(self.status["robot"]["robot_x"]) + origin_y = float(self.status["robot"]["robot_y"]) + # print("raw:", origin_x, origin_y) + offset_radians = -math.radians(self.inertial.heading_offset) + x = origin_x * math.cos(offset_radians) + origin_y * math.sin(offset_radians) + + return x + + def get_y_position(self): + """ returns the y position of the robot""" + origin_x = float(self.status["robot"]["robot_x"]) + origin_y = float(self.status["robot"]["robot_y"]) + offset_radians = -math.radians(self.inertial.heading_offset) + y = origin_y * math.cos(offset_radians) - origin_x * math.sin(offset_radians) + + return y + + def is_move_active(self): + """returns true if a move_at() or move_for() command is active with nonzero speed""" + if self._ws_status_thread.is_move_active_flag_needs_setting: + return True + robot_flags = self.status["robot"]["flags"] + is_move_active = bool(int(robot_flags, 16) & SYS_FLAGS_IS_MOVE_ACTIVE) + return is_move_active + + def is_turn_active(self): + """returns true if a turn(), turn_to(), or turn_for() command is active with nonzero speed""" + if self._ws_status_thread.is_turn_active_flag_needs_setting: + return True + robot_flags = self.status["robot"]["flags"] + is_turn_active = bool(int(robot_flags, 16) & SYS_FLAGS_IS_TURN_ACTIVE) + return is_turn_active + + def is_stopped(self): + """returns true if no move, turn, or spin_wheels command is active (i.e. no wheels should be moving)""" + if self._ws_status_thread.is_moving_flag_needs_clearing: + return True + if self._ws_status_thread.is_moving_flag_needs_setting: + return False + robot_flags = self.status["robot"]["flags"] + is_stopped = not bool(int(robot_flags, 16) & SYS_FLAGS_IS_MOVING) + return is_stopped + #endregion Sensing Motion + + #region Sensing Battery + def get_battery_capacity(self): + """Get the remaining capacity of the battery (relative state of charge) in percent.""" + battery_capacity = self.status["robot"]["battery"] + return battery_capacity + #endregion Sensing Battery + + #region Motion Commands + def set_move_velocity(self, velocity:vex.vexnumber, units:vex.DriveVelocityPercentUnits=vex.DriveVelocityUnits.PERCENT): + """ + overrides the default velocity for all subsequent movement methods in the project.\n + The default move velocity is 50% (100 millimeters per second) + """ + #if velocity is negative throw value error + if velocity < 0: + raise ValueError("velocity must be a positive number") + + if units.value == vex.DriveVelocityUnits.PERCENT.value: + #cannot exeed 100% + if velocity > 100: + velocity = 100 + #convert velocity in percentage to MMPS ex: 100% is 200 MMPS + velocity = int(velocity * 2) + elif units.value == vex.DriveVelocityUnits.MMPS.value: + #cannot exceed MAX velocity MMPS + if velocity > DRIVE_VELOCITY_MAX_MMPS: + velocity = DRIVE_VELOCITY_MAX_MMPS + velocity = int(velocity) + + self.drive_speed = velocity + + def set_turn_velocity(self, velocity:vex.vexnumber, units:vex.TurnVelocityPercentUnits=vex.TurnVelocityUnits.PERCENT): + """ + overrides the default velocity for all subsequent turn methods in the project.\n + The default turn velocity is 50% (75 degrees per second) + """ + #if velocity is negative throw value error + if velocity < 0: + raise ValueError("velocity must be a positive number") + + if units.value == vex.TurnVelocityUnits.PERCENT.value: + #cannot exeed 100% + if velocity > 100: + velocity = 100 + #if velocity is PERCENT convert to DPS ex: 100% is 180 MMPS + velocity = int(velocity * 1.8) + elif units.value == vex.TurnVelocityUnits.DPS.value: + #cannot exceed MAX velocity MMPS + if velocity > TURN_VELOCITY_MAX_DPS: + velocity = TURN_VELOCITY_MAX_DPS + velocity = int(velocity) + + self.turn_speed = velocity + + def move_at(self, angle:vex.vexnumber, velocity=None, + units:vex.DriveVelocityPercentUnits=vex.DriveVelocityUnits.PERCENT): + """ + move indefinitely at angle (-360 to 360 degrees) at velocity (0-100) \n + if velocity is not provided, use the default set by set_move_velocity command \n + The velocity unit is PERCENT (default) or millimeters per second MMPS + """ + if velocity is None: + velocity = self.drive_speed + else: + if units.value == vex.DriveVelocityUnits.PERCENT.value: + #cannot exeed 100% + if velocity > 100: + velocity = 100 + #if velocity is PERCENT convert to MMPS ex: 100% is 200 MMPS + velocity = int(velocity * 2) + elif units.value == vex.DriveVelocityUnits.MMPS.value: + #cannot exceed MAX velocity MMPS + if velocity > DRIVE_VELOCITY_MAX_MMPS: + velocity = DRIVE_VELOCITY_MAX_MMPS + velocity = int(velocity) + # passing negaitive velocity will flip the direction on the firmware side. No need to handle it here + stacking_type = vex.StackingType.STACKING_OFF + message = commands.MoveAt(angle, velocity,stacking_type.value) + self.robot_send(message.to_json()) + + def move_for(self, distance:vex.vexnumber, angle:vex.vexnumber, velocity=None, units:vex.DriveVelocityPercentUnits=vex.DriveVelocityUnits.PERCENT, wait=True): + """move for a distance (mm) at angle (-360 to 360 degrees) at velocity (PERCENT/MMPS). + if velocity or turn_speed is not provided, use the default set by set_move_velocity and set_turn_velocity commands""" + if velocity is None: + velocity = self.drive_speed + else: + if units.value == vex.DriveVelocityUnits.PERCENT.value: + #cannot exeed 100% + if velocity > 100: + velocity = 100 + #if velocity is PERCENT convert to MMPS ex: 100% is 200 MMPS + velocity = int(velocity * 2) + elif units.value == vex.DriveVelocityUnits.MMPS.value: + #cannot exceed MAX velocity MMPS + if velocity > DRIVE_VELOCITY_MAX_MMPS: + velocity = DRIVE_VELOCITY_MAX_MMPS + velocity = int(velocity) + + #if velocity is negative flip the direction + if velocity < 0: + velocity = -velocity + distance = -distance + + turn_speed = self.turn_speed + # if not final_heading: + # final_heading = self.get_heading_raw() + heading = 0 + stacking_type = vex.StackingType.STACKING_OFF + message = commands.MoveFor(distance, angle, velocity, turn_speed, heading, stacking_type.value) + self.robot_send(message.to_json()) + if wait: + self._block_on_state(self.is_move_active) + + def move_with_vectors(self, x, y, r): + """ + moves the robot using vector-based motion, combining horizontal (X-axis) and + vertical (Y-axis) movement and having the robot to rotate at the same time + + x: X-axis velocity (in %). negative values move left and positive values move right\n + y: Y-axis velocity (in %). negative values move backward and positive values move forward\n + r: rotation velocity (in %). negative values move counter-clockwise and positive values move clockwise\n + """ + # clip to +/- 100 + if x > 100: + x = 100 + if x < -100: + x = -100 + if y > 100: + y = 100 + if y < -100: + y = -100 + if r > 100: + r = 100 + if r < -100: + r = -100 + + # scale + x = x * 2.0 + y = y * 2.0 + r = r * 1.8 + + # calculate wheel velocities + w1 = (0.5 * x) + ((0.866) * y) + r + w2 = (0.5 * x) - ((0.866) * y) + r + w3 = r - x + self.spin_wheels(int(w1), int(w2), int(w3)) + + def turn(self, direction: vex.TurnType, velocity=None, units:vex.TurnVelocityPercentUnits=vex.TurnVelocityUnits.PERCENT): + """turn indefinitely at velocity (DPS) in the direction specified by turn_direction (TurnType.LEFT or TurnType.RIGHT). + if velocity is not provided, use the default set by set_turn_velocity command""" + if velocity is None: + velocity = self.turn_speed + else: + if units.value == vex.TurnVelocityUnits.PERCENT.value: + #cannot exeed 100% + if velocity > 100: + velocity = 100 + #if velocity is PERCENT convert to DPS ex: 100% is 180 MMPS + velocity = int(velocity * 1.8) + elif units.value == vex.TurnVelocityUnits.DPS.value: + #cannot exceed MAX velocity MMPS + if velocity > TURN_VELOCITY_MAX_DPS: + velocity = TURN_VELOCITY_MAX_DPS + velocity = int(velocity) + #handle direction flip + if direction == vex.TurnType.LEFT: + velocity = -velocity + stacking_type = vex.StackingType.STACKING_OFF + message = commands.Turn(velocity, stacking_type.value) + self.robot_send(message.to_json()) + + def turn_for(self, direction: vex.TurnType, angle, velocity=None, units:vex.TurnVelocityPercentUnits=vex.TurnVelocityUnits.PERCENT, wait=True): + """turn for a 'angle' number of degrees at turn_rate (deg/sec)""" + if velocity is None: + velocity = self.turn_speed + else: + if units.value == vex.TurnVelocityUnits.PERCENT.value: + #cannot exeed 100% + if velocity > 100: + velocity = 100 + #if velocity is PERCENT convert to DPS ex: 100% is 180 MMPS + velocity = int(velocity * 1.8) + elif units.value == vex.TurnVelocityUnits.DPS.value: + #cannot exceed MAX velocity MMPS + if velocity > TURN_VELOCITY_MAX_DPS: + velocity = TURN_VELOCITY_MAX_DPS + velocity = int(velocity) + #handle direction flip + if direction == vex.TurnType.LEFT: + angle = -angle + stacking_type = vex.StackingType.STACKING_OFF + message = commands.TurnFor(angle, velocity, stacking_type.value) + self.robot_send(message.to_json()) + if wait: + self._block_on_state(self.is_turn_active) + + def turn_to(self, heading:vex.vexnumber, velocity=None, units:vex.TurnVelocityPercentUnits=vex.TurnVelocityUnits.PERCENT, wait=True): + """turn to a heading (degrees) at velocity (deg/sec)\n + heading can be -360 to 360""" + if not (-360 < heading < 360): + raise ValueError("heading must be between -360 and 360") + if velocity is None: + velocity = self.turn_speed + else: + if units.value == vex.TurnVelocityUnits.PERCENT.value: + #cannot exeed 100% + if velocity > 100: + velocity = 100 + #if velocity is PERCENT convert to DPS ex: 100% is 180 MMPS + velocity = int(velocity * 1.8) + elif units.value == vex.TurnVelocityUnits.DPS.value: + #cannot exceed MAX velocity MMPS + if velocity > TURN_VELOCITY_MAX_DPS: + velocity = TURN_VELOCITY_MAX_DPS + velocity = int(velocity) + #handle negative velocity + if velocity < 0: + velocity = -velocity + + heading_offset = self.inertial.heading_offset + heading = math.fmod (heading_offset + heading, 360) + stacking_type = vex.StackingType.STACKING_OFF + message = commands.TurnTo(heading, velocity, stacking_type.value) + self.robot_send(message.to_json()) + if wait: + self._block_on_state(self.is_turn_active) + + def stop_all_movement(self): + """stops all movements of the robot""" + self.move_at(0,0) + self.turn(vex.TurnType.RIGHT, 0) + # clear the is_moving_flag now (used by robot.is_stopped()) + self._ws_status_thread.clear_is_moving_flag() + # trigger this flag to be cleared again next time a status msg is received, in case the robot hasn't update the state yet: + self._ws_status_thread.is_moving_flag_needs_clearing = True + self._ws_status_thread.is_moving_flag_needs_setting = False + + def spin_wheels(self, velocity1: int, velocity2: int, velocity3: int): + """spin all three wheels of the robot at the specified velocities""" + message = commands.SpinWheels(velocity1, velocity2, velocity3) + self.robot_send(message.to_json()) + + def set_xy_position(self, x, y): + """sets the robot’s current position to the specified values""" + offset_radians = -math.radians(self.inertial.heading_offset) + origin_x = x * math.cos(offset_radians) - y * math.sin(offset_radians) + origin_y = y * math.cos(offset_radians) + x * math.sin(offset_radians) + + message = commands.SetPose(origin_x, origin_y) + self.robot_send(message.to_json()) + + for status_counter in range(2): + heartbeat_state = self._ws_status_thread.heartbeat + # wait till we get a new status packet + while heartbeat_state == self._ws_status_thread.heartbeat: + # print("status_counter %d" %(status_counter)) + time.sleep(0.050) + + #endregion Motion Commands + + #region Vision Commands + def has_any_barrel(self): + """returns true if a barrel is held by the kicker""" + ai_objects = list(self.vision.get_data(AiVision.ALL_AIOBJS)) + has_barrel = False + for object in range(len(ai_objects)): + cx = ai_objects[object].originX + ai_objects[object].width/2 + if ai_objects[object].classname in ["BlueBarrel", "OrangeBarrel"] and \ + BARREL_MIN_CX < cx < BARREL_MAX_CX and \ + ai_objects[object].originY > BARREL_MIN_Y: + has_barrel = True + + return has_barrel + + def has_blue_barrel(self): + """returns true if a barrel is held by the kicker""" + ai_objects = list(self.vision.get_data(AiVision.ALL_AIOBJS)) + has_barrel = False + for object in range(len(ai_objects)): + cx = ai_objects[object].originX + ai_objects[object].width/2 + if ai_objects[object].classname in ["BlueBarrel"] and \ + BARREL_MIN_CX < cx < BARREL_MAX_CX and \ + ai_objects[object].originY > BARREL_MIN_Y: + has_barrel = True + return has_barrel + + def has_orange_barrel(self): + """returns true if a barrel is held by the kicker""" + ai_objects = list(self.vision.get_data(AiVision.ALL_AIOBJS)) + has_barrel = False + for object in range(len(ai_objects)): + cx = ai_objects[object].originX + ai_objects[object].width/2 + if ai_objects[object].classname in ["OrangeBarrel"] and \ + BARREL_MIN_CX < cx < BARREL_MAX_CX and \ + ai_objects[object].originY > BARREL_MIN_Y : + has_barrel = True + return has_barrel + + def has_sports_ball(self): + """returns true if a ball is held by the kicker""" + ai_objects = list(self.vision.get_data(AiVision.ALL_AIOBJS)) + has_ball = False + for object in range(len(ai_objects)): + cx = ai_objects[object].originX + ai_objects[object].width/2 + if ai_objects[object].classname in ["SportsBall"] and \ + BALL_MIN_CX < cx < BALL_MAX_CX and \ + ai_objects[object].originY > BALL_MIN_Y : + has_ball = True + return has_ball + #endregion Vision Commands +class Inertial(): + """ AIM Inertial class. Provides methods to interact with the robot's inertial sensor.""" + def __init__(self, robot_instance: Robot): + """Initialize the Gyro with default settings.""" + self.robot_instance = robot_instance + self.heading_offset = 0 + self.rotation_offset = 0 + #region Inertial - Action + def calibrate(self): + """calibrate the IMU. Can't check if calibration is done, so probably do not call for now""" + message = commands.InterialCalibrate() + self.robot_instance.robot_send(message.to_json()) + #endregion Inertial - Action + #region Inertial - Mutators + def set_heading(self, heading): + """sets the robot’s heading to a specified value""" + raw_heading = self.get_heading_raw() + # print("reset heading to %f, get_heading_raw(): %f" %(heading, raw_heading)) + self.heading_offset = raw_heading - heading + + def reset_heading(self): + """robot heading will be set to 0""" + self.set_heading(0) + + def set_rotation(self, rotation): + """sets the robot’s rotation to a specified value""" + raw_rotation = self.get_rotation_raw() + self.rotation_offset = raw_rotation - rotation + + def reset_rotation(self): + """resets the robot’s rotation to 0""" + self.set_rotation(0) + + def set_crash_sensitivity(self, sensitivity=vex.SensitivityType.LOW): + """set the sensitivity of the crash sensor""" + message = commands.InterialSetCrashSensitivity(sensitivity.value) + self.robot_instance.robot_send(message.to_json()) + #endregion Inertial - Mutators + #region Inertial - Getters + def get_heading(self): + """reports the robot’s heading angle. This returns a float in the range 0 to 359.99 degrees""" + raw_heading = self.get_heading_raw() + heading = math.fmod(raw_heading - self.heading_offset, 360) + #round to 2 decimal places + heading = round(heading, 2) + if heading < 0: + heading += 360 + return heading + + def get_heading_raw(self): + """reports raw heading value from IMU""" + raw_heading = self.robot_instance.status["robot"]["heading"] + if type(raw_heading) == str: + raw_heading = float(raw_heading) + return raw_heading + + def get_rotation(self): + """returns the robot’s total rotation in degrees as a float. + This measures how much the robot has rotated relative to its last reset point""" + raw_rotation = self.get_rotation_raw() + rotation = raw_rotation - self.rotation_offset + #round to 2 decimal places + rotation = round(rotation, 2) + return rotation + + def get_rotation_raw(self): + """reports raw rotation value from IMU""" + raw_rotation = self.robot_instance.status["robot"]["rotation"] + if type(raw_rotation) == str: + raw_rotation = float(raw_rotation) + return raw_rotation + + def get_acceleration(self, axis: Union[vex.AxisType, vex.AccelerationType]): + """ returns the robot’s acceleration for a given axis.""" + if axis in [vex.AxisType.X_AXIS, vex.AccelerationType.FORWARD]: + value = self.robot_instance._ws_status_thread.current_status["robot"]["acceleration"]["x"] + elif axis in [vex.AxisType.Y_AXIS, vex.AccelerationType.RIGHTWARD]: + value = self.robot_instance._ws_status_thread.current_status["robot"]["acceleration"]["y"] + elif axis in [vex.AxisType.X_AXIS, vex.AccelerationType.DOWNWARD]: + value = self.robot_instance._ws_status_thread.current_status["robot"]["acceleration"]["z"] + if type(value) == str: + value = float(value) + return value + + def get_turn_rate(self, axis: Union[vex.AxisType, vex.OrientationType]): + """returns the robot’s gyro rate for a given axis in degrees per second (DPS). It returns a float from –1000.00 to 1000.00 degrees per second""" + if axis in [vex.AxisType.X_AXIS, vex.OrientationType.ROLL]: + value = self.robot_instance._ws_status_thread.current_status["robot"]["gyro_rate"]["x"] + elif axis in [vex.AxisType.Y_AXIS, vex.OrientationType.PITCH]: + value = self.robot_instance._ws_status_thread.current_status["robot"]["gyro_rate"]["y"] + elif axis in [vex.AxisType.Z_AXIS, vex.OrientationType.YAW]: + value = self.robot_instance._ws_status_thread.current_status["robot"]["gyro_rate"]["z"] + if type(value) == str: + value = float(value) + return value + + def get_roll(self): + """returns the robot’s roll angle in the range –180.00 to 180.00 degrees as a float""" + value = self.robot_instance.status["robot"]["roll"] + if type(value) == str: + value = float(value) + #round to 2 decimal places + value = round(value, 2) + return value + + def get_pitch(self): + """returns the robot’s pitch angle in the range –90.00 to 90.00 degrees as a float""" + value = self.robot_instance.status["robot"]["pitch"] + if type(value) == str: + value = float(value) + #round to 2 decimal places + value = round(value, 2) + return value + + def get_yaw(self): + """returns the robot’s yaw angle in the range –180.00 to 180.00 degrees as a float""" + value = self.robot_instance.status["robot"]["yaw"] + if type(value) == str: + value = float(value) + #round to 2 decimal places + value = round(value, 2) + return value + + def is_calibrating(self): + """reports whether the gyro is calibrating.""" + if self.robot_instance._ws_status_thread.imu_cal_flag_needs_setting == True: + return True + robot_flags = self.robot_instance._ws_status_thread.current_status["robot"]["flags"] + calibrating = bool(int(robot_flags, 16) & SYS_FLAGS_IMU_CAL) + return calibrating + #endregion Inertial - Getters + #region Inertial - Callbacks + def crashed(self, callback: Callable[...,None], arg: tuple=()): + """calls the specified function when the robot crashes""" + self.robot_instance.add_inertial_crash_callback(callback, arg) + #endregion Inertial - Callbacks + +class Screen(): + """ + Screen class for accessing the robot's screen features + like drawing shapes,text,and showing emojis + """ + def __init__(self, robot_instance: Robot): + self.robot_instance = robot_instance + # store r,b,g values of default fill color + fill_r,fill_g,fill_b = self._return_rgb(vex.Color.BLUE) + self.default_fill_color = ColorRGB(fill_r,fill_g,fill_b) + pen_r,pen_g,pen_b = self._return_rgb(vex.Color.BLUE) + self.default_pen_color = ColorRGB(pen_r,pen_g,pen_b) + + def _return_rgb(self, color): + if isinstance(color, (vex.Color, vex.Color.DefinedColor)): + r = (color.value >> 16) & 0xFF + g = (color.value >> 8) & 0xFF + b = color.value & 0xFF + elif isinstance(color, int): + r = (color >> 16) & 0xFF + g = (color >> 8) & 0xFF + b = color & 0xFF + else: + raise AimException("parameter must be a vex.Color instance or int rgb value") + return r,g,b + + def _return_transparency(self, color): + if isinstance(color, (vex.Color, vex.Color.DefinedColor)): + return color.transparent + return False + + #region Screen - Cursor Print + + def print(self, *args, **kwargs): + """displays text on the robot’s screen at the current cursor position and font""" + try: + out=io.StringIO() + if 'end' not in kwargs: + kwargs['end'] = "" + print(*args,**kwargs, file=out) + text = out.getvalue() + #print("text: ", text) + out.close() + message = commands.ScreenPrint(text) + self.robot_instance.robot_send(message.to_json()) + except Exception as e: + print(f"Error displaying text on screen: {e}") + + def set_cursor(self, row, column): + """sets the cursor’s (row, column) screen position""" + message = commands.ScreenSetCursor(row, column) + self.robot_instance.robot_send(message.to_json()) + + def next_row(self): + """moves the cursor to the next row""" + message = commands.ScreenNextRow() + self.robot_instance.robot_send(message.to_json()) + + def clear_row(self, row: int, color=vex.Color.BLUE): + """clears a row of text.""" + r,g,b = self._return_rgb(color) + message = commands.ScreenClearRow(row, r, g, b) + self.robot_instance.robot_send(message.to_json()) + + def get_row(self): + """returns the current row of the cursor""" + value = self.robot_instance.status["robot"]["screen"]["row"] + if type(value) == str: + value = int(value) + return value + + def get_column(self): + """returns the current column of the cursor""" + value = self.robot_instance.status["robot"]["screen"]["column"] + if type(value) == str: + value = int(value) + return value + + #endregion Screen - Cursor Print + #region Screen - XY Print + def print_at(self, *args, x=0, y=0, **kwargs): + """displays text on the robot’s screen at a specified (x, y) screen coordinate. This method disregards the current cursor position""" + out=io.StringIO() + if 'end' not in kwargs: + kwargs['end'] = "" + print(*args,**kwargs, file=out) + text = out.getvalue() + #print("text: ", text) + out.close() + message = commands.ScreenPrintAt(text, x, y, True) + self.robot_instance.robot_send(message.to_json()) + + def set_origin(self, x, y): + """sets the origin of the screen to the specified (x, y) screen coordinate""" + message = commands.ScreenSetOrigin(x, y) + self.robot_instance.robot_send(message.to_json()) + #endregion Screen - XY Print + #region Screen - Mutators + + def clear_screen(self, color=vex.Color.BLUE): + """clears the robot’s screen of all drawings and text""" + r,g,b = self._return_rgb(color) + message = commands.ScreenClear(r, g, b) + self.robot_instance.robot_send(message.to_json()) + + def set_font(self, fontname: vex.FontType): + """sets the font used for displaying text on the robot’s screen""" + message = commands.ScreenSetFont(fontname.lower()) + self.robot_instance.robot_send(message.to_json()) + + def set_pen_width(self, width: int): + """sets the pen width used for drawing lines and shapes""" + message = commands.ScreenSetPenWidth(width) + self.robot_instance.robot_send(message.to_json()) + + def set_pen_color(self, color): + """sets the pen color used for drawing lines, shapes, and text""" + r,g,b = self._return_rgb(color) + self.default_pen_color = ColorRGB(r,g,b) + message = commands.ScreenSetPenColor(r, g, b) + self.robot_instance.robot_send(message.to_json()) + + def set_fill_color(self, color): + """sets the fill color used when shapes are drawn""" + r,g,b = self._return_rgb(color) + t = self._return_transparency(color) + self.default_fill_color = ColorRGB(r,g,b,t) + message = commands.ScreenSetFillColor(r, g, b, t) + self.robot_instance.robot_send(message.to_json()) + #endregion Screen - Mutators + #region Screen - Draw + def draw_pixel(self, x: int, y: int): + """draws a pixel at the specified (x, y) screen coordinate in the current pen color""" + message = commands.ScreenDrawPixel(x, y) + self.robot_instance.robot_send(message.to_json()) + + def draw_line(self, x1: int, y1: int, x2: int, y2: int): + """draws a line from the first specified screen coordinate (x1, y1) + to the second specified screen coordinate (x2, y2). + It uses the current the pen width set by set_pen_width and pen color set by set_pen_color""" + message = commands.ScreenDrawLine(x1, y1, x2, y2) + self.robot_instance.robot_send(message.to_json()) + + def draw_rectangle(self, x: int, y: int, width: int, height: int, color=None): + """draws a rectangle. It uses the current pen width set by set_pen_width and the pen color + set by set_pen_color for the outline. The fill color, set by set_fill_color, determines the interior color""" + #if color is not provided, use the default fill color + if color: + r,g,b = self._return_rgb(color) + t = self._return_transparency(color) + else: + r = self.default_fill_color.r + g = self.default_fill_color.g + b = self.default_fill_color.b + t = self.default_fill_color.t + message = commands.ScreenDrawRectangle(x, y, width, height, r, g, b, t) + self.robot_instance.robot_send(message.to_json()) + + def draw_circle(self, x: int, y: int, radius: int, color=None): + """draws a circle. It uses the current pen width set by set_pen_width and the pen color set by set_pen_color for the outline. + The fill color, set by set_fill_color, determines the interior color""" + #if color is not provided, use the default fill color + if color: + r,g,b = self._return_rgb(color) + t = self._return_transparency(color) + else: + r = self.default_fill_color.r + g = self.default_fill_color.g + b = self.default_fill_color.b + t = self.default_fill_color.t + message = commands.ScreenDrawCircle(x, y, radius, r, g, b, t) + self.robot_instance.robot_send(message.to_json()) + + def show_file(self, filename: str, x: int, y: int): + """draws a custom user-uploaded image on the robot’s screen at the specified (x, y) screen coordinate""" + #TODO: alowed extensions are correct? + if filename[-3:] not in ("bmp", "png"): + raise InvalidImageFileException(f"extension is {filename[-3:]}; expected extension to be bmp or png") + + message = commands.ScreenDrawImageFromFile(filename, x, y) + self.robot_instance.robot_send(message.to_json()) + + def set_clip_region(self, x: int, y: int, width: int, height: int): + """ + defines a rectangular area on the screen where all drawings and text will be confined. Any content outside this region will not be displayed + """ + message = commands.ScreenSetClipRegion(x, y, width, height) + self.robot_instance.robot_send(message.to_json()) + #endregion Screen - Draw + #region Screen - Emoji + def show_emoji(self, emoji: vex.EmojiType.EmojiType, look: vex.EmojiLookType.EmojiLookType = vex.EmojiLookType.LOOK_FORWARD): + """Show an emoji from a list of preset emojis""" + message = commands.ScreenShowEmoji(emoji.value, look.value) + self.robot_instance.robot_send(message.to_json()) + + def hide_emoji(self): + """hide emoji from being displayed, so that any underlying graphics can be viewed""" + message = commands.ScreenHideEmoji() + self.robot_instance.robot_send(message.to_json()) + #endregion Screen - Emoji + #region Screen - Callbacks + def pressed(self, callback: Callable[...,None], arg: tuple=()): + """Calls the specified function when the screen is pressed, with optional args.""" + self.robot_instance.add_screen_pressed_callback(callback, arg) + + def released(self, callback: Callable[...,None], arg: tuple=()): + """Calls the specified function when the screen is released, with optional args.""" + self.robot_instance.add_screen_released_callback(callback, arg) + + #endregion Screen - Callbacks + #region Screen - Touch + def pressing(self): + """returns true if the screen is being pressed""" + is_pressing = bool(int(self.robot_instance.status["robot"]["touch_flags"], 16) & 0x0001) + return is_pressing + + def x_position(self): + """returns the x position of the screen press""" + touch_x = float(self.robot_instance.status["robot"]["touch_x"]) + return touch_x + + def y_position(self): + """returns the y position of the screen press""" + touch_y = float(self.robot_instance.status["robot"]["touch_y"]) + return touch_y + #endregion Screen - Touch + #region Screen - Vision + def show_aivision(self): + """show the aivision output on the screen""" + message = commands.ScreenShowAivision() + self.robot_instance.robot_send(message.to_json()) + + def hide_aivision(self): + """hide the aivision output""" + message = commands.ScreenHideAivision() + self.robot_instance.robot_send(message.to_json()) + #endregion Screen - Vision + +class Kicker(): + """ Kicker class for accessing the robot's kicker features like kicking and pushing""" + def __init__(self, robot_instance: Robot): + self.robot_instance = robot_instance + pass + + def kick(self, kick_type: vex.KickType): + """activates the Kicker to kick an object with specified levels of force""" + message = commands.KickerKick(kick_type) + self.robot_instance.robot_send(message.to_json()) + + def place(self): + """activates the Kicker in order to place an object gently in front of the robot""" + # json = {"cmd_id": "push"} + self.kick(vex.KickType.SOFT) + +class Sound(): + """ + Sound class for accessing the robot's sound features like playing built-in and uploaded sounds + """ + def __init__(self, robot_instance: Robot): + self.robot_instance = robot_instance + def __note_to_midi(self, note_str): + """ + Converts a musical note string (e.g., "C#5") into a MIDI note number (0-11) and octave. + """ + # Check at least one character is present + if len(note_str) < 1: + raise TypeError("invalid note string") + + # Map first character to note + c = note_str[0].lower() + if c == 'c': + note = 0 + elif c == 'd': + note = 2 + elif c == 'e': + note = 4 + elif c == 'f': + note = 5 + elif c == 'g': + note = 7 + elif c == 'a': + note = 9 + elif c == 'b': + note = 11 + else: + raise TypeError("invalid note string") + + octave = 0 + + # If length=2, second char should be the octave 5–8 + if len(note_str) == 2: + if note_str[1] < '5' or note_str[1] > '8': + raise TypeError("invalid note string") + octave = int(note_str[1]) - 5 + if octave < 0: + octave = 0 + + # If length=3, middle char should be '#' or 'b', last char is octave 5–8 + elif len(note_str) == 3: + if (note_str[2] < '5' or note_str[2] > '8' or (note_str[1] not in ('#', 'b'))): + raise TypeError("invalid note string") + accidental = note_str[1] + if accidental == '#' and note < 11: + note += 1 + elif accidental == 'b' and note > 0: + note -= 1 + + octave = int(note_str[2]) - 5 + if octave < 0: + octave = 0 + else: + raise TypeError("invalid note string") + + return note, octave + + def __set_sound_active(self): + self.robot_instance._ws_status_thread.set_sound_playing_flag() + self.robot_instance._ws_status_thread.set_sound_downloading_flag() + self.robot_instance._ws_status_thread.sound_downloading_flag_needs_setting = True # have it be set again after next status message + self.robot_instance._ws_status_thread.sound_playing_flag_needs_setting = True # have it be set again after next status message + + def play(self, sound: vex.SoundType, volume = 50): + """plays one of the robot’s built-in sounds at a specified volume percentage. + Since this is a non-waiting method, the robot plays the built-in sound and moves to + the next command without waiting for it to finish""" + message = commands.SoundPlay(sound.lower(), volume) + self.robot_instance.robot_send(message.to_json()) + + def play_file(self, name: str, volume = 50): + """plays a custom sound loaded by the user at a specified volume percentage. \n + Current uploading sounds to AIM is supported onnly the VEXcode AIM app. + Since this is a non-waiting method, the robot plays the built-in sound and moves to + the next command without waiting for it to finish""" + message = commands.SoundPlayFile(name, volume) + self.robot_instance.robot_send(message.to_json()) + + def play_local_file(self, filepath: str, volume = 100): + """play a WAV or MP3 file stored on the client side; file will be transmitted to robot\n + Maximum filesize is 255 KB""" + file = pathlib.Path(filepath) + size = file.stat().st_size + if size > SOUND_SIZE_MAX_BYTES: + raise InvalidSoundFileException(f"file size of {size} bytes is too big; max size allowed is {SOUND_SIZE_MAX_BYTES} bytes ({SOUND_SIZE_MAX_BYTES/1024:.1f} kB)") + + extension = file.suffix + filename = file.name + audio = bytearray(64) + if not (extension == ".wav" or extension == ".mp3"): + raise InvalidSoundFileException(f"extension is {extension}; expected extension to be wav or mp3") + try: + f = open(filepath, 'rb') + except FileNotFoundError: + print ("File", filepath, "was not found") + else: + with f: + data = f.read() + + # do some sanity checks to make sure it's really a wave: + if extension == ".wav": + if not (data[0:4] == b'RIFF' and data[8:12] == b'WAVE'): + raise InvalidSoundFileException("file extension was .wav but does not appear to actually be a WAVE file") + channels = int.from_bytes(data[22:24], "little") + if channels > 2: + raise InvalidSoundFileException(f"only mono or stereo is supported, detected {channels} channels.") + if channels == 2: + print("%s is stereo; mono is recommended") + # first 64 bytes of audio is header + audio[0:1] = (0).to_bytes(1, 'little') + + # assuming the mp3 is valid: + elif extension == ".mp3": + audio[0:1] = (1).to_bytes(1, 'little') + + # set volume + audio[1:2] = (volume).to_bytes(1, 'little') + + audio[4:8] = (len(data)).to_bytes(4, 'little') # length of data + audio[8:12] = (0).to_bytes(4, 'little') # file chunk number + audio[32:32+len(filename)] = map(ord, filename[:32]) # filename + audio.extend(data) # append the data + self.robot_instance.robot_send_audio(audio) + self.__set_sound_active() + + def play_note(self, note_string: str, duration=750, volume=50): + """ + plays a specific note for a specific duration. . Since this is a non-waiting method, the robot plays + the specific note and moves to the next command without waiting for it to finish. + + ### Example: + robot.sound.play_note("C5", 2000) + robot.sound.play_note("F#6", 2000, 100) + """ + #get the note number and octave + note, octave = self.__note_to_midi(note_string) + if duration > 4000: + duration = 4000 + if volume > 100: + volume = 100 + if volume < 0: + volume = 0 + message = commands.SoundPlayNote(note, octave, duration, volume) + self.robot_instance.robot_send(message.to_json()) + self.__set_sound_active() + + def is_active(self): + """returns true if sound is currently playing or if it is being transmitted for playing""" + robot_flags = self.robot_instance.status["robot"]["flags"] + sound_active = bool(int(robot_flags, 16) & SYS_FLAGS_SOUND_PLAYING) or bool(int(robot_flags, 16) & SYS_FLAGS_IS_SOUND_DNL) + return sound_active + + def stop(self): + """ + stops a sound that is currently playing. + It will take some time for the sound to actually stop playing. + """ + message = commands.SoundStop() + self.robot_instance.robot_send(message.to_json()) + +class Led(): + """ Led class for accessing the robot's LED features like setting the color of the LEDs""" + def __init__(self, robot_instance: Robot): + self.robot_instance = robot_instance + pass + def __set_led_rgb(self, led: str, r: int, g: int, b: int): + """Turns on the specified LED with the specified RGB values""" + message = commands.LedSet(led, r, g, b) + self.robot_instance.robot_send(message.to_json()) + def on(self, *args): + """ + Sets the color of any one of six LEDs, with RGB values \n + ### Example: + robot.led.on(vex.LightType.ALL_LEDS, vex.Color.BLUE)\n + robot.led.on(1, vex.Color.BLUE) + """ + light_index = "all" + r, g, b = 0, 0, 0 + if len(args) not in [2, 4]: + raise TypeError("must have two or four arguments") + + if isinstance(args[0], int): + if args[0] in range(0,6): + light_index = f"light{args[0]+1}" + else: + light_index = "all" + elif isinstance(args[0], vex.LightType): + light_index = args[0] + + else: + raise TypeError("first argument must be of type int or vex.LightType") + + if len(args) == 2: + if isinstance(args[1], (vex.Color, vex.Color.DefinedColor)): + r = (args[1].value >> 16) & 0xFF + g = (args[1].value >> 8) & 0xFF + b = args[1].value & 0xFF + elif isinstance(args[1], (bool)): # turn white if True, off if False + r = 128 if args[1] else 0 + g = 128 if args[1] else 0 + b = 128 if args[1] else 0 + elif args[1] is None: + r = 0 + g = 0 + b = 0 + else: + raise TypeError("second argument must be of type vex.Color, vex.Color.DefinedColor, bool, or None") + + elif len(args) == 4: + r = args[1] + g = args[2] + b = args[3] + + else: + raise TypeError(f"bad parameters, n_args: {len(args)}") + + self.__set_led_rgb(light_index, r, g, b) + + def off(self, led: vex.LightType): + """turns off the specified LED""" + message = commands.LedSet(led, 0, 0, 0) + self.robot_instance.robot_send(message.to_json()) + +class Colordesc: + '''### Colordesc class - a class for holding an AI vision sensor color definition + + #### Arguments: + index : The color description index (1 to 7) + red : the red color value + green : the green color value + blue : the blue color value + hangle : the range of allowable hue + hdsat : the range of allowable saturation + + #### Returns: + An instance of the Colordesc class + + #### Examples: + COL1 = Colordesc(1, 13, 114, 227, 10.00, 0.20)\\ + COL2 = Colordesc(2, 237, 61, 74, 10.00, 0.20)\\ + ''' + def __init__(self, index, red, green, blue, hangle, hdsat): + self.id = index + self.red = red + self.green = green + self.blue = blue + self.hangle = hangle + self.hdsat = hdsat + pass + +class Codedesc: + '''### Codedesc class - a class for holding AI vision sensor codes + + A code description is a collection of up to five AI vision color descriptions. + #### Arguments: + index : The code description index (1 to 5) + c1 : An AI vision Colordesc + c1 : An AI vision Colordesc + c3 (optional) : An AI vision Colordesc + c4 (optional) : An AI vision Colordesc + c5 (optional) : An AI vision Colordesc + + #### Returns: + An instance of the Codedesc class + + #### Examples: + COL1 = Colordesc(1, 13, 114, 227, 10.00, 0.20)\\ + COL2 = Colordesc(2, 237, 61, 74, 10.00, 0.20)\\ + C1 = Codedesc( 1, COL1, COL2 ) + ''' + def __init__(self, index, c1:Colordesc, c2:Colordesc, *args): + self.id = index + self.cols = [c1, c2] + for arg in args: + if isinstance(arg, Colordesc): + self.cols.append(arg) + +class Tagdesc: + '''### Tagdesc class - a class for holding AI vision sensor tag id + + A tag description holds an apriltag id + #### Arguments: + id : The apriltag id (positive integer, not 0) + + #### Returns: + An instance of the Tagdesc class + + #### Examples: + T1 = Tagdesc( 23 ) + ''' + def __init__(self, index): + self.id = index + pass + + def __int__(self): + return self.id + + def __eq__(self, other): + if isinstance(other, Tagdesc): + return self.id == other.id + elif isinstance(other, int): + return self.id == other + return False + +class AiObjdesc: + '''### AiObjdesc class - a class for holding AI vision sensor AI object id + + A tag description holds an apriltag id + #### Arguments: + id : The AI Object (model) id (positive integer, not 0) + + #### Returns: + An instance of the AiObjdesc class + + #### Examples: + A1 = AiObjdesc( 2 ) + ''' + def __init__(self, index): + self.id = index + pass + + def __int__(self): + return self.id + + def __eq__(self, other): + if isinstance(other, AiObjdesc): + return self.id == other.id + elif isinstance(other, int): + return self.id == other + return False + +class ObjDesc: + """ + ObjDesc class - to represent any object type + """ + def __init__(self, index): + self.id = index + +class _ObjectTypeMask: + unkownObject = 0 + colorObject = (1 << 0) + codeObject = (1 << 1) + modelObject = (1 << 2) + tagObject = (1 << 3) + allObject = (0x3F) + +MATCH_ALL_ID = 0xFFFF +class AiVision(): + """ + AiVision class for accessing the robot's AI Vision Sensor features + """ + ALL_TAGS = Tagdesc(MATCH_ALL_ID) + '''A tag description for get_data indicating all tag objects to be returned''' + ALL_COLORS = Colordesc(MATCH_ALL_ID, 0, 0, 0, 0, 0) + '''A tag description for get_data indicating all color objects to be returned''' + ALL_CODES = Codedesc(MATCH_ALL_ID, ALL_COLORS, ALL_COLORS) + '''A tag description for get_data indicating all code objects to be returned''' + ALL_AIOBJS = AiObjdesc(MATCH_ALL_ID) + '''A tag description for get_data indicating all AI model objects to be returned''' + ALL_OBJECTS = ObjDesc(MATCH_ALL_ID) + '''A tag description for get_data indicating all objects to be returned''' + ALL_AIOBJECTS = AiObjdesc(MATCH_ALL_ID) + '''A description for get_data indicating all AI model objects to be returned''' + + def __init__(self, robot_instance: Robot): + self.robot_instance = robot_instance + self._object_count_val = 0 + self._largest_object = None + + def get_data(self, type, count=8): + '''### filters the data from the AI Vision Sensor frame to return a tuple. + The AI Vision Sensor can detect signatures that include pre-trained objects, AprilTags, or configured Colors and Color Codes. + + Color Signatures and Color Codes must be configured first in the AI Vision Utility in VEXcode before they can be used with this method. + The tuple stores objects ordered from largest to smallest by width, starting at index 0. Each object’s properties can be accessed using its index. + An empty tuple is returned if no matching objects are detected. + + #### Arguments: + type : A color, code or other object type + count (optional) : the maximum number of objects to obtain. default is 8. + + #### Returns: + tuple of AiVisionObject, this will be an empty tuple if nothing is available. + + #### Examples: + #### look for and return 1 object matching COL1 + objects = robot.vision.get_data(COL1) + + #### look for and return a maximum of 4 objects matching SIG_1 + objects = robot.vision.get_data(COL1, 4) + + #### return apriltag objects + objects = robot.vision.get_data(ALL_TAGS, AIVISION_MAX_OBJECTS) + ''' + match_tuple=None + if isinstance(type, Colordesc): + type_mask = _ObjectTypeMask.colorObject + id = type.id + elif isinstance(type, Codedesc): + type_mask = _ObjectTypeMask.codeObject + id = type.id + elif isinstance(type, AiObjdesc): + type_mask = _ObjectTypeMask.modelObject + id = type.id + elif isinstance(type, Tagdesc): + type_mask = _ObjectTypeMask.tagObject + id = type.id + elif isinstance(type, ObjDesc): + type_mask = _ObjectTypeMask.allObject + id = type.id + elif isinstance(type, tuple): + match_tuple = type + if not match_tuple: + raise AimException("tuple passed to get_data is empty") + type_mask = _ObjectTypeMask.allObject + else: + type_mask = _ObjectTypeMask.allObject # default value, changed to match uP by James + id = type # assume the first argument is any object id including match all. + + if count > AIVISION_MAX_OBJECTS: + count = AIVISION_MAX_OBJECTS + + objects = self.robot_instance.status["aivision"]["objects"] + item_count = objects["count"] + ai_object_list = [AiVisionObject() for item in range(item_count)] + # first just extract everything we got from ws_status + for item in range(item_count): + ai_object_list[item].type = objects["items"][item]["type"] + ai_object_list[item].id = objects["items"][item]["id"] + ai_object_list[item].originX = objects["items"][item]["originx"] + ai_object_list[item].originY = objects["items"][item]["originy"] + ai_object_list[item].width = objects["items"][item]["width"] + ai_object_list[item].height = objects["items"][item]["height"] + ai_object_list[item].centerX = int(ai_object_list[item].originX + (ai_object_list[item].width/2)) + ai_object_list[item].centerY = int(ai_object_list[item].originY + (ai_object_list[item].height/2)) + + if ai_object_list[item].type == _ObjectTypeMask.colorObject: + ai_object_list[item].angle = objects["items"][item]["angle"] * 0.01 + + if ai_object_list[item].type == _ObjectTypeMask.codeObject: + ai_object_list[item].angle = objects["items"][item]["angle"] * 0.01 + + if ai_object_list[item].type == _ObjectTypeMask.modelObject: #AI model objects can have a classname + ai_object_list[item].classname = self.robot_instance.status["aivision"]["classnames"]["items"][ai_object_list[item].id]["name"] + ai_object_list[item].score = objects["items"][item]["score"] + + if ai_object_list[item].type == _ObjectTypeMask.tagObject: + ai_object_list[item].tag.x = (objects["items"][item]["x0"],objects["items"][item]["x1"],objects["items"][item]["x2"],objects["items"][item]["x3"]) + ai_object_list[item].tag.y = (objects["items"][item]["y0"],objects["items"][item]["y1"],objects["items"][item]["y2"],objects["items"][item]["y3"]) + + ai_object_list[item].rotation = ai_object_list[item].angle + ai_object_list[item].area = ai_object_list[item].width * ai_object_list[item].height + cx = ai_object_list[item].centerX + cy = ai_object_list[item].centerY + ai_object_list[item].bearing = -34.656 + (cx * 0.22539) + (cy * 0.011526) + (cx * cx * -0.000042011) + (cx * cy * 0.000010433) + (cy * cy * -0.00007073) + + # print("diagnostic: ai_object_list: ", ai_object_list) + num_matches = 0 + sublist = [] + for item in range(item_count): + match_found = False + if match_tuple: + # check all tuple members for a match + for obj in match_tuple: + if isinstance(obj, Colordesc): + if ai_object_list[item].type == _ObjectTypeMask.colorObject and (ai_object_list[item].id == obj.id or MATCH_ALL_ID == obj.id): + match_found = True + elif isinstance(obj, Codedesc): + if ai_object_list[item].type == _ObjectTypeMask.codeObject and (ai_object_list[item].id == obj.id or MATCH_ALL_ID == obj.id): + match_found = True + elif isinstance(obj, AiObjdesc): + if ai_object_list[item].type == _ObjectTypeMask.modelObject and (ai_object_list[item].id == obj.id or MATCH_ALL_ID == obj.id): + match_found = True + elif isinstance(obj, Tagdesc): + if ai_object_list[item].type == _ObjectTypeMask.tagObject and (ai_object_list[item].id == obj.id or MATCH_ALL_ID == obj.id): + match_found = True + elif isinstance(obj, ObjDesc): + if ai_object_list[item].id == obj.id or MATCH_ALL_ID == obj.id: + match_found = True + else: + # assume obj is an int + if ai_object_list[item].id == int(obj) or MATCH_ALL_ID == int(obj): + match_found = True + else: + if ai_object_list[item].id == id or MATCH_ALL_ID == id: + match_found = True + + if ai_object_list[item].type & type_mask: + if match_found: + num_matches += 1 + #sort objects by object area in descending order + current_object_area = ai_object_list[item].height * ai_object_list[item].width + current_object_smallest = True + for i in range(len(sublist)): + if current_object_area >= (sublist[i].width * sublist[i].height): + sublist.insert(i, ai_object_list[item]) # insert item at position i of sublist. + current_object_smallest = False + break + if current_object_smallest: + sublist.append(ai_object_list[item]) #add to the end + + if num_matches > count: + num_matches = count + + self._object_count_val = num_matches + if sublist: + self._largest_object = sublist[0] + else: + self._largest_object = None + return sublist[:num_matches] + + def largest_object(self): + '''### Request the largest object from the last get_data(...) call + + #### Arguments: + None + + #### Returns: + An AiVisionObject object or None if it does not exist + ''' + return self._largest_object + + def object_count(self): + '''### Request the number of objects found in the last get_data call + + #### Arguments: + None + + #### Returns: + The number of objects found in the last get_data call + ''' + return self._object_count_val + + + def tag_detection(self, enable: bool): + '''### Enable or disable apriltag processing + + #### Arguments: + enable : True or False + + #### Returns: + None + ''' + message = commands.VisionTagDetection(enable) + self.robot_instance.robot_send(message.to_json()) + + def color_detection(self, enable: bool, merge: bool = False): + '''### Enable or disable color and code object processing + + #### Arguments: + enable : True or False + merge (optional) : True to enable merging of adjacent color detections + + #### Returns: + None + ''' + message = commands.VisionColorDetection(enable, merge) + self.robot_instance.robot_send(message.to_json()) + + def model_detection(self, enable: bool): + '''### Enable or disable AI model object processing + + #### Arguments: + enable : True or False + + #### Returns: + None + ''' + message = commands.VisionModelDetection(enable) + self.robot_instance.robot_send(message.to_json()) + + def color_description(self, desc: Colordesc): + '''### set a new color description + + #### Arguments: + desc: a color description + + #### Returns: + None + ''' + message = commands.VisionColorDescription(desc.id, desc.red, desc.green, desc.blue, desc.hangle, desc.hdsat) + self.robot_instance.robot_send(message.to_json()) + + def code_description(self, desc: Codedesc ): + '''### set a new code description + + #### Arguments: + desc: a code description + + #### Returns: + None + ''' + message = commands.VisionCodeDescription(desc.id, *desc.cols) + self.robot_instance.robot_send(message.to_json()) + + + def get_camera_image(self): + """ + returns a camera image; starts stream when first called; first image will take about 0.3 seconds to return.\n + Subsequently, images will continually stream from robot and therefore will be immediately available. + """ + if self.robot_instance._ws_img_thread._streaming == False: + # print("starting the stream") + start_time = time.time() + time_elapsed = 0 + self.robot_instance._ws_img_thread.start_stream() + while (self.robot_instance._ws_img_thread.image_list[self.robot_instance._ws_img_thread.current_image_index] == bytes(1) and time_elapsed < 0.5): + time.sleep(0.01) + time_elapsed = time.time() - start_time + image = self.robot_instance._ws_img_thread.image_list[self.robot_instance._ws_img_thread.current_image_index] + if image == bytes(1): + raise NoImageException("no image was received") + return image + +class VisionObject: + '''### VisionObject class - a class with some predefined objects for get_data''' + SPORTS_BALL = AiObjdesc(0) + '''A description for get_data indicating the AI ball objects to be returned''' + BLUE_BARREL = AiObjdesc(1) + '''A description for get_data indicating the AI blue barrel objects to be returned''' + ORANGE_BARREL = AiObjdesc(2) + '''A description for get_data indicating the AI orange barrel objects to be returned''' + AIM_ROBOT = AiObjdesc(3) + '''A description for get_data indicating the AI robot objects to be returned''' + TAG0 = Tagdesc(0) + '''A description for get_data indicating apriltags with id 0 to be returned''' + TAG1 = Tagdesc(1) + '''A description for get_data indicating apriltags with id 1 to be returned''' + TAG2 = Tagdesc(2) + '''A description for get_data indicating apriltags with id 2 to be returned''' + TAG3 = Tagdesc(3) + '''A description for get_data indicating apriltags with id 3 to be returned''' + TAG4 = Tagdesc(4) + '''A description for get_data indicating apriltags with id 4 to be returned''' + TAG5 = Tagdesc(5) + '''A description for get_data indicating apriltags with id 5 to be returned''' + TAG6 = Tagdesc(6) + '''A description for get_data indicating apriltags with id 6 to be returned''' + TAG7 = Tagdesc(7) + '''A description for get_data indicating apriltags with id 7 to be returned''' + TAG8 = Tagdesc(8) + '''A description for get_data indicating apriltags with id 8 to be returned''' + TAG9 = Tagdesc(9) + '''A description for get_data indicating apriltags with id 9 to be returned''' + TAG10 = Tagdesc(10) + '''A description for get_data indicating apriltags with id 10 to be returned''' + TAG11 = Tagdesc(11) + '''A description for get_data indicating apriltags with id 11 to be returned''' + TAG12 = Tagdesc(12) + '''A description for get_data indicating apriltags with id 12 to be returned''' + TAG13 = Tagdesc(13) + '''A description for get_data indicating apriltags with id 13 to be returned''' + TAG14 = Tagdesc(14) + '''A description for get_data indicating apriltags with id 14 to be returned''' + TAG15 = Tagdesc(15) + '''A description for get_data indicating apriltags with id 15 to be returned''' + TAG16 = Tagdesc(16) + '''A description for get_data indicating apriltags with id 16 to be returned''' + TAG17 = Tagdesc(17) + '''A description for get_data indicating apriltags with id 17 to be returned''' + TAG18 = Tagdesc(18) + '''A description for get_data indicating apriltags with id 18 to be returned''' + TAG19 = Tagdesc(19) + '''A description for get_data indicating apriltags with id 19 to be returned''' + TAG20 = Tagdesc(20) + '''A description for get_data indicating apriltags with id 20 to be returned''' + TAG21 = Tagdesc(21) + '''A description for get_data indicating apriltags with id 21 to be returned''' + TAG22 = Tagdesc(22) + '''A description for get_data indicating apriltags with id 22 to be returned''' + TAG23 = Tagdesc(23) + '''A description for get_data indicating apriltags with id 23 to be returned''' + TAG24 = Tagdesc(24) + '''A description for get_data indicating apriltags with id 24 to be returned''' + TAG25 = Tagdesc(25) + '''A description for get_data indicating apriltags with id 25 to be returned''' + TAG26 = Tagdesc(26) + '''A description for get_data indicating apriltags with id 26 to be returned''' + TAG27 = Tagdesc(27) + '''A description for get_data indicating apriltags with id 27 to be returned''' + TAG28 = Tagdesc(28) + '''A description for get_data indicating apriltags with id 28 to be returned''' + TAG29 = Tagdesc(29) + '''A description for get_data indicating apriltags with id 29 to be returned''' + TAG30 = Tagdesc(30) + '''A description for get_data indicating apriltags with id 30 to be returned''' + TAG31 = Tagdesc(31) + '''A description for get_data indicating apriltags with id 31 to be returned''' + TAG32 = Tagdesc(32) + '''A description for get_data indicating apriltags with id 32 to be returned''' + TAG33 = Tagdesc(33) + '''A description for get_data indicating apriltags with id 33 to be returned''' + TAG34 = Tagdesc(34) + '''A description for get_data indicating apriltags with id 34 to be returned''' + TAG35 = Tagdesc(35) + '''A description for get_data indicating apriltags with id 35 to be returned''' + TAG36 = Tagdesc(36) + '''A description for get_data indicating apriltags with id 36 to be returned''' + TAG37 = Tagdesc(37) + '''A description for get_data indicating apriltags with id 37 to be returned''' + ALL_TAGS = Tagdesc(MATCH_ALL_ID) + '''A description for get_data indicating any apriltag to be returned''' + ALL_VISION = ObjDesc(MATCH_ALL_ID) + '''A description for get_data indicating any object to be returned''' + ALL_COLORS = (AiVision.ALL_COLORS, AiVision.ALL_CODES) + '''A description for get_data indicating any color or code to be returned''' + ALL_CARGO = (SPORTS_BALL, BLUE_BARREL, ORANGE_BARREL) + '''A description for get_data indicating AI ball or barrel to be returned''' + +class AiVisionObject(): + """ + AiVisionObject class - a class for holding AI vision sensor object properties + """ + class Tag: + """ + Tag class - a class for holding AI vision sensor tag properties + The tag class is used to hold the coordinates of the four corners of the tag + """ + def __init__(self): + self.x = (0,0,0,0) + self.y = (0,0,0,0) + pass + + def __init__(self): + self.type = 0 + self.id = 0 + self.originX = 0 + self.originY = 0 + self.centerX = 0 + self.centerY = 0 + self.width = 0 + self.height = 0 + self.exists = True + self.angle = 0.0 + self.rotation = 0.0 + self.score = 0 + self.area = 0 + self.bearing = 0.0 + self.classname = '' + self.color = None # not used in remote + self.tag = AiVisionObject.Tag() + +class Timer: + '''### Timer class - create a new timer + + This class is used to create a new timer\\ + A timer can be used to measure time, access the system time and run a function at a time in the future. + + #### Arguments: + None + + #### Returns: + An instance of the Timer class + + #### Examples: + t1 = Timer() + ''' + def __init__(self): + self.start_time = time.time() + + def time(self, units=vex.TimeUnits.MSEC): + '''### return the current time for this timer + + #### Arguments: + units (optional) : the units that the time should be returned in, default is MSEC + + #### Returns: + An the current time in specified units. + + #### Examples: + ''' + elapsed_time = time.time() - self.start_time + if units == vex.TimeUnits.SECONDS: + # seconds as float in 2 decimal places + return round(elapsed_time, 2) + elif units == vex.TimeUnits.MSEC: + # miliseconds as int - no decimal + return int(elapsed_time * 1000) + else: + raise ValueError("Invalid time unit") + + def reset(self): + '''### reset the timer to 0 + + #### Arguments: + None + + #### Returns: + None + + #### Examples: + ''' + self.start_time = time.time() + + def event(self, callback: Callable[...,None], delay: int, arg: tuple=()): + '''### register a function to be called in the future + + #### Arguments: + callback : A function that will called after the supplied delay + delay : The delay before the callback function is called. + arg (optional) : A tuple that is used to pass arguments to the function. + + #### Returns: + None + + #### Examples: + def foo(arg): + print('timer has expired ', arg) + + t1 = Timer()\\ + t1.event(foo, 1000, ('Hello',)) + ''' + def delayed_call(): + time.sleep(delay / 1000) + callback(*arg) + + threading.Thread(target=delayed_call).start() + +class Thread(): + """ Thread class for running a function in a separate thread""" + def __init__(self, func, args=None): + if args: + self.t = threading.Thread(target=func, args=args) + else: + self.t = threading.Thread(target=func) + self.t.start() + \ No newline at end of file diff --git a/resources/python/vex/settings.json b/resources/python/vex/settings.json new file mode 100644 index 0000000..6b53a65 --- /dev/null +++ b/resources/python/vex/settings.json @@ -0,0 +1,5 @@ +{ + "connection": { + "host": "192.168.4.1" + } +} \ No newline at end of file diff --git a/resources/python/vex/settings.py b/resources/python/vex/settings.py new file mode 100644 index 0000000..442f6e0 --- /dev/null +++ b/resources/python/vex/settings.py @@ -0,0 +1,48 @@ +# ================================================================================================= +# Copyright (c) Innovation First 2025. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# ================================================================================================= +""" +AIM WebSocket API - Settings + +Settings class to read and manage the JSON configuration file. +""" +import json +import os + +class Settings: + """ + Settings class to read the JSON configuration file and provide access to the properties. + """ + + def __init__(self): + """ + Initialize the Settings class and load the settings from the JSON configuration file. + """ + self.file_path = os.path.join(os.path.dirname(__file__), 'settings.json') + self.config = self._load_settings() + + def _load_settings(self): + """ + Load the settings from the JSON configuration file. + + Returns: + dict: Dictionary containing the configuration settings. + """ + if not os.path.exists(self.file_path): + raise FileNotFoundError(f"Configuration file not found: {self.file_path}") + + with open(self.file_path, 'r') as file: + config = json.load(file) + + return config + + @property + def host(self): + """ + Get the host property from the configuration settings. + + Returns: + str: Host property value. + """ + return self.config.get('connection', {}).get('host', 'localhost') \ No newline at end of file diff --git a/resources/python/vex/vex_globals.py b/resources/python/vex/vex_globals.py new file mode 100644 index 0000000..65c128f --- /dev/null +++ b/resources/python/vex/vex_globals.py @@ -0,0 +1,295 @@ +# ================================================================================================= +# Copyright (c) Innovation First 2025. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# ================================================================================================= +""" +AIM WebSocket API - Globals + +This module defines global constants and enums used in the AIM WebSocket API. These globals +are provided to improve code readability and ease of use, especially for beginners and users +familiar with the VEXcode API. + +Globals in this module are designed to match the VEXcode API documentation. +If these conflict with existing types or variables in your project, +you may choose not to import this module. +""" + +# ---------------------------------------------------------- +# pylint: disable=unnecessary-pass,unused-argument,line-too-long,too-many-lines +# pylint: disable=missing-module-docstring,missing-function-docstring,missing-class-docstring +# pylint: disable=invalid-name,unused-import,redefined-outer-name + +from vex import KickType +from vex import AxisType +from vex import OrientationType +from vex import AccelerationType +from vex import LightType +from vex import Color +from vex import FontType +from vex import EmojiType +from vex import EmojiLookType +from vex import SoundType +from vex import VisionObject + +SOFT = KickType.SOFT +'''A kick type of soft''' +MEDIUM = KickType.MEDIUM +'''A kick type of medium''' +HARD = KickType.HARD +'''A kick type of hard''' + +X_AXIS = AxisType.X_AXIS +'''The X axis of the Inertial sensor.''' +Y_AXIS = AxisType.Y_AXIS +'''The Y axis of the Inertial sensor.''' +Z_AXIS = AxisType.Z_AXIS +'''The Z axis of the Inertial sensor.''' + +ROLL = OrientationType.ROLL +'''roll, orientation around the X axis of the Inertial sensor.''' +PITCH = OrientationType.PITCH +'''pitch, orientation around the Y axis of the Inertial sensor.''' +YAW = OrientationType.YAW +'''yaw, orientation around the Z axis of the Inertial sensor.''' + +FORWARD = AccelerationType.FORWARD +'''The X acceleration axis of the Inertial sensor.''' +RIGHTWARD = AccelerationType.RIGHTWARD +'''The Y acceleration axis of the Inertial sensor.''' +DOWNWARD = AccelerationType.DOWNWARD +'''The Z acceleration axis of the Inertial sensor.''' + +LED1 = LightType.LED1 +'''The LED at the 315 degree position on AIM''' +LED2 = LightType.LED2 +'''The LED at the 265 degree position on AIM''' +LED3 = LightType.LED3 +'''The LED at the 210 degree position on AIM''' +LED4 = LightType.LED4 +'''The LED at the 155 degree position on AIM''' +LED5 = LightType.LED5 +'''The LED at the 100 degree position on AIM''' +LED6 = LightType.LED6 +'''The LED at the 45 degree position on AIM''' +ALL_LEDS = LightType.ALL_LEDS +'''All LEDs on AIM''' + +BLACK = Color.BLACK +'''predefined Color black''' +WHITE = Color.WHITE +'''predefined Color white''' +RED = Color.RED +'''predefined Color red''' +GREEN = Color.GREEN +'''predefined Color green''' +BLUE = Color.BLUE +'''predefined Color blue''' +YELLOW = Color.YELLOW +'''predefined Color yellow''' +ORANGE = Color.ORANGE +'''predefined Color orange''' +PURPLE = Color.PURPLE +'''predefined Color purple''' +CYAN = Color.CYAN +'''predefined Color cyan''' +TRANSPARENT = Color.TRANSPARENT +'''predefined Color transparent''' + +MONO12 = FontType.MONO12 +'''monotype font of size 12''' +MONO15 = FontType.MONO15 +'''monotype font of size 15''' +MONO20 = FontType.MONO20 +'''monotype font of size 20''' +MONO24 = FontType.MONO24 +'''monotype font of size 24''' +MONO30 = FontType.MONO30 +'''monotype font of size 30''' +MONO36 = FontType.MONO36 +'''monotype font of size 36''' +MONO40 = FontType.MONO40 +'''monotype font of size 40''' +MONO60 = FontType.MONO60 +'''monotype font of size 60''' +PROP20 = FontType.PROP20 +'''proportional font of size 20''' +PROP24 = FontType.PROP24 +'''proportional font of size 24''' +PROP30 = FontType.PROP30 +'''proportional font of size 30''' +PROP36 = FontType.PROP36 +'''proportional font of size 36''' +PROP40 = FontType.PROP40 +'''proportional font of size 40''' +PROP60 = FontType.PROP60 +'''proportional font of size 60''' + +EXCITED = EmojiType.EXCITED +CONFIDENT = EmojiType.CONFIDENT +SILLY = EmojiType.SILLY +AMAZED = EmojiType.AMAZED +STRONG = EmojiType.STRONG +THRILLED = EmojiType.THRILLED +HAPPY = EmojiType.HAPPY +PROUD = EmojiType.PROUD +LAUGHING = EmojiType.LAUGHING +OPTIMISTIC = EmojiType.OPTIMISTIC +DETERMINED = EmojiType.DETERMINED +AFFECTIONATE = EmojiType.AFFECTIONATE +CALM = EmojiType.CALM +QUIET = EmojiType.QUIET +SHY = EmojiType.SHY +CHEERFUL = EmojiType.CHEERFUL +LOVED = EmojiType.LOVED +SURPRISED = EmojiType.SURPRISED +THINKING = EmojiType.THINKING +TIRED = EmojiType.TIRED +CONFUSED = EmojiType.CONFUSED +BORED = EmojiType.BORED +EMBARRASSED = EmojiType.EMBARRASSED +WORRIED = EmojiType.WORRIED +SAD = EmojiType.SAD +SICK = EmojiType.SICK +DISAPPOINTED = EmojiType.DISAPPOINTED +NERVOUS = EmojiType.NERVOUS +ANNOYED = EmojiType.ANNOYED +STRESSED = EmojiType.STRESSED +ANGRY = EmojiType.ANGRY +FRUSTRATED = EmojiType.FRUSTRATED +JEALOUS = EmojiType.JEALOUS +SHOCKED = EmojiType.SHOCKED +FEAR = EmojiType.FEAR +DISGUST = EmojiType.DISGUST + +LOOK_FORWARD = EmojiLookType.LOOK_FORWARD +'''The emoji will look forwards.''' +LOOK_RIGHT = EmojiLookType.LOOK_RIGHT +'''The emoji will look to the right.''' +LOOK_LEFT = EmojiLookType.LOOK_LEFT +'''The emoji will look to the left.''' + +DOORBELL = SoundType.DOORBELL +TADA = SoundType.TADA +FAIL = SoundType.FAIL +SPARKLE = SoundType.SPARKLE +FLOURISH = SoundType.FLOURISH +MOVE_FORWARD = SoundType.FORWARD +MOVE_REVERSE = SoundType.REVERSE +TURN_RIGHT = SoundType.RIGHT +TURN_LEFT = SoundType.LEFT +BLINKER = SoundType.BLINKER +CRASH = SoundType.CRASH +BRAKES = SoundType.BRAKES +HUAH = SoundType.HUAH +PICKUP = SoundType.PICKUP +CHEER = SoundType.CHEER +SENSING = SoundType.SENSING +DETECTED = SoundType.DETECTED +OBSTACLE = SoundType.OBSTACLE +LOOPING = SoundType.LOOPING +COMPLETE = SoundType.COMPLETE +PAUSE = SoundType.PAUSE +RESUME = SoundType.RESUME +SEND = SoundType.SEND +RECEIVE = SoundType.RECEIVE + + +ACT_HAPPY = SoundType.ACT_HAPPY +ACT_SAD = SoundType.ACT_SAD +ACT_EXCITED = SoundType.ACT_EXCITED +ACT_ANGRY = SoundType.ACT_ANGRY +ACT_SILLY = SoundType.ACT_SILLY + +SPORTS_BALL = VisionObject.SPORTS_BALL +'''A description for get_data indicating the AI ball objects to be returned''' +BLUE_BARREL = VisionObject.BLUE_BARREL +'''A description for get_data indicating the AI blue barrel objects to be returned''' +ORANGE_BARREL = VisionObject.ORANGE_BARREL +'''A description for get_data indicating the AI orange barrel objects to be returned''' +AIM_ROBOT = VisionObject.AIM_ROBOT +'''A description for get_data indicating the AI robot objects to be returned''' +TAG0 = VisionObject.TAG0 +'''A description for get_data indicating apriltags with id 0 to be returned''' +TAG1 = VisionObject.TAG1 +'''A description for get_data indicating apriltags with id 1 to be returned''' +TAG2 = VisionObject.TAG2 +'''A description for get_data indicating apriltags with id 2 to be returned''' +TAG3 = VisionObject.TAG3 +'''A description for get_data indicating apriltags with id 3 to be returned''' +TAG4 = VisionObject.TAG4 +'''A description for get_data indicating apriltags with id 4 to be returned''' +TAG5 = VisionObject.TAG5 +'''A description for get_data indicating apriltags with id 5 to be returned''' +TAG6 = VisionObject.TAG6 +'''A description for get_data indicating apriltags with id 6 to be returned''' +TAG7 = VisionObject.TAG7 +'''A description for get_data indicating apriltags with id 7 to be returned''' +TAG8 = VisionObject.TAG8 +'''A description for get_data indicating apriltags with id 8 to be returned''' +TAG9 = VisionObject.TAG9 +'''A description for get_data indicating apriltags with id 9 to be returned''' +TAG10 = VisionObject.TAG10 +'''A description for get_data indicating apriltags with id 10 to be returned''' +TAG11 = VisionObject.TAG11 +'''A description for get_data indicating apriltags with id 11 to be returned''' +TAG12 = VisionObject.TAG12 +'''A description for get_data indicating apriltags with id 12 to be returned''' +TAG13 = VisionObject.TAG13 +'''A description for get_data indicating apriltags with id 13 to be returned''' +TAG14 = VisionObject.TAG14 +'''A description for get_data indicating apriltags with id 14 to be returned''' +TAG15 = VisionObject.TAG15 +'''A description for get_data indicating apriltags with id 15 to be returned''' +TAG16 = VisionObject.TAG16 +'''A description for get_data indicating apriltags with id 16 to be returned''' +TAG17 = VisionObject.TAG17 +'''A description for get_data indicating apriltags with id 17 to be returned''' +TAG18 = VisionObject.TAG18 +'''A description for get_data indicating apriltags with id 18 to be returned''' +TAG19 = VisionObject.TAG19 +'''A description for get_data indicating apriltags with id 19 to be returned''' +TAG20 = VisionObject.TAG20 +'''A description for get_data indicating apriltags with id 20 to be returned''' +TAG21 = VisionObject.TAG21 +'''A description for get_data indicating apriltags with id 21 to be returned''' +TAG22 = VisionObject.TAG22 +'''A description for get_data indicating apriltags with id 22 to be returned''' +TAG23 = VisionObject.TAG23 +'''A description for get_data indicating apriltags with id 23 to be returned''' +TAG24 = VisionObject.TAG24 +'''A description for get_data indicating apriltags with id 24 to be returned''' +TAG25 = VisionObject.TAG25 +'''A description for get_data indicating apriltags with id 25 to be returned''' +TAG26 = VisionObject.TAG26 +'''A description for get_data indicating apriltags with id 26 to be returned''' +TAG27 = VisionObject.TAG27 +'''A description for get_data indicating apriltags with id 27 to be returned''' +TAG28 = VisionObject.TAG28 +'''A description for get_data indicating apriltags with id 28 to be returned''' +TAG29 = VisionObject.TAG29 +'''A description for get_data indicating apriltags with id 29 to be returned''' +TAG30 = VisionObject.TAG30 +'''A description for get_data indicating apriltags with id 30 to be returned''' +TAG31 = VisionObject.TAG31 +'''A description for get_data indicating apriltags with id 31 to be returned''' +TAG32 = VisionObject.TAG32 +'''A description for get_data indicating apriltags with id 32 to be returned''' +TAG33 = VisionObject.TAG33 +'''A description for get_data indicating apriltags with id 33 to be returned''' +TAG34 = VisionObject.TAG34 +'''A description for get_data indicating apriltags with id 34 to be returned''' +TAG35 = VisionObject.TAG35 +'''A description for get_data indicating apriltags with id 35 to be returned''' +TAG36 = VisionObject.TAG36 +'''A description for get_data indicating apriltags with id 36 to be returned''' +TAG37 = VisionObject.TAG37 +'''A description for get_data indicating apriltags with id 37 to be returned''' + +ALL_TAGS = VisionObject.ALL_TAGS +'''A description for get_data indicating any apriltag to be returned''' +ALL_VISION = VisionObject.ALL_VISION +'''A description for get_data indicating any object to be returned''' +ALL_COLORS = VisionObject.ALL_COLORS +'''A description for get_data indicating any color or code to be returned''' +ALL_CARGO = VisionObject.ALL_CARGO +'''A description for get_data indicating AI ball or barrel to be returned''' diff --git a/resources/python/vex/vex_messages.py b/resources/python/vex/vex_messages.py new file mode 100644 index 0000000..6bd9653 --- /dev/null +++ b/resources/python/vex/vex_messages.py @@ -0,0 +1,656 @@ +# ================================================================================================= +# Copyright (c) Innovation First 2025. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# ================================================================================================= +""" +AIM WebSocket - Messages + +This module contains definitions for the Websocket messages. +""" +class VexWebSocketCommand: + def __init__(self, cmd_id: str): + self.cmd_id = cmd_id + + def to_json(self) -> dict: + return { + "cmd_id": self.cmd_id + } + +#region General Commands +class ProgramInit(VexWebSocketCommand): + def __init__(self): + super().__init__("program_init") + + +#endregion General Commands + +#region Movement Commands +class MoveAt(VexWebSocketCommand): + def __init__(self, angle=0.0, speed=0.0, stacking_type=0): + super().__init__("drive") + self.angle = angle + self.speed = speed + self.stacking_type = stacking_type + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "angle": self.angle, + "speed": self.speed, + "stacking_type": self.stacking_type + }) + return base_data + +class MoveFor(VexWebSocketCommand): + def __init__(self, distance =0.0, angle=0.0, drive_speed=0.0, turn_speed=0.0, final_heading=0,stacking_type=0): + super().__init__("drive_for") + self.distance = distance + self.angle = angle + self.drive_speed = drive_speed + self.turn_speed = turn_speed + self.final_heading = final_heading + self.stacking_type = stacking_type + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "distance": self.distance, + "angle": self.angle, + "final_heading" : self.final_heading, + "drive_speed": self.drive_speed, + "turn_speed": self.turn_speed, + "stacking_type": self.stacking_type + }) + return base_data + +class MoveWithVector(VexWebSocketCommand): + def __init__(self, x=0, t=0, r=0): + super().__init__("drive_with_vector") + self.x = x + self.t = t + self.r = r + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x": self.x, + "t": self.t, + "r": self.r + }) + return base_data + +class Turn(VexWebSocketCommand): + def __init__(self, turn_rate=0.0, stacking_type=0): + super().__init__("turn") + self.turn_rate = turn_rate + self.stacking_type = stacking_type + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "turn_rate": self.turn_rate, + "stacking_type": self.stacking_type + }) + return base_data + +class TurnTo(VexWebSocketCommand): + def __init__(self, heading=0.0, turn_rate=0.0, stacking_type=0): + super().__init__("turn_to") + self.heading = heading + self.turn_rate = turn_rate + self.stacking_type = stacking_type + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "heading": self.heading, + "turn_rate": self.turn_rate, + "stacking_type": self.stacking_type + }) + return base_data + +class TurnFor(VexWebSocketCommand): + def __init__(self, angle=0, turn_rate=0.0, stacking_type=0): + super().__init__("turn_for") + self.angle = angle + self.turn_rate = turn_rate + self.stacking_type = stacking_type + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "angle": self.angle, + "turn_rate": self.turn_rate, + "stacking_type": self.stacking_type + }) + return base_data + +class SpinWheels(VexWebSocketCommand): + def __init__(self, vel1=0, vel2=0, vel3=0): + super().__init__("spin_wheels") + self.vel1 = vel1 + self.vel2 = vel2 + self.vel3 = vel3 + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "vel1": self.vel1, + "vel2": self.vel2, + "vel3": self.vel3 + }) + return base_data + +class SetPose(VexWebSocketCommand): + def __init__(self, x=0, y=0): + super().__init__("set_pose") + self.x = x + self.y = y + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x": self.x, + "y": self.y + }) + return base_data +#endregion Movement Commands + +#region Screen Commands +class ScreenPrint(VexWebSocketCommand): + def __init__(self, string=""): + super().__init__("lcd_print") + self.string = string + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "string": self.string + }) + return base_data + +class ScreenPrintAt(VexWebSocketCommand): + def __init__(self, string="", x=0, y=0, b_opaque=True): + super().__init__("lcd_print_at") + self.string = string + self.x = x + self.y = y + self.b_opaque = b_opaque + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x": self.x, + "y": self.y, + "string": self.string, + "b_opaque": self.b_opaque + }) + return base_data + +class ScreenSetCursor(VexWebSocketCommand): + def __init__(self, row=0, col=0): + super().__init__("lcd_set_cursor") + self.row = row + self.col = col + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "row": self.row, + "col": self.col + }) + return base_data + +class ScreenSetOrigin(VexWebSocketCommand): + def __init__(self, x=0, y=0): + super().__init__("lcd_set_origin") + self.x = x + self.y = y + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x": self.x, + "y": self.y + }) + return base_data + +class ScreenNextRow(VexWebSocketCommand): + def __init__(self): + super().__init__("lcd_next_row") + + def to_json(self): + return super().to_json() + +class ScreenClearRow(VexWebSocketCommand): + def __init__(self, row=0, r=0,g=0,b=0): + super().__init__("lcd_clear_row") + self.row = row + self.r = r + self.g = g + self.b = b + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "number": self.row, + "r": self.r, + "g": self.g, + "b": self.b + }) + return base_data +class ScreenClear(VexWebSocketCommand): + def __init__(self, r=0, g=0, b=0): + super().__init__("lcd_clear_screen") + self.r = r + self.g = g + self.b = b + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "r": self.r, + "g": self.g, + "b": self.b + }) + return base_data + +class ScreenSetFont(VexWebSocketCommand): + def __init__(self, fontname): + super().__init__("lcd_set_font") + self.fontname = fontname + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "fontname": self.fontname + }) + return base_data + +class ScreenSetPenWidth(VexWebSocketCommand): + def __init__(self, width): + super().__init__("lcd_set_pen_width") + self.width = width + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "width": self.width + }) + return base_data + +class ScreenSetPenColor(VexWebSocketCommand): + def __init__(self, r=0, g=0, b=0): + super().__init__("lcd_set_pen_color") + self.r = r + self.g = g + self.b = b + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "r": self.r, + "g": self.g, + "b": self.b + }) + return base_data +class ScreenSetFillColor(VexWebSocketCommand): + def __init__(self, r=0, g=0, b=0, transparent=False): + super().__init__("lcd_set_fill_color") + self.r = r + self.g = g + self.b = b + self.b_transparency = transparent + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "r": self.r, + "g": self.g, + "b": self.b, + "b_transparency":self.b_transparency + }) + return base_data + +class ScreenDrawLine(VexWebSocketCommand): + def __init__(self, x1=0, y1=0, x2=0, y2=0): + super().__init__("lcd_draw_line") + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x1": self.x1, + "y1": self.y1, + "x2": self.x2, + "y2": self.y2 + }) + return base_data + +class ScreenDrawRectangle(VexWebSocketCommand): + def __init__(self, x=0, y=0, width=0, height=0, r=0, g=0, b=0, transparent=False): + super().__init__("lcd_draw_rectangle") + self.x = x + self.y = y + self.width = width + self.height = height + self.r = r + self.g = g + self.b = b + self.b_transparency = transparent + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x": self.x, + "y": self.y, + "width": self.width, + "height": self.height, + "r": self.r, + "g": self.g, + "b": self.b, + "b_transparency":self.b_transparency + }) + return base_data + +class ScreenDrawCircle(VexWebSocketCommand): + def __init__(self, x=0, y=0, radius=0, r=0, g=0, b=0, transparent=False): + super().__init__("lcd_draw_circle") + self.x = x + self.y = y + self.radius = radius + self.r = r + self.g = g + self.b = b + self.b_transparency = transparent + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x": self.x, + "y": self.y, + "radius": self.radius, + "r": self.r, + "g": self.g, + "b": self.b, + "b_transparency":self.b_transparency + }) + return base_data + +class ScreenDrawPixel(VexWebSocketCommand): + def __init__(self, x=0, y=0): + super().__init__("lcd_draw_pixel") + self.x = x + self.y = y + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x": self.x, + "y": self.y + }) + return base_data + +class ScreenDrawImageFromFile(VexWebSocketCommand): + def __init__(self, filename="", x=0, y=0): + super().__init__("lcd_draw_image_from_file") + self.filename = filename + self.x = x + self.y = y + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "filename": self.filename, + "x": self.x, + "y": self.y + }) + return base_data + +class ScreenSetClipRegion(VexWebSocketCommand): + def __init__(self, x=0, y=0, width=0, height=0): + super().__init__("lcd_set_clip_region") + self.x = x + self.y = y + self.width = width + self.height = height + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "x": self.x, + "y": self.y, + "width": self.width, + "height": self.height + }) + return base_data + +class ScreenShowEmoji(VexWebSocketCommand): + def __init__(self, name=0, look=0): + super().__init__("show_emoji") + self.name = name + self.look = look + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "name": self.name, + "look": self.look + }) + return base_data + +class ScreenHideEmoji(VexWebSocketCommand): + def __init__(self): + super().__init__("hide_emoji") + def to_json(self): + return super().to_json() + +class ScreenShowAivision(VexWebSocketCommand): + def __init__(self, name=0, look=0): + super().__init__("show_aivision") + def to_json(self): + return super().to_json() + +class ScreenHideAivision(VexWebSocketCommand): + def __init__(self, name=0, look=0): + super().__init__("hide_aivision") + def to_json(self): + return super().to_json() +#endregion Screen Commands + +#region Interial Commands +class InterialCalibrate(VexWebSocketCommand): + def __init__(self): + super().__init__("imu_calibrate") + + def to_json(self): + return super().to_json() + +class InterialSetCrashSensitivity(VexWebSocketCommand): + def __init__(self, sensitivity=0): + super().__init__("imu_set_crash_threshold") + self.sensitivity = sensitivity + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "sensitivity": self.sensitivity + }) + return base_data +#endregion Interial Commands + +#region Kicker Commands +class KickerKick(VexWebSocketCommand): + def __init__(self, kick_type=""): + super().__init__(kick_type) + + def to_json(self): + return super().to_json() +#endregion Kicker Commands + +#region Sound Commands +class SoundPlay(VexWebSocketCommand): + def __init__(self, name="", volume=0): + super().__init__("play_sound") + self.name = name + self.volume = volume + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "name": self.name, + "volume": self.volume + }) + return base_data +class SoundPlayFile(VexWebSocketCommand): + def __init__(self, name="", volume=0): + super().__init__("play_file") + self.name = name + self.volume = volume + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "name": self.name, + "volume": self.volume + }) + return base_data +class SoundPlayNote(VexWebSocketCommand): + def __init__(self, note=0, octave=0, duration=500, volume=0): + super().__init__("play_note") + self.note = note + self.octave = octave + self.duration = duration + self.volume = volume + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "note": self.note, + "octave": self.octave, + "duration": self.duration, + "volume": self.volume + }) + return base_data + +class SoundStop(VexWebSocketCommand): + def __init__(self): + super().__init__("stop_sound") + + def to_json(self): + return super().to_json() + +#endregion Sound Commands + +#region LED Commands +class LedSet(VexWebSocketCommand): + def __init__(self, led="", r=0, g=0, b=0): + super().__init__("light_set") + self.led = led + self.r = r + self.g = g + self.b = b + + def to_json(self): + base_data = super().to_json() + base_data.update({ + self.led: { + "r": self.r, + "g": self.g, + "b": self.b + } + }) + return base_data + +#endregion LED Commands + +#region AiVision Commands +class VisionColorDescription(VexWebSocketCommand): + def __init__(self, id, r, g, b, hangle, hdsat ): + super().__init__("color_description") + self.id = id + self.r = r + self.g = g + self.b = b + self.hdsat = hdsat + self.hangle = hangle + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "id": self.id, + "red": self.r, + "green": self.g, + "blue": self.b, + "hangle": self.hangle, + "hdsat": self.hdsat + }) + return base_data + +class VisionCodeDescription(VexWebSocketCommand): + def __init__(self, id, c1, c2, *args): + super().__init__("code_description") + self.id = id + self.c1 = c1.id + self.c2 = c2.id + self.c3 = -1 + self.c4 = -1 + self.c5 = -1 + if( len(args) > 0 ): + self.c3 = args[0].id + if( len(args) > 1 ): + self.c3 = args[1].id + if( len(args) > 2 ): + self.c3 = args[2].id + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "id": self.id, + "c1": self.c1, + "c2": self.c2, + "c3": self.c3, + "c4": self.c4, + "c5": self.c5 + }) + return base_data +class VisionTagDetection(VexWebSocketCommand): + def __init__(self, enable=True): + super().__init__("tag_detection") + self.b_enable = enable + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "b_enable": self.b_enable + }) + return base_data + +class VisionColorDetection(VexWebSocketCommand): + def __init__(self, enable=True, merge=True): + super().__init__("color_detection") + self.b_enable = enable + self.b_merge = merge + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "b_enable": self.b_enable, + "b_merge": self.b_merge + }) + return base_data +class VisionModelDetection(VexWebSocketCommand): + def __init__(self, enable=True): + super().__init__("model_detection") + self.b_enable = enable + + def to_json(self): + base_data = super().to_json() + base_data.update({ + "b_enable": self.b_enable + }) + return base_data +#endregion AiVision Commands diff --git a/resources/python/vex/vex_types.py b/resources/python/vex/vex_types.py new file mode 100644 index 0000000..99e131d --- /dev/null +++ b/resources/python/vex/vex_types.py @@ -0,0 +1,389 @@ +# ================================================================================================= +# Copyright (c) Innovation First 2025. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# ================================================================================================= +""" +AIM WebSocket API - Types + +This module defines various types and enums used in the AIM WebSocket API. +""" +from enum import Enum +from typing import Union +import time +class vexEnum: + '''Base class for all enumerated types''' + value = 0 + name = "" + + def __init__(self, value, name): + self.value = value + self.name = name + + def __str__(self): + return self.name + + def __repr__(self): + return self.name + + def __hash__(self): + return self.value + +class SoundType(str, Enum): + DOORBELL = "DOORBELL" + TADA = "TADA" + FAIL = "FAIL" + SPARKLE = "SPARKLE" + FLOURISH = "FLOURISH" + FORWARD = "FORWARD" + REVERSE = "REVERSE" + RIGHT = "RIGHT" + LEFT = "LEFT" + BLINKER = "BLINKER" + CRASH = "CRASH" + BRAKES = "BRAKES" + HUAH = "HUAH" + PICKUP = "PICKUP" + #PLACE = "PLACE" + #KICK = "KICK" + CHEER = "CHEER" + SENSING = "SENSING" + DETECTED = "DETECTED" + OBSTACLE = "OBSTACLE" + LOOPING = "LOOPING" + COMPLETE = "COMPLETE" + PAUSE = "PAUSE" + RESUME = "RESUME" + SEND = "SEND" + RECEIVE = "RECEIVE" + #CHIRP = "CHIRP" + + ACT_HAPPY = "ACT_HAPPY" + ACT_SAD = "ACT_SAD" + ACT_EXCITED = "ACT_EXCITED" + ACT_ANGRY = "ACT_ANGRY" + ACT_SILLY = "ACT_SILLY" + +class FontType(str, Enum): + MONO20 = "MONO20" + MONO24 = "MONO24" + MONO30 = "MONO30" + MONO36 = "MONO36" + MONO40 = "MONO40" + MONO60 = "MONO60" + PROP20 = "PROP20" + PROP24 = "PROP24" + PROP30 = "PROP30" + PROP36 = "PROP36" + PROP40 = "PROP40" + PROP60 = "PROP60" + MONO15 = "MONO15" + MONO12 = "MONO12" + +class KickType(str, Enum): + SOFT = "kick_soft" + MEDIUM = "kick_medium" + HARD = "kick_hard" + +class AxisType(Enum): + """The defined units for inertial sensor axis.""" + X_AXIS = 0 + Y_AXIS = 1 + Z_AXIS = 2 + +class TurnType(Enum): + LEFT = 0 + RIGHT = 1 +class OrientationType: + '''The defined units for inertial sensor orientation.''' + ROLL = 0 + PITCH = 1 + YAW = 2 +class AccelerationType: + '''The defined units for inertial sensor acceleration.''' + FORWARD = 0 + RIGHTWARD = 1 + DOWNWARD = 2 + +class PercentUnits: + '''The measurement units for percentage values.''' + class PercentUnits(vexEnum): + pass + PERCENT = PercentUnits(0, "PERCENT") + '''A percentage unit that represents a value from 0% to 100%''' +class RotationUnits: + '''The measurement units for rotation values.''' + class RotationUnits(vexEnum): + pass + DEG = RotationUnits(0, "DEG") + '''A rotation unit that is measured in degrees.''' + REV = RotationUnits(1, "REV") + '''A rotation unit that is measured in revolutions.''' + RAW = RotationUnits(99, "RAW") + '''A rotation unit that is measured in raw data form.''' +class DriveVelocityUnits: + '''The measurement units for drive velocity values.''' + class DriveVelocityUnits(vexEnum): + pass + PERCENT = DriveVelocityUnits(0, "PCT") + '''A velocity unit that is measured in percentage.''' + MMPS = DriveVelocityUnits(1, "MMPS") + '''A velocity unit that is measured in mm per second.''' +class TurnVelocityUnits: + '''The measurement units for turn velocity values.''' + class TurnVelocityUnits(vexEnum): + pass + PERCENT = TurnVelocityUnits(0, "PCT") + '''A velocity unit that is measured in percentage.''' + DPS = TurnVelocityUnits(1, "DPS") + '''A velocity unit that is measured in degrees per second.''' + +class TimeUnits: + '''The measurement units for time values.''' + class TimeUnits(vexEnum): + pass + SECONDS = TimeUnits(0, "SECONDS") + '''A time unit that is measured in seconds.''' + MSEC = TimeUnits(1, "MSEC") + '''A time unit that is measured in milliseconds.''' +class DistanceUnits: + '''The measurement units for distance values.''' + class DistanceUnits(vexEnum): + pass + MM = DistanceUnits(0, "MM") + '''A distance unit that is measured in millimeters.''' + IN = DistanceUnits(1, "IN") + '''A distance unit that is measured in inches.''' + CM = DistanceUnits(2, "CM") + '''A distance unit that is measured in centimeters.''' +class VoltageUnits: + '''The measurement units for voltage values.''' + class VoltageUnits(vexEnum): + pass + VOLT = VoltageUnits(0, "VOLT") + '''A voltage unit that is measured in volts.''' + MV = VoltageUnits(0, "mV") + '''A voltage unit that is measured in millivolts.''' + +# ---------------------------------------------------------- +# globals +# ---------------------------------------------------------- +PERCENT = PercentUnits.PERCENT +'''A percentage unit that represents a value from 0% to 100%''' +LEFT = TurnType.LEFT +'''A turn unit that is defined as left turning.''' +RIGHT = TurnType.LEFT +'''A turn unit that is defined as right turning.''' +DEGREES = RotationUnits.DEG +'''A rotation unit that is measured in degrees.''' +TURNS = RotationUnits.REV +'''A rotation unit that is measured in revolutions.''' +SECONDS = TimeUnits.SECONDS +'''A time unit that is measured in seconds.''' +MSEC = TimeUnits.MSEC +'''A time unit that is measured in milliseconds.''' +INCHES = DistanceUnits.IN +'''A distance unit that is measured in inches.''' +MM = DistanceUnits.MM +'''A distance unit that is measured in millimeters.''' +VOLT = VoltageUnits.VOLT +'''A voltage unit that is measured in volts.''' +MV = VoltageUnits.MV +'''A voltage unit that is measured in millivolts.''' +MMPS = DriveVelocityUnits.MMPS +'''units of mm per second''' +DPS = TurnVelocityUnits.DPS +'''units of degrees per second''' +OFF = False +'''used to turn off an LED''' + +vexnumber = Union[int, float] +# drivetrain move functions take either DriveVelocity or percentage units +DriveVelocityPercentUnits = Union[DriveVelocityUnits.DriveVelocityUnits, PercentUnits.PercentUnits] +# drivetrain turn functions take either TurnVelocity or percentage units +TurnVelocityPercentUnits = Union[TurnVelocityUnits.TurnVelocityUnits, PercentUnits.PercentUnits] + +class LightType(str, Enum): + LED1 = "light1" + LED2 = "light2" + LED3 = "light3" + LED4 = "light4" + LED5 = "light5" + LED6 = "light6" + ALL_LEDS = "all" + +class Color: + '''### Color class - create a new color + + This class is used to create instances of color objects + + #### Arguments: + value : The color value, can be specified in various ways, see examples. + + #### Returns: + An instance of the Color class + + #### Examples: + # create blue using hex value\\ + c = Color(0x0000ff)\n + # create blue using r, g, b values\\ + c = Color(0, 0, 255)\n + # create blue using web string\\ + c = Color("#00F")\n + # create blue using web string (alternate)\\ + c = Color("#0000FF")\n + # create red using an existing object\\ + c = Color(Color.RED) + ''' + class DefinedColor: + def __init__(self, value, transparent=False): + self.value = value + self.transparent = transparent + + BLACK = DefinedColor(0x000000) + '''predefined Color black''' + WHITE = DefinedColor(0xFFFFFF) + '''predefined Color white''' + RED = DefinedColor(0xFF0000) + '''predefined Color red''' + GREEN = DefinedColor(0x00FF00) + '''predefined Color green''' + BLUE = DefinedColor(0x001871) + '''predefined Color blue''' + YELLOW = DefinedColor(0xFFFF00) + '''predefined Color yellow''' + ORANGE = DefinedColor(0xFF8500) + '''predefined Color orange''' + PURPLE = DefinedColor(0xFF00FF) + '''predefined Color purple''' + CYAN = DefinedColor(0x00FFFF) + '''predefined Color cyan''' + TRANSPARENT = DefinedColor(0x000000, True) + '''predefined Color transparent''' + + def __init__(self, *args): + self.transparent = False + if len(args) == 1 and isinstance(args[0], int): + self.value: int = args[0] + elif len(args) == 3 and all(isinstance(arg, int) for arg in args): + self.value = ((args[0] & 0xFF) << 16) + ((args[1] & 0xFF) << 8) + (args[2] & 0xFF) + else: + raise TypeError("bad parameters") + + def set_rgb(self, *args): + '''### change existing Color instance to new rgb value + + #### Arguments: + value : The color value, can be specified in various ways, see examples. + + #### Returns: + integer value representing the color + + #### Examples: + # create a color that is red + c = Color(0xFF0000) + # change color to blue using single value + c.rgb(0x0000FF) + # change color to green using three values + c.rgb(0, 255, 0) + ''' + if len(args) == 1 and isinstance(args[0], int): + self.value = args[0] + if len(args) == 3 and all(isinstance(arg, int) for arg in args): + self.value = ((args[0] & 0xFF) << 16) + ((args[1] & 0xFF) << 8) + (args[2] & 0xFF) + + # ---------------------------------------------------------- + +def sleep(duration: vexnumber, units=TimeUnits.MSEC): + '''### delay the current thread for the provided number of seconds or milliseconds. + + #### Arguments: + duration: The number of seconds or milliseconds to sleep for + units: The units of duration, optional, default is milliseconds + + #### Returns: + None + ''' + if units == TimeUnits.MSEC: + time.sleep(duration / 1000) + else: + time.sleep(duration) + +def wait(duration: vexnumber, units=TimeUnits.MSEC): + '''### delay the current thread for the provided number of seconds or milliseconds. + + #### Arguments: + duration: The number of seconds or milliseconds to sleep for + units: The units of duration, optional, default is milliseconds + + #### Returns: + None + ''' + if units == TimeUnits.MSEC: + time.sleep(duration / 1000) + else: + time.sleep(duration) + +class EmojiType: + class EmojiType(vexEnum): + pass + EXCITED = EmojiType( 0, "EXCITED") + CONFIDENT = EmojiType( 1, "CONFIDENT") + SILLY = EmojiType( 2, "SILLY") + AMAZED = EmojiType( 3, "AMAZED") + STRONG = EmojiType( 4, "STRONG") + THRILLED = EmojiType( 5, "THRILLED") + HAPPY = EmojiType( 6, "HAPPY") + PROUD = EmojiType( 7, "PROUD") + LAUGHING = EmojiType( 8, "LAUGHING") + OPTIMISTIC = EmojiType(9, "OPTIMISTIC") + DETERMINED = EmojiType(10, "DETERMINED") + AFFECTIONATE = EmojiType(11, "AFFECTIONATE") + CALM = EmojiType(12, "CALM") + QUIET = EmojiType(13, "QUIET") + SHY = EmojiType(14, "SHY") + CHEERFUL = EmojiType(15, "CHEERFUL") + LOVED = EmojiType(16, "LOVED") + SURPRISED = EmojiType(17, "SURPRISED") + THINKING = EmojiType(18, "THINKING") + TIRED = EmojiType(19, "TIRED") + CONFUSED = EmojiType(20, "CONFUSED") + BORED = EmojiType(21, "BORED") + EMBARRASSED = EmojiType(22, "EMBARRASSED") + WORRIED = EmojiType(23, "WORRIED") + SAD = EmojiType(24, "SAD") + SICK = EmojiType(25, "SICK") + DISAPPOINTED = EmojiType(26, "DISAPPOINTED") + NERVOUS = EmojiType(27, "NERVOUS") + ANNOYED = EmojiType(30, "ANNOYED") + STRESSED = EmojiType(31, "STRESSED") + ANGRY = EmojiType(32, "ANGRY") + FRUSTRATED = EmojiType(33, "FRUSTRATED") + JEALOUS = EmojiType(34, "JEALOUS") + SHOCKED = EmojiType(35, "SHOCKED") + FEAR = EmojiType(36, "FEAR") + DISGUST = EmojiType(37, "DISGUST") + +Emoji = EmojiType + +# ---------------------------------------------------------- + +class EmojiLookType: + class EmojiLookType(vexEnum): + pass + LOOK_FORWARD = EmojiLookType( 0, "LOOK_FORWARD") + LOOK_RIGHT = EmojiLookType( 1, "LOOK_RIGHT") + LOOK_LEFT = EmojiLookType( 2, "LOOK_LEFT") + +EmojiLook = EmojiLookType + +class StackingType(Enum): + STACKING_OFF = 0 + STACKING_MOVE_RELATIVE = 1 + STACKING_MOVE_GLOBAL = 2 + +class SensitivityType: + class SensitivityType(vexEnum): + pass + LOW = SensitivityType( 0, "LOW") + MEDIUM = SensitivityType( 1, "MEDIUM") + HIGH = SensitivityType( 2, "HIGH") From 370adceab4724bfe3657501da04f06714086fd5e Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Tue, 8 Jul 2025 18:19:58 -0500 Subject: [PATCH 12/25] fixed startup error --- src/main/index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/index.js b/src/main/index.js index ac1fdae..490e11d 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -68,6 +68,11 @@ async function createWindow() { } }); + // Add the event listener here, AFTER creating the window + win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { + console.error('Window failed to load:', errorDescription); + }); + // Load the url of the dev server if in development mode // Load the index.html when not in development if (isDevelopment) { @@ -389,7 +394,3 @@ ipcMain.on("toMain", (event, { data }) => { event.reply("fromMain", reply); //win.webContents.send("fromMain", reply); }); - -win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { - console.error('Window failed to load:', errorDescription); -}); From 762a32b6a77e5b2b68d5a92adcfdef529e4252b0 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Wed, 9 Jul 2025 11:56:59 -0500 Subject: [PATCH 13/25] added turn left/ right block and Vex Category --- resources/python/VEXServer.py | 19 ++++++- src/main/index.js | 31 ++++++------ src/main/preload.js | 3 ++ src/renderer/js/blockly-main.js | 7 +-- src/renderer/js/categories.js | 7 +++ src/renderer/js/customblock.js | 75 ++++++++++++++++++++++++++-- src/renderer/js/interpreter-api.js | 4 ++ src/renderer/js/main.js | 38 ++++++++++---- src/renderer/js/wrapper-functions.js | 9 ++++ 9 files changed, 157 insertions(+), 36 deletions(-) diff --git a/resources/python/VEXServer.py b/resources/python/VEXServer.py index 585255c..8bff941 100644 --- a/resources/python/VEXServer.py +++ b/resources/python/VEXServer.py @@ -24,6 +24,7 @@ async def handle_command(websocket, path=None): async for message in websocket: command = json.loads(message) action = command.get("action", "") + if action == "led_on": color_name = command.get("color", "BLUE") # Map string color names to vex.Color constants @@ -42,6 +43,7 @@ async def handle_command(websocket, path=None): robot.led.on(ALL_LEDS, color) # Send a response back to the client await websocket.send(json.dumps({"status": "success", "action": "led_on", "color": color_name})) + elif action == "move": distance = command.get("distance", 100) heading = command.get("heading", 0) @@ -50,6 +52,21 @@ async def handle_command(websocket, path=None): robot.move_for(distance, heading) # Send a response back to the client await websocket.send(json.dumps({"status": "success", "action": "move", "distance": distance, "heading": heading})) + + elif action == "turn_left": + degrees = command.get("degrees", 90) + print(f"Turning robot left: {degrees} degrees") + robot.turn_for(vex.TurnType.LEFT, degrees) # Correct VEX method + # Send a response back to the client + await websocket.send(json.dumps({"status": "success", "action": "turn_left", "degrees": degrees})) + + elif action == "turn_right": + degrees = command.get("degrees", 90) + print(f"Turning robot right: {degrees} degrees") + robot.turn_for(vex.TurnType.RIGHT, degrees) # Correct VEX method + # Send a response back to the client + await websocket.send(json.dumps({"status": "success", "action": "turn_right", "degrees": degrees})) + else: print(f"Unknown command: {command}") # Send an error response back to the client @@ -59,7 +76,7 @@ async def handle_command(websocket, path=None): await websocket.send(json.dumps({"status": "error", "message": str(e)})) async def main(): - port = 8765 + port = 8777 print(f"Starting WebSocket server on ws://127.0.0.1:{port}") async with websockets.serve(handle_command, "127.0.0.1", port): await asyncio.Future() # run forever diff --git a/src/main/index.js b/src/main/index.js index 490e11d..e7fe534 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -43,7 +43,7 @@ async function createWindow() { // ---- End Python VEXServer ---- // Wait for the Python WebSocket server to be ready (up to 10 seconds) - await waitOn({ resources: ['tcp:127.0.0.1:8765'], timeout: 10000 }); + await waitOn({ resources: ['tcp:127.0.0.1:8777'], timeout: 10000 }); // If you'd like to set up auto-updating for your app, // I'd recommend looking at https://github.com/iffy/electron-updater-example @@ -145,7 +145,7 @@ async function createWindow() { } }); - const ws = new WebSocket('ws://127.0.0.1:8765'); + const ws = new WebSocket('ws://127.0.0.1:8777'); ws.on('open', function open() { console.log('WebSocket connection opened'); @@ -218,6 +218,18 @@ async function createWindow() { tello.ccw(recent_val); }); + ipcMain.on("vex-turn-left", (event, degrees) => { + console.log(`[VEX] Turn left ${degrees}°`); + const turnCommand = { action: "turn_left", degrees: degrees }; + sendCommand(turnCommand); + }); + + ipcMain.on("vex-turn-right", (event, degrees) => { + console.log(`[VEX] Turn right ${degrees}°`); + const turnCommand = { action: "turn_right", degrees: degrees }; + sendCommand(turnCommand); + }); + let isUp = false; // ipcMain.on("manual-control", (event, response) => { @@ -264,21 +276,6 @@ async function createWindow() { console.log("Received command from renderer:", command); sendCommand(command); // Use the existing sendCommand function }); - - javascriptGenerator.forBlock["move"] = function (block) { - var distance = block.getFieldValue("DISTANCE"); - var heading = block.getFieldValue("HEADING"); - var code = `electronAPI.sendCommand({ action: "move", distance: ${distance}, heading: ${heading} });\n`; - console.log("Generated code for move block:", code); - return code; - }; - - javascriptGenerator.forBlock["led_control"] = function (block) { - var color = block.getFieldValue("COLOR"); - var code = `electronAPI.sendCommand({ action: "led_on", color: "${color}" });\n`; - console.log("Generated code for LED block:", code); - return code; - }; } // This method will be called when Electron has finished diff --git a/src/main/preload.js b/src/main/preload.js index b7b34bb..3981219 100644 --- a/src/main/preload.js +++ b/src/main/preload.js @@ -25,6 +25,9 @@ process.once("loaded", () => { droneDown: (response) => ipcRenderer.send("drone-down", response), droneForward: (response) => ipcRenderer.send("drone-forward", response), droneBack: (response) => ipcRenderer.send("drone-back", response), + //Vex commands + vexTurnLeft: (degrees) => ipcRenderer.send("vex-turn-left", degrees), + vexTurnRight: (degrees) => ipcRenderer.send("vex-turn-right", degrees), cw: (response) => ipcRenderer.send("cw", response), ccw: (response) => ipcRenderer.send("ccw", response), getBLEList: (callback) => ipcRenderer.on("device_list", callback), diff --git a/src/renderer/js/blockly-main.js b/src/renderer/js/blockly-main.js index cd7dc8e..0794e1a 100644 --- a/src/renderer/js/blockly-main.js +++ b/src/renderer/js/blockly-main.js @@ -9,13 +9,13 @@ import Interpreter from "js-interpreter"; import { InterpreterAPI } from "./interpreter-api.js"; Blockly.setLocale(locale); -let { cat_logic, cat_loops, cat_math, cat_sep, cat_data, cat_drone } = Categories; +let { cat_logic, cat_loops, cat_math, cat_sep, cat_data, cat_drone, cat_vex } = Categories; export const BlocklyMain = class { constructor() { createCustomBlocks(); this.interpreter = null; - this.runner = null; // may need to use window here + this.runner = null; this.latestCode = ""; let cat_robot = { @@ -31,7 +31,8 @@ export const BlocklyMain = class { cat_sep, cat_data, cat_drone, - cat_robot // Add the new category here + cat_vex, // Add VEX category here + cat_robot ]); this.workspace = Blockly.inject("blocklyDiv", { diff --git a/src/renderer/js/categories.js b/src/renderer/js/categories.js index 2d996ab..faa4463 100644 --- a/src/renderer/js/categories.js +++ b/src/renderer/js/categories.js @@ -42,6 +42,13 @@ export const Categories = { ] }, + // Add the new VEX category + cat_vex: { + name: "VEX", + colour: 230, + modules: ["vex_turn_left", "vex_turn_right"] + }, + cat_vars: { name: "Variables", colour: 100, diff --git a/src/renderer/js/customblock.js b/src/renderer/js/customblock.js index 84886b0..b82fab6 100644 --- a/src/renderer/js/customblock.js +++ b/src/renderer/js/customblock.js @@ -442,14 +442,24 @@ javascriptGenerator.forBlock["move"] = function (block) { return code; }; -var ledBlock = { +// LED Control Block +var ledControl = { type: "led_control", - message0: "set LED color to %1", + message0: "turn LED %1", args0: [ { type: "field_dropdown", name: "COLOR", - options: [["BLUE", "BLUE"], ["RED", "RED"], ["GREEN", "GREEN"], ["ORANGE", "ORANGE"]] + options: [ + ["Red", "RED"], + ["Green", "GREEN"], + ["Blue", "BLUE"], + ["White", "WHITE"], + ["Yellow", "YELLOW"], + ["Orange", "ORANGE"], + ["Purple", "PURPLE"], + ["Cyan", "CYAN"] + ] } ], previousStatement: null, @@ -459,12 +469,67 @@ var ledBlock = { Blockly.Blocks["led_control"] = { init: function () { - this.jsonInit(ledBlock); + this.jsonInit(ledControl); } }; javascriptGenerator.forBlock["led_control"] = function (block) { var color = block.getFieldValue("COLOR"); - var code = `window.sendCommand({ action: "led_on", color: "${color}" });\n`; + var code = `electronAPI.sendCommand({ action: "led_on", color: "${color}" });\n`; + console.log("Generated code for LED block:", code); return code; }; + +// VEX Turn Left Block +var vexTurnLeft = { + type: "vex_turn_left", + message0: "turn VEX left %1 degrees", + args0: [ + { + type: "input_value", + name: "degrees", + check: "Number" + } + ], + previousStatement: null, + nextStatement: null, + colour: 230 +}; + +Blockly.Blocks["vex_turn_left"] = { + init: function () { + this.jsonInit(vexTurnLeft); + } +}; + +javascriptGenerator.forBlock["vex_turn_left"] = function (block, generator) { + var degrees = generator.valueToCode(block, "degrees", Order.ATOMIC) || "90"; + return `vex_turn_left(${degrees});\n`; +}; + +// VEX Turn Right Block +var vexTurnRight = { + type: "vex_turn_right", + message0: "turn VEX right %1 degrees", + args0: [ + { + type: "input_value", + name: "degrees", + check: "Number" + } + ], + previousStatement: null, + nextStatement: null, + colour: 230 +}; + +Blockly.Blocks["vex_turn_right"] = { + init: function () { + this.jsonInit(vexTurnRight); + } +}; + +javascriptGenerator.forBlock["vex_turn_right"] = function (block, generator) { + var degrees = generator.valueToCode(block, "degrees", Order.ATOMIC); + return `vex_turn_right(${degrees});\n`; +}; diff --git a/src/renderer/js/interpreter-api.js b/src/renderer/js/interpreter-api.js index 166981c..966152a 100644 --- a/src/renderer/js/interpreter-api.js +++ b/src/renderer/js/interpreter-api.js @@ -21,6 +21,10 @@ export const InterpreterAPI = class { cw: this.wrapperFunctions.cw, takeoff: this.wrapperFunctions.takeoff, land: this.wrapperFunctions.land, + // VEX commands + vex_turn_left: this.wrapperFunctions.vex_turn_left, + vex_turn_right: this.wrapperFunctions.vex_turn_right, + // ← NEW: expose muscle energy from window.filteredSample getMuscleEnergy: () => window.filteredSample diff --git a/src/renderer/js/main.js b/src/renderer/js/main.js index 1d5303a..663efde 100644 --- a/src/renderer/js/main.js +++ b/src/renderer/js/main.js @@ -15,19 +15,37 @@ import { ChannelVis } from "./channel_vis.js"; import { BlocklyMain } from "./blockly-main.js"; import { BandPowerVis } from "./band-power-vis.js"; -const ws = new WebSocket("ws://127.0.0.1:8765"); +let ws; +let wsReconnectAttempts = 0; +const maxReconnectAttempts = 5; -ws.onopen = () => { - console.log("WebSocket connection established"); -}; +// function connectWebSocket() { +// ws = new WebSocket("ws://127.0.0.1:8777"); -ws.onerror = (error) => { - console.error("WebSocket error:", error); -}; +// ws.onopen = () => { +// console.log("WebSocket connection established"); +// wsReconnectAttempts = 0; +// }; -ws.onmessage = (event) => { - console.log("Message from server:", event.data); -}; +// ws.onerror = (error) => { +// console.log("WebSocket connection attempt failed, retrying..."); +// }; + +// ws.onclose = () => { +// if (wsReconnectAttempts < maxReconnectAttempts) { +// wsReconnectAttempts++; +// console.log(`WebSocket reconnecting... attempt ${wsReconnectAttempts}`); +// setTimeout(connectWebSocket, 2000); // Wait 2 seconds before retry +// } +// }; + +// ws.onmessage = (event) => { +// console.log("Message from server:", event.data); +// }; +// } + +// Wait a bit before connecting to give Python server time to start +// setTimeout(connectWebSocket, 3000); function sendCommand(command) { window.electronAPI.sendCommand(command); diff --git a/src/renderer/js/wrapper-functions.js b/src/renderer/js/wrapper-functions.js index c310b23..4f0d785 100644 --- a/src/renderer/js/wrapper-functions.js +++ b/src/renderer/js/wrapper-functions.js @@ -68,6 +68,15 @@ export const WrapperFunctions = class { window.electronAPI.droneBack(value); } + vex_turn_left(degrees) { + window.electronAPI.vexTurnLeft(degrees); + } + + vex_turn_right(degrees) { + window.electronAPI.vexTurnRight(degrees); + } + + ccw(value) { console.log("ccw"); window.electronAPI.ccw(value); From 42d6f448d8193740bfb12fcfbdaa8fa94d312fb7 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Wed, 9 Jul 2025 14:58:27 -0500 Subject: [PATCH 14/25] added venv setup --- .gitignore | 5 ++++- setup-venv.sh | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 setup-venv.sh diff --git a/.gitignore b/.gitignore index bcbf80a..c9a5e58 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,7 @@ yarn-error.log .yarn-integrity -build \ No newline at end of file +build + +# Ignore virtual environments +venv/ diff --git a/setup-venv.sh b/setup-venv.sh new file mode 100644 index 0000000..1e2b9fd --- /dev/null +++ b/setup-venv.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +echo "Creating and configuring Python virtual environment..." + +# Check if Python is installed +if ! command -v python &> /dev/null && ! command -v python3 &> /dev/null; then + echo "Python is not installed. Please run the system setup script first." + exit 1 +fi + +# Use python or python3 +PYTHON_CMD=$(command -v python || command -v python3) + +# Create venv if it doesn't exist +if [ ! -d "venv" ]; then + echo "Creating venv..." + $PYTHON_CMD -m venv venv +else + echo "venv already exists." +fi + +# Activate the venv +echo "Activating virtual environment..." +source venv/Scripts/activate + +# Install dependencies +if [ -f "requirements.txt" ]; then + echo "Installing from requirements.txt..." + pip install --upgrade pip + pip install -r requirements.txt +else + echo "No requirements.txt found. Installing known dependencies..." + pip install --upgrade pip + pip install websocket-client +fi + +echo "Setup complete. To activate later, run:" +echo "source venv/Scripts/activate" From dae318695fffd488d2ab177338484c2e1d754f30 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Wed, 9 Jul 2025 15:17:28 -0500 Subject: [PATCH 15/25] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 946f5a5..44cf361 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ -e . opencv-python -pyaudio \ No newline at end of file +pyaudio +websockets>=10.0 \ No newline at end of file From 076764b028b03ea9d6a674648c51abce1fe9e38e Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Wed, 9 Jul 2025 15:38:15 -0500 Subject: [PATCH 16/25] Update requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 44cf361..fc0ba24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ --e . opencv-python pyaudio websockets>=10.0 \ No newline at end of file From b1f1bd2798353a5aef3ab3e00b83188e9d1fdba2 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Wed, 9 Jul 2025 15:48:28 -0500 Subject: [PATCH 17/25] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fc0ba24..811d3db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ opencv-python pyaudio -websockets>=10.0 \ No newline at end of file +websockets>=10.0 +websocket-client>=1.5.1 \ No newline at end of file From eea135d57fafbefce95af4d21290f0185e5f3a74 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Thu, 10 Jul 2025 00:08:17 -0500 Subject: [PATCH 18/25] updated vex category --- resources/python/VEXServer.py | 10 ++- src/main/index.js | 25 ++++++ src/main/preload.js | 4 + src/renderer/js/categories.js | 13 ++- src/renderer/js/customblock.js | 116 ++++++++++++++++++++++++++- src/renderer/js/interpreter-api.js | 5 +- src/renderer/js/wrapper-functions.js | 15 ++++ 7 files changed, 176 insertions(+), 12 deletions(-) diff --git a/resources/python/VEXServer.py b/resources/python/VEXServer.py index 8bff941..af14953 100644 --- a/resources/python/VEXServer.py +++ b/resources/python/VEXServer.py @@ -45,13 +45,15 @@ async def handle_command(websocket, path=None): await websocket.send(json.dumps({"status": "success", "action": "led_on", "color": color_name})) elif action == "move": - distance = command.get("distance", 100) + distance_inches = command.get("distance", 4) # Default to 4 inches instead of 100mm heading = command.get("heading", 0) + # Convert inches to millimeters (1 inch = 25.4 mm) + distance_mm = distance_inches * 25.4 print(f"Received move command: {command}") - print(f"Moving robot: Distance={distance}, Heading={heading}") - robot.move_for(distance, heading) + print(f"Moving robot: Distance={distance_inches} inches ({distance_mm} mm), Heading={heading}") + robot.move_for(distance_mm, heading) # Send a response back to the client - await websocket.send(json.dumps({"status": "success", "action": "move", "distance": distance, "heading": heading})) + await websocket.send(json.dumps({"status": "success", "action": "move", "distance_inches": distance_inches, "distance_mm": distance_mm, "heading": heading})) elif action == "turn_left": degrees = command.get("degrees", 90) diff --git a/src/main/index.js b/src/main/index.js index e7fe534..a313731 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -218,6 +218,7 @@ async function createWindow() { tello.ccw(recent_val); }); + //Vex commands ipcMain.on("vex-turn-left", (event, degrees) => { console.log(`[VEX] Turn left ${degrees}°`); const turnCommand = { action: "turn_left", degrees: degrees }; @@ -230,6 +231,30 @@ async function createWindow() { sendCommand(turnCommand); }); + ipcMain.on("vex-forward", (event, distance) => { + console.log(`[VEX] Move forward ${distance} inches`); + const moveCommand = { action: "move", distance: distance, heading: 0 }; + sendCommand(moveCommand); + }); + + ipcMain.on("vex-back", (event, distance) => { + console.log(`[VEX] Move back ${distance} inches`); + const moveCommand = { action: "move", distance: distance, heading: 180 }; + sendCommand(moveCommand); + }); + + ipcMain.on("vex-left", (event, distance) => { + console.log(`[VEX] Move left ${distance} inches`); + const moveCommand = { action: "move", distance: distance, heading: 270 }; + sendCommand(moveCommand); + }); + + ipcMain.on("vex-right", (event, distance) => { + console.log(`[VEX] Move right ${distance} inches`); + const moveCommand = { action: "move", distance: distance, heading: 90 }; + sendCommand(moveCommand); + }); + let isUp = false; // ipcMain.on("manual-control", (event, response) => { diff --git a/src/main/preload.js b/src/main/preload.js index 3981219..4a58353 100644 --- a/src/main/preload.js +++ b/src/main/preload.js @@ -28,6 +28,10 @@ process.once("loaded", () => { //Vex commands vexTurnLeft: (degrees) => ipcRenderer.send("vex-turn-left", degrees), vexTurnRight: (degrees) => ipcRenderer.send("vex-turn-right", degrees), + vexForward: (distance) => ipcRenderer.send("vex-forward", distance), + vexBack: (distance) => ipcRenderer.send("vex-back", distance), + vexLeft: (distance) => ipcRenderer.send("vex-left", distance), + vexRight: (distance) => ipcRenderer.send("vex-right", distance), cw: (response) => ipcRenderer.send("cw", response), ccw: (response) => ipcRenderer.send("ccw", response), getBLEList: (callback) => ipcRenderer.on("device_list", callback), diff --git a/src/renderer/js/categories.js b/src/renderer/js/categories.js index faa4463..f6ce1cc 100644 --- a/src/renderer/js/categories.js +++ b/src/renderer/js/categories.js @@ -29,7 +29,7 @@ export const Categories = { cat_drone: { name: "Drone", - colour: 70, + colour: 230, modules: [ "drone_up", "drone_down", @@ -45,8 +45,15 @@ export const Categories = { // Add the new VEX category cat_vex: { name: "VEX", - colour: 230, - modules: ["vex_turn_left", "vex_turn_right"] + colour: 70, + modules: [ + "vex_forward", + "vex_back", + "vex_left", + "vex_right", + "vex_turn_left", + "vex_turn_right" + ] }, cat_vars: { diff --git a/src/renderer/js/customblock.js b/src/renderer/js/customblock.js index b82fab6..206172c 100644 --- a/src/renderer/js/customblock.js +++ b/src/renderer/js/customblock.js @@ -483,7 +483,7 @@ javascriptGenerator.forBlock["led_control"] = function (block) { // VEX Turn Left Block var vexTurnLeft = { type: "vex_turn_left", - message0: "turn VEX left %1 degrees", + message0: "turn left %1 degrees", args0: [ { type: "input_value", @@ -493,7 +493,7 @@ var vexTurnLeft = { ], previousStatement: null, nextStatement: null, - colour: 230 + colour: 70 }; Blockly.Blocks["vex_turn_left"] = { @@ -510,7 +510,7 @@ javascriptGenerator.forBlock["vex_turn_left"] = function (block, generator) { // VEX Turn Right Block var vexTurnRight = { type: "vex_turn_right", - message0: "turn VEX right %1 degrees", + message0: "turn right %1 degrees", args0: [ { type: "input_value", @@ -520,7 +520,7 @@ var vexTurnRight = { ], previousStatement: null, nextStatement: null, - colour: 230 + colour: 70 }; Blockly.Blocks["vex_turn_right"] = { @@ -533,3 +533,111 @@ javascriptGenerator.forBlock["vex_turn_right"] = function (block, generator) { var degrees = generator.valueToCode(block, "degrees", Order.ATOMIC); return `vex_turn_right(${degrees});\n`; }; + +// VEX Forward Block +var vexForward = { + type: "vex_forward", + message0: "forward %1 inches", + args0: [ + { + type: "input_value", + name: "distance", + check: "Number" + } + ], + previousStatement: null, + nextStatement: null, + colour: 70 +}; + +Blockly.Blocks["vex_forward"] = { + init: function () { + this.jsonInit(vexForward); + } +}; + +javascriptGenerator.forBlock["vex_forward"] = function (block, generator) { + var distance = generator.valueToCode(block, "distance", Order.ATOMIC) || "4"; + return `vex_forward(${distance});\n`; +}; + +// VEX Back Block +var vexBack = { + type: "vex_back", + message0: "back %1 inches", + args0: [ + { + type: "input_value", + name: "distance", + check: "Number" + } + ], + previousStatement: null, + nextStatement: null, + colour: 70 +}; + +Blockly.Blocks["vex_back"] = { + init: function () { + this.jsonInit(vexBack); + } +}; + +javascriptGenerator.forBlock["vex_back"] = function (block, generator) { + var distance = generator.valueToCode(block, "distance", Order.ATOMIC) || "4"; + return `vex_back(${distance});\n`; +}; + +// VEX Left Block +var vexLeft = { + type: "vex_left", + message0: "left %1 inches", + args0: [ + { + type: "input_value", + name: "distance", + check: "Number" + } + ], + previousStatement: null, + nextStatement: null, + colour: 70 +}; + +Blockly.Blocks["vex_left"] = { + init: function () { + this.jsonInit(vexLeft); + } +}; + +javascriptGenerator.forBlock["vex_left"] = function (block, generator) { + var distance = generator.valueToCode(block, "distance", Order.ATOMIC) || "4"; + return `vex_left(${distance});\n`; +}; + +// VEX Right Block +var vexRight = { + type: "vex_right", + message0: "right %1 inches", + args0: [ + { + type: "input_value", + name: "distance", + check: "Number" + } + ], + previousStatement: null, + nextStatement: null, + colour: 70 +}; + +Blockly.Blocks["vex_right"] = { + init: function () { + this.jsonInit(vexRight); + } +}; + +javascriptGenerator.forBlock["vex_right"] = function (block, generator) { + var distance = generator.valueToCode(block, "distance", Order.ATOMIC) || "4"; + return `vex_right(${distance});\n`; +}; diff --git a/src/renderer/js/interpreter-api.js b/src/renderer/js/interpreter-api.js index 966152a..35e6d8a 100644 --- a/src/renderer/js/interpreter-api.js +++ b/src/renderer/js/interpreter-api.js @@ -24,7 +24,10 @@ export const InterpreterAPI = class { // VEX commands vex_turn_left: this.wrapperFunctions.vex_turn_left, vex_turn_right: this.wrapperFunctions.vex_turn_right, - + vex_forward: this.wrapperFunctions.vex_forward, + vex_back: this.wrapperFunctions.vex_back, + vex_left: this.wrapperFunctions.vex_left, + vex_right: this.wrapperFunctions.vex_right, // ← NEW: expose muscle energy from window.filteredSample getMuscleEnergy: () => window.filteredSample diff --git a/src/renderer/js/wrapper-functions.js b/src/renderer/js/wrapper-functions.js index 4f0d785..8e73430 100644 --- a/src/renderer/js/wrapper-functions.js +++ b/src/renderer/js/wrapper-functions.js @@ -76,6 +76,21 @@ export const WrapperFunctions = class { window.electronAPI.vexTurnRight(degrees); } + vex_forward(distance) { + window.electronAPI.vexForward(distance); + } + + vex_back(distance) { + window.electronAPI.vexBack(distance); + } + + vex_left(distance) { + window.electronAPI.vexLeft(distance); + } + + vex_right(distance) { + window.electronAPI.vexRight(distance); + } ccw(value) { console.log("ccw"); From f70d5318e50815e7358259d2ad90fa7a3f13481c Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Thu, 10 Jul 2025 00:31:12 -0500 Subject: [PATCH 19/25] added start bash script - fixed colors --- Neuroscope.bat | 14 ++++++++++++++ src/renderer/js/customblock.js | 16 ++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 Neuroscope.bat diff --git a/Neuroscope.bat b/Neuroscope.bat new file mode 100644 index 0000000..725e10a --- /dev/null +++ b/Neuroscope.bat @@ -0,0 +1,14 @@ +@echo off +setlocal + +REM This script should be placed inside the neuroscope-emg folder + +echo Starting Neuroscope EMG... + +REM Activate virtual environment +call venv\Scripts\activate.bat + +REM Start the application +yarn serve + +pause diff --git a/src/renderer/js/customblock.js b/src/renderer/js/customblock.js index 206172c..75f9614 100644 --- a/src/renderer/js/customblock.js +++ b/src/renderer/js/customblock.js @@ -242,7 +242,7 @@ var droneUp = { args0: [{ type: "input_value", name: "value", check: "Number" }], previousStatement: null, nextStatement: null, - colour: drone_blocks_color + colour: 230 }; Blockly.Blocks["drone_up"] = { @@ -266,7 +266,7 @@ var droneDown = { args0: [{ type: "input_value", name: "value", check: "Number" }], previousStatement: null, nextStatement: null, - colour: drone_blocks_color + colour: 230 }; Blockly.Blocks["drone_down"] = { @@ -289,7 +289,7 @@ var droneForward = { args0: [{ type: "input_value", name: "value", check: "Number" }], previousStatement: null, nextStatement: null, - colour: drone_blocks_color + colour: 230 }; Blockly.Blocks["drone_forward"] = { @@ -312,7 +312,7 @@ var droneBack = { args0: [{ type: "input_value", name: "value", check: "Number" }], previousStatement: null, nextStatement: null, - colour: drone_blocks_color + colour: 230 }; Blockly.Blocks["drone_back"] = { @@ -335,7 +335,7 @@ var ccw = { args0: [{ type: "input_value", name: "value", check: "Number" }], previousStatement: null, nextStatement: null, - colour: drone_blocks_color + colour: 230 }; Blockly.Blocks["ccw"] = { @@ -358,7 +358,7 @@ var cw = { args0: [{ type: "input_value", name: "value", check: "Number" }], previousStatement: null, nextStatement: null, - colour: drone_blocks_color + colour: 230 }; Blockly.Blocks["cw"] = { @@ -381,7 +381,7 @@ var takeoff = { args0: [], previousStatement: null, nextStatement: null, - colour: drone_blocks_color + colour: 230 }; Blockly.Blocks["takeoff"] = { @@ -402,7 +402,7 @@ var land = { args0: [], previousStatement: null, nextStatement: null, - colour: drone_blocks_color + colour: 100 }; Blockly.Blocks["land"] = { From 4f79cf2fe1d33ad9c26005f070a29d99eec8b37a Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Thu, 10 Jul 2025 11:08:15 -0500 Subject: [PATCH 20/25] Added NeuroScope Icon --- neuroIcon.ico | Bin 0 -> 130752 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 neuroIcon.ico diff --git a/neuroIcon.ico b/neuroIcon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8d6aebfc939b413cdb6083dac7ec4c910109f357 GIT binary patch literal 130752 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 literal 0 HcmV?d00001 From 2510906acf9bedd8996d78e9bcc9ffcf573142fd Mon Sep 17 00:00:00 2001 From: Chris Crawford Date: Thu, 10 Jul 2025 12:24:32 -0500 Subject: [PATCH 21/25] Update signal.js --- .gitignore | 2 +- programs/maze_program_emg.xml | 1 + src/renderer/js/signal.js | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 programs/maze_program_emg.xml diff --git a/.gitignore b/.gitignore index c9a5e58..b8bc0ac 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,7 @@ yarn-error.log .pnp.js # Yarn Integrity file .yarn-integrity - +*.pyc build diff --git a/programs/maze_program_emg.xml b/programs/maze_program_emg.xml new file mode 100644 index 0000000..eda4ac1 --- /dev/null +++ b/programs/maze_program_emg.xml @@ -0,0 +1 @@ +1000GT9033 \ No newline at end of file diff --git a/src/renderer/js/signal.js b/src/renderer/js/signal.js index dad35ce..bcb0a85 100644 --- a/src/renderer/js/signal.js +++ b/src/renderer/js/signal.js @@ -44,13 +44,16 @@ export const Signal = class { let new_sample = Math.abs(sample.data[0] * this.EMG_SIGNAL_MULTIPLIER); let filtered_data = this.filter.singleStep(new_sample); //console.log("my sample", filtered_data); - window.filteredSample = filtered_data; + // window.filteredSample = filtered_data; + let value_for_kids = Math.abs(sample.data[0] * 100000).toFixed(2); // easier for students to interpret if (Date.now() - this.last_signal_update > this.value_refresh_delay_ms) { - this.signal_value_dom.innerHTML = Math.abs(sample.data[0] * 100000).toFixed(2); // easier for students to interpret + this.signal_value_dom.innerHTML = value_for_kids; // easier for students to interpret this.last_signal_update = Date.now(); } + window.filteredSample = value_for_kids; + if (!this.channels[electrode]) { this.channels[electrode] = []; this.channels_d3_plot[electrode] = []; @@ -60,7 +63,7 @@ export const Signal = class { this.channels[electrode].shift(); } - let formatted_data = filtered_data - this.x_top_padding; + let formatted_data = filtered_data - this.x_top_padding * 1.6; this.channels[electrode].push(formatted_data); //console.log(this.channels[electrode]); } From 4013a5b88b0b3ccf172b7eb29d036571511641ef Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Thu, 10 Jul 2025 21:06:34 -0500 Subject: [PATCH 22/25] Camp Neuroscope EMG V1.0 - no longer degrees for turn block (for kids) - Shortcut is ready to be used (Neuroscope-EMG) -Updated gitignore (pycache) --- .gitignore | 1 + Neuroscope-EMG.lnk | Bin 0 -> 2442 bytes src/renderer/js/customblock.js | 32 ++++++++++---------------------- 3 files changed, 11 insertions(+), 22 deletions(-) create mode 100644 Neuroscope-EMG.lnk diff --git a/.gitignore b/.gitignore index b8bc0ac..af1208c 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ build # Ignore virtual environments venv/ +/resources/python/vex/__pycache__ diff --git a/Neuroscope-EMG.lnk b/Neuroscope-EMG.lnk new file mode 100644 index 0000000000000000000000000000000000000000..3d44cb2e98ce00a91b9879c7e31af3ace6369d6e GIT binary patch literal 2442 zcmds2Ye-a45dLlnWe>ZSMMb6t8`)#AsK~8keeAkRiz~Zp<>mIU>lI7eyX;je>;+LG z*pGsxwxA^JMGq=6|F}Jf9zUcaA}IS&QACND){p2%G;?>?%)TL=FFM<&77Gz zb6o(0lp!<&*NqwW4(o8F6aRj1_)cq2hp?yp&W&Nc9YTEnA*u%%D7l$MDiifknww|6@Qz#j9_B28%EGsf%^I6IRDU}lug5&Yz1EupfP z3e5r90~4_U^T0WJC!OAv-iL0^%k_cc9E~ZTbxMm23-{YdnUKC8;(`ci|7gLCbtpp( zDNiAauk0T0qgjM;&+)V$N~Am&KZ~^HDryk<7P&-MgSs3nE8n8Y1*%?EqXlHnxNIa! zHo`0W)!Lw@D_TI6i~3mex-v2-!VGy(-{v8osz*i%`JqyTbmD{Lbvd?BoMaS`RHtmz z7*Uh^YDeyuce!8AWc)1F*1uw%q#?&EwWSjf=hh*nTC{1gTao2aPLk!L&)1(n3x z>8#p#mQj!-{?JMk@1?xINW_;ou|b)!H1sgJAz|eVM)h literal 0 HcmV?d00001 diff --git a/src/renderer/js/customblock.js b/src/renderer/js/customblock.js index 75f9614..7e1ae07 100644 --- a/src/renderer/js/customblock.js +++ b/src/renderer/js/customblock.js @@ -480,17 +480,11 @@ javascriptGenerator.forBlock["led_control"] = function (block) { return code; }; -// VEX Turn Left Block +// VEX Turn Left Block (simplified - no degrees input) var vexTurnLeft = { type: "vex_turn_left", - message0: "turn left %1 degrees", - args0: [ - { - type: "input_value", - name: "degrees", - check: "Number" - } - ], + message0: "turn left", + args0: [], // Remove the degrees input previousStatement: null, nextStatement: null, colour: 70 @@ -503,21 +497,15 @@ Blockly.Blocks["vex_turn_left"] = { }; javascriptGenerator.forBlock["vex_turn_left"] = function (block, generator) { - var degrees = generator.valueToCode(block, "degrees", Order.ATOMIC) || "90"; - return `vex_turn_left(${degrees});\n`; + // Use a default value of 90 degrees since there's no input + return `vex_turn_left(90);\n`; }; -// VEX Turn Right Block +// VEX Turn Right Block (simplified - no degrees input) var vexTurnRight = { type: "vex_turn_right", - message0: "turn right %1 degrees", - args0: [ - { - type: "input_value", - name: "degrees", - check: "Number" - } - ], + message0: "turn right", + args0: [], // Remove the degrees input previousStatement: null, nextStatement: null, colour: 70 @@ -530,8 +518,8 @@ Blockly.Blocks["vex_turn_right"] = { }; javascriptGenerator.forBlock["vex_turn_right"] = function (block, generator) { - var degrees = generator.valueToCode(block, "degrees", Order.ATOMIC); - return `vex_turn_right(${degrees});\n`; + // Use a default value of 90 degrees since there's no input + return `vex_turn_right(90);\n`; }; // VEX Forward Block From 5f2c31c655f74cff05c7e3ab856820adf92b5c10 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Thu, 10 Jul 2025 21:24:44 -0500 Subject: [PATCH 23/25] Removed command delay --- src/main/index.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/index.js b/src/main/index.js index a313731..e3fbd9f 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -155,18 +155,10 @@ async function createWindow() { console.error('WebSocket error:', err); }); - let lastCommandTime = 0; - const commandInterval = 3000; // 3 seconds - function sendCommand(command) { - const currentTime = Date.now(); - if (currentTime - lastCommandTime >= commandInterval) { - ws.send(JSON.stringify(command)); - console.log("Command sent:", command); - lastCommandTime = currentTime; - } else { - console.log("Command skipped to avoid spamming:", command); - } + ws.send(JSON.stringify(command)); + console.log("Command sent:", command); + // Remove all the timing logic } ipcMain.on("drone-up", (event, response) => { From 9f3935c75d36e5a27b189617a0f4d2f6a887d2b3 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Wed, 16 Jul 2025 13:03:55 -0500 Subject: [PATCH 24/25] Revert "Add support for Gaglion" (dont push) --- package-lock.json | 651 ++-------------- package.json | 3 +- src/main/index.js | 22 +- src/renderer/index.html | 3 - src/renderer/js/ble.js | 22 +- src/renderer/js/channel_vis.js | 4 +- src/renderer/js/ganglion-client.js | 157 ---- src/renderer/js/main.js | 6 +- src/renderer/js/signal.js | 52 +- yarn.lock | 1118 ++++++++++++---------------- 10 files changed, 555 insertions(+), 1483 deletions(-) delete mode 100644 src/renderer/js/ganglion-client.js diff --git a/package-lock.json b/package-lock.json index de32e1e..7ea2a6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,16 @@ { "name": "electron-vuejs-parcel", - "version": "1.3.0", + "version": "1.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "electron-vuejs-parcel", - "version": "1.3.0", + "version": "1.0.11", "license": "MIT", "dependencies": { - "@openbci/utilities": "^1.0.0", - "blockly": "^10.4.3", "d3": "^7.9.0", - "js-interpreter": "^5.1.1", - "rxjs": "^7.8.1", - "wait-on": "^5.3.0", - "ws": "^8.18.2" + "rxjs": "^7.8.1" }, "devDependencies": { "@parcel/transformer-sass": "2.0.0-beta.3.1", @@ -760,23 +755,6 @@ "node": ">= 8" } }, - "node_modules/@openbci/utilities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@openbci/utilities/-/utilities-1.0.0.tgz", - "integrity": "sha512-fhQ4lqL2qG9TDpWnLtWPG9zbYMBNNyPF/CdJY29zpSfu6E407luYr8weuViWsGcq24PZSfC9ubaNCYa8pL4j+g==", - "dependencies": { - "buffer": "^5.0.8", - "buffer-equal": "^1.0.0", - "clone": "^2.0.0", - "gaussian": "^1.0.0", - "mathjs": "^4.0.0", - "performance-now": "^2.1.0", - "streamsearch": "^0.1.2" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@parcel/babel-ast-utils": { "version": "2.0.0-beta.3.1", "resolved": "https://registry.npmjs.org/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.0-beta.3.1.tgz", @@ -2268,14 +2246,6 @@ "node": ">=10" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "engines": { - "node": ">= 10" - } - }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -2664,10 +2634,11 @@ "license": "MIT" }, "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/abortcontroller-polyfill": { "version": "1.7.3", @@ -2710,17 +2681,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3168,6 +3128,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -3285,6 +3246,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -3343,241 +3305,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/blockly": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/blockly/-/blockly-10.4.3.tgz", - "integrity": "sha512-+opfBmQnSiv7vTiY/TkDEBOslxUyfj8luS3S+qs1NnQKjInC+Waf2l9cNsMh9J8BMkmiCIT+Ed/3mmjIaL9wug==", - "dependencies": { - "jsdom": "22.1.0" - } - }, - "node_modules/blockly/node_modules/cssstyle": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", - "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", - "dependencies": { - "rrweb-cssom": "^0.6.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/blockly/node_modules/data-urls": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", - "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/blockly/node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/blockly/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/blockly/node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/blockly/node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/blockly/node_modules/jsdom": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", - "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", - "dependencies": { - "abab": "^2.0.6", - "cssstyle": "^3.0.0", - "data-urls": "^4.0.0", - "decimal.js": "^10.4.3", - "domexception": "^4.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.4", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.1", - "ws": "^8.13.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/blockly/node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/blockly/node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/blockly/node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/blockly/node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "dependencies": { - "punycode": "^2.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/blockly/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/blockly/node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/blockly/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/blockly/node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/blockly/node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "engines": { - "node": ">=12" - } - }, - "node_modules/blockly/node_modules/whatwg-url": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", - "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", - "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/blockly/node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "engines": { - "node": ">=12" - } - }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -3812,6 +3539,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, "funding": [ { "type": "github", @@ -3846,6 +3574,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4019,18 +3748,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -4349,6 +4066,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -4501,6 +4219,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -4523,14 +4242,6 @@ "dev": true, "license": "MIT" }, - "node_modules/complex.js": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.10.tgz", - "integrity": "sha512-PsT3WqpnTjS2ijoMM8XodCi/BYO04vkS8kBg1YXcqf5KcnKVV6uXUc1eeLHhBksj8i7Vu9iQF2/6ZG9gqI6CPQ==", - "engines": { - "node": "*" - } - }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -5760,6 +5471,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -5783,11 +5495,6 @@ "node": ">=0.10.0" } }, - "node_modules/decimal.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==" - }, "node_modules/decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -5989,6 +5696,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -6242,19 +5950,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -6610,47 +6305,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -6711,11 +6365,6 @@ "dev": true, "license": "MIT" }, - "node_modules/escape-latex": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.0.3.tgz", - "integrity": "sha512-GfKaG/7FOKdIdciylIzgaShBTPjdGQ5LJ2EcKLKXPLpcMO1MvCEVotkhydEShwCINRacZr2r3fk5A1PwZ4e5sA==" - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -7198,14 +6847,6 @@ "node": ">=0.4.x" } }, - "node_modules/fraction.js": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.8.tgz", - "integrity": "sha512-8Jx2AkFIFQtFaF8wP7yUIW+lnCgzPbxsholryMZ+oPK6kKjY/nUrvMKtq1+A8aSAeFau7+G/zfO8aGk2Aw1wCA==", - "engines": { - "node": "*" - } - }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -7242,20 +6883,11 @@ "license": "ISC" }, "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gaussian": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/gaussian/-/gaussian-1.3.0.tgz", - "integrity": "sha512-rYQ0ESfB+z0t7G95nHH80Zh7Pgg9A0FUYoZqV0yPec5WJZWKIHV2MPYpiJNy8oZAeVqyKwC10WXKSCnUQ5iDVg==", - "engines": { - "node": ">= 0.6.0" - } + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true, + "license": "MIT" }, "node_modules/generic-names": { "version": "2.0.1", @@ -7288,23 +6920,15 @@ } }, "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7320,18 +6944,6 @@ "node": ">=6" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -7526,17 +7138,6 @@ "node": ">=8" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/got": { "version": "11.8.6", "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", @@ -7648,23 +7249,11 @@ } }, "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "^1.0.3" - }, + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7757,17 +7346,6 @@ "minimalistic-assert": "^1.0.1" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/hex-color-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", @@ -7916,19 +7494,6 @@ "node": ">=8.0.0" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/http-proxy-middleware": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz", @@ -7983,18 +7548,6 @@ "dev": true, "license": "MIT" }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -8031,6 +7584,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -8598,11 +8152,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" - }, "node_modules/is-regex": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", @@ -8837,11 +8386,6 @@ "node": ">=4" } }, - "node_modules/javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" - }, "node_modules/joi": { "version": "17.4.0", "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", @@ -8855,17 +8399,6 @@ "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/js-interpreter": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/js-interpreter/-/js-interpreter-5.2.1.tgz", - "integrity": "sha512-offKOHFrtvQckRY0g4Bp6l4QbRUap/pFQ6HgwMgOaqqGmG7hNh21FZpHUJa99XWRyoWUlj7J/P70jHznRB4kUg==", - "dependencies": { - "minimist": "^1.2.8" - }, - "bin": { - "js-interpreter": "lib/cli.min.js" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9325,40 +8858,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mathjs": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-4.4.2.tgz", - "integrity": "sha512-T5zGIbDT/JGmzIu2Bocq4U8gbcmQVCyZaJbBCHKmJkLMQoWuh1SOuFH98doj1JEQwjpKkq3rqdUCuy3vLlBZOA==", - "dependencies": { - "complex.js": "2.0.10", - "decimal.js": "9.0.1", - "escape-latex": "1.0.3", - "fraction.js": "4.0.8", - "javascript-natural-sort": "0.7.1", - "seed-random": "2.2.0", - "tiny-emitter": "2.0.2", - "typed-function": "1.0.3" - }, - "bin": { - "mathjs": "bin/cli.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/mathjs/node_modules/decimal.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-9.0.1.tgz", - "integrity": "sha512-2h0iKbJwnImBk4TGk7CG1xadoA0g3LDPlQhQzbZ221zvG0p2YVUedbKIPsOZXKZGx6YmZMJKYOalpCMxSdDqTQ==" - }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -9443,6 +8942,7 @@ "version": "1.48.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -9452,6 +8952,7 @@ "version": "2.1.31", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.48.0" @@ -9508,12 +9009,11 @@ } }, "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true, + "license": "MIT" }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -9559,6 +9059,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -9762,9 +9263,11 @@ "license": "MIT" }, "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true, + "license": "MIT" }, "node_modules/oauth-sign": { "version": "0.9.0", @@ -10474,6 +9977,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true, "license": "MIT" }, "node_modules/picomatch": { @@ -11542,6 +11046,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true, "license": "MIT" }, "node_modules/public-encrypt": { @@ -11571,9 +11076,11 @@ } }, "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -11717,11 +11224,6 @@ "node": ">=0.4.x" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -12090,6 +11592,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true, "license": "MIT" }, "node_modules/resolve": { @@ -12243,11 +11746,6 @@ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, - "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12369,11 +11867,6 @@ "node": ">=8" } }, - "node_modules/seed-random": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", - "integrity": "sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==" - }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -12966,14 +12459,6 @@ "xtend": "^4.0.2" } }, - "node_modules/streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -13237,6 +12722,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, "license": "MIT" }, "node_modules/temp-file": { @@ -13321,11 +12807,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tiny-emitter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", - "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" - }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -13565,14 +13046,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typed-function": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.0.3.tgz", - "integrity": "sha512-sVC/1pm70oELDFMdYtFXMFqyawenLoaDiAXA3QvOAwKF/WvFNTSJN23cY2lFNL8iP0kh3T0PPKewrboO8XUVGQ==", - "engines": { - "node": ">= 6" - } - }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -13874,15 +13347,6 @@ "querystring": "0.2.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -14333,6 +13797,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, "license": "MIT" }, "node_modules/xtend": { @@ -14525,4 +13990,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 4da481b..b51d930 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "identity": null }, "win": { - "icon": "src/renderer/icons/icon.png" + "icon": "src/renderer/icons/icon.png" }, "dmg": { "contents": [ @@ -88,7 +88,6 @@ }, "homepage": "./", "dependencies": { - "@openbci/utilities": "^1.0.0", "blockly": "^10.4.3", "d3": "^7.9.0", "js-interpreter": "^5.1.1", diff --git a/src/main/index.js b/src/main/index.js index e3fbd9f..bade9e9 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -110,16 +110,26 @@ async function createWindow() { /* Code to integrate BLE and Tello Drone */ - // Electron native event listener for selecting a bluetooth device - // Purpose of this function is to get the callback function and send the device list to the renderer win.webContents.on("select-bluetooth-device", (event, deviceList, callback) => { - console.log("select-bluetooth-device"); bleCallback = callback; event.preventDefault(); - console.log(deviceList); + //console.log(deviceList); win.webContents.send("device_list", deviceList); - + /* + deviceList.map((x) => { + console.log(x.deviceName); + }); + */ let result = null; + //selectBluetoothCallback = callback + + /* + const result = deviceList.find((device) => { + return device.deviceName === MUSE_DEVICE_NAME; + }); + */ + + //console.log(MuseClient) if (result) { callback(result.deviceId); @@ -133,11 +143,11 @@ async function createWindow() { }); setInterval(() => { + //console.log(tello.getState()); let drone_state = tello.getState(); win.webContents.send("drone_state", drone_state); }, 5000); - // This function triggers the connection to the selected BLE device ipcMain.on("select-ble-device", (event, selected_ble_device) => { console.log("device selected: ", selected_ble_device); if (bleCallback) { diff --git a/src/renderer/index.html b/src/renderer/index.html index 14f98b7..627e61b 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -24,9 +24,6 @@ class="ui raised card centered" style="width: 100%; height: 40vh" > -

    - Muscle Energy 0 -

    diff --git a/src/renderer/js/ble.js b/src/renderer/js/ble.js index cbbf7ab..993bd98 100644 --- a/src/renderer/js/ble.js +++ b/src/renderer/js/ble.js @@ -1,13 +1,9 @@ import { MuseElectronClient } from "./muse-client.js"; -import { GanglionClient } from "./ganglion-client.js"; export const BLE = class { constructor(callback, connect_button_id = "bluetooth") { - //this.device = new MuseElectronClient(); - this.device = new GanglionClient(); + this.device = new MuseElectronClient(); this.callback = callback; - this.device_name = ""; - this.ble_device_name = ""; // Connect Events document.getElementById(connect_button_id).onclick = function (e) { @@ -16,7 +12,6 @@ export const BLE = class { this.connect(); }.bind(this); - // This is a listener for the BLE list window.electronAPI.getBLEList((event, list) => { let ble_device_list = []; for (let device in list) { @@ -36,7 +31,6 @@ export const BLE = class { return; } let ble_device = event.target.getAttribute("data"); - this.ble_device_name = event.target.getAttribute("name"); window.electronAPI.selectBluetoothDevice(ble_device); $(".ui.modal").modal("hide"); //console.log(); @@ -55,7 +49,7 @@ export const BLE = class { >

    ${device_name}

    - +
    `; @@ -74,16 +68,8 @@ export const BLE = class { async connect() { await this.device.connect(); - // console.log(this.ble_device_name); - - // EMG DATA (Ganglion) - if (this.ble_device_name.includes("Ganglion-")) { - // console.log("Ganglion-"); - // EEG Data (Muse) - this.device.stream.subscribe(this.callback); - } else if (this.ble_device_name.includes("Muse-")) { - this.device.eegReadings.subscribe(this.callback); - } + // EEG DATA + this.device.eegReadings.subscribe(this.callback); } get_device() { diff --git a/src/renderer/js/channel_vis.js b/src/renderer/js/channel_vis.js index 837b20d..77532ff 100644 --- a/src/renderer/js/channel_vis.js +++ b/src/renderer/js/channel_vis.js @@ -1,9 +1,9 @@ import * as d3 from "d3"; export const ChannelVis = class { - constructor(signal_div_height) { + constructor() { this.width = window.innerWidth * 0.4; - this.height = window.innerHeight * signal_div_height; + this.height = window.innerHeight * 0.09; const signal_amplitude = 300; this.svgs = {}; diff --git a/src/renderer/js/ganglion-client.js b/src/renderer/js/ganglion-client.js deleted file mode 100644 index eaad3cf..0000000 --- a/src/renderer/js/ganglion-client.js +++ /dev/null @@ -1,157 +0,0 @@ -import { constants, debug, utilities } from "@openbci/utilities"; -// import { fromEvent, merge, Subject, BehaviorSubject } from "rxjs"; -import { - fromEvent, - merge, - Subject, - BehaviorSubject, - tap, - first, - map, - takeUntil, - scan, - concatMap, - filter, - share, - mergeMap, - fromEvent -} from "rxjs"; - -let parseGanglion = utilities.parseGanglion; -let numberOfChannelsForBoardType = constants.numberOfChannelsForBoardType; -let rawDataToSampleObjectDefault = constants.rawDataToSampleObjectDefault; - -const serviceId = 0xfe84; -const boardName = "ganglion"; -const characteristicsByType = { - reader: "2d30c082-f39f-4ce6-923f-3484ea480596", - writer: "2d30c083-f39f-4ce6-923f-3484ea480596", - connection: "2d30c084-f39f-4ce6-923f-3484ea480596" -}; -const onCharacteristic = "characteristicvaluechanged"; -const onDisconnected = "gattserverdisconnected"; -const deviceOptions = { - filters: [{ namePrefix: "Ganglion-" }], - optionalServices: [serviceId] -}; - -const commandStrings = { - start: "b", - accelData: "n" -}; - -const renameDataProp = ({ channelData, ...sample }) => ({ - ...sample, - data: channelData -}); - -export const GanglionClient = class { - constructor(options = {}) { - this.GANGLION_SERVICE = serviceId; - // this.MUSE_SERVICE = 0xfe8d; - this.options = deviceOptions; - this.signalMultiplier = 10000; - this.gatt = null; - this.device = null; - this.deviceName = null; - this.service = null; - this.characteristics = null; - this.onDisconnect$ = new Subject(); - this.boardName = boardName; - this.channelSize = numberOfChannelsForBoardType(boardName); - this.rawDataPacketToSample = rawDataToSampleObjectDefault(this.channelSize); - this.connectionStatus = new BehaviorSubject(false); - this.stream = new Subject().pipe( - map((event) => this.eventToBufferMapper(event)), - tap((buffer) => this.setRawDataPacket(buffer)), - map(() => parseGanglion(this.rawDataPacketToSample)), - mergeMap((x) => x), - map(renameDataProp), - takeUntil(this.onDisconnect$) - ); - this.accelData = this.stream.pipe(filter((sample) => sample.accelData.length)); - } - - eventToBufferMapper(event) { - return new Uint8Array(event.target.value.buffer); - } - - setRawDataPacket(buffer) { - this.rawDataPacketToSample.rawDataPacket = buffer; - } - - async connect() { - this.device = await navigator.bluetooth.requestDevice(deviceOptions); - this.addDisconnectedEvent(); - this.gatt = await this.device.gatt.connect(); - this.deviceName = this.gatt.device.name; - this.service = await this.gatt.getPrimaryService(serviceId); - this.setCharacteristics(await this.service.getCharacteristics()); - this.connectionStatus.next(true); - - await this.start(); - - // this.stream.subscribe((sample) => { - // //console.log(sample.data); - // let new_sample = sample.data[0] * this.signalMultiplier; - // console.log(new_sample); - // }); - - //console.log(this.device); - //console.log(utilities.parseGanglion); - } - - setCharacteristics(characteristics) { - this.characteristics = Object.entries(characteristicsByType).reduce( - (map, [name, uuid]) => ({ - ...map, - [name]: characteristics.find((c) => c.uuid === uuid) - }), - {} - ); - } - - async start() { - const { reader, writer } = this.characteristics; - const commands = Object.entries(commandStrings).reduce( - (acc, [key, command]) => ({ - ...acc, - [key]: new TextEncoder().encode(command) - }), - {} - ); - - reader.startNotifications(); - reader.addEventListener(onCharacteristic, (event) => { - this.stream.next(event); - }); - - if (this.options.accelData) { - await writer.writeValue(commands.accelData); - reader.readValue(); - } - await writer.writeValue(commands.start); - reader.readValue(); - } - - addDisconnectedEvent() { - fromEvent(this.device, onDisconnected) - .pipe(first()) - .subscribe(() => { - this.gatt = null; - this.device = null; - this.deviceName = null; - this.service = null; - this.characteristics = null; - this.connectionStatus.next(false); - }); - } - - disconnect() { - if (!this.gatt) { - return; - } - this.onDisconnect$.next(); - this.gatt.disconnect(); - } -}; diff --git a/src/renderer/js/main.js b/src/renderer/js/main.js index 663efde..beaf71b 100644 --- a/src/renderer/js/main.js +++ b/src/renderer/js/main.js @@ -56,12 +56,10 @@ window.sendCommand = sendCommand; export const NeuroScope = class { constructor() { this.blocklyMain = new BlocklyMain(); - this.signal_handler = new Signal(512, "ganglion"); + this.signal_handler = new Signal(512); this.bpBis = new BandPowerVis(); this.events = new Events(this.blocklyMain); - //this.ble = new BLE(this.signal_handler.add_data.bind(this.signal_handler)); - this.ble = new BLE(this.signal_handler.add_data_ganglion.bind(this.signal_handler)); - + this.ble = new BLE(this.signal_handler.add_data.bind(this.signal_handler)); this.feature_extractor = new FeatureExtractor(256); this.blocklyMain.start(); setTimeout(() => { diff --git a/src/renderer/js/signal.js b/src/renderer/js/signal.js index bcb0a85..3fe28a7 100644 --- a/src/renderer/js/signal.js +++ b/src/renderer/js/signal.js @@ -2,31 +2,11 @@ import { ChannelVis } from "./channel_vis.js"; export const Signal = class { - constructor(buffer_size = 256, device = "muse") { + constructor(buffer_size = 256) { this.channels = {}; this.channels_d3_plot = {}; this.BUFFER_SIZE = buffer_size; - let signal_div_height = device === "ganglion" ? 0.5 : 0.09; // For now if device is muse, we use 0.09, if device is ganglion, we use 0.2 - this.x_top_padding = device === "ganglion" ? window.innerHeight * 3 : 0; - console.log("top padding", this.x_top_padding); - this.channel_vis = new ChannelVis(signal_div_height); - this.signal_value_dom = document.querySelector("#signal_value"); - this.last_signal_update = Date.now(); - this.value_refresh_delay_ms = 100; - this.EMG_SIGNAL_MULTIPLIER = 10000000; - let Fili = window.fili; - this.sampleRate = 250; - // this.lowFreq = lowFreq; - // this.highFreq = highFreq; - this.filterOrder = 100; - this.firCalculator = new Fili.FirCoeffs(); - this.coeffs = this.firCalculator.lowpass({ - order: this.filterOrder, - Fs: this.sampleRate, - Fc: 3 - }); - - this.filter = new Fili.FirFilter(this.coeffs); + this.channel_vis = new ChannelVis(); //this.tensor = new TensorDSP("muse"); /* @@ -40,34 +20,6 @@ export const Signal = class { // This will come with a computational cost. } - add_data_ganglion(sample, electrode = 0) { - let new_sample = Math.abs(sample.data[0] * this.EMG_SIGNAL_MULTIPLIER); - let filtered_data = this.filter.singleStep(new_sample); - //console.log("my sample", filtered_data); - // window.filteredSample = filtered_data; - - let value_for_kids = Math.abs(sample.data[0] * 100000).toFixed(2); // easier for students to interpret - if (Date.now() - this.last_signal_update > this.value_refresh_delay_ms) { - this.signal_value_dom.innerHTML = value_for_kids; // easier for students to interpret - this.last_signal_update = Date.now(); - } - - window.filteredSample = value_for_kids; - - if (!this.channels[electrode]) { - this.channels[electrode] = []; - this.channels_d3_plot[electrode] = []; - } - - if (this.channels[electrode].length > this.BUFFER_SIZE - 1) { - this.channels[electrode].shift(); - } - - let formatted_data = filtered_data - this.x_top_padding * 1.6; - this.channels[electrode].push(formatted_data); - //console.log(this.channels[electrode]); - } - add_data(sample) { //console.log(sample); let { electrode, samples } = sample; diff --git a/yarn.lock b/yarn.lock index 927f1cc..462dd2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"7zip-bin@~5.1.1": + version "5.1.1" + resolved "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz" + integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== + "@babel/code-frame@^7.14.5": version "7.14.5" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz" @@ -14,7 +19,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz" integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.12.0": +"@babel/core@^7.12.0": version "7.14.6" resolved "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz" integrity sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA== @@ -297,7 +302,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -310,19 +315,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@openbci/utilities@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@openbci/utilities/-/utilities-1.0.0.tgz" - integrity sha512-fhQ4lqL2qG9TDpWnLtWPG9zbYMBNNyPF/CdJY29zpSfu6E407luYr8weuViWsGcq24PZSfC9ubaNCYa8pL4j+g== - dependencies: - buffer "^5.0.8" - buffer-equal "^1.0.0" - clone "^2.0.0" - gaussian "^1.0.0" - mathjs "^4.0.0" - performance-now "^2.1.0" - streamsearch "^0.1.2" - "@parcel/babel-ast-utils@2.0.0-beta.3.1": version "2.0.0-beta.3.1" resolved "https://registry.npmjs.org/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.0-beta.3.1.tgz" @@ -390,7 +382,7 @@ "@parcel/transformer-raw" "2.0.0-beta.3.1" "@parcel/transformer-react-refresh-wrap" "2.0.0-beta.3.1" -"@parcel/core@^2.0.0-alpha.3.1", "@parcel/core@2.0.0-beta.3.1": +"@parcel/core@2.0.0-beta.3.1": version "2.0.0-beta.3.1" resolved "https://registry.npmjs.org/@parcel/core/-/core-2.0.0-beta.3.1.tgz" integrity sha512-Rkp92SBcVyr5qlPEoV7gVzNRYstHxo6ah4NadSdNN0QzXtcLUGkrAPXBGJpjlifXqXogyz0D3QjAaPBpk6p/8Q== @@ -929,7 +921,7 @@ "@tootallnate/once@2": version "2.0.0" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@types/cacheable-request@^6.0.1": @@ -1003,6 +995,14 @@ dependencies: undici-types "~5.26.4" +"@types/plist@^3.0.1": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/plist/-/plist-3.0.5.tgz#9a0c49c0f9886c8c8696a7904dd703f6284036e0" + integrity sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA== + dependencies: + "@types/node" "*" + xmlbuilder ">=11.0.1" + "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz" @@ -1015,6 +1015,11 @@ dependencies: "@types/node" "*" +"@types/verror@^1.10.3": + version "1.10.10" + resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.10.tgz#d5a4b56abac169bfbc8b23d291363a682e6fa087" + integrity sha512-l4MM0Jppn18hb9xmM6wwD1uTdShpf9Pn80aXTStnK1C94gtPvJcV2FrDmbOQUAQfJ1cKZHktkQUDwEqaAKXMMg== + "@types/yargs-parser@*": version "20.2.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz" @@ -1113,14 +1118,19 @@ resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.1.2.tgz" integrity sha512-EmH/poaDWBPJaPILXNI/1fvUbArJQmmTyVCwvvyDYDFnkPoTclAbHRAtyIvqfez7jybTDn077HTNILpxlsoWhg== -"7zip-bin@~5.1.1": - version "5.1.1" - resolved "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz" - integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== +"@xmldom/xmldom@^0.8.8": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" + integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== -abab@^2.0.0, abab@^2.0.6: +abab@^2.0.0: + version "2.0.5" + resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + +abab@^2.0.6: version "2.0.6" - resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== abortcontroller-polyfill@^1.1.9: @@ -1148,7 +1158,7 @@ acorn@^6.0.1, acorn@^6.0.4: agent-base@6: version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" @@ -1158,7 +1168,7 @@ ajv-keywords@^3.4.1: resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.12.0, ajv@^6.12.3, ajv@^6.9.1: +ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.3: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1205,14 +1215,7 @@ ansi-styles@^2.2.1: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= -ansi-styles@^3.2.0: - version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^3.2.1: +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -1244,10 +1247,10 @@ app-builder-lib@22.11.7: resolved "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.11.7.tgz" integrity sha512-pS9/cR4/TnNZVAHZECiSvvwTBzbwblj7KBBZkMKDG57nibq0I1XY8zAaYeHFdlYTyrRcz9JUXbAqJKezya7UFQ== dependencies: + "7zip-bin" "~5.1.1" "@develar/schema-utils" "~2.6.5" "@electron/universal" "1.0.5" "@malept/flatpak-bundler" "^0.4.0" - "7zip-bin" "~5.1.1" async-exit-hook "^2.0.1" bluebird-lst "^1.0.9" builder-util "22.11.7" @@ -1339,7 +1342,7 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" -assert-plus@^1.0.0, assert-plus@1.0.0: +assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= @@ -1433,6 +1436,11 @@ base-x@^3.0.8: dependencies: safe-buffer "^5.0.1" +base64-js@^1.3.1, base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.npmjs.org/base/-/base-0.11.2.tgz" @@ -1446,11 +1454,6 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" @@ -1479,7 +1482,7 @@ bl@^4.1.0: blockly@^10.4.3: version "10.4.3" - resolved "https://registry.npmjs.org/blockly/-/blockly-10.4.3.tgz" + resolved "https://registry.yarnpkg.com/blockly/-/blockly-10.4.3.tgz#a3ca155e2ba7bae59883a143b8b901c781bfac0d" integrity sha512-+opfBmQnSiv7vTiY/TkDEBOslxUyfj8luS3S+qs1NnQKjInC+Waf2l9cNsMh9J8BMkmiCIT+Ed/3mmjIaL9wug== dependencies: jsdom "22.1.0" @@ -1501,12 +1504,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0: - version "5.2.0" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz" - integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== - -bn.js@^5.1.1: +bn.js@^5.0.0, bn.js@^5.1.1: version "5.2.0" resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== @@ -1653,7 +1651,7 @@ buffer-crc32@~0.2.3: resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-equal@^1.0.0, buffer-equal@1.0.0: +buffer-equal@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz" integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= @@ -1668,9 +1666,9 @@ buffer-xor@^1.0.3: resolved "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= -buffer@^5.0.8, buffer@^5.5.0: +buffer@^5.1.0, buffer@^5.5.0: version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -1697,9 +1695,9 @@ builder-util@22.11.7: resolved "https://registry.npmjs.org/builder-util/-/builder-util-22.11.7.tgz" integrity sha512-ihqUe5ey82LM9qqQe0/oIcaSm9w+B9UjcsWJZxJliTBsbU+sErOpDFpHW+sim0veiTF/EIcGUh9HoduWw+l9FA== dependencies: + "7zip-bin" "~5.1.1" "@types/debug" "^4.1.5" "@types/fs-extra" "^9.0.11" - "7zip-bin" "~5.1.1" app-builder-bin "3.5.13" bluebird-lst "^1.0.9" builder-util-runtime "8.7.7" @@ -1768,14 +1766,6 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" @@ -1844,25 +1834,7 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^2.4.1: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1879,7 +1851,7 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@^3.5.0, "chokidar@>=3.0.0 <4.0.0": +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.0: version "3.5.2" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== @@ -1949,6 +1921,14 @@ cli-spinners@^2.5.0: resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz" integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" @@ -1979,7 +1959,7 @@ clone@^1.0.2: resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -clone@^2.0.0, clone@^2.1.1: +clone@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= @@ -2015,16 +1995,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + color-string@^1.5.4: version "1.5.5" resolved "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz" @@ -2063,21 +2043,6 @@ command-exists@^1.2.6: resolved "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - -commander@^7.0.0: - version "7.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@2.9.0: version "2.9.0" resolved "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" @@ -2085,15 +2050,20 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" -commander@7: +commander@7, commander@^7.0.0: version "7.2.0" resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -complex.js@2.0.10: - version "2.0.10" - resolved "https://registry.npmjs.org/complex.js/-/complex.js-2.0.10.tgz" - integrity sha512-PsT3WqpnTjS2ijoMM8XodCi/BYO04vkS8kBg1YXcqf5KcnKVV6uXUc1eeLHhBksj8i7Vu9iQF2/6ZG9gqI6CPQ== +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== component-emitter@^1.2.1: version "1.3.0" @@ -2181,7 +2151,7 @@ core-js@^3.2.1: resolved "https://registry.npmjs.org/core-js/-/core-js-3.15.1.tgz" integrity sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg== -core-util-is@~1.0.0, core-util-is@1.0.2: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -2196,6 +2166,13 @@ cosmiconfig@^5.0.0: js-yaml "^3.13.1" parse-json "^4.0.0" +crc@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz" @@ -2276,7 +2253,7 @@ crypto-random-string@^2.0.0: resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css-color-names@^0.0.4, css-color-names@0.0.4: +css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz" integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= @@ -2324,14 +2301,6 @@ css-selector-tokenizer@^0.7.0: cssesc "^3.0.0" fastparse "^1.1.2" -css-tree@^1.1.2: - version "1.1.3" - resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz" @@ -2340,6 +2309,14 @@ css-tree@1.0.0-alpha.37: mdn-data "2.0.4" source-map "^0.6.1" +css-tree@^1.1.2: + version "1.1.3" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz" @@ -2425,7 +2402,7 @@ csso@^4.0.2: dependencies: css-tree "^1.1.2" -cssom@^0.3.4, cssom@0.3.x: +cssom@0.3.x, cssom@^0.3.4: version "0.3.8" resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== @@ -2439,7 +2416,7 @@ cssstyle@^1.1.1: cssstyle@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg== dependencies: rrweb-cssom "^0.6.0" @@ -2449,21 +2426,21 @@ csstype@^2.6.8: resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz" integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A== -d3-array@^3.2.0, "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: version "3.2.4" - resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== dependencies: internmap "1 - 2" d3-axis@3: version "3.0.0" - resolved "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== d3-brush@3: version "3.0.0" - resolved "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== dependencies: d3-dispatch "1 - 3" @@ -2474,38 +2451,38 @@ d3-brush@3: d3-chord@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== dependencies: d3-path "1 - 3" "d3-color@1 - 3", d3-color@3: version "3.1.0" - resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== d3-contour@4: version "4.0.2" - resolved "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== dependencies: d3-array "^3.2.0" d3-delaunay@6: version "6.0.4" - resolved "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== dependencies: delaunator "5" "d3-dispatch@1 - 3", d3-dispatch@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== "d3-drag@2 - 3", d3-drag@3: version "3.0.0" - resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== dependencies: d3-dispatch "1 - 3" @@ -2513,7 +2490,7 @@ d3-delaunay@6: "d3-dsv@1 - 3", d3-dsv@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== dependencies: commander "7" @@ -2522,19 +2499,19 @@ d3-delaunay@6: "d3-ease@1 - 3", d3-ease@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== d3-fetch@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== dependencies: d3-dsv "1 - 3" d3-force@3: version "3.0.0" - resolved "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== dependencies: d3-dispatch "1 - 3" @@ -2543,51 +2520,51 @@ d3-force@3: "d3-format@1 - 3", d3-format@3: version "3.1.0" - resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== d3-geo@3: version "3.1.1" - resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== dependencies: d3-array "2.5.0 - 3" d3-hierarchy@3: version "3.1.2" - resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== "d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== dependencies: d3-color "1 - 3" -d3-path@^3.1.0, "d3-path@1 - 3", d3-path@3: +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== d3-polygon@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== "d3-quadtree@1 - 3", d3-quadtree@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== d3-random@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== d3-scale-chromatic@3: version "3.1.0" - resolved "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== dependencies: d3-color "1 - 3" @@ -2595,7 +2572,7 @@ d3-scale-chromatic@3: d3-scale@4: version "4.0.2" - resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== dependencies: d3-array "2.10.0 - 3" @@ -2606,38 +2583,38 @@ d3-scale@4: "d3-selection@2 - 3", d3-selection@3: version "3.0.0" - resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== d3-shape@3: version "3.2.0" - resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== dependencies: d3-path "^3.1.0" "d3-time-format@2 - 4", d3-time-format@4: version "4.1.0" - resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== dependencies: d3-time "1 - 3" "d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: version "3.1.0" - resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== dependencies: d3-array "2 - 3" "d3-timer@1 - 3", d3-timer@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== "d3-transition@2 - 3", d3-transition@3: version "3.0.1" - resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== dependencies: d3-color "1 - 3" @@ -2648,7 +2625,7 @@ d3-shape@3: d3-zoom@3: version "3.0.0" - resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== dependencies: d3-dispatch "1 - 3" @@ -2659,7 +2636,7 @@ d3-zoom@3: d3@^7.9.0: version "7.9.0" - resolved "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== dependencies: d3-array "3" @@ -2711,7 +2688,7 @@ data-urls@^1.1.0: data-urls@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4" integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g== dependencies: abab "^2.0.6" @@ -2730,21 +2707,21 @@ date-time@^3.1.0: dependencies: time-zone "^1.0.0" -debug@^2.2.0: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@^2.3.3: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: - ms "2.0.0" + ms "2.1.2" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@4: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -2758,27 +2735,15 @@ debug@^4.3.2: dependencies: ms "2.1.2" -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decimal.js@^10.4.3: - version "10.5.0" - resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz" - integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== - -decimal.js@9.0.1: - version "9.0.1" - resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-9.0.1.tgz" - integrity sha512-2h0iKbJwnImBk4TGk7CG1xadoA0g3LDPlQhQzbZ221zvG0p2YVUedbKIPsOZXKZGx6YmZMJKYOalpCMxSdDqTQ== + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== decode-uri-component@^0.2.0: version "0.2.0" @@ -2857,7 +2822,7 @@ define-property@^2.0.2: delaunator@5: version "5.0.1" - resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== dependencies: robust-predicates "^3.0.2" @@ -2925,14 +2890,19 @@ dmg-builder@22.11.7: optionalDependencies: dmg-license "^1.0.9" -dom-serializer@^1.0.1: - version "1.3.2" - resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz" - integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" +dmg-license@^1.0.9: + version "1.0.11" + resolved "https://registry.yarnpkg.com/dmg-license/-/dmg-license-1.0.11.tgz#7b3bc3745d1b52be7506b4ee80cb61df6e4cd79a" + integrity sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q== + dependencies: + "@types/plist" "^3.0.1" + "@types/verror" "^1.10.3" + ajv "^6.10.0" + crc "^3.8.0" + iconv-corefoundation "^1.1.7" + plist "^3.0.4" + smart-buffer "^4.0.2" + verror "^1.10.0" dom-serializer@0: version "0.2.2" @@ -2942,21 +2912,30 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + domain-browser@^3.5.0: version "3.5.0" resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-3.5.0.tgz" integrity sha512-zrzUu6auyZWRexjCEPJnfWc30Hupxh2lJZOJAF3qa2bCuD4O/55t0FvQt3ZMhEw++gjNkwdkOVZh8yA32w/Vfw== -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz" - integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== - domelementtype@1: version "1.3.1" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + domexception@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz" @@ -2966,7 +2945,7 @@ domexception@^1.0.1: domexception@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== dependencies: webidl-conversions "^7.0.0" @@ -3024,15 +3003,6 @@ dotenv@^9.0.2: resolved "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz" integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" @@ -3201,10 +3171,10 @@ entities@^2.0.0: resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz" - integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== env-paths@^2.2.0: version "2.2.1" @@ -3240,33 +3210,6 @@ es-abstract@^1.17.2, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es- string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" @@ -3301,11 +3244,6 @@ escape-html@~1.0.3: resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-latex@1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/escape-latex/-/escape-latex-1.0.3.tgz" - integrity sha512-GfKaG/7FOKdIdciylIzgaShBTPjdGQ5LJ2EcKLKXPLpcMO1MvCEVotkhydEShwCINRacZr2r3fk5A1PwZ4e5sA== - escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" @@ -3386,15 +3324,7 @@ extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" -extend-shallow@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend-shallow@^3.0.2: +extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz" integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= @@ -3432,43 +3362,43 @@ extract-zip@^2.0.1: optionalDependencies: "@types/yauzl" "^2.9.1" -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1: - version "3.2.5" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== +fast-glob@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz" + integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.0" merge2 "^1.3.0" micromatch "^4.0.2" - picomatch "^2.2.1" -fast-glob@3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz" - integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== +fast-glob@^3.1.1: + version "3.2.5" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.0" merge2 "^1.3.0" micromatch "^4.0.2" + picomatch "^2.2.1" fast-json-stable-stringify@^2.0.0: version "2.1.0" @@ -3596,14 +3526,12 @@ forever-agent@~0.6.1: integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= form-data@^4.0.0: - version "4.0.3" - resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz" - integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" mime-types "^2.1.12" form-data@~2.3.2: @@ -3620,11 +3548,6 @@ format@^0.2.0: resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz" integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= -fraction.js@4.0.8: - version "4.0.8" - resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.8.tgz" - integrity sha512-8Jx2AkFIFQtFaF8wP7yUIW+lnCgzPbxsholryMZ+oPK6kKjY/nUrvMKtq1+A8aSAeFau7+G/zfO8aGk2Aw1wCA== - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz" @@ -3650,17 +3573,7 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0: - version "9.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-extra@^9.0.1: +fs-extra@^9.0.0, fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -3675,15 +3588,15 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -function-bind@^1.1.1, function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -gaussian@^1.0.0: - version "1.3.0" - resolved "https://registry.npmjs.org/gaussian/-/gaussian-1.3.0.tgz" - integrity sha512-rYQ0ESfB+z0t7G95nHH80Zh7Pgg9A0FUYoZqV0yPec5WJZWKIHV2MPYpiJNy8oZAeVqyKwC10WXKSCnUQ5iDVg== +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== generic-names@^2.0.1: version "2.0.1" @@ -3702,35 +3615,20 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.2.6: - version "1.3.0" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" get-port@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz" integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - get-stream@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" @@ -3826,11 +3724,6 @@ globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - got@^11.8.5: version "11.8.6" resolved "https://registry.npmjs.org/got/-/got-11.8.6.tgz" @@ -3915,17 +3808,10 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: +has-symbols@^1.0.1, has-symbols@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has-value@^0.3.1: version "0.3.1" @@ -3992,13 +3878,6 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz" @@ -4049,7 +3928,7 @@ html-encoding-sniffer@^1.0.2: html-encoding-sniffer@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== dependencies: whatwg-encoding "^2.0.0" @@ -4101,7 +3980,7 @@ http-cache-semantics@^4.0.0: http-proxy-agent@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== dependencies: "@tootallnate/once" "2" @@ -4152,18 +4031,19 @@ https-browserify@^1.0.0: https-proxy-agent@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" -iconv-lite@^0.6.2, iconv-lite@0.6, iconv-lite@0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== +iconv-corefoundation@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz#31065e6ab2c9272154c8b0821151e2c88f1b002a" + integrity sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ== dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" + cli-truncate "^2.1.0" + node-addon-api "^1.6.3" iconv-lite@0.4.24: version "0.4.24" @@ -4172,7 +4052,14 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -icss-replace-symbols@^1.1.0, icss-replace-symbols@1.1.0: +iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +icss-replace-symbols@1.1.0, icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz" integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= @@ -4235,24 +4122,24 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - ini@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + "internmap@1 - 2": version "2.0.3" - resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== is-absolute-url@^2.0.0: @@ -4523,7 +4410,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: is-potential-custom-element-name@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== is-regex@^1.1.3: @@ -4594,7 +4481,7 @@ is-yarn-global@^0.3.0: resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== -isarray@~1.0.0, isarray@1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -4636,11 +4523,6 @@ jake@^10.6.1: filelist "^1.0.1" minimatch "^3.0.4" -javascript-natural-sort@0.7.1: - version "0.7.1" - resolved "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz" - integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== - joi@^17.3.0: version "17.4.0" resolved "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz" @@ -4653,9 +4535,9 @@ joi@^17.3.0: "@sideway/pinpoint" "^2.0.0" js-interpreter@^5.1.1: - version "5.2.1" - resolved "https://registry.npmjs.org/js-interpreter/-/js-interpreter-5.2.1.tgz" - integrity sha512-offKOHFrtvQckRY0g4Bp6l4QbRUap/pFQ6HgwMgOaqqGmG7hNh21FZpHUJa99XWRyoWUlj7J/P70jHznRB4kUg== + version "5.1.1" + resolved "https://registry.yarnpkg.com/js-interpreter/-/js-interpreter-5.1.1.tgz#dbe0c01c4b986c1b6c68ed90e39b9b9fb62b285b" + integrity sha512-LsN3y0ja1UUUOmxxaK2ikEVLqdR1hZjixNg5UDWtFao2BxjM6AWVah45SpRtntPiQdhWmpfl/rHKA32JqZ7RjA== dependencies: minimist "^1.2.8" @@ -4684,6 +4566,35 @@ jsbn@~0.1.0: resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +jsdom@22.1.0: + version "22.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8" + integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== + dependencies: + abab "^2.0.6" + cssstyle "^3.0.0" + data-urls "^4.0.0" + decimal.js "^10.4.3" + domexception "^4.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.4" + parse5 "^7.1.2" + rrweb-cssom "^0.6.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^12.0.1" + ws "^8.13.0" + xml-name-validator "^4.0.0" + jsdom@^14.1.0: version "14.1.0" resolved "https://registry.npmjs.org/jsdom/-/jsdom-14.1.0.tgz" @@ -4716,35 +4627,6 @@ jsdom@^14.1.0: ws "^6.1.2" xml-name-validator "^3.0.0" -jsdom@22.1.0: - version "22.1.0" - resolved "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz" - integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== - dependencies: - abab "^2.0.6" - cssstyle "^3.0.0" - data-urls "^4.0.0" - decimal.js "^10.4.3" - domexception "^4.0.0" - form-data "^4.0.0" - html-encoding-sniffer "^3.0.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.1" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.4" - parse5 "^7.1.2" - rrweb-cssom "^0.6.0" - saxes "^6.0.0" - symbol-tree "^3.2.4" - tough-cookie "^4.1.2" - w3c-xmlserializer "^4.0.0" - webidl-conversions "^7.0.0" - whatwg-encoding "^2.0.0" - whatwg-mimetype "^3.0.0" - whatwg-url "^12.0.1" - ws "^8.13.0" - xml-name-validator "^4.0.0" - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -4792,21 +4674,7 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.0: - version "2.2.0" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" - -json5@^2.1.2: - version "2.2.0" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" - -json5@^2.2.0: +json5@^2.1.0, json5@^2.1.2, json5@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -4882,12 +4750,7 @@ kind-of@^5.0.0: resolved "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== -kind-of@^6.0.0: - version "6.0.3" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kind-of@^6.0.2: +kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -5034,25 +4897,6 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -mathjs@^4.0.0: - version "4.4.2" - resolved "https://registry.npmjs.org/mathjs/-/mathjs-4.4.2.tgz" - integrity sha512-T5zGIbDT/JGmzIu2Bocq4U8gbcmQVCyZaJbBCHKmJkLMQoWuh1SOuFH98doj1JEQwjpKkq3rqdUCuy3vLlBZOA== - dependencies: - complex.js "2.0.10" - decimal.js "9.0.1" - escape-latex "1.0.3" - fraction.js "4.0.8" - javascript-natural-sort "0.7.1" - seed-random "2.2.0" - tiny-emitter "2.0.2" - typed-function "1.0.3" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz" @@ -5119,22 +4963,15 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz" - integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== - mime-db@1.48.0: version "1.48.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz" integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.31" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz" - integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== - dependencies: - mime-db "1.48.0" +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== mime-types@2.1.18: version "2.1.18" @@ -5143,6 +4980,13 @@ mime-types@2.1.18: dependencies: mime-db "~1.33.0" +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.31" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz" + integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== + dependencies: + mime-db "1.48.0" + mime@^2.5.2: version "2.5.2" resolved "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz" @@ -5173,16 +5017,21 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.4, minimatch@3.0.4: +minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.8: +minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minimist@^1.2.8: version "1.2.8" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== mixin-deep@^1.2.0: @@ -5242,6 +5091,11 @@ nice-try@^1.0.4: resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-addon-api@^1.6.3: + version "1.7.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" + integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== + node-addon-api@^3.0.2: version "3.2.1" resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz" @@ -5304,10 +5158,15 @@ nullthrows@^1.1.1: resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== -nwsapi@^2.1.3, nwsapi@^2.2.4: - version "2.2.20" - resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz" - integrity sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA== +nwsapi@^2.1.3: + version "2.2.0" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +nwsapi@^2.2.4: + version "2.2.8" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.8.tgz#a3552e65b74bf8cc89d0480c4132b61dbe54eccf" + integrity sha512-GU/I3lTEFQ9mkEm07Q7HvdRajss8E1wVMGOk3/lHl60QPseG+B3BIQY+JUjYWw7gF8cCeoQCXd4N7DB7avw0Rg== oauth-sign@~0.9.0: version "0.9.0" @@ -5541,18 +5400,18 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse5@^7.1.2: - version "7.3.0" - resolved "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz" - integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== - dependencies: - entities "^6.0.0" - parse5@5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz" integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== +parse5@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" @@ -5644,6 +5503,15 @@ pify@^3.0.0: resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +plist@^3.0.4: + version "3.1.0" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" + integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== + dependencies: + "@xmldom/xmldom" "^0.8.8" + base64-js "^1.5.1" + xmlbuilder "^15.1.1" + pn@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz" @@ -5772,6 +5640,13 @@ postcss-minify-selectors@^4.0.2: postcss "^7.0.0" postcss-selector-parser "^3.0.0" +postcss-modules-extract-imports@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz" + integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= + dependencies: + postcss "^6.0.1" + postcss-modules-extract-imports@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz" @@ -5784,11 +5659,12 @@ postcss-modules-extract-imports@^3.0.0: resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== -postcss-modules-extract-imports@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz" - integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= +postcss-modules-local-by-default@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= dependencies: + css-selector-tokenizer "^0.7.0" postcss "^6.0.1" postcss-modules-local-by-default@^3.0.2: @@ -5810,10 +5686,10 @@ postcss-modules-local-by-default@^4.0.0: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-local-by-default@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz" - integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= +postcss-modules-scope@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= dependencies: css-selector-tokenizer "^0.7.0" postcss "^6.0.1" @@ -5833,12 +5709,12 @@ postcss-modules-scope@^3.0.0: dependencies: postcss-selector-parser "^6.0.4" -postcss-modules-scope@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz" - integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= +postcss-modules-values@1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz" + integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= dependencies: - css-selector-tokenizer "^0.7.0" + icss-replace-symbols "^1.1.0" postcss "^6.0.1" postcss-modules-values@^3.0.0: @@ -5856,14 +5732,6 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-modules-values@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz" - integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= - dependencies: - icss-replace-symbols "^1.1.0" - postcss "^6.0.1" - postcss-modules@^3.2.2: version "3.2.2" resolved "https://registry.npmjs.org/postcss-modules/-/postcss-modules-3.2.2.tgz" @@ -6003,6 +5871,15 @@ postcss-reduce-transforms@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-selector-parser@6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + postcss-selector-parser@^3.0.0: version "3.1.2" resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz" @@ -6020,15 +5897,6 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-selector-parser@6.0.2: - version "6.0.2" - resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - postcss-svgo@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz" @@ -6052,15 +5920,28 @@ postcss-value-parser@^3.0.0: resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.2: +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== +postcss@6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz" + integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@7.0.32: + version "7.0.32" + resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz" + integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" postcss@^6.0.1: version "6.0.23" @@ -6080,7 +5961,7 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.0.0, postcss@^8.1.0, postcss@^8.1.10: +postcss@^8.0.5, postcss@^8.1.10, postcss@^8.2.1: version "8.3.5" resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz" integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== @@ -6089,42 +5970,6 @@ postcss@^8.0.0, postcss@^8.1.0, postcss@^8.1.10: nanoid "^3.1.23" source-map-js "^0.6.2" -postcss@^8.0.5: - version "8.3.5" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz" - integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.23" - source-map-js "^0.6.2" - -postcss@^8.2.1: - version "8.3.5" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz" - integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.23" - source-map-js "^0.6.2" - -postcss@6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz" - integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= - dependencies: - chalk "^1.1.3" - source-map "^0.5.6" - supports-color "^3.2.3" - -postcss@7.0.32: - version "7.0.32" - resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz" - integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - posthtml-parser@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.6.0.tgz" @@ -6182,11 +6027,16 @@ progress@^2.0.3: resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.28: version "1.8.0" resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz" @@ -6207,26 +6057,26 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.4.1: +punycode@^1.3.2, punycode@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +punycode@^2.3.0: version "2.3.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - pupa@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz" @@ -6259,19 +6109,19 @@ querystring-es3@^0.2.1: resolved "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= -querystring@^0.2.0: - version "0.2.1" - resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz" - integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== - querystring@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + querystringify@^2.1.1: version "2.2.0" - resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== queue-microtask@^1.2.2: @@ -6339,15 +6189,6 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" -readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - "readable-stream@1 || 2": version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" @@ -6361,6 +6202,15 @@ readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.0.0, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" @@ -6421,7 +6271,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.34, request@^2.88.0: +request@^2.88.0: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -6556,12 +6406,12 @@ roarr@^2.15.3: robust-predicates@^3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== rrweb-cssom@^0.6.0: version "0.6.0" - resolved "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== run-parallel@^1.1.9: @@ -6573,17 +6423,10 @@ run-parallel@^1.1.9: rw@1: version "1.3.3" - resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== -rxjs@^6.5.2: - version "6.6.7" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -rxjs@^6.6.3: +rxjs@^6.5.2, rxjs@^6.6.3: version "6.6.7" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -6614,7 +6457,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -safer-buffer@^2.0.2, safer-buffer@^2.1.0, "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -6647,16 +6490,11 @@ saxes@^3.1.9: saxes@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== dependencies: xmlchars "^2.2.0" -seed-random@2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz" - integrity sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ== - semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" @@ -6669,41 +6507,17 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver@^5.4.1, semver@^5.5.0, semver@^5.7.0, "semver@2 || 3 || 4 || 5": +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^6.2.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^6.3.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2: - version "7.3.5" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - -semver@^7.3.4: - version "7.3.5" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - -semver@^7.3.5: +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -6800,6 +6614,15 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" @@ -6809,6 +6632,11 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +smart-buffer@^4.0.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz" @@ -6868,12 +6696,7 @@ source-map-url@^0.4.0: resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@^0.5.6: +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -7006,25 +6829,6 @@ stream-http@^3.1.0: readable-stream "^3.6.0" xtend "^4.0.2" -streamsearch@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz" - integrity sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA== - -string_decoder@^1.1.1, string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - string-hash@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz" @@ -7064,6 +6868,20 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" +string_decoder@^1.1.1, string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" @@ -7202,11 +7020,6 @@ timsort@^0.3.0: resolved "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-emitter@2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz" - integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== - tmp-promise@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.2.tgz" @@ -7272,9 +7085,9 @@ tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: punycode "^2.1.1" tough-cookie@^4.1.2: - version "4.1.4" - resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz" - integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== dependencies: psl "^1.1.33" punycode "^2.1.1" @@ -7290,7 +7103,7 @@ tr46@^1.0.1: tr46@^4.1.1: version "4.1.1" - resolved "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== dependencies: punycode "^2.3.0" @@ -7351,11 +7164,6 @@ type-fest@^0.20.2: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typed-function@1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/typed-function/-/typed-function-1.0.3.tgz" - integrity sha512-sVC/1pm70oELDFMdYtFXMFqyawenLoaDiAXA3QvOAwKF/WvFNTSJN23cY2lFNL8iP0kh3T0PPKewrboO8XUVGQ== - typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" @@ -7432,7 +7240,7 @@ universalify@^0.1.0: universalify@^0.2.0: version "0.2.0" - resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== universalify@^2.0.0: @@ -7499,7 +7307,7 @@ url-parse-lax@^3.0.0: url-parse@^1.5.3: version "1.5.10" - resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" @@ -7587,12 +7395,21 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +verror@^1.10.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb" + integrity sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vm-browserify@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vue@^3.0.0, vue@3.1.2: +vue@^3.0.0: version "3.1.2" resolved "https://registry.npmjs.org/vue/-/vue-3.1.2.tgz" integrity sha512-q/rbKpb7aofax4ugqu2k/uj7BYuNPcd6Z5/qJtfkJQsE0NkwVoCyeSh7IZGH61hChwYn3CEkh4bHolvUPxlQ+w== @@ -7619,7 +7436,7 @@ w3c-xmlserializer@^1.1.2: w3c-xmlserializer@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== dependencies: xml-name-validator "^4.0.0" @@ -7649,7 +7466,7 @@ webidl-conversions@^4.0.2: webidl-conversions@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: @@ -7661,7 +7478,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: whatwg-encoding@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== dependencies: iconv-lite "0.6.3" @@ -7673,12 +7490,12 @@ whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: whatwg-mimetype@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== whatwg-url@^12.0.0, whatwg-url@^12.0.1: version "12.0.1" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c" integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ== dependencies: tr46 "^4.1.1" @@ -7793,10 +7610,10 @@ ws@^7.0.0: resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.13.0, ws@^8.18.2: - version "8.18.2" - resolved "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz" - integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== +ws@^8.13.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== xdg-basedir@^4.0.0: version "4.0.0" @@ -7810,9 +7627,14 @@ xml-name-validator@^3.0.0: xml-name-validator@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== +xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== + xmlchars@^2.1.1, xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" From f0e37c6785210d46db1cca6d1bb1cb14a4009a26 Mon Sep 17 00:00:00 2001 From: VinceIngram07 Date: Sun, 21 Sep 2025 16:50:32 -0500 Subject: [PATCH 25/25] Fix production build path and add Python server integration for executable deployment Changes: * src/main/index.js: - Fixed production build path from loadURL to loadFile with correct relative path - Added Python process spawning with venv detection - Implemented WebSocket connection validation with 10s timeout - Added proper process cleanup on app quit * resources/python/VEXServer_dev.py (new): - Created simulation mode WebSocket server for development - Mock VEX commands (led_on, move, turn_left, turn_right) with logging - Enables testing without physical VEX robot hardware - Runs on ws://127.0.0.1:8777 with JSON command protocol * package.json: - Updated electron-builder config to include build/renderer directory - Added extraResources for Python files bundling - Enhanced build scripts for Python executable creation - Added PyInstaller to requirements for standalone deployment * VEXServer.spec (new): - PyInstaller specification for Python server executable - Proper dependency handling and console output * requirements.txt: - Added PyInstaller dependency for build process Enables successful Windows executable creation with integrated robot control server. Two-process architecture provides reliable VEX robot communication in both development and production environments. --- BUILD_GUIDE.md | 145 ++++++++++++++++++++++++++++++ VEXServer.spec | 55 ++++++++++++ package.json | 23 ++++- requirements.txt | 3 +- resources/python/VEXServer.py | 145 ++++++++++++++++++++++++------ resources/python/VEXServer_dev.py | 79 ++++++++++++++++ src/main/index.js | 84 ++++++++++++++--- test_websocket.py | 29 ++++++ yarn.lock | 5 ++ 9 files changed, 522 insertions(+), 46 deletions(-) create mode 100644 BUILD_GUIDE.md create mode 100644 VEXServer.spec create mode 100644 resources/python/VEXServer_dev.py create mode 100644 test_websocket.py diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md new file mode 100644 index 0000000..72eba47 --- /dev/null +++ b/BUILD_GUIDE.md @@ -0,0 +1,145 @@ +# NeuroBlock Build Guide + +This guide explains how to build NeuroBlock as a Windows executable with an integrated Python server. + +## Build System Overview + +NeuroBlock now supports building as a standalone Windows executable that includes: +1. **Electron App**: The main GUI application +2. **Python Server**: A WebSocket server that controls the VEX robot + +## Prerequisites + +1. **Node.js and Yarn**: For building the Electron application +2. **Python 3.10+**: For building the Python server executable +3. **Git**: For version control + +## Quick Start + +### 1. Install Dependencies + +```bash +# Install Node.js dependencies +yarn install + +# Configure Python environment (creates virtual environment) +# This will be done automatically when you run the build scripts +``` + +### 2. Build Everything + +```bash +# Build both Python executable and Electron app +yarn build:all +``` + +This command will: +1. Create a Python virtual environment +2. Install Python dependencies +3. Build a standalone Python executable (`VEXServer.exe`) +4. Build the Vue.js renderer +5. Package everything into a Windows installer + +## Individual Build Commands + +### Python Server Only +```bash +yarn build:python +``` +Creates: `dist/python/VEXServer.exe` + +### Vue.js Renderer Only +```bash +yarn build +``` +Creates: `build/renderer/` directory with compiled assets + +### Windows Executable Only +```bash +yarn build:win +``` +Creates: `dist/NeuroBlock Setup 1.3.0.exe` + +## Development Mode + +```bash +yarn serve +``` + +This starts: +- Development server for the renderer on `http://localhost:3000` +- Python WebSocket server on `ws://127.0.0.1:8777` +- Electron main process with hot reload + +## Build Output + +After running `yarn build:all`, you'll find: + +- **`dist/NeuroBlock Setup 1.3.0.exe`**: Windows installer +- **`dist/python/VEXServer.exe`**: Standalone Python server +- **`build/renderer/`**: Compiled web assets + +## Production Path Fix + +The main issue that was resolved: +- **Before**: Main process tried to load from `../renderer/index.html` +- **After**: Main process loads from `../../build/renderer/index.html` +- **Method**: Changed from `win.loadURL()` to `win.loadFile()` for better path resolution + +## Python Server Integration + +The Python server is automatically started by the Electron main process: + +- **Development**: Uses `python` command with source files +- **Production**: Uses bundled `VEXServer.exe` executable +- **Communication**: WebSocket on port 8777 +- **Fallback**: If bundled executable not found, falls back to system Python + +## Architecture + +``` +NeuroBlock.exe (Electron Main Process) +├── Renderer Process (Vue.js UI) +└── Python Server (VEXServer.exe) + └── WebSocket Server (port 8777) + └── VEX Robot Control +``` + +## Troubleshooting + +### White Screen Issue +If you see a white screen, the renderer path is incorrect. This has been fixed by updating the main process to use the correct build directory. + +### Python Server Not Starting +1. Check if Python is installed +2. Ensure all Python dependencies are installed +3. Check console for Python error messages +4. Verify port 8777 is not in use + +### Build Failures +1. Run `yarn install` to ensure all dependencies are installed +2. Check that Python virtual environment is properly configured +3. Ensure sufficient disk space for build process + +## File Structure + +``` +neuroscope/ +├── src/main/index.js # Electron main process (updated) +├── package.json # Build configuration (updated) +├── VEXServer.spec # PyInstaller specification (new) +├── requirements.txt # Python dependencies (updated) +├── dist/ # Build output +│ ├── NeuroBlock Setup 1.3.0.exe +│ └── python/VEXServer.exe +└── build/renderer/ # Compiled web assets +``` + +## Key Changes Made + +1. **Fixed production build path** in `src/main/index.js` +2. **Added Python executable support** with fallback to source files +3. **Updated electron-builder configuration** to include build directory +4. **Created PyInstaller spec file** for better Python build control +5. **Added comprehensive build scripts** for different scenarios +6. **Improved error handling** and process cleanup \ No newline at end of file diff --git a/VEXServer.spec b/VEXServer.spec new file mode 100644 index 0000000..2398505 --- /dev/null +++ b/VEXServer.spec @@ -0,0 +1,55 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + +a = Analysis( + ['resources/python/VEXServer.py'], + pathex=['resources/python'], + binaries=[], + datas=[ + ('resources/python/vex', 'vex'), + ], + hiddenimports=[ + 'websockets', + 'asyncio', + 'json', + 'vex', + 'vex.vex_globals', + 'vex.vex_types', + 'vex.vex_messages', + 'vex.aim', + 'vex.settings' + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) + +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='VEXServer', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) \ No newline at end of file diff --git a/package.json b/package.json index b51d930..844f3d6 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,10 @@ "vue:build": "cross-env NODE_ENV=production parcel build ./src/renderer/index.html --dist-dir build/renderer --public-url ./ --no-source-maps --no-scope-hoist", "electron:build": "electron-builder build -c.mac.target=dir", "build:local": "yarn build && yarn electron:build", + "build:win": "yarn build && electron-builder --win", + "build:python": "C:/dev/repos/VexEEG/neuroscope/.venv/Scripts/python.exe -m PyInstaller VEXServer.spec --distpath dist/python", + "build:all": "yarn build:python && yarn build && yarn build:win", + "prebuild:python": "echo 'PyInstaller already installed in virtual environment'", "clean": "rmdir build && rmdir .cache && rmdir dist && rmdir .parcel-cache", "patch": "npm version patch -m \"v%s\"", "postversion": "git push && git push --tags", @@ -58,18 +62,31 @@ "productName": "NeuroBlock", "compression": "maximum", "files": [ + "build/main/**/*", + "build/renderer/**/*", "src/main/**/*", - "src/renderer/**/*", - "resources/python/**/*", "node_modules/**/*" ], + "extraResources": [ + { + "from": "resources/python", + "to": "python" + }, + { + "from": "dist/python/VEXServer.exe", + "to": "python/VEXServer.exe", + "filter": [ + "**/*" + ] + } + ], "mac": { "icon": "src/renderer/icons/icon.icns", "category": "public.app-category.productivity", "identity": null }, "win": { - "icon": "src/renderer/icons/icon.png" + "icon": "src/renderer/icons/icon.png" }, "dmg": { "contents": [ diff --git a/requirements.txt b/requirements.txt index 811d3db..e2daf16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ opencv-python pyaudio websockets>=10.0 -websocket-client>=1.5.1 \ No newline at end of file +websocket-client>=1.5.1 +pyinstaller>=5.0 \ No newline at end of file diff --git a/resources/python/VEXServer.py b/resources/python/VEXServer.py index af14953..4cff8b4 100644 --- a/resources/python/VEXServer.py +++ b/resources/python/VEXServer.py @@ -1,11 +1,50 @@ import asyncio import websockets import json -from vex import * -from vex.vex_globals import * -# Robot initialization for AIM platform -robot = Robot() +# Try to import VEX libraries, but handle gracefully if robot not connected +try: + from vex import * + from vex.vex_globals import * + + # Try to initialize robot, but catch connection errors + try: + robot = Robot() + VEX_AVAILABLE = True + print("VEX robot initialized successfully") + except Exception as e: + print(f"Warning: Could not connect to VEX robot: {e}") + print("Running in simulation mode - commands will be logged but not executed") + robot = None + VEX_AVAILABLE = False + + # Create mock constants for simulation + RED = "RED" + GREEN = "GREEN" + BLUE = "BLUE" + WHITE = "WHITE" + YELLOW = "YELLOW" + ORANGE = "ORANGE" + PURPLE = "PURPLE" + CYAN = "CYAN" + ALL_LEDS = "ALL_LEDS" + +except ImportError as e: + print(f"Warning: VEX libraries not available: {e}") + print("Running in simulation mode - commands will be logged but not executed") + robot = None + VEX_AVAILABLE = False + + # Create mock constants for simulation + RED = "RED" + GREEN = "GREEN" + BLUE = "BLUE" + WHITE = "WHITE" + YELLOW = "YELLOW" + ORANGE = "ORANGE" + PURPLE = "PURPLE" + CYAN = "CYAN" + ALL_LEDS = "ALL_LEDS" # color_list = [ # RED, GREEN, BLUE, WHITE, YELLOW, ORANGE, PURPLE, CYAN @@ -18,7 +57,6 @@ # robot.led.off(ALL_LEDS) # Command handler for VEX AIM -# Made 'path' optional so it works with the current websockets API async def handle_command(websocket, path=None): try: async for message in websocket: @@ -27,7 +65,6 @@ async def handle_command(websocket, path=None): if action == "led_on": color_name = command.get("color", "BLUE") - # Map string color names to vex.Color constants color_map = { "RED": RED, "GREEN": GREEN, @@ -38,41 +75,85 @@ async def handle_command(websocket, path=None): "PURPLE": PURPLE, "CYAN": CYAN, } - color = color_map.get(color_name.upper(), BLUE) # Default to BLUE if not found - print(f"Turning LED on with color: {color_name}") - robot.led.on(ALL_LEDS, color) - # Send a response back to the client - await websocket.send(json.dumps({"status": "success", "action": "led_on", "color": color_name})) + color = color_map.get(color_name.upper(), BLUE) + print(f"LED Command: Turning on {color_name} LED") + + if VEX_AVAILABLE and robot: + try: + robot.led.on(ALL_LEDS, color) + status = "success" + except Exception as e: + print(f"Error executing LED command: {e}") + status = "error" + else: + print("(Simulation mode - no physical robot)") + status = "success_simulation" + + await websocket.send(json.dumps({"status": status, "action": "led_on", "color": color_name})) elif action == "move": - distance_inches = command.get("distance", 4) # Default to 4 inches instead of 100mm + distance_inches = command.get("distance", 4) heading = command.get("heading", 0) - # Convert inches to millimeters (1 inch = 25.4 mm) distance_mm = distance_inches * 25.4 - print(f"Received move command: {command}") - print(f"Moving robot: Distance={distance_inches} inches ({distance_mm} mm), Heading={heading}") - robot.move_for(distance_mm, heading) - # Send a response back to the client - await websocket.send(json.dumps({"status": "success", "action": "move", "distance_inches": distance_inches, "distance_mm": distance_mm, "heading": heading})) + print(f"Move Command: Distance={distance_inches} inches ({distance_mm} mm), Heading={heading}°") + + if VEX_AVAILABLE and robot: + try: + robot.move_for(distance_mm, heading) + status = "success" + except Exception as e: + print(f"Error executing move command: {e}") + status = "error" + else: + print("(Simulation mode - no physical robot)") + status = "success_simulation" + + await websocket.send(json.dumps({ + "status": status, + "action": "move", + "distance_inches": distance_inches, + "distance_mm": distance_mm, + "heading": heading + })) elif action == "turn_left": degrees = command.get("degrees", 90) - print(f"Turning robot left: {degrees} degrees") - robot.turn_for(vex.TurnType.LEFT, degrees) # Correct VEX method - # Send a response back to the client - await websocket.send(json.dumps({"status": "success", "action": "turn_left", "degrees": degrees})) + print(f"Turn Command: Left {degrees}°") + + if VEX_AVAILABLE and robot: + try: + robot.turn_for(vex.TurnType.LEFT, degrees) + status = "success" + except Exception as e: + print(f"Error executing turn left command: {e}") + status = "error" + else: + print("(Simulation mode - no physical robot)") + status = "success_simulation" + + await websocket.send(json.dumps({"status": status, "action": "turn_left", "degrees": degrees})) elif action == "turn_right": degrees = command.get("degrees", 90) - print(f"Turning robot right: {degrees} degrees") - robot.turn_for(vex.TurnType.RIGHT, degrees) # Correct VEX method - # Send a response back to the client - await websocket.send(json.dumps({"status": "success", "action": "turn_right", "degrees": degrees})) + print(f"Turn Command: Right {degrees}°") + + if VEX_AVAILABLE and robot: + try: + robot.turn_for(vex.TurnType.RIGHT, degrees) + status = "success" + except Exception as e: + print(f"Error executing turn right command: {e}") + status = "error" + else: + print("(Simulation mode - no physical robot)") + status = "success_simulation" + + await websocket.send(json.dumps({"status": status, "action": "turn_right", "degrees": degrees})) else: print(f"Unknown command: {command}") - # Send an error response back to the client await websocket.send(json.dumps({"status": "error", "message": "Unknown command"})) + except Exception as e: print(f"Error handling command: {e}") await websocket.send(json.dumps({"status": "error", "message": str(e)})) @@ -80,8 +161,16 @@ async def handle_command(websocket, path=None): async def main(): port = 8777 print(f"Starting WebSocket server on ws://127.0.0.1:{port}") + print(f"VEX robot available: {VEX_AVAILABLE}") + async with websockets.serve(handle_command, "127.0.0.1", port): + print("WebSocket server is ready and listening...") await asyncio.Future() # run forever if __name__ == "__main__": - asyncio.run(main()) + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\nServer shutting down...") + except Exception as e: + print(f"Server error: {e}") diff --git a/resources/python/VEXServer_dev.py b/resources/python/VEXServer_dev.py new file mode 100644 index 0000000..27abd84 --- /dev/null +++ b/resources/python/VEXServer_dev.py @@ -0,0 +1,79 @@ +import asyncio +import websockets +import json + +print("Starting VEX WebSocket Server (Development Mode)") +print("VEX libraries not loaded - running in simulation mode") + +# Mock VEX constants for simulation +VEX_AVAILABLE = False +RED = "RED" +GREEN = "GREEN" +BLUE = "BLUE" +WHITE = "WHITE" +YELLOW = "YELLOW" +ORANGE = "ORANGE" +PURPLE = "PURPLE" +CYAN = "CYAN" +ALL_LEDS = "ALL_LEDS" + +# Command handler for VEX AIM +async def handle_command(websocket, path=None): + try: + async for message in websocket: + command = json.loads(message) + action = command.get("action", "") + + if action == "led_on": + color_name = command.get("color", "BLUE") + print(f"LED Command: Turning on {color_name} LED (Simulation)") + await websocket.send(json.dumps({"status": "success_simulation", "action": "led_on", "color": color_name})) + + elif action == "move": + distance_inches = command.get("distance", 4) + heading = command.get("heading", 0) + distance_mm = distance_inches * 25.4 + print(f"Move Command: Distance={distance_inches} inches ({distance_mm} mm), Heading={heading}° (Simulation)") + + await websocket.send(json.dumps({ + "status": "success_simulation", + "action": "move", + "distance_inches": distance_inches, + "distance_mm": distance_mm, + "heading": heading + })) + + elif action == "turn_left": + degrees = command.get("degrees", 90) + print(f"Turn Command: Left {degrees}° (Simulation)") + await websocket.send(json.dumps({"status": "success_simulation", "action": "turn_left", "degrees": degrees})) + + elif action == "turn_right": + degrees = command.get("degrees", 90) + print(f"Turn Command: Right {degrees}° (Simulation)") + await websocket.send(json.dumps({"status": "success_simulation", "action": "turn_right", "degrees": degrees})) + + else: + print(f"Unknown command: {command}") + await websocket.send(json.dumps({"status": "error", "message": "Unknown command"})) + + except Exception as e: + print(f"Error handling command: {e}") + await websocket.send(json.dumps({"status": "error", "message": str(e)})) + +async def main(): + port = 8777 + print(f"Starting WebSocket server on ws://127.0.0.1:{port}") + print(f"VEX robot available: {VEX_AVAILABLE}") + + async with websockets.serve(handle_command, "127.0.0.1", port): + print("WebSocket server is ready and listening...") + await asyncio.Future() # run forever + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\nServer shutting down...") + except Exception as e: + print(f"Server error: {e}") \ No newline at end of file diff --git a/src/main/index.js b/src/main/index.js index bade9e9..789a195 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -25,25 +25,68 @@ let pythonProcess; async function createWindow() { // ---- Start Python VEXServer ---- - const pythonExe = 'python'; // or full path to python if not in PATH - const script = path.join(__dirname, '..', '..', 'resources', 'python', 'VEXServer.py'); - pythonProcess = spawn(pythonExe, [script]); + let pythonExe, script; - pythonProcess.stdout.on('data', (data) => { - console.log(`PYTHON: ${data}`); - }); + if (isProduction) { + // Check if we have a bundled Python executable + const bundledPythonPath = path.join(process.resourcesPath, 'python', 'VEXServer.exe'); + const fs = require('fs'); - pythonProcess.stderr.on('data', (data) => { - console.error(`PYTHON ERROR: ${data}`); - }); + if (fs.existsSync(bundledPythonPath)) { + // Use bundled Python executable + pythonExe = bundledPythonPath; + script = null; + } else { + // Fallback to system Python with bundled script + pythonExe = 'python'; + script = path.join(process.resourcesPath, 'python', 'VEXServer.py'); + } + } else { + // Development mode - use virtual environment Python + const venvPython = path.join(__dirname, '..', '..', '.venv', 'Scripts', 'python.exe'); + const fs = require('fs'); - pythonProcess.on('close', (code) => { - console.log(`Python process exited with code ${code}`); - }); + if (fs.existsSync(venvPython)) { + pythonExe = venvPython; + console.log('Using virtual environment Python:', venvPython); + } else { + pythonExe = 'python'; + console.log('Virtual environment not found, using system Python'); + } + // Use development version that doesn't require physical robot + script = path.join(__dirname, '..', '..', 'resources', 'python', 'VEXServer_dev.py'); + } + + try { + pythonProcess = script ? spawn(pythonExe, [script]) : spawn(pythonExe); + + pythonProcess.stdout.on('data', (data) => { + console.log(`PYTHON: ${data}`); + }); + + pythonProcess.stderr.on('data', (data) => { + console.error(`PYTHON ERROR: ${data}`); + }); + + pythonProcess.on('close', (code) => { + console.log(`Python process exited with code ${code}`); + }); + + pythonProcess.on('error', (error) => { + console.error(`Failed to start Python process: ${error.message}`); + }); + } catch (error) { + console.error(`Error starting Python server: ${error.message}`); + } // ---- End Python VEXServer ---- // Wait for the Python WebSocket server to be ready (up to 10 seconds) - await waitOn({ resources: ['tcp:127.0.0.1:8777'], timeout: 10000 }); + try { + await waitOn({ resources: ['tcp:127.0.0.1:8777'], timeout: 10000 }); + console.log('Python WebSocket server is ready'); + } catch (error) { + console.error('Failed to connect to Python WebSocket server:', error.message); + } // If you'd like to set up auto-updating for your app, // I'd recommend looking at https://github.com/iffy/electron-updater-example @@ -78,7 +121,8 @@ async function createWindow() { if (isDevelopment) { win.loadURL(selfHost); } else { - win.loadURL(`file://${path.join(__dirname, "../../build/renderer/index.html")}`); + // Fix the path for production builds + win.loadFile(path.join(__dirname, "../../build/renderer/index.html")); } // Only do these things when in development @@ -312,6 +356,11 @@ app.on("ready", createWindow); // Quit when all windows are closed. app.on("window-all-closed", () => { + // Clean up Python process + if (pythonProcess && !pythonProcess.killed) { + pythonProcess.kill(); + } + // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== "darwin") { @@ -319,6 +368,13 @@ app.on("window-all-closed", () => { } }); +app.on("before-quit", () => { + // Clean up Python process before quitting + if (pythonProcess && !pythonProcess.killed) { + pythonProcess.kill(); + } +}); + app.on("activate", () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. diff --git a/test_websocket.py b/test_websocket.py new file mode 100644 index 0000000..c0049a4 --- /dev/null +++ b/test_websocket.py @@ -0,0 +1,29 @@ +import asyncio +import websockets +import json + +async def test_websocket(): + uri = "ws://localhost:8777" + try: + print(f"Connecting to {uri}...") + async with websockets.connect(uri) as websocket: + print("✅ Connected to VEX WebSocket server!") + + # Test a simple command + test_command = { + "action": "move", + "distance": 5, + "heading": 0 + } + + print(f"Sending test command: {test_command}") + await websocket.send(json.dumps(test_command)) + + response = await websocket.recv() + print(f"✅ Server response: {response}") + + except Exception as e: + print(f"❌ Connection failed: {e}") + +if __name__ == "__main__": + asyncio.run(test_websocket()) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 462dd2d..e261fd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7615,6 +7615,11 @@ ws@^8.13.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== +ws@^8.18.2: + version "8.18.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz"

    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