diff --git a/.github/workflows/gradle.yaml b/.github/workflows/gradle.yaml index 3e90c7d18e..9d1b711612 100644 --- a/.github/workflows/gradle.yaml +++ b/.github/workflows/gradle.yaml @@ -285,7 +285,7 @@ jobs: ./bundle_dmg.sh --volname SlimeVR --icon slimevr 180 170 --app-drop-link 480 170 \ --window-size 660 400 --hide-extension ../macos/SlimeVR.app \ --volicon ../macos/SlimeVR.app/Contents/Resources/icon.icns --skip-jenkins \ - --eula ../../../../LICENSE-MIT slimevr.dmg ../macos/SlimeVR.app + --eula ../../../../../LICENSE-MIT slimevr.dmg ../macos/SlimeVR.app - uses: actions/upload-artifact@v4 with: diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..32f5979c17 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +update-notifier=false diff --git a/Cargo.lock b/Cargo.lock index 23fc1864de..a3823723de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2018,6 +2018,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -3188,7 +3197,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3827,6 +3836,7 @@ dependencies = [ "discord-sdk", "flexi_logger", "glob", + "itertools", "libloading 0.8.5", "log", "log-panics", diff --git a/dev.slimevr.SlimeVR.metainfo.xml b/dev.slimevr.SlimeVR.metainfo.xml index 141ea2040c..688c848ad4 100644 --- a/dev.slimevr.SlimeVR.metainfo.xml +++ b/dev.slimevr.SlimeVR.metainfo.xml @@ -65,6 +65,7 @@ work. If not, see . + https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.2 https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.1 https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.1-rc.3 https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.1-rc.2 diff --git a/flake.nix b/flake.nix index 809182a6c7..2bdecbac9d 100644 --- a/flake.nix +++ b/flake.nix @@ -92,7 +92,7 @@ harfbuzz libffi libsoup_3 - openssl + openssl.dev pango pkg-config treefmt diff --git a/gui/.env b/gui/.env new file mode 100644 index 0000000000..7ce15648fa --- /dev/null +++ b/gui/.env @@ -0,0 +1,8 @@ +VITE_FIRMWARE_TOOL_URL=https://fw-tool-api.slimevr.io +VITE_FIRMWARE_TOOL_S3_URL=https://fw-tool-bucket.slimevr.io +FIRMWARE_TOOL_SCHEMA_URL=https://fw-tool-api.slimevr.io/api-json + + +# VITE_FIRMWARE_TOOL_URL=http://localhost:3000 +# VITE_FIRMWARE_TOOL_S3_URL=http://localhost:9000 +# FIRMWARE_TOOL_SCHEMA_URL=http://localhost:3000/api-json diff --git a/gui/.eslintrc.json b/gui/.eslintrc.json deleted file mode 100644 index dc120fbd29..0000000000 --- a/gui/.eslintrc.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true, - "jest": true - }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@dword-design/import-alias/recommended"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": ["react-hooks", "@typescript-eslint"], - "rules": { - "react/react-in-jsx-scope": "off", - "react/prop-types": "off", - "spaced-comment": "error", - "quotes": ["error", "single"], - "no-duplicate-imports": "error", - "no-inline-styles": "off", - "@typescript-eslint/no-explicit-any": "off", - "react/no-unescaped-entities": "off", - "camelcase": "error", - "@typescript-eslint/no-unused-vars": [ - "warn", - { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_" - } - ], - "@dword-design/import-alias/prefer-alias": [ - "error", - { - "alias": { - "@": "./src/" - } - } - ] - }, - "settings": { - "import/resolver": { - "typescript": {} - }, - "react": { - "version": "detect" - } - } -} diff --git a/gui/.gitignore b/gui/.gitignore index a1ac58f664..90069ef5a6 100644 --- a/gui/.gitignore +++ b/gui/.gitignore @@ -28,6 +28,7 @@ yarn-error.log* # vite /dist /stats.html +vite.config.ts.timestamp* # eslint .eslintcache diff --git a/gui/.lintstagedrc.mjs b/gui/.lintstagedrc.mjs index 3f4d417894..cf8c7a097c 100644 --- a/gui/.lintstagedrc.mjs +++ b/gui/.lintstagedrc.mjs @@ -1,5 +1,5 @@ export default { '**/*.{ts,tsx}': () => 'tsc -p tsconfig.json --noEmit', - '**/*.{js,jsx,ts,tsx}': 'eslint --max-warnings=0 --cache --fix', + 'src/**/*.{js,jsx,ts,tsx}': 'eslint --max-warnings=0 --no-warn-ignored --cache --fix', '**/*.{js,jsx,ts,tsx,css,md,json}': 'prettier --write', }; diff --git a/gui/eslint.config.js b/gui/eslint.config.js new file mode 100644 index 0000000000..c4d534218e --- /dev/null +++ b/gui/eslint.config.js @@ -0,0 +1,79 @@ +import { FlatCompat } from '@eslint/eslintrc'; +import eslint from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +const compat = new FlatCompat(); + +export const gui = [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + ...compat.extends('plugin:@dword-design/import-alias/recommended'), + ...compat.plugins('eslint-plugin-react-hooks'), + // Add import-alias rule inside compat because plugin doesn't like flat configs + ...compat.config({ + rules: { + '@dword-design/import-alias/prefer-alias': [ + 'error', + { + alias: { + '@': './src/', + }, + }, + ], + }, + }), + { + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parser: tseslint.parser, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.browser, + ...globals.jest, + }, + }, + files: ['src/**/*.{js,jsx,ts,tsx,json}'], + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + 'spaced-comment': 'error', + quotes: ['error', 'single'], + 'no-duplicate-imports': 'error', + 'no-inline-styles': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'react/no-unescaped-entities': 'off', + camelcase: 'error', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], + }, + settings: { + 'import/resolver': { + typescript: {}, + }, + react: { + version: 'detect', + }, + }, + }, + // Global ignore + { + ignores: ['**/firmware-tool-api/'], + }, +]; + +export default gui; diff --git a/gui/openapi-codegen.config.ts b/gui/openapi-codegen.config.ts new file mode 100644 index 0000000000..3e007342bc --- /dev/null +++ b/gui/openapi-codegen.config.ts @@ -0,0 +1,28 @@ +import { + generateSchemaTypes, + generateReactQueryComponents, +} from '@openapi-codegen/typescript'; +import { defineConfig } from '@openapi-codegen/cli'; +import dotenv from 'dotenv'; + +dotenv.config() + +export default defineConfig({ + firmwareTool: { + from: { + source: 'url', + url: process.env.FIRMWARE_TOOL_SCHEMA_URL ?? 'http://localhost:3000/api-json', + }, + outputDir: 'src/firmware-tool-api', + to: async (context) => { + const filenamePrefix = 'firmwareTool'; + const { schemasFiles } = await generateSchemaTypes(context, { + filenamePrefix, + }); + await generateReactQueryComponents(context, { + filenamePrefix, + schemasFiles, + }); + }, + }, +}); diff --git a/gui/package.json b/gui/package.json index 58c18e6520..f3a0c160d4 100644 --- a/gui/package.json +++ b/gui/package.json @@ -2,13 +2,17 @@ "name": "slimevr-ui", "version": "0.5.1", "private": true, + "type": "module", "dependencies": { "@fluent/bundle": "^0.18.0", "@fluent/react": "^0.15.2", "@fontsource/poppins": "^5.1.0", "@formatjs/intl-localematcher": "^0.2.32", + "@hookform/resolvers": "^3.6.0", "@react-three/drei": "^9.114.3", "@react-three/fiber": "^8.17.10", + "@tailwindcss/typography": "^0.5.15", + "@tanstack/react-query": "^5.48.0", "@tauri-apps/api": "^2.0.2", "@tauri-apps/plugin-dialog": "^2.0.0", "@tauri-apps/plugin-fs": "^2.0.0", @@ -26,15 +30,18 @@ "react-error-boundary": "^4.0.13", "react-helmet": "^6.1.0", "react-hook-form": "^7.53.0", + "react-markdown": "^9.0.1", "react-modal": "^3.16.1", "react-responsive": "^10.0.0", "react-router-dom": "^6.26.2", + "remark-gfm": "^4.0.0", "semver": "^7.6.3", "solarxr-protocol": "file:../solarxr-protocol", "three": "^0.163.0", "ts-pattern": "^5.4.0", "typescript": "^5.6.3", - "use-double-tap": "^1.3.6" + "use-double-tap": "^1.3.6", + "yup": "^1.4.0" }, "scripts": { "start": "vite --force", @@ -46,10 +53,14 @@ "lint:fix": "tsc --noEmit && eslint --fix --max-warnings=0 \"src/**/*.{js,jsx,ts,tsx,json}\" && pnpm run format", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,scss,md,json}\"", "preview-vite": "vite preview", - "javaversion-build": "cd src-tauri/src/ && javac JavaVersion.java && jar cvfe JavaVersion.jar JavaVersion JavaVersion.class" + "javaversion-build": "cd src-tauri/src/ && javac JavaVersion.java && jar cvfe JavaVersion.jar JavaVersion JavaVersion.class", + "gen:javaversion": "cd src-tauri/src/ && javac JavaVersion.java && jar cvfe JavaVersion.jar JavaVersion JavaVersion.class", + "gen:firmware-tool": "openapi-codegen gen firmwareTool" }, "devDependencies": { "@dword-design/eslint-plugin-import-alias": "^4.0.9", + "@openapi-codegen/cli": "^2.0.2", + "@openapi-codegen/typescript": "^8.0.2", "@tailwindcss/forms": "^0.5.9", "@tauri-apps/cli": "^2.0.2", "@types/file-saver": "^2.0.7", @@ -64,6 +75,7 @@ "@vitejs/plugin-react": "^4.3.2", "autoprefixer": "^10.4.20", "cross-env": "^7.0.3", + "dotenv": "^16.4.5", "eslint": "^8.57.1", "eslint-config-airbnb": "^19.0.4", "eslint-import-resolver-typescript": "^3.6.3", @@ -71,6 +83,7 @@ "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-hooks": "^4.6.2", + "globals": "^15.10.0", "prettier": "^3.3.3", "rollup-plugin-visualizer": "^5.12.0", "sass": "^1.79.4", @@ -78,6 +91,7 @@ "tailwind-gradient-mask-image": "^1.2.0", "tailwindcss": "^3.4.13", "ts-xor": "^1.3.0", - "vite": "^5.4.8" + "vite": "^5.4.8", + "typescript-eslint": "^8.8.0" } } diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl index e95b99a28c..9badaaf3c0 100644 --- a/gui/public/i18n/en/translation.ftl +++ b/gui/public/i18n/en/translation.ftl @@ -75,6 +75,19 @@ body_part-RIGHT_LITTLE_PROXIMAL = Right little proximal body_part-RIGHT_LITTLE_INTERMEDIATE = Right little intermediate body_part-RIGHT_LITTLE_DISTAL = Right little distal +## BoardType +board_type-UNKNOWN = Unknown +board_type-NODEMCU = NodeMCU +board_type-CUSTOM = Custom Board +board_type-WROOM32 = WROOM32 +board_type-WEMOSD1MINI = Wemos D1 Mini +board_type-TTGO_TBASE = TTGO T-Base +board_type-ESP01 = ESP-01 +board_type-SLIMEVR = SlimeVR +board_type-LOLIN_C3_MINI = Lolin C3 Mini +board_type-BEETLE32C3 = Beetle ESP32-C3 +board_type-ES32C3DEVKITM1 = Espressif ESP32-C3 DevKitM-1 + ## Proportions skeleton_bone-NONE = None skeleton_bone-HEAD = Head Shift @@ -253,6 +266,11 @@ tracker-settings-name_section-label = Tracker name tracker-settings-forget = Forget tracker tracker-settings-forget-description = Removes the tracker from the SlimeVR Server and prevent it from connecting to it until the server is restarted. The configuration of the tracker won't be lost. tracker-settings-forget-label = Forget tracker +tracker-settings-update-unavailable = Cannot be updated (DIY) +tracker-settings-update-up_to_date = Up to date +tracker-settings-update-available = { $versionName } is now available +tracker-settings-update = Update now +tracker-settings-update-title = Firmware version ## Tracker part card info tracker-part_card-no_name = No name @@ -325,6 +343,7 @@ settings-sidebar-utils = Utilities settings-sidebar-serial = Serial console settings-sidebar-appearance = Appearance settings-sidebar-notifications = Notifications +settings-sidebar-firmware-tool = DIY Firmware Tool settings-sidebar-advanced = Advanced ## SteamVR settings @@ -701,6 +720,7 @@ onboarding-wifi_creds-submit = Submit! onboarding-wifi_creds-ssid = .label = Wi-Fi name .placeholder = Enter Wi-Fi name +onboarding-wifi_creds-ssid-required = Wi-Fi name is required onboarding-wifi_creds-password = .label = Password .placeholder = Enter password @@ -750,6 +770,7 @@ onboarding-connect_tracker-issue-serial = I'm having trouble connecting! onboarding-connect_tracker-usb = USB Tracker onboarding-connect_tracker-connection_status-none = Looking for trackers onboarding-connect_tracker-connection_status-serial_init = Connecting to serial device +onboarding-connect_tracker-connection_status-obtaining_mac_address = Obtaining the tracker mac address onboarding-connect_tracker-connection_status-provisioning = Sending Wi-Fi credentials onboarding-connect_tracker-connection_status-connecting = Trying to connect to Wi-Fi onboarding-connect_tracker-connection_status-looking_for_server = Looking for server @@ -1115,6 +1136,165 @@ status_system-StatusSteamVRDisconnected = { $type -> status_system-StatusTrackerError = The { $trackerName } tracker has an error. status_system-StatusUnassignedHMD = The VR headset should be assigned as a head tracker. + +## Firmware tool globals +firmware_tool-next_step = Next Step +firmware_tool-previous_step = Previous Step +firmware_tool-ok = Looks good +firmware_tool-retry = Retry + +firmware_tool-loading = Loading... + +## Firmware tool Steps +firmware_tool = DIY Firmware tool +firmware_tool-description = + Allows you to configure and flash your DIY trackers +firmware_tool-not_available = Oops, the firmware tool is not available at the moment. Come back later! +firmware_tool-not_compatible = The firmware tool is not compatible with this version of the server. Please update your server! + +firmware_tool-board_step = Select your Board +firmware_tool-board_step-description = Select one of the boards listed below. + +firmware_tool-board_pins_step = Check the pins +firmware_tool-board_pins_step-description = + Please verify that the selected pins are correct. + If you followed the SlimeVR documentation the defaults values should be correct +firmware_tool-board_pins_step-enable_led = Enable LED +firmware_tool-board_pins_step-led_pin = + .label = LED Pin + .placeholder = Enter the pin address of the LED + +firmware_tool-board_pins_step-battery_type = Select the battery type +firmware_tool-board_pins_step-battery_type-BAT_EXTERNAL = External battery +firmware_tool-board_pins_step-battery_type-BAT_INTERNAL = Internal battery +firmware_tool-board_pins_step-battery_type-BAT_INTERNAL_MCP3021 = Internal MCP3021 +firmware_tool-board_pins_step-battery_type-BAT_MCP3021 = MCP3021 + + +firmware_tool-board_pins_step-battery_sensor_pin = + .label = Battery sensor Pin + .placeholder = Enter the pin address of battery sensor +firmware_tool-board_pins_step-battery_resistor = + .label = Battery Resistor (Ohms) + .placeholder = Enter the value of battery resistor +firmware_tool-board_pins_step-battery_shield_resistor-0 = + .label = Battery Shield R1 (Ohms) + .placeholder = Enter the value of Battery Shield R1 +firmware_tool-board_pins_step-battery_shield_resistor-1 = + .label = Battery Shield R2 (Ohms) + .placeholder = Enter the value of Battery Shield R2 + +firmware_tool-add_imus_step = Declare your IMUs +firmware_tool-add_imus_step-description = + Please add the IMUs that your tracker has + If you followed the SlimeVR documentation the defaults values should be correct +firmware_tool-add_imus_step-imu_type-label = IMU type +firmware_tool-add_imus_step-imu_type-placeholder = Select the type of IMU +firmware_tool-add_imus_step-imu_rotation = + .label = IMU Rotation (deg) + .placeholder = Rotation angle of the IMU +firmware_tool-add_imus_step-scl_pin = + .label = SCL Pin + .placeholder = Pin address of SCL +firmware_tool-add_imus_step-sda_pin = + .label = SDA Pin + .placeholder = Pin address of SDA +firmware_tool-add_imus_step-int_pin = + .label = INT Pin + .placeholder = Pin address of INT +firmware_tool-add_imus_step-optional_tracker = + .label = Optional tracker +firmware_tool-add_imus_step-show_less = Show Less +firmware_tool-add_imus_step-show_more = Show More +firmware_tool-add_imus_step-add_more = Add more IMUs + +firmware_tool-select_firmware_step = Select the firmware version +firmware_tool-select_firmware_step-description = + Please choose what version of the firmware you want to use +firmware_tool-select_firmware_step-show-third-party = + .label = Show third party firmwares + +firmware_tool-flash_method_step = Flashing Method +firmware_tool-flash_method_step-description = + Please select the flashing method you want to use +firmware_tool-flash_method_step-ota = + .label = OTA + .description = Use the over the air method. Your tracker will use the Wi-Fi to update it's firmware. Works only on already setup trackers. +firmware_tool-flash_method_step-serial = + .label = Serial + .description = Use a USB cable to update your tracker. + +firmware_tool-flashbtn_step = Press the boot btn +firmware_tool-flashbtn_step-description = Before going into the next step there is a few things you need to do + +firmware_tool-flashbtn_step-board_SLIMEVR = Turn off the tracker, remove the case (if any), connect a USB cable to this computer, then do one of the following steps according to your SlimeVR board revision: +firmware_tool-flashbtn_step-board_SLIMEVR-r11 = Turn on the tracker while shorting the second rectangular FLASH pad from the edge on the top side of the board, and the metal shield of the microcontroller +firmware_tool-flashbtn_step-board_SLIMEVR-r12 = Turn on the tracker while shorting the circular FLASH pad on the top side of the board, and the metal shield of the microcontroller +firmware_tool-flashbtn_step-board_SLIMEVR-r14 = Turn on the tracker while pushing in the FLASH button on the top side of the board + +firmware_tool-flashbtn_step-board_OTHER = Before flashing you will probably need to put the tracker into bootloader mode. + Most of the time it means pressing the boot button on the board before the flashing process starts. + If the flashing process timeout at the begining of the flashing it probably means that the tracker was not in bootloader mode + Please refer to the flashing instructions of your board to know how to turn on the boatloader mode + + + +firmware_tool-flash_method_ota-devices = Detected OTA Devices: +firmware_tool-flash_method_ota-no_devices = There are no boards that can be updated using OTA, make sure you selected the correct board type +firmware_tool-flash_method_serial-wifi = Wi-Fi Credentials: +firmware_tool-flash_method_serial-devices-label = Detected Serial Devices: +firmware_tool-flash_method_serial-devices-placeholder = Select a serial device +firmware_tool-flash_method_serial-no_devices = There are no compatible serial devices detected, make sure the tracker is plugged in + +firmware_tool-build_step = Building +firmware_tool-build_step-description = + The firmware is building, please wait + +firmware_tool-flashing_step = Flashing +firmware_tool-flashing_step-description = + Your trackers are flashing, please follow the instructions on the screen +firmware_tool-flashing_step-warning = Do not unplug or restart the tracker during the upload process unless told to, it may make your board unusable +firmware_tool-flashing_step-flash_more = Flash more trackers +firmware_tool-flashing_step-exit = Exit + +## firmware tool build status +firmware_tool-build-CREATING_BUILD_FOLDER = Creating the build folder +firmware_tool-build-DOWNLOADING_FIRMWARE = Downloading the firmware +firmware_tool-build-EXTRACTING_FIRMWARE = Extracting the firmware +firmware_tool-build-SETTING_UP_DEFINES = Configuring the defines +firmware_tool-build-BUILDING = Building the firmware +firmware_tool-build-SAVING = Saving the build +firmware_tool-build-DONE = Build Complete +firmware_tool-build-ERROR = Unable to build the firmware + +## Firmware update status +firmware_update-status-DOWNLOADING = Downloading the firmware +firmware_update-status-NEED_MANUAL_REBOOT = Waiting for the user to reboot the tracker +firmware_update-status-AUTHENTICATING = Authenticating with the mcu +firmware_update-status-UPLOADING = Uploading the firmware +firmware_update-status-SYNCING_WITH_MCU = Syncing with the mcu +firmware_update-status-REBOOTING = Rebooting the tracker +firmware_update-status-PROVISIONING = Setting Wi-Fi credentials +firmware_update-status-DONE = Update complete! +firmware_update-status-ERROR_DEVICE_NOT_FOUND = Could not find the device +firmware_update-status-ERROR_TIMEOUT = The update process timed out +firmware_update-status-ERROR_DOWNLOAD_FAILED = Could not download the firmware +firmware_update-status-ERROR_AUTHENTICATION_FAILED = Could not authenticate with the mcu +firmware_update-status-ERROR_UPLOAD_FAILED = Could not upload the firmware +firmware_update-status-ERROR_PROVISIONING_FAILED = Could not set the Wi-Fi credentials +firmware_update-status-ERROR_UNSUPPORTED_METHOD = The update method is not supported +firmware_update-status-ERROR_UNKNOWN = Unknown error + +## Dedicated Firmware Update Page +firmware_update-title = Firmware update +firmware_update-devices = Available Devices +firmware_update-devices-description = Please select the trackers you want to update to the latest version of SlimeVR firmware +firmware_update-no_devices = Plase make sure that the trackers you want to update are ON and connected to the Wi-Fi! +firmware_update-changelog-title = Updating to {$version} +firmware_update-looking_for_devices = Looking for devices to update... +firmware_update-retry = Retry +firmware_update-update = Update Selected Trackers + ## Tray Menu tray_menu-show = Show tray_menu-hide = Hide diff --git a/gui/public/images/R11_board_reset.webp b/gui/public/images/R11_board_reset.webp new file mode 100644 index 0000000000..c4870d76bd Binary files /dev/null and b/gui/public/images/R11_board_reset.webp differ diff --git a/gui/public/images/R12_board_reset.webp b/gui/public/images/R12_board_reset.webp new file mode 100644 index 0000000000..12aabb61b5 Binary files /dev/null and b/gui/public/images/R12_board_reset.webp differ diff --git a/gui/public/images/R14_board_reset_sw.webp b/gui/public/images/R14_board_reset_sw.webp new file mode 100644 index 0000000000..5c1d89a06d Binary files /dev/null and b/gui/public/images/R14_board_reset_sw.webp differ diff --git a/gui/src-tauri/Cargo.toml b/gui/src-tauri/Cargo.toml index 2f6985599f..dfda2bb448 100644 --- a/gui/src-tauri/Cargo.toml +++ b/gui/src-tauri/Cargo.toml @@ -53,6 +53,7 @@ rfd = { version = "0.15", features = ["gtk3"], default-features = false } dirs-next = "2.0.0" discord-sdk = "0.3.6" tokio = { version = "1.37.0", features = ["time"] } +itertools = "0.13.0" [target.'cfg(windows)'.dependencies] win32job = "1" diff --git a/gui/src-tauri/src/main.rs b/gui/src-tauri/src/main.rs index 337ca37b59..fda7f594a1 100644 --- a/gui/src-tauri/src/main.rs +++ b/gui/src-tauri/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(all(not(debug_assertions), windows), windows_subsystem = "windows")] +use std::env; use std::panic; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; @@ -68,88 +69,125 @@ fn main() -> Result<()> { let tauri_context = tauri::generate_context!(); // Set up loggers and global handlers - let _logger = { - use flexi_logger::{ - Age, Cleanup, Criterion, Duplicate, FileSpec, Logger, Naming, WriteMode, - }; - use tauri::Error; - - // Based on https://docs.rs/tauri/2.0.0-alpha.10/src/tauri/path/desktop.rs.html#238-256 - #[cfg(target_os = "macos")] - let path = dirs_next::home_dir().ok_or(Error::UnknownPath).map(|dir| { - dir.join("Library/Logs") - .join(&tauri_context.config().identifier) - }); - - #[cfg(not(target_os = "macos"))] - let path = dirs_next::data_dir() - .ok_or(Error::UnknownPath) - .map(|dir| dir.join(&tauri_context.config().identifier).join("logs")); - - Logger::try_with_env_or_str("info")? - .log_to_file( - FileSpec::default().directory(path.expect("We need a log dir")), - ) - .format_for_files(|w, now, record| { - util::logger_format(w, now, record, false) - }) - .format_for_stderr(|w, now, record| { - util::logger_format(w, now, record, true) - }) - .rotate( - Criterion::Age(Age::Day), - Naming::Timestamps, - Cleanup::KeepLogFiles(2), - ) - .duplicate_to_stderr(Duplicate::All) - .write_mode(WriteMode::BufferAndFlush) - .start()? - }; + let _logger = setup_logger(&tauri_context); // Ensure child processes die when spawned on windows // and then check for WebView2's existence #[cfg(windows)] - { - use crate::util::webview2_exists; - use win32job::{ExtendedLimitInfo, Job}; - - let mut info = ExtendedLimitInfo::new(); - info.limit_kill_on_job_close(); - let job = Job::create_with_limit_info(&mut info).expect("Failed to create Job"); - job.assign_current_process() - .expect("Failed to assign current process to Job"); - - // We don't do anything with the job anymore, but we shouldn't drop it because that would - // terminate our process tree. So we intentionally leak it instead. - std::mem::forget(job); - - if !webview2_exists() { - // This makes a dialog appear which let's you press Ok or Cancel - // If you press Ok it will open the SlimeVR installer documentation - use rfd::{ - MessageButtons, MessageDialog, MessageDialogResult, MessageLevel, - }; + setup_webview2()?; - let confirm = MessageDialog::new() - .set_title("SlimeVR") - .set_description("Couldn't find WebView2 installed. You can install it with the SlimeVR installer") - .set_buttons(MessageButtons::OkCancel) - .set_level(MessageLevel::Error) - .show(); - if confirm == MessageDialogResult::Ok { - open::that("https://docs.slimevr.dev/server-setup/installing-and-connecting.html#install-the-latest-slimevr-installer").unwrap(); - } - return Ok(()); - } - } + // Check for environment variables that can affect the server, and if so, warn in log and GUI + check_environment_variables(); // Spawn server process let exit_flag = Arc::new(AtomicBool::new(false)); let backend = Arc::new(Mutex::new(Option::::None)); - let backend_termination = backend.clone(); - let run_path = get_launch_path(cli); - let server_info = if let Some(p) = run_path { + let server_info = execute_server(cli)?; + let build_result = setup_tauri( + tauri_context, + server_info, + exit_flag.clone(), + backend.clone(), + ); + + tauri_build_result(build_result, exit_flag, backend); + + Ok(()) +} + +fn setup_logger(context: &tauri::Context) -> Result { + use flexi_logger::{ + Age, Cleanup, Criterion, Duplicate, FileSpec, Logger, Naming, WriteMode, + }; + use tauri::Error; + + // Based on https://docs.rs/tauri/2.0.0-alpha.10/src/tauri/path/desktop.rs.html#238-256 + #[cfg(target_os = "macos")] + let path = dirs_next::home_dir() + .ok_or(Error::UnknownPath) + .map(|dir| dir.join("Library/Logs").join(&context.config().identifier)); + + #[cfg(not(target_os = "macos"))] + let path = dirs_next::data_dir() + .ok_or(Error::UnknownPath) + .map(|dir| dir.join(&context.config().identifier).join("logs")); + + Ok(Logger::try_with_env_or_str("info")? + .log_to_file(FileSpec::default().directory(path.expect("We need a log dir"))) + .format_for_files(|w, now, record| util::logger_format(w, now, record, false)) + .format_for_stderr(|w, now, record| util::logger_format(w, now, record, true)) + .rotate( + Criterion::Age(Age::Day), + Naming::Timestamps, + Cleanup::KeepLogFiles(2), + ) + .duplicate_to_stderr(Duplicate::All) + .write_mode(WriteMode::BufferAndFlush) + .start()?) +} + +#[cfg(windows)] +fn setup_webview2() -> Result<()> { + use crate::util::webview2_exists; + use win32job::{ExtendedLimitInfo, Job}; + + let mut info = ExtendedLimitInfo::new(); + info.limit_kill_on_job_close(); + let job = Job::create_with_limit_info(&mut info).expect("Failed to create Job"); + job.assign_current_process() + .expect("Failed to assign current process to Job"); + + // We don't do anything with the job anymore, but we shouldn't drop it because that would + // terminate our process tree. So we intentionally leak it instead. + std::mem::forget(job); + + if !webview2_exists() { + // This makes a dialog appear which let's you press Ok or Cancel + // If you press Ok it will open the SlimeVR installer documentation + use rfd::{MessageButtons, MessageDialog, MessageDialogResult, MessageLevel}; + + let confirm = MessageDialog::new() + .set_title("SlimeVR") + .set_description("Couldn't find WebView2 installed. You can install it with the SlimeVR installer") + .set_buttons(MessageButtons::OkCancel) + .set_level(MessageLevel::Error) + .show(); + if confirm == MessageDialogResult::Ok { + open::that("https://docs.slimevr.dev/server-setup/installing-and-connecting.html#install-the-latest-slimevr-installer").unwrap(); + } + } + Ok(()) +} + +fn check_environment_variables() { + use itertools::Itertools; + const ENVS_TO_CHECK: &[&str] = &["_JAVA_OPTIONS", "JAVA_TOOL_OPTIONS"]; + let checked_envs = ENVS_TO_CHECK + .into_iter() + .filter_map(|e| { + let Ok(data) = env::var(e) else { + return None; + }; + log::warn!("{e} is set to: {data}"); + Some(e) + }) + .join(", "); + + if !checked_envs.is_empty() { + rfd::MessageDialog::new() + .set_title("SlimeVR") + .set_description(format!("You have environment variables {} set, which may cause the SlimeVR Server to fail to launch properly.", checked_envs)) + .set_level(rfd::MessageLevel::Warning) + .show(); + } +} + +fn execute_server( + cli: Cli, +) -> Result> { + use const_format::formatcp; + if let Some(p) = get_launch_path(cli) { log::info!("Server found on path: {}", p.to_str().unwrap()); // Check if any Java already installed is compatible @@ -159,19 +197,29 @@ fn main() -> Result<()> { .then(|| jre.into_os_string()) .or_else(|| valid_java_paths().first().map(|x| x.0.to_owned())); let Some(java_bin) = java_bin else { - show_error(&format!("Couldn't find a compatible Java version, please download Java {} or higher", MINIMUM_JAVA_VERSION)); - return Ok(()); + show_error(formatcp!( + "Couldn't find a compatible Java version, please download Java {} or higher", + MINIMUM_JAVA_VERSION + )); + return Ok(None); }; log::info!("Using Java binary: {:?}", java_bin); - Some((java_bin, p)) + Ok(Some((java_bin, p))) } else { log::warn!("No server found. We will not start the server."); - None - }; + Ok(None) + } +} +fn setup_tauri( + context: tauri::Context, + server_info: Option<(std::ffi::OsString, std::path::PathBuf)>, + exit_flag: Arc, + backend: Arc>>, +) -> Result { let exit_flag_terminated = exit_flag.clone(); - let build_result = tauri::Builder::default() + tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_os::init()) @@ -281,7 +329,14 @@ fn main() -> Result<()> { // WindowEvent::Resized(_) => std::thread::sleep(std::time::Duration::from_nanos(1)), _ => (), }) - .build(tauri_context); + .build(context) +} + +fn tauri_build_result( + build_result: Result, + exit_flag: Arc, + backend: Arc>>, +) { match build_result { Ok(app) => { app.run(move |app_handle, event| match event { @@ -295,7 +350,7 @@ fn main() -> Result<()> { Err(e) => log::error!("failed to save window state: {}", e), } - let mut lock = backend_termination.lock().unwrap(); + let mut lock = backend.lock().unwrap(); let Some(ref mut child) = *lock else { return }; let write_result = child.write(b"exit\n"); match write_result { @@ -339,6 +394,4 @@ fn main() -> Result<()> { show_error(&error.to_string()); } } - - Ok(()) } diff --git a/gui/src/App.tsx b/gui/src/App.tsx index 95fca8aaa9..70fef69860 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -51,11 +51,14 @@ import { useBreakpoint, useIsTauri } from './hooks/breakpoint'; import { VRModePage } from './components/vr-mode/VRModePage'; import { InterfaceSettings } from './components/settings/pages/InterfaceSettings'; import { error, log } from './utils/logging'; +import { FirmwareToolSettings } from './components/firmware-tool/FirmwareTool'; import { AppLayout } from './AppLayout'; import { Preload } from './components/Preload'; import { UnknownDeviceModal } from './components/UnknownDeviceModal'; import { useDiscordPresence } from './hooks/discord-presence'; +import { EmptyLayout } from './components/EmptyLayout'; import { AdvancedSettings } from './components/settings/pages/AdvancedSettings'; +import { FirmwareUpdate } from './components/firmware-update/FirmwareUpdate'; import { UsageChoose } from './components/onboarding/pages/usage-reason/UsageChoose'; import { VRUsageChoose } from './components/onboarding/pages/usage-reason/VRUsageChoose'; import { StandaloneUsageSetup } from './components/onboarding/pages/usage-reason/StandaloneUsageSetup'; @@ -89,6 +92,14 @@ function Layout() { } /> + + + + } + /> } > + } /> } /> } /> } /> @@ -295,19 +307,16 @@ export default function App() {
-
- {!websocketAPI.isConnected && ( - <> - -
- {websocketAPI.isFirstConnection - ? l10n.getString('websocket-connecting') - : l10n.getString('websocket-connection_lost')} -
- - )} - {websocketAPI.isConnected && } -
+ {!websocketAPI.isConnected && ( + +
+ {websocketAPI.isFirstConnection + ? l10n.getString('websocket-connecting') + : l10n.getString('websocket-connection_lost')} +
+
+ )} + {websocketAPI.isConnected && }
diff --git a/gui/src/components/EmptyLayout.scss b/gui/src/components/EmptyLayout.scss new file mode 100644 index 0000000000..d14e5d1b05 --- /dev/null +++ b/gui/src/components/EmptyLayout.scss @@ -0,0 +1,7 @@ +.empty-layout { + display: grid; + grid-template: + 't' var(--topbar-h) + 'c' calc(100% - var(--topbar-h)) + / 100%; +} diff --git a/gui/src/components/EmptyLayout.tsx b/gui/src/components/EmptyLayout.tsx new file mode 100644 index 0000000000..443ea60a8c --- /dev/null +++ b/gui/src/components/EmptyLayout.tsx @@ -0,0 +1,16 @@ +import { ReactNode } from 'react'; +import { TopBar } from './TopBar'; +import './EmptyLayout.scss'; + +export function EmptyLayout({ children }: { children: ReactNode }) { + return ( +
+
+ +
+
+ {children} +
+
+ ); +} diff --git a/gui/src/components/SerialDetectionModal.tsx b/gui/src/components/SerialDetectionModal.tsx index 70b6597a06..0164eb6099 100644 --- a/gui/src/components/SerialDetectionModal.tsx +++ b/gui/src/components/SerialDetectionModal.tsx @@ -39,12 +39,6 @@ export function SerialDetectionModal() { const openWifi = () => { setShowWifiForm(true); - // if (!hasWifiCreds) { - // setShowWifiForm(true); - // } else { - // closeModal(); - // nav('/onboarding/connect-trackers', { state: { alonePage: true } }); - // } }; const modalWifiSubmit = (form: WifiFormData) => { @@ -58,7 +52,11 @@ export function SerialDetectionModal() { ({ device }: NewSerialDeviceResponseT) => { if ( config?.watchNewDevices && - !['/settings/serial', '/onboarding/connect-trackers'].includes(pathname) + ![ + '/settings/serial', + '/onboarding/connect-trackers', + '/settings/firmware-tool', + ].includes(pathname) ) { setOpen(device); } diff --git a/gui/src/components/TopBar.tsx b/gui/src/components/TopBar.tsx index 63973af56a..4e5f76f44d 100644 --- a/gui/src/components/TopBar.tsx +++ b/gui/src/components/TopBar.tsx @@ -290,7 +290,9 @@ export function TopBar({ await invoke('update_tray_text'); } else if ( config?.connectedTrackersWarning && - connectedIMUTrackers.length > 0 + connectedIMUTrackers.filter( + (t) => t.tracker.status !== TrackerStatus.TIMED_OUT + ).length > 0 ) { setConnectedTrackerWarning(true); } else { diff --git a/gui/src/components/UnknownDeviceModal.tsx b/gui/src/components/UnknownDeviceModal.tsx index 0a291f3d91..847214fd83 100644 --- a/gui/src/components/UnknownDeviceModal.tsx +++ b/gui/src/components/UnknownDeviceModal.tsx @@ -25,7 +25,9 @@ export function UnknownDeviceModal() { RpcMessage.UnknownDeviceHandshakeNotification, ({ macAddress }: UnknownDeviceHandshakeNotificationT) => { if ( - ['/onboarding/connect-trackers'].includes(pathname) || + ['/onboarding/connect-trackers', '/settings/firmware-tool'].includes( + pathname + ) || state.ignoredTrackers.has(macAddress as string) || (currentTracker !== null && currentTracker !== macAddress) ) diff --git a/gui/src/components/commons/A.tsx b/gui/src/components/commons/A.tsx index 6b42733dd8..9085b8a976 100644 --- a/gui/src/components/commons/A.tsx +++ b/gui/src/components/commons/A.tsx @@ -1,11 +1,13 @@ import { open } from '@tauri-apps/plugin-shell'; import { ReactNode } from 'react'; -export function A({ href, children }: { href: string; children?: ReactNode }) { +export function A({ href, children }: { href?: string; children?: ReactNode }) { return ( open(href).catch(() => window.open(href, '_blank'))} + onClick={() => + href && open(href).catch(() => window.open(href, '_blank')) + } className="underline" > {children} diff --git a/gui/src/components/commons/Checkbox.tsx b/gui/src/components/commons/Checkbox.tsx index 7287403612..ac2d824118 100644 --- a/gui/src/components/commons/Checkbox.tsx +++ b/gui/src/components/commons/Checkbox.tsx @@ -2,6 +2,10 @@ import classNames from 'classnames'; import { useMemo } from 'react'; import { Control, Controller } from 'react-hook-form'; +export const CHECKBOX_CLASSES = classNames( + 'bg-background-50 border-background-50 rounded-md w-5 h-5 text-accent-background-30 focus:border-accent-background-40 focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent' +); + export function CheckBox({ label, variant = 'checkbox', @@ -25,9 +29,7 @@ export function CheckBox({ const classes = useMemo(() => { const vriantsMap = { checkbox: { - checkbox: classNames( - 'bg-background-50 border-background-50 rounded-md w-5 h-5 text-accent-background-30 focus:border-accent-background-40 focus:ring-transparent focus:ring-offset-transparent focus:outline-transparent' - ), + checkbox: CHECKBOX_CLASSES, toggle: '', pin: '', }, diff --git a/gui/src/components/commons/Input.tsx b/gui/src/components/commons/Input.tsx index 4c518497cc..0f229ba41e 100644 --- a/gui/src/components/commons/Input.tsx +++ b/gui/src/components/commons/Input.tsx @@ -98,7 +98,7 @@ export const InputInside = forwardRef< > {type === 'password' && (
diff --git a/gui/src/components/commons/ProgressBar.tsx b/gui/src/components/commons/ProgressBar.tsx index afc776b75d..61afbae020 100644 --- a/gui/src/components/commons/ProgressBar.tsx +++ b/gui/src/components/commons/ProgressBar.tsx @@ -7,12 +7,14 @@ export function ProgressBar({ height = 10, colorClass = 'bg-accent-background-20', animated = false, + bottom = false, }: { progress: number; parts?: number; height?: number; colorClass?: string; animated?: boolean; + bottom?: boolean; }) { return (
@@ -25,6 +27,7 @@ export function ProgressBar({ colorClass={colorClass} animated={animated} parts={parts} + bottom={bottom} > ))}
@@ -38,6 +41,7 @@ export function Bar({ height, animated, colorClass, + bottom, }: { index: number; progress: number; @@ -45,6 +49,7 @@ export function Bar({ height: number; colorClass: string; animated: boolean; + bottom: boolean; }) { const value = useMemo( () => Math.min(Math.max((progress * parts) / 1 - index, 0), 1), @@ -52,12 +57,16 @@ export function Bar({ ); return (
-
+
(null); + const refTop = useRef(null); + const [shouldAnimate, setShouldAnimate] = useState(false); + const { height } = useElemSize(ref); + + const isSelected = active === index; + const isPrevious = active > index; + + useEffect(() => { + if (!refTop.current) return; + if (isSelected) + setTimeout(() => { + if (!refTop.current) return; + refTop.current.scrollIntoView({ behavior: 'smooth' }); + }, 500); + }, [isSelected]); + + useLayoutEffect(() => { + setShouldAnimate(true); + }, [active]); + + // Make it so it wont try to animate the size + // if we are not changing active step + useDebouncedEffect( + () => { + setShouldAnimate(false); + }, + [active], + 1000 + ); + + return ( +
  • + + {isPrevious ? ( + + ) : ( + {index + 1} + )} + +
    +
    + {title} +
    +
    +
    {children}
    +
    +
    +
  • + ); +} + +type VerticalStepComponentType = FC<{ + nextStep: () => void; + prevStep: () => void; + goTo: (id: string) => void; + isActive: boolean; +}>; + +export type VerticalStep = { + title: string; + id?: string; + component: VerticalStepComponentType; +}; + +export default function VerticalStepper({ steps }: { steps: VerticalStep[] }) { + const [currStep, setStep] = useState(0); + + const nextStep = () => { + if (currStep + 1 === steps.length) return; + setStep(currStep + 1); + }; + + const prevStep = () => { + if (currStep - 1 < 0) return; + setStep(currStep - 1); + }; + + const goTo = (id: string) => { + const step = steps.findIndex(({ id: stepId }) => stepId === id); + if (step === -1) throw new Error('step not found'); + + setStep(step); + }; + + return ( +
      + {steps.map(({ title, component: StepComponent }, index) => ( + + + + ))} +
    + ); +} diff --git a/gui/src/components/firmware-tool/AddImusStep.tsx b/gui/src/components/firmware-tool/AddImusStep.tsx new file mode 100644 index 0000000000..60e627fcbf --- /dev/null +++ b/gui/src/components/firmware-tool/AddImusStep.tsx @@ -0,0 +1,308 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon'; +import { useFirmwareTool } from '@/hooks/firmware-tool'; +import { Button } from '@/components/commons/Button'; +import { Control, useForm } from 'react-hook-form'; +import { + CreateImuConfigDTO, + Imudto, +} from '@/firmware-tool-api/firmwareToolSchemas'; +import { Dropdown } from '@/components/commons/Dropdown'; +import { TrashIcon } from '@/components/commons/icon/TrashIcon'; +import { Input } from '@/components/commons/Input'; +import { + ArrowDownIcon, + ArrowUpIcon, +} from '@/components/commons/icon/ArrowIcons'; +import { useEffect, useRef, useState } from 'react'; +import classNames from 'classnames'; +import { useElemSize } from '@/hooks/layout'; +import { useGetFirmwaresImus } from '@/firmware-tool-api/firmwareToolComponents'; +import { CheckBox } from '@/components/commons/Checkbox'; + +function IMUCard({ + control, + imuTypes, + hasIntPin, + index, + onDelete, +}: { + imuTypes: Imudto[]; + hasIntPin: boolean; + control: Control<{ imus: CreateImuConfigDTO[] }, any>; + index: number; + onDelete: () => void; +}) { + const { l10n } = useLocalization(); + const [open, setOpen] = useState(false); + const ref = useRef(null); + const { height } = useElemSize(ref); + + return ( +
    +
    +
    + + {index + 1} + +
    +
    +
    + + + + +
    +
    +
    + + + + + + + + {hasIntPin && ( + + + + )} + +
    +
    +
    +
    + +
    +
    +
    setOpen(!open)} + > + + {l10n.getString( + open + ? 'firmware_tool-add_imus_step-show_less' + : 'firmware_tool-add_imus_step-show_more' + )} + + {!open && } + {open && } +
    +
    + ); +} + +export function AddImusStep({ + nextStep, + prevStep, + isActive, +}: { + nextStep: () => void; + prevStep: () => void; + goTo: (id: string) => void; + isActive: boolean; +}) { + const { l10n } = useLocalization(); + const { + isStepLoading: isLoading, + newConfig, + defaultConfig, + updateImus, + } = useFirmwareTool(); + + const { + control, + formState: { isValid: isValidState }, + reset, + watch, + } = useForm<{ imus: CreateImuConfigDTO[] }>({ + defaultValues: { + imus: [], + }, + reValidateMode: 'onChange', + mode: 'onChange', + }); + + useEffect(() => { + reset({ + imus: newConfig?.imusConfig || [], + }); + }, [isActive]); + + const { isFetching, data: imuTypes } = useGetFirmwaresImus({}); + + const isAckchuallyLoading = isFetching || isLoading; + const form = watch(); + + const addImu = () => { + if (!newConfig || !defaultConfig) throw new Error('unreachable'); + + const imuPinToAdd = + defaultConfig.imuDefaults[form.imus.length ?? 0] ?? + defaultConfig.imuDefaults[0]; + const imuTypeToAdd: CreateImuConfigDTO['type'] = + form.imus[0]?.type ?? 'IMU_BNO085'; + reset({ + imus: [...form.imus, { ...imuPinToAdd, type: imuTypeToAdd }], + }); + }; + const deleteImu = (index: number) => { + reset({ imus: form.imus.filter((_, i) => i !== index) }); + }; + + return ( + <> +
    +
    + + {l10n.getString('firmware_tool-board_pins_step-description')} + +
    +
    + {!isAckchuallyLoading && imuTypes && newConfig && ( + <> +
    +
    1 + ? 'md:grid-cols-2 mobile-settings:grid-cols-1' + : 'grid-cols-1' + )} + > + {form.imus.map((imu, index) => ( + t == imu.type) + ?.hasIntPin ?? false + } + index={index} + onDelete={() => deleteImu(index)} + > + ))} +
    +
    + + + +
    +
    +
    + + + + + + +
    + + )} + {isAckchuallyLoading && ( +
    + + + + +
    + )} +
    +
    + + ); +} diff --git a/gui/src/components/firmware-tool/BoardPinsStep.tsx b/gui/src/components/firmware-tool/BoardPinsStep.tsx new file mode 100644 index 0000000000..c2b8d32ff0 --- /dev/null +++ b/gui/src/components/firmware-tool/BoardPinsStep.tsx @@ -0,0 +1,198 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon'; +import { useFirmwareTool } from '@/hooks/firmware-tool'; +import { Button } from '@/components/commons/Button'; +import { useForm } from 'react-hook-form'; +import { Input } from '@/components/commons/Input'; +import { useEffect } from 'react'; +import { CheckBox } from '@/components/commons/Checkbox'; +import { CreateBoardConfigDTO } from '@/firmware-tool-api/firmwareToolSchemas'; +import { Dropdown } from '@/components/commons/Dropdown'; +import classNames from 'classnames'; +import { useGetFirmwaresBatteries } from '@/firmware-tool-api/firmwareToolComponents'; + +export type BoardPinsForm = Omit; + +export function BoardPinsStep({ + nextStep, + prevStep, +}: { + nextStep: () => void; + prevStep: () => void; +}) { + const { l10n } = useLocalization(); + const { + isStepLoading: isLoading, + defaultConfig, + updatePins, + } = useFirmwareTool(); + const { isFetching, data: batteryTypes } = useGetFirmwaresBatteries({}); + + const { reset, control, watch, formState } = useForm({ + reValidateMode: 'onChange', + defaultValues: { + batteryResistances: [0, 0, 0], + }, + mode: 'onChange', + }); + + const formValue = watch(); + const ledEnabled = watch('enableLed'); + const batteryType = watch('batteryType'); + + useEffect(() => { + if (!defaultConfig) return; + const { type, ...resetConfig } = defaultConfig.boardConfig; + reset({ + ...resetConfig, + }); + }, [defaultConfig]); + + return ( + <> +
    +
    + + {l10n.getString('firmware_tool-board_pins_step-description')} + +
    +
    + {!isLoading && !isFetching && batteryTypes && ( +
    +
    + + + + +
    +
    + ({ + label: l10n.getString( + 'firmware_tool-board_pins_step-battery_type-' + battery + ), + value: battery, + }))} + > + {batteryType === 'BAT_EXTERNAL' && ( +
    + + + + + + + + + + + + +
    + )} +
    +
    + )} + {(isLoading || isFetching) && ( +
    + + + + +
    + )} +
    +
    + + + + + + +
    +
    + + ); +} diff --git a/gui/src/components/firmware-tool/BuildStep.tsx b/gui/src/components/firmware-tool/BuildStep.tsx new file mode 100644 index 0000000000..d7195159d8 --- /dev/null +++ b/gui/src/components/firmware-tool/BuildStep.tsx @@ -0,0 +1,111 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { fetchPostFirmwaresBuild } from '@/firmware-tool-api/firmwareToolComponents'; +import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon'; +import { useFirmwareTool } from '@/hooks/firmware-tool'; +import { + BuildResponseDTO, + CreateBuildFirmwareDTO, +} from '@/firmware-tool-api/firmwareToolSchemas'; +import { useEffect, useMemo } from 'react'; +import { firmwareToolBaseUrl } from '@/firmware-tool-api/firmwareToolFetcher'; +import { Button } from '@/components/commons/Button'; + +export function BuildStep({ + isActive, + goTo, + nextStep, +}: { + nextStep: () => void; + prevStep: () => void; + goTo: (id: string) => void; + isActive: boolean; +}) { + const { l10n } = useLocalization(); + const { isGlobalLoading, newConfig, setBuildStatus, buildStatus } = + useFirmwareTool(); + + const startBuild = async () => { + try { + const res = await fetchPostFirmwaresBuild({ + body: newConfig as CreateBuildFirmwareDTO, + }); + + setBuildStatus(res); + if (res.status !== 'DONE') { + const events = new EventSource( + `${firmwareToolBaseUrl}/firmwares/build-status/${res.id}` + ); + events.onmessage = ({ data }) => { + const buildEvent: BuildResponseDTO = JSON.parse(data); + setBuildStatus(buildEvent); + }; + } + } catch (e) { + console.error(e); + setBuildStatus({ id: '', status: 'ERROR' }); + } + }; + + useEffect(() => { + if (!isActive) return; + startBuild(); + }, [isActive]); + + useEffect(() => { + if (!isActive) return; + if (buildStatus.status === 'DONE') { + nextStep(); + } + }, [buildStatus]); + + const hasPendingBuild = useMemo( + () => !['DONE', 'ERROR'].includes(buildStatus.status), + [buildStatus.status] + ); + + return ( + <> +
    +
    + + {l10n.getString('firmware_tool-build_step-description')} + +
    +
    + {!isGlobalLoading && ( +
    + + + {l10n.getString('firmware_tool-build-' + buildStatus.status)} + +
    + )} + {isGlobalLoading && ( +
    + + + + +
    + )} +
    +
    + + + +
    +
    + + ); +} diff --git a/gui/src/components/firmware-tool/DeviceCard.tsx b/gui/src/components/firmware-tool/DeviceCard.tsx new file mode 100644 index 0000000000..1ddfb3cc4f --- /dev/null +++ b/gui/src/components/firmware-tool/DeviceCard.tsx @@ -0,0 +1,132 @@ +import { CHECKBOX_CLASSES } from '@/components/commons/Checkbox'; +import { ProgressBar } from '@/components/commons/ProgressBar'; +import { Typography } from '@/components/commons/Typography'; +import { firmwareUpdateErrorStatus } from '@/hooks/firmware-tool'; +import { useLocalization } from '@fluent/react'; +import classNames from 'classnames'; +import { Control, Controller } from 'react-hook-form'; +import { + FirmwareUpdateStatus, + TrackerStatus as TrackerStatusEnum, +} from 'solarxr-protocol'; +import { TrackerStatus } from '@/components/tracker/TrackerStatus'; + +interface DeviceCardProps { + deviceNames: string[]; + status?: FirmwareUpdateStatus; + online?: boolean | null; +} + +interface DeviceCardControlProps { + control?: Control; + name?: string; + progress?: number; + disabled?: boolean; +} + +export function DeviceCardContent({ deviceNames, status }: DeviceCardProps) { + const { l10n } = useLocalization(); + + return ( +
    +
    + {deviceNames.map((name) => ( + + {name} + + ))} +
    + {status !== undefined ? ( + + {l10n.getString( + 'firmware_update-status-' + FirmwareUpdateStatus[status] + )} + + ) : ( + // placeholder so the size of the component does not change if there is no status + )} +
    + ); +} + +export function DeviceCardControl({ + control, + name, + progress, + disabled = false, + online = null, + ...props +}: DeviceCardControlProps & DeviceCardProps) { + return ( +
    + {control && name ? ( + ( + + )} + > + ) : ( +
    + +
    + )} +
    + +
    + {online !== null && ( +
    + +
    + )} +
    + ); +} diff --git a/gui/src/components/firmware-tool/FirmwareTool.tsx b/gui/src/components/firmware-tool/FirmwareTool.tsx new file mode 100644 index 0000000000..b8d9ab7d7e --- /dev/null +++ b/gui/src/components/firmware-tool/FirmwareTool.tsx @@ -0,0 +1,140 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { + FirmwareToolContextC, + useFirmwareToolContext, +} from '@/hooks/firmware-tool'; +import { AddImusStep } from './AddImusStep'; +import { SelectBoardStep } from './SelectBoardStep'; +import { BoardPinsStep } from './BoardPinsStep'; +import VerticalStepper from '@/components/commons/VerticalStepper'; +import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon'; +import { Button } from '@/components/commons/Button'; +import { SelectFirmwareStep } from './SelectFirmwareStep'; +import { BuildStep } from './BuildStep'; +import { FlashingMethodStep } from './FlashingMethodStep'; +import { FlashingStep } from './FlashingStep'; +import { FlashBtnStep } from './FlashBtnStep'; +import { FirmwareUpdateMethod } from 'solarxr-protocol'; +import { useMemo } from 'react'; + +function FirmwareToolContent() { + const { l10n } = useLocalization(); + const context = useFirmwareToolContext(); + const { isError, isGlobalLoading: isLoading, retry, isCompatible } = context; + + const steps = useMemo(() => { + const steps = [ + { + id: 'SelectBoard', + component: SelectBoardStep, + title: l10n.getString('firmware_tool-board_step'), + }, + { + component: BoardPinsStep, + title: l10n.getString('firmware_tool-board_pins_step'), + }, + { + component: AddImusStep, + title: l10n.getString('firmware_tool-add_imus_step'), + }, + { + id: 'SelectFirmware', + component: SelectFirmwareStep, + title: l10n.getString('firmware_tool-select_firmware_step'), + }, + { + component: FlashingMethodStep, + id: 'FlashingMethod', + title: l10n.getString('firmware_tool-flash_method_step'), + }, + { + component: BuildStep, + title: l10n.getString('firmware_tool-build_step'), + }, + { + component: FlashingStep, + title: l10n.getString('firmware_tool-flashing_step'), + }, + ]; + + if ( + context.defaultConfig?.needBootPress && + context.selectedDevices?.find( + ({ type }) => type === FirmwareUpdateMethod.SerialFirmwareUpdate + ) + ) { + steps.splice(5, 0, { + component: FlashBtnStep, + title: l10n.getString('firmware_tool-flashbtn_step'), + }); + } + return steps; + }, [context.defaultConfig?.needBootPress, context.selectedDevices, l10n]); + + return ( + +
    + + {l10n.getString('firmware_tool')} + +
    + <> + {l10n + .getString('firmware_tool-description') + .split('\n') + .map((line, i) => ( + + {line} + + ))} + +
    +
    + {isError && ( +
    + + {!isCompatible ? ( + + + + ) : ( + + + + )} + + + +
    + )} + {isLoading && ( +
    + + + + +
    + )} + {!isError && !isLoading && } +
    +
    +
    + ); +} + +export function FirmwareToolSettings() { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, // default: true + }, + }, + }); + return ( + + + + ); +} diff --git a/gui/src/components/firmware-tool/FlashBtnStep.tsx b/gui/src/components/firmware-tool/FlashBtnStep.tsx new file mode 100644 index 0000000000..1f948f35c3 --- /dev/null +++ b/gui/src/components/firmware-tool/FlashBtnStep.tsx @@ -0,0 +1,86 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { Button } from '@/components/commons/Button'; +import { + boardTypeToFirmwareToolBoardType, + useFirmwareTool, +} from '@/hooks/firmware-tool'; +import { BoardType } from 'solarxr-protocol'; + +export function FlashBtnStep({ + nextStep, +}: { + nextStep: () => void; + prevStep: () => void; + goTo: (id: string) => void; + isActive: boolean; +}) { + const { l10n } = useLocalization(); + const { defaultConfig } = useFirmwareTool(); + + return ( + <> +
    +
    + + {l10n.getString('firmware_tool-flashbtn_step-description')} + + {defaultConfig?.boardConfig.type === + boardTypeToFirmwareToolBoardType[BoardType.SLIMEVR] ? ( + <> + + {l10n.getString('firmware_tool-flashbtn_step-board_SLIMEVR')} + +
    +
    + R11 + + {l10n.getString( + 'firmware_tool-flashbtn_step-board_SLIMEVR-r11' + )} + + +
    +
    + R12 + + {l10n.getString( + 'firmware_tool-flashbtn_step-board_SLIMEVR-r12' + )} + + +
    + +
    + R14 + + {l10n.getString( + 'firmware_tool-flashbtn_step-board_SLIMEVR-r14' + )} + + +
    +
    + + ) : ( + <> + + {l10n.getString('firmware_tool-flashbtn_step-board_OTHER')} + + + )} +
    + + + +
    +
    +
    + + ); +} diff --git a/gui/src/components/firmware-tool/FlashingMethodStep.tsx b/gui/src/components/firmware-tool/FlashingMethodStep.tsx new file mode 100644 index 0000000000..f4405d9849 --- /dev/null +++ b/gui/src/components/firmware-tool/FlashingMethodStep.tsx @@ -0,0 +1,421 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon'; +import { + boardTypeToFirmwareToolBoardType, + useFirmwareTool, +} from '@/hooks/firmware-tool'; +import { Control, UseFormReset, UseFormWatch, useForm } from 'react-hook-form'; +import { Radio } from '@/components/commons/Radio'; +import { useWebsocketAPI } from '@/hooks/websocket-api'; +import { useEffect, useLayoutEffect, useState } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; + +import { + BoardType, + DeviceDataT, + FirmwareUpdateMethod, + NewSerialDeviceResponseT, + RpcMessage, + SerialDeviceT, + SerialDevicesRequestT, + SerialDevicesResponseT, + TrackerStatus, +} from 'solarxr-protocol'; +import { Button } from '@/components/commons/Button'; +import { useAppContext } from '@/hooks/app'; +import { Input } from '@/components/commons/Input'; +import { Dropdown } from '@/components/commons/Dropdown'; +import { useOnboarding } from '@/hooks/onboarding'; +import { DeviceCardControl } from './DeviceCard'; +import { getTrackerName } from '@/hooks/tracker'; +import { ObjectSchema, object, string } from 'yup'; + +interface FlashingMethodForm { + flashingMethod?: string; + serial?: { + selectedDevicePort: string; + ssid: string; + password?: string; + }; + ota?: { + selectedDevices: { [key: string]: boolean }; + }; +} + +function SerialDevicesList({ + control, + watch, + reset, +}: { + control: Control; + watch: UseFormWatch; + reset: UseFormReset; +}) { + const { l10n } = useLocalization(); + const { selectDevices } = useFirmwareTool(); + const { sendRPCPacket, useRPCPacket } = useWebsocketAPI(); + const [devices, setDevices] = useState>({}); + const { state, setWifiCredentials } = useOnboarding(); + + useLayoutEffect(() => { + sendRPCPacket(RpcMessage.SerialDevicesRequest, new SerialDevicesRequestT()); + selectDevices(null); + reset({ + flashingMethod: FirmwareUpdateMethod.SerialFirmwareUpdate.toString(), + serial: { + ...state.wifi, + selectedDevicePort: undefined, + }, + ota: undefined, + }); + }, []); + + useRPCPacket( + RpcMessage.SerialDevicesResponse, + (res: SerialDevicesResponseT) => { + setDevices((old) => + res.devices.reduce( + (curr, device) => ({ + ...curr, + [device?.port?.toString() ?? 'unknown']: device, + }), + old + ) + ); + } + ); + + useRPCPacket( + RpcMessage.NewSerialDeviceResponse, + ({ device }: NewSerialDeviceResponseT) => { + if (device?.port) + setDevices((old) => ({ + ...old, + [device?.port?.toString() ?? 'unknown']: device, + })); + } + ); + + const serialValues = watch('serial'); + + useEffect(() => { + if (!serialValues) { + selectDevices(null); + return; + } + + setWifiCredentials(serialValues.ssid, serialValues.password); + if ( + serialValues.selectedDevicePort && + devices[serialValues.selectedDevicePort] + ) { + selectDevices([ + { + type: FirmwareUpdateMethod.SerialFirmwareUpdate, + deviceId: serialValues.selectedDevicePort, + deviceNames: [ + devices[serialValues.selectedDevicePort].name?.toString() ?? + 'unknown', + ], + }, + ]); + } else { + selectDevices(null); + } + }, [JSON.stringify(serialValues), devices]); + + return ( + <> + + + +
    + + + + + + +
    + + + + {Object.keys(devices).length === 0 ? ( + + + + ) : ( + ({ + label: devices[port].name?.toString() ?? 'unknown', + value: port, + }))} + placeholder={l10n.getString( + 'firmware_tool-flash_method_serial-devices-placeholder' + )} + display="block" + direction="down" + > + )} + + ); +} + +function OTADevicesList({ + control, + watch, + reset, +}: { + control: Control; + watch: UseFormWatch; + reset: UseFormReset; +}) { + const { l10n } = useLocalization(); + const { selectDevices, newConfig } = useFirmwareTool(); + const { state } = useAppContext(); + + const devices = + state.datafeed?.devices.filter(({ trackers, hardwareInfo }) => { + // We make sure the device is not one of these types + if ( + hardwareInfo?.officialBoardType === BoardType.SLIMEVR_LEGACY || + hardwareInfo?.officialBoardType === BoardType.SLIMEVR_DEV || + hardwareInfo?.officialBoardType === BoardType.CUSTOM + ) + return false; + + // if the device has no trackers it is prob misconfigured so we skip for safety + if (trackers.length <= 0) return false; + + // We make sure that the tracker is in working condition before doing ota as an error (that could be hardware) + // could cause an error during the update + if (!trackers.every(({ status }) => status === TrackerStatus.OK)) + return false; + + const boardType = hardwareInfo?.officialBoardType ?? BoardType.UNKNOWN; + return ( + boardTypeToFirmwareToolBoardType[boardType] === + newConfig?.boardConfig?.type + ); + }) || []; + + const deviceNames = ({ trackers }: DeviceDataT) => + trackers + .map(({ info }) => getTrackerName(l10n, info)) + .filter((i): i is string => !!i); + + const selectedDevices = watch('ota.selectedDevices'); + + useLayoutEffect(() => { + reset({ + flashingMethod: FirmwareUpdateMethod.OTAFirmwareUpdate.toString(), + ota: { + selectedDevices: devices.reduce( + (curr, { id }) => ({ ...curr, [id?.id ?? 0]: false }), + {} + ), + }, + serial: undefined, + }); + selectDevices(null); + }, []); + + useEffect(() => { + if (selectedDevices) { + selectDevices( + Object.keys(selectedDevices) + .filter((d) => selectedDevices[d]) + .map((id) => id.substring('id-'.length)) + .map((id) => { + const device = devices.find( + ({ id: dId }) => id === dId?.id.toString() + ); + + if (!device) throw new Error('no device found'); + return { + type: FirmwareUpdateMethod.OTAFirmwareUpdate, + deviceId: id, + deviceNames: deviceNames(device), + }; + }) + ); + } + }, [JSON.stringify(selectedDevices)]); + + return ( + <> + + + + {devices.length === 0 && ( + + + + )} +
    + {devices.map((device) => ( + + ))} +
    + + ); +} + +export function FlashingMethodStep({ + nextStep, + prevStep, +}: { + nextStep: () => void; + prevStep: () => void; + isActive: boolean; +}) { + const { l10n } = useLocalization(); + const { isGlobalLoading, selectedDevices } = useFirmwareTool(); + + const { + control, + watch, + reset, + formState: { isValid }, + } = useForm({ + reValidateMode: 'onChange', + mode: 'onChange', + resolver: yupResolver( + object({ + flashingMethod: string().optional(), + serial: object().when('flashingMethod', { + is: FirmwareUpdateMethod.SerialFirmwareUpdate.toString(), + then: (s) => + s + .shape({ + selectedDevicePort: string().required(), + ssid: string().required( + l10n.getString('onboarding-wifi_creds-ssid-required') + ), + password: string(), + }) + .required(), + otherwise: (s) => s.optional(), + }), + ota: object().when('flashingMethod', { + is: FirmwareUpdateMethod.OTAFirmwareUpdate.toString(), + then: (s) => + s + .shape({ + selectedDevices: object(), + }) + .required(), + otherwise: (s) => s.optional(), + }), + }) as ObjectSchema + ), + }); + + const flashingMethod = watch('flashingMethod'); + + return ( + <> +
    +
    + + {l10n.getString('firmware_tool-flash_method_step-description')} + +
    +
    + {!isGlobalLoading && ( +
    +
    + + + + + + +
    + {flashingMethod === + FirmwareUpdateMethod.SerialFirmwareUpdate.toString() && ( + + )} + {flashingMethod === + FirmwareUpdateMethod.OTAFirmwareUpdate.toString() && ( + + )} +
    + + + + + + +
    +
    + )} + {isGlobalLoading && ( +
    + + + + +
    + )} +
    +
    + + ); +} diff --git a/gui/src/components/firmware-tool/FlashingStep.tsx b/gui/src/components/firmware-tool/FlashingStep.tsx new file mode 100644 index 0000000000..3c7f33bcc2 --- /dev/null +++ b/gui/src/components/firmware-tool/FlashingStep.tsx @@ -0,0 +1,220 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { + SelectedDevice, + firmwareUpdateErrorStatus, + getFlashingRequests, + useFirmwareTool, +} from '@/hooks/firmware-tool'; +import { useEffect, useMemo, useState } from 'react'; +import { useWebsocketAPI } from '@/hooks/websocket-api'; +import { + DeviceIdTableT, + FirmwareUpdateMethod, + FirmwareUpdateStatus, + FirmwareUpdateStatusResponseT, + FirmwareUpdateStopQueuesRequestT, + RpcMessage, +} from 'solarxr-protocol'; +import { useOnboarding } from '@/hooks/onboarding'; +import { DeviceCardControl } from './DeviceCard'; +import { WarningBox } from '@/components/commons/TipBox'; +import { Button } from '@/components/commons/Button'; +import { useNavigate } from 'react-router-dom'; +import { firmwareToolS3BaseUrl } from '@/firmware-tool-api/firmwareToolFetcher'; + +export function FlashingStep({ + goTo, + isActive, +}: { + nextStep: () => void; + prevStep: () => void; + goTo: (id: string) => void; + isActive: boolean; +}) { + const nav = useNavigate(); + const { l10n } = useLocalization(); + const { selectedDevices, buildStatus, selectDevices, defaultConfig } = + useFirmwareTool(); + const { state: onboardingState } = useOnboarding(); + const { sendRPCPacket, useRPCPacket } = useWebsocketAPI(); + const [status, setStatus] = useState<{ + [key: string]: { + status: FirmwareUpdateStatus; + type: FirmwareUpdateMethod; + progress: number; + deviceNames: string[]; + }; + }>({}); + + const clear = () => { + setStatus({}); + sendRPCPacket( + RpcMessage.FirmwareUpdateStopQueuesRequest, + new FirmwareUpdateStopQueuesRequestT() + ); + }; + + const queueFlashing = (selectedDevices: SelectedDevice[]) => { + clear(); + if (!buildStatus.firmwareFiles) + throw new Error('invalid state - no firmware files'); + const requests = getFlashingRequests( + selectedDevices, + buildStatus.firmwareFiles.map(({ url, ...fields }) => ({ + url: `${firmwareToolS3BaseUrl}/${url}`, + ...fields, + })), + onboardingState, + defaultConfig + ); + + requests.forEach((req) => { + sendRPCPacket(RpcMessage.FirmwareUpdateRequest, req); + }); + }; + + useEffect(() => { + if (!isActive) return; + if (!selectedDevices) + throw new Error('invalid state - no selected devices'); + queueFlashing(selectedDevices); + return () => clear(); + }, [isActive]); + + useRPCPacket( + RpcMessage.FirmwareUpdateStatusResponse, + (data: FirmwareUpdateStatusResponseT) => { + if (!data.deviceId) throw new Error('no device id'); + const id = + data.deviceId instanceof DeviceIdTableT + ? data.deviceId.id?.id + : data.deviceId.port; + if (!id) throw new Error('invalid device id'); + + const selectedDevice = selectedDevices?.find( + ({ deviceId }) => deviceId == id.toString() + ); + + // We skip the status as it can be old trackers still sending status + if (!selectedDevice) return; + + setStatus((last) => ({ + ...last, + [id.toString()]: { + progress: data.progress / 100, + status: data.status, + type: selectedDevice.type, + deviceNames: selectedDevice.deviceNames, + }, + })); + } + ); + + const trackerWithErrors = useMemo( + () => + Object.keys(status).filter((id) => + firmwareUpdateErrorStatus.includes(status[id].status) + ), + [status, firmwareUpdateErrorStatus] + ); + + const retryError = () => { + const devices = trackerWithErrors.map((id) => { + const device = status[id]; + return { + type: device.type, + deviceId: id, + deviceNames: device.deviceNames, + }; + }); + + selectDevices(devices); + queueFlashing(devices); + }; + + const hasPendingTrackers = useMemo( + () => + Object.keys(status).filter((id) => + [ + FirmwareUpdateStatus.NEED_MANUAL_REBOOT, + FirmwareUpdateStatus.DOWNLOADING, + FirmwareUpdateStatus.AUTHENTICATING, + FirmwareUpdateStatus.REBOOTING, + FirmwareUpdateStatus.SYNCING_WITH_MCU, + FirmwareUpdateStatus.UPLOADING, + FirmwareUpdateStatus.PROVISIONING, + ].includes(status[id].status) + ).length > 0, + [status] + ); + + const shouldShowRebootWarning = useMemo( + () => + Object.keys(status).find((id) => + [ + FirmwareUpdateStatus.REBOOTING, + FirmwareUpdateStatus.UPLOADING, + ].includes(status[id].status) + ), + [status] + ); + + return ( + <> +
    +
    + + {l10n.getString('firmware_tool-flashing_step-description')} + +
    + +
    + {shouldShowRebootWarning && ( + + Warning + + )} + + {Object.keys(status).map((id) => { + const val = status[id]; + + return ( + + ); + })} +
    + + + + + + + + + +
    +
    +
    + + ); +} diff --git a/gui/src/components/firmware-tool/SelectBoardStep.tsx b/gui/src/components/firmware-tool/SelectBoardStep.tsx new file mode 100644 index 0000000000..0bef4807c9 --- /dev/null +++ b/gui/src/components/firmware-tool/SelectBoardStep.tsx @@ -0,0 +1,95 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon'; +import { + firmwareToolToBoardType, + useFirmwareTool, +} from '@/hooks/firmware-tool'; +import { CreateBoardConfigDTO } from '@/firmware-tool-api/firmwareToolSchemas'; +import classNames from 'classnames'; +import { Button } from '@/components/commons/Button'; +import { useGetFirmwaresBoards } from '@/firmware-tool-api/firmwareToolComponents'; +import { BoardType } from 'solarxr-protocol'; + +export function SelectBoardStep({ + nextStep, + goTo, +}: { + nextStep: () => void; + prevStep: () => void; + goTo: (id: string) => void; +}) { + const { l10n } = useLocalization(); + const { selectBoard, newConfig, defaultConfig } = useFirmwareTool(); + const { isFetching, data: boards } = useGetFirmwaresBoards({}); + + return ( + <> +
    +
    + + {l10n.getString('firmware_tool-board_step-description')} + +
    +
    + {!isFetching && ( +
    +
    + {boards?.map((board) => ( +
    { + selectBoard(board as CreateBoardConfigDTO['type']); + }} + > + {l10n.getString( + `board_type-${ + BoardType[ + firmwareToolToBoardType[ + board as CreateBoardConfigDTO['type'] + ] ?? BoardType.UNKNOWN + ] + }` + )} +
    + ))} +
    +
    + + + +
    +
    + )} + {isFetching && ( +
    + + + + +
    + )} +
    +
    + + ); +} diff --git a/gui/src/components/firmware-tool/SelectFirmwareStep.tsx b/gui/src/components/firmware-tool/SelectFirmwareStep.tsx new file mode 100644 index 0000000000..be19e875b8 --- /dev/null +++ b/gui/src/components/firmware-tool/SelectFirmwareStep.tsx @@ -0,0 +1,120 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { useGetFirmwaresVersions } from '@/firmware-tool-api/firmwareToolComponents'; +import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon'; +import { useFirmwareTool } from '@/hooks/firmware-tool'; +import classNames from 'classnames'; +import { Button } from '@/components/commons/Button'; +import { useMemo } from 'react'; +import { CheckBox } from '@/components/commons/Checkbox'; +import { useForm } from 'react-hook-form'; + +export function SelectFirmwareStep({ + nextStep, + prevStep, + goTo, +}: { + nextStep: () => void; + prevStep: () => void; + goTo: (id: string) => void; +}) { + const { l10n } = useLocalization(); + const { selectVersion, newConfig, defaultConfig } = useFirmwareTool(); + const { isFetching, data: firmwares } = useGetFirmwaresVersions({}); + + const { control, watch } = useForm<{ thirdParty: boolean }>({}); + + const showThirdParty = watch('thirdParty'); + + const getName = (name: string) => { + return showThirdParty ? name : name.substring(name.indexOf('/') + 1); + }; + + const filteredFirmwares = useMemo(() => { + return firmwares?.filter( + ({ name }) => name.split('/')[0] === 'SlimeVR' || showThirdParty + ); + }, [firmwares, showThirdParty]); + + return ( + <> +
    +
    + + {l10n.getString('firmware_tool-select_firmware_step-description')} + +
    + + + +
    +
    +
    + {!isFetching && ( +
    +
    +
    + {filteredFirmwares?.map((firmware) => ( +
    { + selectVersion(firmware.name); + }} + > + {getName(firmware.name)} +
    + ))} +
    +
    +
    + + + + + + +
    +
    + )} + {isFetching && ( +
    + + + + +
    + )} +
    +
    + + ); +} diff --git a/gui/src/components/firmware-update/FirmwareUpdate.tsx b/gui/src/components/firmware-update/FirmwareUpdate.tsx new file mode 100644 index 0000000000..6ddd77080f --- /dev/null +++ b/gui/src/components/firmware-update/FirmwareUpdate.tsx @@ -0,0 +1,402 @@ +import { Localized, ReactLocalization, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { getTrackerName } from '@/hooks/tracker'; +import { ComponentProps, useEffect, useMemo, useState } from 'react'; +import { + BoardType, + DeviceDataT, + DeviceIdTableT, + FirmwareUpdateMethod, + FirmwareUpdateStatus, + FirmwareUpdateStatusResponseT, + FirmwareUpdateStopQueuesRequestT, + HardwareInfoT, + RpcMessage, + TrackerStatus, +} from 'solarxr-protocol'; +import semver from 'semver'; +import classNames from 'classnames'; +import { Button } from '@/components/commons/Button'; +import Markdown from 'react-markdown'; +import remark from 'remark-gfm'; +import { WarningBox } from '@/components/commons/TipBox'; +import { FirmwareRelease, useAppContext } from '@/hooks/app'; +import { DeviceCardControl } from '@/components/firmware-tool/DeviceCard'; +import { Control, useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom'; +import { useWebsocketAPI } from '@/hooks/websocket-api'; +import { + firmwareUpdateErrorStatus, + getFlashingRequests, + SelectedDevice, +} from '@/hooks/firmware-tool'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { object } from 'yup'; +import { LoaderIcon, SlimeState } from '@/components/commons/icon/LoaderIcon'; +import { A } from '@/components/commons/A'; + +export function checkForUpdate( + currentFirmwareRelease: FirmwareRelease, + hardwareInfo: HardwareInfoT +) { + return ( + // TODO: This is temporary, end goal is to support all board types + hardwareInfo.officialBoardType === BoardType.SLIMEVR && + semver.valid(currentFirmwareRelease.version) && + semver.valid(hardwareInfo.firmwareVersion?.toString() ?? 'none') && + semver.lt( + hardwareInfo.firmwareVersion?.toString() ?? 'none', + currentFirmwareRelease.version + ) + ); +} + +interface FirmwareUpdateForm { + selectedDevices: { [key: string]: boolean }; +} + +interface UpdateStatus { + status: FirmwareUpdateStatus; + type: FirmwareUpdateMethod; + progress: number; + deviceNames: string[]; +} + +const deviceNames = ({ trackers }: DeviceDataT, l10n: ReactLocalization) => + trackers + .map(({ info }) => getTrackerName(l10n, info)) + .filter((i): i is string => !!i); + +const DeviceList = ({ + control, + devices, +}: { + control: Control; + devices: DeviceDataT[]; +}) => { + const { l10n } = useLocalization(); + + return devices.map((device, index) => ( + + )); +}; + +const StatusList = ({ status }: { status: Record }) => { + const statusKeys = Object.keys(status); + + return statusKeys.map((id, index) => { + const val = status[id]; + + if (!val) throw new Error('there should always be a val'); + const { state } = useAppContext(); + const device = state.datafeed?.devices.find( + ({ id: dId }) => id === dId?.id.toString() + ); + + return ( + status === TrackerStatus.OK + )} + > + ); + }); +}; + +const MarkdownLink = (props: ComponentProps<'a'>) => ( +
    {props.children} +); + +export function FirmwareUpdate() { + const navigate = useNavigate(); + const { l10n } = useLocalization(); + const { sendRPCPacket, useRPCPacket } = useWebsocketAPI(); + const [selectedDevices, setSelectedDevices] = useState([]); + const { state, currentFirmwareRelease } = useAppContext(); + const [status, setStatus] = useState>({}); + + const devices = + state.datafeed?.devices.filter( + ({ trackers, hardwareInfo }) => + trackers.length > 0 && + currentFirmwareRelease && + hardwareInfo && + checkForUpdate(currentFirmwareRelease, hardwareInfo) && + trackers.every(({ status }) => status === TrackerStatus.OK) + ) || []; + + useRPCPacket( + RpcMessage.FirmwareUpdateStatusResponse, + (data: FirmwareUpdateStatusResponseT) => { + if (!data.deviceId) throw new Error('no device id'); + const id = + data.deviceId instanceof DeviceIdTableT + ? data.deviceId.id?.id + : data.deviceId.port; + if (!id) throw new Error('invalid device id'); + + const selectedDevice = selectedDevices?.find( + ({ deviceId }) => deviceId === id.toString() + ); + + // We skip the status as it can be old trackers still sending status + if (!selectedDevice) return; + + setStatus((last) => ({ + ...last, + [id.toString()]: { + progress: data.progress / 100, + status: data.status, + type: selectedDevice.type, + deviceNames: selectedDevice.deviceNames, + }, + })); + } + ); + + const { + control, + watch, + reset, + formState: { isValid }, + } = useForm({ + reValidateMode: 'onChange', + mode: 'onChange', + defaultValues: { + selectedDevices: devices.reduce( + (curr, { id }) => ({ ...curr, [id?.id ?? 0]: false }), + {} + ), + }, + resolver: yupResolver( + object({ + selectedDevices: object().test( + 'at-least-one-true', + 'At least one field must be true', + (value) => { + if (typeof value !== 'object' || value === null) return false; + return Object.values(value).some((val) => val === true); + } + ), + }) + ), + }); + + const selectedDevicesForm = watch('selectedDevices'); + + const clear = () => { + setStatus({}); + sendRPCPacket( + RpcMessage.FirmwareUpdateStopQueuesRequest, + new FirmwareUpdateStopQueuesRequestT() + ); + }; + + useEffect(() => { + if (!currentFirmwareRelease) { + navigate('/'); + return; + } + return () => { + clear(); + }; + }, []); + + const queueFlashing = (selectedDevices: SelectedDevice[]) => { + clear(); + const firmwareFile = currentFirmwareRelease?.firmwareFile; + if (!firmwareFile) throw new Error('invalid state - no firmware file'); + const requests = getFlashingRequests( + selectedDevices, + [{ isFirmware: true, firmwareId: '', url: firmwareFile, offset: 0 }], + { wifi: undefined, alonePage: false, progress: 0 }, // we do not use serial + null // we do not use serial + ); + + requests.forEach((req) => { + sendRPCPacket(RpcMessage.FirmwareUpdateRequest, req); + }); + }; + + const trackerWithErrors = useMemo( + () => + Object.keys(status).filter((id) => + firmwareUpdateErrorStatus.includes(status[id].status) + ), + [status] + ); + + const hasPendingTrackers = useMemo( + () => + Object.keys(status).filter((id) => + [ + FirmwareUpdateStatus.NEED_MANUAL_REBOOT, + FirmwareUpdateStatus.DOWNLOADING, + FirmwareUpdateStatus.AUTHENTICATING, + FirmwareUpdateStatus.REBOOTING, + FirmwareUpdateStatus.SYNCING_WITH_MCU, + FirmwareUpdateStatus.UPLOADING, + FirmwareUpdateStatus.PROVISIONING, + ].includes(status[id].status) + ).length > 0, + [status] + ); + + const shouldShowRebootWarning = useMemo( + () => + Object.keys(status).find((id) => + [ + FirmwareUpdateStatus.REBOOTING, + FirmwareUpdateStatus.UPLOADING, + ].includes(status[id].status) + ), + [status] + ); + + const retryError = () => { + const devices = trackerWithErrors.map((id) => { + const device = status[id]; + return { + type: device.type, + deviceId: id, + deviceNames: device.deviceNames, + }; + }); + + reset({ + selectedDevices: devices.reduce( + (curr, { deviceId }) => ({ ...curr, [deviceId]: true }), + {} + ), + }); + queueFlashing(devices); + }; + + const startUpdate = () => { + const selectedDevices = Object.keys(selectedDevicesForm) + .filter((d) => selectedDevicesForm[d]) + .map((id) => { + const device = devices.find(({ id: dId }) => id === dId?.id.toString()); + + if (!device) throw new Error('no device found'); + return { + type: FirmwareUpdateMethod.OTAFirmwareUpdate, + deviceId: id, + deviceNames: deviceNames(device, l10n), + }; + }); + if (!selectedDevices) + throw new Error('invalid state - no selected devices'); + setSelectedDevices(selectedDevices); + queueFlashing(selectedDevices); + }; + + const canStartUpdate = + isValid && + devices.length !== 0 && + !hasPendingTrackers && + trackerWithErrors.length === 0; + const canRetry = + isValid && devices.length !== 0 && trackerWithErrors.length !== 0; + + const statusKeys = Object.keys(status); + + return ( +
    +
    + + + +
    +
    + + + + + + +
    + {devices.length === 0 && + !hasPendingTrackers && + statusKeys.length == 0 && ( + + Warning + + )} + {shouldShowRebootWarning && ( + + Warning + + )} +
    + {statusKeys.length > 0 ? ( + + ) : ( + + )} + {devices.length === 0 && statusKeys.length === 0 && ( +
    + + + + +
    + )} +
    +
    +
    +
    + + + +
    + + {currentFirmwareRelease?.changelog} + +
    +
    +
    +
    + + + + + + +
    +
    +
    + ); +} diff --git a/gui/src/components/home/Home.tsx b/gui/src/components/home/Home.tsx index 7af6ae6bf4..cb5bbd60b2 100644 --- a/gui/src/components/home/Home.tsx +++ b/gui/src/components/home/Home.tsx @@ -51,7 +51,7 @@ export function Home() {
    status.prioritized) .length === 0 && 'hidden' )} @@ -70,7 +70,7 @@ export function Home() { ))}
    -
    +
    {trackers.length === 0 && (
    @@ -80,7 +80,7 @@ export function Home() { )} {!config?.debug && trackers.length > 0 && ( -
    +
    {trackers.map(({ tracker, device }, index) => ( sendToSettings(tracker)} smol + showUpdates interactable warning={Object.values(statuses).some((status) => trackerStatusRelated(tracker, status) diff --git a/gui/src/components/onboarding/pages/ConnectTracker.tsx b/gui/src/components/onboarding/pages/ConnectTracker.tsx index cc93ba47fe..71b1214632 100644 --- a/gui/src/components/onboarding/pages/ConnectTracker.tsx +++ b/gui/src/components/onboarding/pages/ConnectTracker.tsx @@ -3,11 +3,9 @@ import classNames from 'classnames'; import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { - AddUnknownDeviceRequestT, RpcMessage, StartWifiProvisioningRequestT, StopWifiProvisioningRequestT, - UnknownDeviceHandshakeNotificationT, WifiProvisioningStatus, WifiProvisioningStatusResponseT, } from 'solarxr-protocol'; @@ -97,15 +95,6 @@ export function ConnectTrackersPage() { } ); - useRPCPacket( - RpcMessage.UnknownDeviceHandshakeNotification, - ({ macAddress }: UnknownDeviceHandshakeNotificationT) => - sendRPCPacket( - RpcMessage.AddUnknownDeviceRequest, - new AddUnknownDeviceRequestT(macAddress) - ) - ); - const isError = provisioningStatus === WifiProvisioningStatus.CONNECTION_ERROR || provisioningStatus === WifiProvisioningStatus.COULD_NOT_FIND_SERVER; diff --git a/gui/src/components/onboarding/pages/Home.tsx b/gui/src/components/onboarding/pages/Home.tsx index b9df6460af..3c33c21d3b 100644 --- a/gui/src/components/onboarding/pages/Home.tsx +++ b/gui/src/components/onboarding/pages/Home.tsx @@ -13,7 +13,7 @@ export function HomePage() { return ( <> -
    +
    diff --git a/gui/src/components/settings/SettingsLayout.tsx b/gui/src/components/settings/SettingsLayout.tsx index 491d53a8fc..40b6c37e72 100644 --- a/gui/src/components/settings/SettingsLayout.tsx +++ b/gui/src/components/settings/SettingsLayout.tsx @@ -40,6 +40,10 @@ export function SettingSelectorMobile() { label: l10n.getString('settings-sidebar-serial'), value: { url: '/settings/serial' }, }, + { + label: l10n.getString('settings-sidebar-firmware-tool'), + value: { url: '/settings/firmware-tool' }, + }, { label: l10n.getString('settings-sidebar-advanced'), value: { url: '/settings/advanced' }, @@ -99,7 +103,7 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
    -
    +
    {l10n.getString('settings-sidebar-serial')} + + {l10n.getString('settings-sidebar-firmware-tool')} +
    diff --git a/gui/src/components/tracker/TrackerCard.tsx b/gui/src/components/tracker/TrackerCard.tsx index 044647d3db..38a4c7f6c0 100644 --- a/gui/src/components/tracker/TrackerCard.tsx +++ b/gui/src/components/tracker/TrackerCard.tsx @@ -11,6 +11,10 @@ import { TrackerStatus } from './TrackerStatus'; import classNames from 'classnames'; import { useTracker } from '@/hooks/tracker'; import { BodyPartIcon } from '@/components/commons/BodyPartIcon'; +import { DownloadIcon } from '@/components/commons/icon/DownloadIcon'; +import { Link } from 'react-router-dom'; +import { useAppContext } from '@/hooks/app'; +import { checkForUpdate } from '@/components/firmware-update/FirmwareUpdate'; function TrackerBig({ device, @@ -122,6 +126,7 @@ export function TrackerCard({ bg = 'bg-background-60', shakeHighlight = true, warning = false, + showUpdates = false, }: { tracker: TrackerDataT; device?: DeviceDataT; @@ -132,33 +137,51 @@ export function TrackerCard({ shakeHighlight?: boolean; onClick?: MouseEventHandler; warning?: boolean; + showUpdates?: boolean; }) { + const { currentFirmwareRelease } = useAppContext(); const { useVelocity } = useTracker(tracker); const velocity = useVelocity(); return ( -
    - {smol && } - {!smol && } +
    +
    + {smol && } + {!smol && } +
    + {showUpdates && + tracker.status !== TrackerStatusEnum.DISCONNECTED && + currentFirmwareRelease && + device?.hardwareInfo && + checkForUpdate(currentFirmwareRelease, device.hardwareInfo) && ( + +
    +
    +
    + +
    +
    + + )}
    ); } diff --git a/gui/src/components/tracker/TrackerSettings.tsx b/gui/src/components/tracker/TrackerSettings.tsx index ccbc66fb06..0062943df5 100644 --- a/gui/src/components/tracker/TrackerSettings.tsx +++ b/gui/src/components/tracker/TrackerSettings.tsx @@ -1,4 +1,4 @@ -import { useLocalization } from '@fluent/react'; +import { Localized, useLocalization } from '@fluent/react'; import classNames from 'classnames'; import { IPv4 } from 'ip-num/IPNumber'; import { useEffect, useMemo, useState } from 'react'; @@ -6,6 +6,7 @@ import { useForm } from 'react-hook-form'; import { useParams } from 'react-router-dom'; import { AssignTrackerRequestT, + BoardType, BodyPart, ForgetDeviceRequestT, ImuType, @@ -36,6 +37,8 @@ import { TrackerCard } from './TrackerCard'; import { Quaternion } from 'three'; import { useAppContext } from '@/hooks/app'; import { MagnetometerToggleSetting } from '@/components/settings/pages/MagnetometerToggleSetting'; +import semver from 'semver'; +import { checkForUpdate } from '@/components/firmware-update/FirmwareUpdate'; const rotationsLabels: [Quaternion, string][] = [ [rotationToQuatMap.BACK, 'tracker-rotation-back'], @@ -149,6 +152,26 @@ export function TrackerSettingsPage() { } }, [firstLoad]); + const boardType = useMemo(() => { + if (tracker?.device?.hardwareInfo?.officialBoardType) { + return l10n.getString( + 'board_type-' + + BoardType[ + tracker?.device?.hardwareInfo?.officialBoardType ?? + BoardType.UNKNOWN + ] + ); + } else if (tracker?.device?.hardwareInfo?.boardType) { + return tracker?.device?.hardwareInfo?.boardType; + } else { + return '--'; + } + }, [ + tracker?.device?.hardwareInfo?.officialBoardType, + tracker?.device?.hardwareInfo?.boardType, + l10n, + ]); + const macAddress = useMemo(() => { if ( /(?:[a-zA-Z\d]{2}:){5}[a-zA-Z\d]{2}/.test( @@ -161,6 +184,18 @@ export function TrackerSettingsPage() { return null; }, [tracker?.device?.hardwareInfo?.hardwareIdentifier]); + const { currentFirmwareRelease } = useAppContext(); + + const needUpdate = + currentFirmwareRelease && + tracker?.device?.hardwareInfo && + checkForUpdate(currentFirmwareRelease, tracker?.device?.hardwareInfo); + const updateUnavailable = + tracker?.device?.hardwareInfo?.officialBoardType !== BoardType.SLIMEVR || + !semver.valid( + tracker?.device?.hardwareInfo?.firmwareVersion?.toString() ?? 'none' + ); + return (
    )} - {/*
    - Firmware version -
    - - {tracker?.device?.hardwareInfo?.firmwareVersion} - - - - - Up to date - + { +
    + + + Firmware version + + +
    + + v{tracker?.device?.hardwareInfo?.firmwareVersion} + + - + {updateUnavailable && ( + + Cannot be updated (DIY) + + )} + {!updateUnavailable && ( + <> + {!needUpdate && ( + + Up to date + + )} + {needUpdate && ( + + + New version available + + + )} + + )} +
    + + +
    - -
    */} + } +
    @@ -237,22 +306,6 @@ export function TrackerSettingsPage() { ).toString()}
    -
    - - {l10n.getString('tracker-infos-version')} - - - {tracker?.device?.hardwareInfo?.firmwareVersion || '--'} - -
    - {/*
    - - {l10n.getString('tracker-infos-hardware_rev')} - - - {tracker?.device?.hardwareInfo?.hardwareRevision || '--'} - -
    */}
    {l10n.getString('tracker-infos-hardware_identifier')} @@ -285,9 +338,7 @@ export function TrackerSettingsPage() { {l10n.getString('tracker-infos-board_type')} - - {tracker?.device?.hardwareInfo?.boardType || '--'} - + {boardType}
    diff --git a/gui/src/firmware-tool-api/firmwareToolComponents.ts b/gui/src/firmware-tool-api/firmwareToolComponents.ts new file mode 100644 index 0000000000..12c65bab12 --- /dev/null +++ b/gui/src/firmware-tool-api/firmwareToolComponents.ts @@ -0,0 +1,659 @@ +/** + * Generated by @openapi-codegen + * + * @version 0.0.1 + */ +import * as reactQuery from '@tanstack/react-query'; +import { useFirmwareToolContext, FirmwareToolContext } from './firmwareToolContext'; +import type * as Fetcher from './firmwareToolFetcher'; +import { firmwareToolFetch } from './firmwareToolFetcher'; +import type * as Schemas from './firmwareToolSchemas'; + +export type GetIsCompatibleVersionPathParams = { + version: string; +}; + +export type GetIsCompatibleVersionError = Fetcher.ErrorWrapper; + +export type GetIsCompatibleVersionVariables = { + pathParams: GetIsCompatibleVersionPathParams; +} & FirmwareToolContext['fetcherOptions']; + +/** + * Is this api compatible with the server version given + */ +export const fetchGetIsCompatibleVersion = ( + variables: GetIsCompatibleVersionVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + Schemas.VerionCheckResponse, + GetIsCompatibleVersionError, + undefined, + {}, + {}, + GetIsCompatibleVersionPathParams + >({ url: '/is-compatible/{version}', method: 'get', ...variables, signal }); + +/** + * Is this api compatible with the server version given + */ +export const useGetIsCompatibleVersion = ( + variables: GetIsCompatibleVersionVariables, + options?: Omit< + reactQuery.UseQueryOptions< + Schemas.VerionCheckResponse, + GetIsCompatibleVersionError, + TData + >, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery< + Schemas.VerionCheckResponse, + GetIsCompatibleVersionError, + TData + >({ + queryKey: queryKeyFn({ + path: '/is-compatible/{version}', + operationId: 'getIsCompatibleVersion', + variables, + }), + queryFn: ({ signal }) => + fetchGetIsCompatibleVersion({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type GetFirmwaresError = Fetcher.ErrorWrapper; + +export type GetFirmwaresResponse = Schemas.FirmwareDTO[]; + +export type GetFirmwaresVariables = FirmwareToolContext['fetcherOptions']; + +/** + * List all the built firmwares + */ +export const fetchGetFirmwares = ( + variables: GetFirmwaresVariables, + signal?: AbortSignal +) => + firmwareToolFetch({ + url: '/firmwares', + method: 'get', + ...variables, + signal, + }); + +/** + * List all the built firmwares + */ +export const useGetFirmwares = ( + variables: GetFirmwaresVariables, + options?: Omit< + reactQuery.UseQueryOptions, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery({ + queryKey: queryKeyFn({ + path: '/firmwares', + operationId: 'getFirmwares', + variables, + }), + queryFn: ({ signal }) => + fetchGetFirmwares({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type PostFirmwaresBuildError = Fetcher.ErrorWrapper<{ + status: 400; + payload: Schemas.VersionNotFoundExeption; +}>; + +export type PostFirmwaresBuildVariables = { + body: Schemas.CreateBuildFirmwareDTO; +} & FirmwareToolContext['fetcherOptions']; + +/** + * Build a firmware from the requested configuration + */ +export const fetchPostFirmwaresBuild = ( + variables: PostFirmwaresBuildVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + Schemas.BuildResponseDTO, + PostFirmwaresBuildError, + Schemas.CreateBuildFirmwareDTO, + {}, + {}, + {} + >({ url: '/firmwares/build', method: 'post', ...variables, signal }); + +/** + * Build a firmware from the requested configuration + */ +export const usePostFirmwaresBuild = ( + options?: Omit< + reactQuery.UseMutationOptions< + Schemas.BuildResponseDTO, + PostFirmwaresBuildError, + PostFirmwaresBuildVariables + >, + 'mutationFn' + > +) => { + const { fetcherOptions } = useFirmwareToolContext(); + return reactQuery.useMutation< + Schemas.BuildResponseDTO, + PostFirmwaresBuildError, + PostFirmwaresBuildVariables + >({ + mutationFn: (variables: PostFirmwaresBuildVariables) => + fetchPostFirmwaresBuild({ ...fetcherOptions, ...variables }), + ...options, + }); +}; + +export type GetFirmwaresBuildStatusIdPathParams = { + id: string; +}; + +export type GetFirmwaresBuildStatusIdError = Fetcher.ErrorWrapper; + +export type GetFirmwaresBuildStatusIdVariables = { + pathParams: GetFirmwaresBuildStatusIdPathParams; +} & FirmwareToolContext['fetcherOptions']; + +/** + * Get the build status of a firmware + * This is a SSE (Server Sent Event) + * you can use the web browser api to check for the build status and update the ui in real time + */ +export const fetchGetFirmwaresBuildStatusId = ( + variables: GetFirmwaresBuildStatusIdVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + Schemas.ObservableType, + GetFirmwaresBuildStatusIdError, + undefined, + {}, + {}, + GetFirmwaresBuildStatusIdPathParams + >({ + url: '/firmwares/build-status/{id}', + method: 'get', + ...variables, + signal, + }); + +/** + * Get the build status of a firmware + * This is a SSE (Server Sent Event) + * you can use the web browser api to check for the build status and update the ui in real time + */ +export const useGetFirmwaresBuildStatusId = ( + variables: GetFirmwaresBuildStatusIdVariables, + options?: Omit< + reactQuery.UseQueryOptions< + Schemas.ObservableType, + GetFirmwaresBuildStatusIdError, + TData + >, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery< + Schemas.ObservableType, + GetFirmwaresBuildStatusIdError, + TData + >({ + queryKey: queryKeyFn({ + path: '/firmwares/build-status/{id}', + operationId: 'getFirmwaresBuildStatusId', + variables, + }), + queryFn: ({ signal }) => + fetchGetFirmwaresBuildStatusId({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type GetFirmwaresBoardsError = Fetcher.ErrorWrapper; + +export type GetFirmwaresBoardsResponse = string[]; + +export type GetFirmwaresBoardsVariables = FirmwareToolContext['fetcherOptions']; + +/** + * List all the possible board types + */ +export const fetchGetFirmwaresBoards = ( + variables: GetFirmwaresBoardsVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + GetFirmwaresBoardsResponse, + GetFirmwaresBoardsError, + undefined, + {}, + {}, + {} + >({ url: '/firmwares/boards', method: 'get', ...variables, signal }); + +/** + * List all the possible board types + */ +export const useGetFirmwaresBoards = ( + variables: GetFirmwaresBoardsVariables, + options?: Omit< + reactQuery.UseQueryOptions< + GetFirmwaresBoardsResponse, + GetFirmwaresBoardsError, + TData + >, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery< + GetFirmwaresBoardsResponse, + GetFirmwaresBoardsError, + TData + >({ + queryKey: queryKeyFn({ + path: '/firmwares/boards', + operationId: 'getFirmwaresBoards', + variables, + }), + queryFn: ({ signal }) => + fetchGetFirmwaresBoards({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type GetFirmwaresVersionsError = Fetcher.ErrorWrapper; + +export type GetFirmwaresVersionsResponse = Schemas.ReleaseDTO[]; + +export type GetFirmwaresVersionsVariables = FirmwareToolContext['fetcherOptions']; + +/** + * List all the possible versions to build a firmware from + */ +export const fetchGetFirmwaresVersions = ( + variables: GetFirmwaresVersionsVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + GetFirmwaresVersionsResponse, + GetFirmwaresVersionsError, + undefined, + {}, + {}, + {} + >({ url: '/firmwares/versions', method: 'get', ...variables, signal }); + +/** + * List all the possible versions to build a firmware from + */ +export const useGetFirmwaresVersions = ( + variables: GetFirmwaresVersionsVariables, + options?: Omit< + reactQuery.UseQueryOptions< + GetFirmwaresVersionsResponse, + GetFirmwaresVersionsError, + TData + >, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery< + GetFirmwaresVersionsResponse, + GetFirmwaresVersionsError, + TData + >({ + queryKey: queryKeyFn({ + path: '/firmwares/versions', + operationId: 'getFirmwaresVersions', + variables, + }), + queryFn: ({ signal }) => + fetchGetFirmwaresVersions({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type GetFirmwaresImusError = Fetcher.ErrorWrapper; + +export type GetFirmwaresImusResponse = Schemas.Imudto[]; + +export type GetFirmwaresImusVariables = FirmwareToolContext['fetcherOptions']; + +/** + * List all the possible imus to use + */ +export const fetchGetFirmwaresImus = ( + variables: GetFirmwaresImusVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + GetFirmwaresImusResponse, + GetFirmwaresImusError, + undefined, + {}, + {}, + {} + >({ url: '/firmwares/imus', method: 'get', ...variables, signal }); + +/** + * List all the possible imus to use + */ +export const useGetFirmwaresImus = ( + variables: GetFirmwaresImusVariables, + options?: Omit< + reactQuery.UseQueryOptions, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery({ + queryKey: queryKeyFn({ + path: '/firmwares/imus', + operationId: 'getFirmwaresImus', + variables, + }), + queryFn: ({ signal }) => + fetchGetFirmwaresImus({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type GetFirmwaresBatteriesError = Fetcher.ErrorWrapper; + +export type GetFirmwaresBatteriesResponse = string[]; + +export type GetFirmwaresBatteriesVariables = FirmwareToolContext['fetcherOptions']; + +/** + * List all the battery types + */ +export const fetchGetFirmwaresBatteries = ( + variables: GetFirmwaresBatteriesVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + GetFirmwaresBatteriesResponse, + GetFirmwaresBatteriesError, + undefined, + {}, + {}, + {} + >({ url: '/firmwares/batteries', method: 'get', ...variables, signal }); + +/** + * List all the battery types + */ +export const useGetFirmwaresBatteries = ( + variables: GetFirmwaresBatteriesVariables, + options?: Omit< + reactQuery.UseQueryOptions< + GetFirmwaresBatteriesResponse, + GetFirmwaresBatteriesError, + TData + >, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery< + GetFirmwaresBatteriesResponse, + GetFirmwaresBatteriesError, + TData + >({ + queryKey: queryKeyFn({ + path: '/firmwares/batteries', + operationId: 'getFirmwaresBatteries', + variables, + }), + queryFn: ({ signal }) => + fetchGetFirmwaresBatteries({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type GetFirmwaresDefaultConfigBoardPathParams = { + board: + | 'BOARD_SLIMEVR' + | 'BOARD_NODEMCU' + | 'BOARD_WROOM32' + | 'BOARD_WEMOSD1MINI' + | 'BOARD_TTGO_TBASE' + | 'BOARD_ESP01' + | 'BOARD_LOLIN_C3_MINI' + | 'BOARD_BEETLE32C3' + | 'BOARD_ES32C3DEVKITM1'; +}; + +export type GetFirmwaresDefaultConfigBoardError = Fetcher.ErrorWrapper; + +export type GetFirmwaresDefaultConfigBoardVariables = { + pathParams: GetFirmwaresDefaultConfigBoardPathParams; +} & FirmwareToolContext['fetcherOptions']; + +/** + * Gives the default pins / configuration of a given board + */ +export const fetchGetFirmwaresDefaultConfigBoard = ( + variables: GetFirmwaresDefaultConfigBoardVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + Schemas.DefaultBuildConfigDTO, + GetFirmwaresDefaultConfigBoardError, + undefined, + {}, + {}, + GetFirmwaresDefaultConfigBoardPathParams + >({ + url: '/firmwares/default-config/{board}', + method: 'get', + ...variables, + signal, + }); + +/** + * Gives the default pins / configuration of a given board + */ +export const useGetFirmwaresDefaultConfigBoard = < + TData = Schemas.DefaultBuildConfigDTO, +>( + variables: GetFirmwaresDefaultConfigBoardVariables, + options?: Omit< + reactQuery.UseQueryOptions< + Schemas.DefaultBuildConfigDTO, + GetFirmwaresDefaultConfigBoardError, + TData + >, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery< + Schemas.DefaultBuildConfigDTO, + GetFirmwaresDefaultConfigBoardError, + TData + >({ + queryKey: queryKeyFn({ + path: '/firmwares/default-config/{board}', + operationId: 'getFirmwaresDefaultConfigBoard', + variables, + }), + queryFn: ({ signal }) => + fetchGetFirmwaresDefaultConfigBoard({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type GetFirmwaresIdPathParams = { + id: string; +}; + +export type GetFirmwaresIdError = Fetcher.ErrorWrapper<{ + status: 404; + payload: Schemas.HttpException; +}>; + +export type GetFirmwaresIdVariables = { + pathParams: GetFirmwaresIdPathParams; +} & FirmwareToolContext['fetcherOptions']; + +/** + * Get the inforamtions about a firmware from its id + * also provide more informations than the simple list, like pins and imus and files + */ +export const fetchGetFirmwaresId = ( + variables: GetFirmwaresIdVariables, + signal?: AbortSignal +) => + firmwareToolFetch< + Schemas.FirmwareDetailDTO, + GetFirmwaresIdError, + undefined, + {}, + {}, + GetFirmwaresIdPathParams + >({ url: '/firmwares/{id}', method: 'get', ...variables, signal }); + +/** + * Get the inforamtions about a firmware from its id + * also provide more informations than the simple list, like pins and imus and files + */ +export const useGetFirmwaresId = ( + variables: GetFirmwaresIdVariables, + options?: Omit< + reactQuery.UseQueryOptions, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery({ + queryKey: queryKeyFn({ + path: '/firmwares/{id}', + operationId: 'getFirmwaresId', + variables, + }), + queryFn: ({ signal }) => + fetchGetFirmwaresId({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type GetHealthError = Fetcher.ErrorWrapper; + +export type GetHealthVariables = FirmwareToolContext['fetcherOptions']; + +/** + * Gives the status of the api + * this endpoint will always return true + */ +export const fetchGetHealth = (variables: GetHealthVariables, signal?: AbortSignal) => + firmwareToolFetch({ + url: '/health', + method: 'get', + ...variables, + signal, + }); + +/** + * Gives the status of the api + * this endpoint will always return true + */ +export const useGetHealth = ( + variables: GetHealthVariables, + options?: Omit< + reactQuery.UseQueryOptions, + 'queryKey' | 'queryFn' | 'initialData' + > +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = useFirmwareToolContext(options); + return reactQuery.useQuery({ + queryKey: queryKeyFn({ + path: '/health', + operationId: 'getHealth', + variables, + }), + queryFn: ({ signal }) => + fetchGetHealth({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +}; + +export type QueryOperation = + | { + path: '/is-compatible/{version}'; + operationId: 'getIsCompatibleVersion'; + variables: GetIsCompatibleVersionVariables; + } + | { + path: '/firmwares'; + operationId: 'getFirmwares'; + variables: GetFirmwaresVariables; + } + | { + path: '/firmwares/build-status/{id}'; + operationId: 'getFirmwaresBuildStatusId'; + variables: GetFirmwaresBuildStatusIdVariables; + } + | { + path: '/firmwares/boards'; + operationId: 'getFirmwaresBoards'; + variables: GetFirmwaresBoardsVariables; + } + | { + path: '/firmwares/versions'; + operationId: 'getFirmwaresVersions'; + variables: GetFirmwaresVersionsVariables; + } + | { + path: '/firmwares/imus'; + operationId: 'getFirmwaresImus'; + variables: GetFirmwaresImusVariables; + } + | { + path: '/firmwares/batteries'; + operationId: 'getFirmwaresBatteries'; + variables: GetFirmwaresBatteriesVariables; + } + | { + path: '/firmwares/default-config/{board}'; + operationId: 'getFirmwaresDefaultConfigBoard'; + variables: GetFirmwaresDefaultConfigBoardVariables; + } + | { + path: '/firmwares/{id}'; + operationId: 'getFirmwaresId'; + variables: GetFirmwaresIdVariables; + } + | { + path: '/health'; + operationId: 'getHealth'; + variables: GetHealthVariables; + }; diff --git a/gui/src/firmware-tool-api/firmwareToolContext.ts b/gui/src/firmware-tool-api/firmwareToolContext.ts new file mode 100644 index 0000000000..77e9cd9148 --- /dev/null +++ b/gui/src/firmware-tool-api/firmwareToolContext.ts @@ -0,0 +1,99 @@ +import type { QueryKey, UseQueryOptions } from '@tanstack/react-query'; +import { QueryOperation } from './firmwareToolComponents'; + +export type FirmwareToolContext = { + fetcherOptions: { + /** + * Headers to inject in the fetcher + */ + headers?: {}; + /** + * Query params to inject in the fetcher + */ + queryParams?: {}; + }; + queryOptions: { + /** + * Set this to `false` to disable automatic refetching when the query mounts or changes query keys. + * Defaults to `true`. + */ + enabled?: boolean; + }; + /** + * Query key manager. + */ + queryKeyFn: (operation: QueryOperation) => QueryKey; +}; + +/** + * Context injected into every react-query hook wrappers + * + * @param queryOptions options from the useQuery wrapper + */ +export function useFirmwareToolContext< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + _queryOptions?: Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' + > +): FirmwareToolContext { + return { + fetcherOptions: {}, + queryOptions: {}, + queryKeyFn, + }; +} + +export const queryKeyFn = (operation: QueryOperation) => { + const queryKey: unknown[] = hasPathParams(operation) + ? operation.path + .split('/') + .filter(Boolean) + .map((i) => resolvePathParam(i, operation.variables.pathParams)) + : operation.path.split('/').filter(Boolean); + + if (hasQueryParams(operation)) { + queryKey.push(operation.variables.queryParams); + } + + if (hasBody(operation)) { + queryKey.push(operation.variables.body); + } + + return queryKey; +}; +// Helpers +const resolvePathParam = (key: string, pathParams: Record) => { + if (key.startsWith('{') && key.endsWith('}')) { + return pathParams[key.slice(1, -1)]; + } + return key; +}; + +const hasPathParams = ( + operation: QueryOperation +): operation is QueryOperation & { + variables: { pathParams: Record }; +} => { + return Boolean((operation.variables as any).pathParams); +}; + +const hasBody = ( + operation: QueryOperation +): operation is QueryOperation & { + variables: { body: Record }; +} => { + return Boolean((operation.variables as any).body); +}; + +const hasQueryParams = ( + operation: QueryOperation +): operation is QueryOperation & { + variables: { queryParams: Record }; +} => { + return Boolean((operation.variables as any).queryParams); +}; diff --git a/gui/src/firmware-tool-api/firmwareToolFetcher.ts b/gui/src/firmware-tool-api/firmwareToolFetcher.ts new file mode 100644 index 0000000000..c20cc4c6ba --- /dev/null +++ b/gui/src/firmware-tool-api/firmwareToolFetcher.ts @@ -0,0 +1,109 @@ +import { FirmwareToolContext } from './firmwareToolContext'; + +export const firmwareToolBaseUrl = + import.meta.env.VITE_FIRMWARE_TOOL_URL ?? 'http://localhost:3000'; +export const firmwareToolS3BaseUrl = + import.meta.env.VITE_FIRMWARE_TOOL_S3_URL ?? 'http://localhost:9099'; + +export type ErrorWrapper = TError | { status: 'unknown'; payload: string }; + +export type FirmwareToolFetcherOptions = { + url: string; + method: string; + body?: TBody; + headers?: THeaders; + queryParams?: TQueryParams; + pathParams?: TPathParams; + signal?: AbortSignal; +} & FirmwareToolContext['fetcherOptions']; + +export async function firmwareToolFetch< + TData, + TError, + TBody extends {} | FormData | undefined | null, + THeaders extends {}, + TQueryParams extends {}, + TPathParams extends {}, +>({ + url, + method, + body, + headers, + pathParams, + queryParams, + signal, +}: FirmwareToolFetcherOptions< + TBody, + THeaders, + TQueryParams, + TPathParams +>): Promise { + try { + const requestHeaders: HeadersInit = { + 'Content-Type': 'application/json', + ...headers, + }; + + /** + * As the fetch API is being used, when multipart/form-data is specified + * the Content-Type header must be deleted so that the browser can set + * the correct boundary. + * https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects#sending_files_using_a_formdata_object + */ + if (requestHeaders['Content-Type'].toLowerCase().includes('multipart/form-data')) { + delete requestHeaders['Content-Type']; + } + + const response = await window.fetch( + `${firmwareToolBaseUrl}${resolveUrl(url, queryParams, pathParams)}`, + { + signal, + method: method.toUpperCase(), + body: body + ? body instanceof FormData + ? body + : JSON.stringify(body) + : undefined, + headers: requestHeaders, + } + ); + if (!response.ok) { + let error: ErrorWrapper; + try { + error = await response.json(); + } catch (e) { + error = { + status: 'unknown' as const, + payload: + e instanceof Error ? `Unexpected error (${e.message})` : 'Unexpected error', + }; + } + + throw error; + } + + if (response.headers.get('content-type')?.includes('json')) { + return await response.json(); + } else { + // if it is not a json response, assume it is a blob and cast it to TData + return (await response.blob()) as unknown as TData; + } + } catch (e) { + let errorObject: Error = { + name: 'unknown' as const, + message: e instanceof Error ? `Network error (${e.message})` : 'Network error', + stack: e as string, + }; + throw errorObject; + } +} + +const resolveUrl = ( + url: string, + queryParams: Record = {}, + pathParams: Record = {} +) => { + let query = new URLSearchParams(queryParams).toString(); + if (query) query = `?${query}`; + return url.replace(/\{\w*\}/g, (key) => pathParams[key.slice(1, -1)]) + query; +}; diff --git a/gui/src/firmware-tool-api/firmwareToolSchemas.ts b/gui/src/firmware-tool-api/firmwareToolSchemas.ts new file mode 100644 index 0000000000..df109c7214 --- /dev/null +++ b/gui/src/firmware-tool-api/firmwareToolSchemas.ts @@ -0,0 +1,608 @@ +/** + * Generated by @openapi-codegen + * + * @version 0.0.1 + */ +export type VerionCheckResponse = { + success: boolean; + reason?: { + message: string; + versions: string; + }; +}; + +/** + * Root object declaring a built firmware + * this object contains: + * - the status of the build + * - the the repository and commit used as source + */ +export type FirmwareDTO = { + /** + * UUID of the firmware + * + * @format uuid + */ + id: string; + /** + * Id of the firmware version used. + * Usually the commit id of the source + * used to build the firmware + */ + releaseId: string; + /** + * Current status of the build + * this value will change during the build + * process + * + * BUILDING -> DONE \\ the firmwrare is build and ready + * -> FAILED \\ the build failled and will be garbage collected + */ + buildStatus: + | 'CREATING_BUILD_FOLDER' + | 'DOWNLOADING_FIRMWARE' + | 'EXTRACTING_FIRMWARE' + | 'SETTING_UP_DEFINES' + | 'BUILDING' + | 'SAVING' + | 'DONE' + | 'ERROR'; + /** + * The repository and branch used as source of the firmware + */ + buildVersion: string; + /** + * The date of creation of this firmware build + * + * @format date-time + */ + createdAt: string; +}; + +export type BuildResponseDTO = { + /** + * Id of the firmware + * + * @format uuid + */ + id: string; + /** + * Build status of the firmware + */ + status: + | 'CREATING_BUILD_FOLDER' + | 'DOWNLOADING_FIRMWARE' + | 'EXTRACTING_FIRMWARE' + | 'SETTING_UP_DEFINES' + | 'BUILDING' + | 'SAVING' + | 'DONE' + | 'ERROR'; + /** + * List of built firmware files, only set if the build succeeded + */ + firmwareFiles?: FirmwareFileDTO[]; +}; + +export type FirmwareFileDTO = { + /** + * Url to the file + */ + url: string; + /** + * Address of the partition + */ + offset: number; + /** + * Is this file the main firmware + */ + isFirmware: boolean; + /** + * Id of the linked firmware + * + * @format uuid + */ + firmwareId: string; +}; + +export type CreateBuildFirmwareDTO = { + /** + * Repository of the firmware used + */ + version: string; + /** + * Board config, used to declare the pins used by the board + */ + boardConfig: CreateBoardConfigDTO; + /** + * Imu config, list of all the imus used and their pins + * + * @minItems 1 + */ + imusConfig: CreateImuConfigDTO[]; +}; + +export type CreateBoardConfigDTO = { + /** + * Type of the board + */ + type: + | 'BOARD_SLIMEVR' + | 'BOARD_NODEMCU' + | 'BOARD_WROOM32' + | 'BOARD_WEMOSD1MINI' + | 'BOARD_TTGO_TBASE' + | 'BOARD_ESP01' + | 'BOARD_LOLIN_C3_MINI' + | 'BOARD_BEETLE32C3' + | 'BOARD_ES32C3DEVKITM1'; + /** + * Pin address of the indicator LED + */ + ledPin: string; + /** + * Is the indicator LED enabled + */ + enableLed: boolean; + /** + * Is the led inverted + */ + ledInverted: boolean; + /** + * Pin address of the battery indicator + */ + batteryPin: string; + /** + * Type of battery + */ + batteryType: 'BAT_EXTERNAL' | 'BAT_INTERNAL' | 'BAT_MCP3021' | 'BAT_INTERNAL_MCP3021'; + /** + * Array of the different battery resistors, [indicator, SHIELD_R1, SHIELD_R2] + * + * @minItems 3 + * @maxItems 3 + */ + batteryResistances: number[]; +}; + +export type CreateImuConfigDTO = { + /** + * Type of the imu + */ + type: + | 'IMU_BNO085' + | 'IMU_MPU9250' + | 'IMU_MPU6500' + | 'IMU_BNO080' + | 'IMU_BNO055' + | 'IMU_BNO086' + | 'IMU_MPU6050' + | 'IMU_BMI160' + | 'IMU_ICM20948' + | 'IMU_BMI270'; + /** + * Pin address of the imu int pin + * not all imus use it + */ + intPin: string | null; + /** + * Rotation of the imu in degrees + */ + rotation: number; + /** + * Pin address of the scl pin + */ + sclPin: string; + /** + * Pin address of the sda pin + */ + sdaPin: string; + /** + * Is this imu optionnal + * Allows for extensions to be unplugged + */ + optional: boolean; +}; + +export type VersionNotFoundExeption = { + cause: void; + name: string; + message: string; + stack?: string; +}; + +/** + * A representation of any set of values over any amount of time. This is the most basic building block + * of RxJS. + */ +export type ObservableType = { + /** + * @deprecated true + */ + source?: Observableany; + /** + * @deprecated true + */ + operator?: OperatoranyType; +}; + +/** + * A representation of any set of values over any amount of time. This is the most basic building block + * of RxJS. + */ +export type Observableany = { + /** + * @deprecated true + */ + source?: Observableany; + /** + * @deprecated true + */ + operator?: Operatoranyany; +}; + +/** + * * + */ +export type Operatoranyany = {}; + +/** + * * + */ +export type OperatoranyType = {}; + +export type ReleaseDTO = { + /** + * id of the release, usually the commit id + */ + id: string; + /** + * url of the release + */ + url: string; + /** + * name of the release + */ + name: string; + /** + * url of the source archive + */ + zipball_url: string; + /** + * Is this release a pre release + */ + prerelease: boolean; + /** + * Is this release a draft + */ + draft: boolean; +}; + +export type Imudto = { + /** + * Type of the imu + */ + type: + | 'IMU_BNO085' + | 'IMU_MPU9250' + | 'IMU_MPU6500' + | 'IMU_BNO080' + | 'IMU_BNO055' + | 'IMU_BNO086' + | 'IMU_MPU6050' + | 'IMU_BMI160' + | 'IMU_ICM20948' + | 'IMU_BMI270'; + /** + * Does that imu type require a int pin + */ + hasIntPin: boolean; + /** + * First address of the imu + */ + imuStartAddress: number; + /** + * Increment of the address for each new imus + */ + addressIncrement: number; +}; + +export type DefaultBuildConfigDTO = { + /** + * Default config of the selected board + * contains all the default pins information about the selected board + */ + boardConfig: CreateBoardConfigDTO; + /** + * Inform the flashing utility that the user need to press the boot (or Flash) button + * on the tracker + */ + needBootPress?: boolean; + /** + * Inform the flashing utility that the board will need a reboot after + * being flashed + */ + needManualReboot?: boolean; + /** + * Will use the default values and skip the customisation options + */ + shouldOnlyUseDefaults?: boolean; + /** + * List of the possible imus pins, usually only two items will be sent + * + * @minItems 1 + */ + imuDefaults: IMUDefaultDTO[]; + /** + * Gives the offset of the firmare file in the eeprom. Used for flashing + */ + application_offset: number; +}; + +export type IMUDefaultDTO = { + /** + * Type of the imu + */ + type?: + | 'IMU_BNO085' + | 'IMU_MPU9250' + | 'IMU_MPU6500' + | 'IMU_BNO080' + | 'IMU_BNO055' + | 'IMU_BNO086' + | 'IMU_MPU6050' + | 'IMU_BMI160' + | 'IMU_ICM20948' + | 'IMU_BMI270'; + /** + * Pin address of the imu int pin + * not all imus use it + */ + intPin: string | null; + /** + * Rotation of the imu in degrees + */ + rotation?: number; + /** + * Pin address of the scl pin + */ + sclPin: string; + /** + * Pin address of the sda pin + */ + sdaPin: string; + /** + * Is this imu optionnal + * Allows for extensions to be unplugged + */ + optional: boolean; +}; + +export type BoardConfigDTONullable = { + /** + * Unique id of the board config, used for relations + * + * @format uuid + */ + id: string; + /** + * Type of the board + */ + type: + | 'BOARD_SLIMEVR' + | 'BOARD_NODEMCU' + | 'BOARD_WROOM32' + | 'BOARD_WEMOSD1MINI' + | 'BOARD_TTGO_TBASE' + | 'BOARD_ESP01' + | 'BOARD_LOLIN_C3_MINI' + | 'BOARD_BEETLE32C3' + | 'BOARD_ES32C3DEVKITM1'; + /** + * Pin address of the indicator LED + */ + ledPin: string; + /** + * Is the indicator LED enabled + */ + enableLed: boolean; + /** + * Is the led inverted + */ + ledInverted: boolean; + /** + * Pin address of the battery indicator + */ + batteryPin: string; + /** + * Type of battery + */ + batteryType: 'BAT_EXTERNAL' | 'BAT_INTERNAL' | 'BAT_MCP3021' | 'BAT_INTERNAL_MCP3021'; + /** + * Array of the different battery resistors, [indicator, SHIELD_R1, SHIELD_R2] + * + * @minItems 3 + * @maxItems 3 + */ + batteryResistances: number[]; + /** + * Id of the linked firmware, used for relations + * + * @format uuid + */ + firmwareId: string; +}; + +export type FirmwareDetailDTO = { + /** + * Pins informations about the board + */ + boardConfig: BoardConfigDTONullable; + /** + * List of the declared imus, and their pin configuration + * + * @minItems 1 + */ + imusConfig: ImuConfigDTO[]; + /** + * List of the built files / partitions with their url and offsets + */ + firmwareFiles: FirmwareFileDTO[]; + /** + * UUID of the firmware + * + * @format uuid + */ + id: string; + /** + * Id of the firmware version used. + * Usually the commit id of the source + * used to build the firmware + */ + releaseId: string; + /** + * Current status of the build + * this value will change during the build + * process + * + * BUILDING -> DONE \\ the firmwrare is build and ready + * -> FAILED \\ the build failled and will be garbage collected + */ + buildStatus: + | 'CREATING_BUILD_FOLDER' + | 'DOWNLOADING_FIRMWARE' + | 'EXTRACTING_FIRMWARE' + | 'SETTING_UP_DEFINES' + | 'BUILDING' + | 'SAVING' + | 'DONE' + | 'ERROR'; + /** + * The repository and branch used as source of the firmware + */ + buildVersion: string; + /** + * The date of creation of this firmware build + * + * @format date-time + */ + createdAt: string; +}; + +export type BoardConfigDTO = { + /** + * Unique id of the board config, used for relations + * + * @format uuid + */ + id: string; + /** + * Type of the board + */ + type: + | 'BOARD_SLIMEVR' + | 'BOARD_NODEMCU' + | 'BOARD_WROOM32' + | 'BOARD_WEMOSD1MINI' + | 'BOARD_TTGO_TBASE' + | 'BOARD_ESP01' + | 'BOARD_LOLIN_C3_MINI' + | 'BOARD_BEETLE32C3' + | 'BOARD_ES32C3DEVKITM1'; + /** + * Pin address of the indicator LED + */ + ledPin: string; + /** + * Is the indicator LED enabled + */ + enableLed: boolean; + /** + * Is the led inverted + */ + ledInverted: boolean; + /** + * Pin address of the battery indicator + */ + batteryPin: string; + /** + * Type of battery + */ + batteryType: 'BAT_EXTERNAL' | 'BAT_INTERNAL' | 'BAT_MCP3021' | 'BAT_INTERNAL_MCP3021'; + /** + * Array of the different battery resistors, [indicator, SHIELD_R1, SHIELD_R2] + * + * @minItems 3 + * @maxItems 3 + */ + batteryResistances: number[]; + /** + * Id of the linked firmware, used for relations + * + * @format uuid + */ + firmwareId: string; +}; + +export type ImuConfigDTO = { + /** + * Unique id of the config + * this probably will never be shown to the user as it is moslty use for relations + * + * @format uuid + */ + id: string; + /** + * Type of the imu + */ + type: + | 'IMU_BNO085' + | 'IMU_MPU9250' + | 'IMU_MPU6500' + | 'IMU_BNO080' + | 'IMU_BNO055' + | 'IMU_BNO086' + | 'IMU_MPU6050' + | 'IMU_BMI160' + | 'IMU_ICM20948' + | 'IMU_BMI270'; + /** + * Rotation of the imu in degrees + */ + rotation: number; + /** + * Pin address of the imu int pin + * not all imus use it + */ + intPin: string | null; + /** + * Pin address of the scl pin + */ + sclPin: string; + /** + * Pin address of the sda pin + */ + sdaPin: string; + /** + * Is this imu optionnal + * Allows for extensions to be unplugged + */ + optional: boolean; + /** + * id of the linked firmware, used for relations + * + * @format uuid + */ + firmwareId: string; +}; + +/** + * Defines the base Nest HTTP exception, which is handled by the default + * Exceptions Handler. + */ +export type HttpException = { + cause: void; + name: string; + message: string; + stack?: string; +}; diff --git a/gui/src/firmware-tool-api/firmwareToolUtils.ts b/gui/src/firmware-tool-api/firmwareToolUtils.ts new file mode 100644 index 0000000000..0b4d2f86df --- /dev/null +++ b/gui/src/firmware-tool-api/firmwareToolUtils.ts @@ -0,0 +1,15 @@ +type ComputeRange< + N extends number, + Result extends Array = [], +> = Result['length'] extends N + ? Result + : ComputeRange; + +export type ClientErrorStatus = Exclude< + ComputeRange<500>[number], + ComputeRange<400>[number] +>; +export type ServerErrorStatus = Exclude< + ComputeRange<600>[number], + ComputeRange<500>[number] +>; diff --git a/gui/src/hooks/app.ts b/gui/src/hooks/app.ts index a9f7758857..13e2150c9b 100644 --- a/gui/src/hooks/app.ts +++ b/gui/src/hooks/app.ts @@ -6,6 +6,7 @@ import { useEffect, useMemo, useReducer, + useState, } from 'react'; import { BoneT, @@ -23,6 +24,14 @@ import { useConfig } from './config'; import { useDataFeedConfig } from './datafeed-config'; import { useWebsocketAPI } from './websocket-api'; import { error } from '@/utils/logging'; +import { cacheWrap } from './cache'; + +export interface FirmwareRelease { + name: string; + version: string; + changelog: string; + firmwareFile: string; +} export interface FlatDeviceTracker { device?: DeviceDataT; @@ -39,6 +48,7 @@ export interface AppState { } export interface AppContext { + currentFirmwareRelease: FirmwareRelease | null; state: AppState; trackers: FlatDeviceTracker[]; dispatch: Dispatch; @@ -69,6 +79,8 @@ export function useProvideAppContext(): AppContext { datafeed: new DataFeedUpdateT(), ignoredTrackers: new Set(), }); + const [currentFirmwareRelease, setCurrentFirmwareRelease] = + useState(null); useEffect(() => { if (isConnected) { @@ -115,7 +127,55 @@ export function useProvideAppContext(): AppContext { } }); + useEffect(() => { + const fetchCurrentFirmwareRelease = async () => { + const releases: any[] | null = JSON.parse( + await cacheWrap( + 'firmware-releases', + () => + fetch('https://api.github.com/repos/SlimeVR/SlimeVR-Tracker-ESP/releases') + .then((res) => res.text()) + .catch(() => 'null'), + 1000 * 60 * 60 + ) + ); + if (!releases) return null; + + const firstRelease = releases.find( + (release) => + release.prerelease === false && + release.assets && + release.assets.find( + (asset: any) => + asset.name === 'BOARD_SLIMEVR-firmware.bin' && asset.browser_download_url + ) + ); + + let version = firstRelease.tag_name; + if (version.charAt(0) === 'v') { + version = version.substring(1); + } + + if (firstRelease) { + return { + name: firstRelease.name, + version, + changelog: firstRelease.body, + firmwareFile: firstRelease.assets.find( + (asset: any) => + asset.name === 'BOARD_SLIMEVR-firmware.bin' && asset.browser_download_url + ).browser_download_url, + }; + } else { + return null; + } + }; + + fetchCurrentFirmwareRelease().then((res) => setCurrentFirmwareRelease(res)); + }, []); + return { + currentFirmwareRelease, state, trackers, dispatch, diff --git a/gui/src/hooks/breakpoint.ts b/gui/src/hooks/breakpoint.ts index bb70683760..0bd522ea24 100644 --- a/gui/src/hooks/breakpoint.ts +++ b/gui/src/hooks/breakpoint.ts @@ -3,9 +3,8 @@ import { useMediaQuery } from 'react-responsive'; import tailwindConfig from '../../tailwind.config'; const fullConfig = resolveConfig(tailwindConfig as any); -const breakpoints = tailwindConfig.theme.screens; -type BreakpointKey = keyof typeof breakpoints; +type BreakpointKey = keyof typeof tailwindConfig.theme.screens; export function useBreakpoint(breakpointKey: K) { // FIXME There is a flickering issue caused by this, because isMobile is not resolved fast enough diff --git a/gui/src/hooks/cache.ts b/gui/src/hooks/cache.ts new file mode 100644 index 0000000000..371424d042 --- /dev/null +++ b/gui/src/hooks/cache.ts @@ -0,0 +1,68 @@ +import { isTauri } from '@tauri-apps/api/core'; +import { createStore } from '@tauri-apps/plugin-store'; + +interface CrossStorage { + set(key: string, value: string): Promise; + get(key: string): Promise; + delete(key: string): Promise; +} + +const localStore: CrossStorage = { + get: async (key) => localStorage.getItem(`slimevr-cache/${key}`), + set: async (key, value) => localStorage.setItem(`slimevr-cache/${key}`, value), + delete: async (key) => { + localStorage.removeItem(`slimevr-cache/${key}`); + return true; + }, +}; + +const store: CrossStorage = isTauri() + ? await createStore('gui-cache.dat', { autoSave: 100 as never }) + : localStore; + +export async function cacheGet(key: string): Promise { + const itemStr = await store.get(key); + + if (!itemStr) { + return null; + } + + const item = JSON.parse(itemStr); + const now = new Date(); + + if (now.getTime() > item.expiry) { + await store.delete(key); + return null; + } + + return item.value; +} + +export async function cacheSet(key: string, value: unknown, ttl: number | undefined) { + const now = new Date(); + const item = { + value, + expiry: ttl ? now.getTime() + ttl : 0, + }; + + await store.set(key, JSON.stringify(item)); +} + +export async function cacheWrap( + key: string, + orDefault: () => Promise, + ttl: number | undefined +) { + const realItem = await store.get(key); + if (!realItem) { + const defaultItem = await orDefault(); + await cacheSet(key, defaultItem, ttl); + return defaultItem; + } else { + return (await cacheGet(key))!; + } +} + +export async function cacheDelete(key: string) { + await store.delete(key); +} diff --git a/gui/src/hooks/firmware-tool.ts b/gui/src/hooks/firmware-tool.ts new file mode 100644 index 0000000000..8da569bc6b --- /dev/null +++ b/gui/src/hooks/firmware-tool.ts @@ -0,0 +1,252 @@ +import { createContext, useContext, useState } from 'react'; +import { + fetchGetFirmwaresDefaultConfigBoard, + useGetHealth, + useGetIsCompatibleVersion, +} from '@/firmware-tool-api/firmwareToolComponents'; +import { + BuildResponseDTO, + CreateBoardConfigDTO, + CreateBuildFirmwareDTO, + DefaultBuildConfigDTO, + FirmwareFileDTO, +} from '@/firmware-tool-api/firmwareToolSchemas'; +import { BoardPinsForm } from '@/components/firmware-tool/BoardPinsStep'; +import { DeepPartial } from 'react-hook-form'; +import { + BoardType, + DeviceIdT, + FirmwarePartT, + FirmwareUpdateMethod, + FirmwareUpdateRequestT, + FirmwareUpdateStatus, + OTAFirmwareUpdateT, + SerialDevicePortT, + SerialFirmwareUpdateT, +} from 'solarxr-protocol'; +import { OnboardingContext } from './onboarding'; + +export type PartialBuildFirmware = DeepPartial; +export type FirmwareBuildStatus = BuildResponseDTO; +export type SelectedDevice = { + type: FirmwareUpdateMethod; + deviceId: string | number; + deviceNames: string[]; +}; + +export const boardTypeToFirmwareToolBoardType: Record< + Exclude< + BoardType, + // This boards will not be handled by the firmware tool. + // These are either impossible to compile automatically or deprecated + BoardType.CUSTOM | BoardType.SLIMEVR_DEV | BoardType.SLIMEVR_LEGACY + >, + CreateBoardConfigDTO['type'] | null +> = { + [BoardType.UNKNOWN]: null, + [BoardType.NODEMCU]: 'BOARD_NODEMCU', + [BoardType.WROOM32]: 'BOARD_WROOM32', + [BoardType.WEMOSD1MINI]: 'BOARD_WEMOSD1MINI', + [BoardType.TTGO_TBASE]: 'BOARD_TTGO_TBASE', + [BoardType.ESP01]: 'BOARD_ESP01', + [BoardType.SLIMEVR]: 'BOARD_SLIMEVR', + [BoardType.LOLIN_C3_MINI]: 'BOARD_LOLIN_C3_MINI', + [BoardType.BEETLE32C3]: 'BOARD_BEETLE32C3', + [BoardType.ES32C3DEVKITM1]: 'BOARD_ES32C3DEVKITM1', +}; + +export const firmwareToolToBoardType: Record = + Object.fromEntries( + Object.entries(boardTypeToFirmwareToolBoardType).map((a) => a.reverse()) + ); + +export const firmwareUpdateErrorStatus = [ + FirmwareUpdateStatus.ERROR_AUTHENTICATION_FAILED, + FirmwareUpdateStatus.ERROR_DEVICE_NOT_FOUND, + FirmwareUpdateStatus.ERROR_DOWNLOAD_FAILED, + FirmwareUpdateStatus.ERROR_PROVISIONING_FAILED, + FirmwareUpdateStatus.ERROR_TIMEOUT, + FirmwareUpdateStatus.ERROR_UNKNOWN, + FirmwareUpdateStatus.ERROR_UNSUPPORTED_METHOD, + FirmwareUpdateStatus.ERROR_UPLOAD_FAILED, +]; + +export interface FirmwareToolContext { + selectBoard: (boardType: CreateBoardConfigDTO['type']) => Promise; + selectVersion: (version: CreateBuildFirmwareDTO['version']) => void; + updatePins: (form: BoardPinsForm) => void; + updateImus: (imus: CreateBuildFirmwareDTO['imusConfig']) => void; + setBuildStatus: (buildStatus: FirmwareBuildStatus) => void; + selectDevices: (device: SelectedDevice[] | null) => void; + retry: () => void; + buildStatus: FirmwareBuildStatus; + defaultConfig: DefaultBuildConfigDTO | null; + newConfig: PartialBuildFirmware | null; + selectedDevices: SelectedDevice[] | null; + isStepLoading: boolean; + isGlobalLoading: boolean; + isCompatible: boolean; + isError: boolean; +} + +export const FirmwareToolContextC = createContext( + undefined as any +); + +export function useFirmwareTool() { + const context = useContext(FirmwareToolContextC); + if (!context) { + throw new Error('useFirmwareTool must be within a FirmwareToolContext Provider'); + } + return context; +} + +export function useFirmwareToolContext(): FirmwareToolContext { + const [defaultConfig, setDefaultConfig] = useState( + null + ); + const [selectedDevices, selectDevices] = useState(null); + const [newConfig, setNewConfig] = useState({}); + const [isLoading, setLoading] = useState(false); + const { isError, isLoading: isInitialLoading, refetch } = useGetHealth({}); + const compatibilityCheckEnabled = !!__VERSION_TAG__; + const { isLoading: isCompatibilityLoading, data: compatibilityData } = + useGetIsCompatibleVersion( + { pathParams: { version: __VERSION_TAG__ } }, + { enabled: compatibilityCheckEnabled } + ); + const [buildStatus, setBuildStatus] = useState({ + status: 'CREATING_BUILD_FOLDER', + id: '', + }); + + return { + selectBoard: async (boardType: CreateBoardConfigDTO['type']) => { + setLoading(true); + const boardDefaults = await fetchGetFirmwaresDefaultConfigBoard({ + pathParams: { board: boardType }, + }); + setDefaultConfig(boardDefaults); + if (boardDefaults.shouldOnlyUseDefaults) { + setNewConfig((currConfig) => ({ + ...currConfig, + ...boardDefaults, + imusConfig: boardDefaults.imuDefaults, + })); + } else { + setNewConfig((currConfig) => ({ + ...currConfig, + boardConfig: { ...currConfig.boardConfig, type: boardType }, + imusConfig: [], + })); + } + setLoading(false); + }, + updatePins: (form: BoardPinsForm) => { + setNewConfig((currConfig) => { + return { + ...currConfig, + imusConfig: [...(currConfig?.imusConfig || [])], + boardConfig: { + ...currConfig.boardConfig, + ...form, + }, + }; + }); + }, + updateImus: (imus: CreateBuildFirmwareDTO['imusConfig']) => { + setNewConfig((currConfig) => { + return { + ...currConfig, + imusConfig: imus.map(({ rotation, ...fields }) => ({ + ...fields, + rotation: Number(rotation), + })), // Make sure that the rotation is handled as number + }; + }); + }, + retry: async () => { + setLoading(true); + await refetch(); + setLoading(false); + }, + selectVersion: (version: CreateBuildFirmwareDTO['version']) => { + setNewConfig((currConfig) => ({ ...currConfig, version })); + }, + setBuildStatus, + selectDevices, + selectedDevices, + buildStatus, + defaultConfig, + newConfig, + isStepLoading: isLoading, + isGlobalLoading: isInitialLoading || isCompatibilityLoading, + isCompatible: !compatibilityCheckEnabled || (compatibilityData?.success ?? false), + isError: isError || (!compatibilityData?.success && compatibilityCheckEnabled), + }; +} + +export const getFlashingRequests = ( + devices: SelectedDevice[], + firmwareFiles: FirmwareFileDTO[], + onboardingState: OnboardingContext['state'], + defaultConfig: DefaultBuildConfigDTO | null +) => { + const firmware = firmwareFiles.find(({ isFirmware }) => isFirmware); + if (!firmware) throw new Error('invalid state - no firmware to find'); + + const requests = []; + + for (const device of devices) { + switch (device.type) { + case FirmwareUpdateMethod.OTAFirmwareUpdate: { + const dId = new DeviceIdT(); + dId.id = +device.deviceId; + + const part = new FirmwarePartT(); + part.offset = 0; + part.url = firmware.url; + + const method = new OTAFirmwareUpdateT(); + method.deviceId = dId; + method.firmwarePart = part; + + const req = new FirmwareUpdateRequestT(); + req.method = method; + req.methodType = FirmwareUpdateMethod.OTAFirmwareUpdate; + requests.push(req); + break; + } + case FirmwareUpdateMethod.SerialFirmwareUpdate: { + const id = new SerialDevicePortT(); + id.port = device.deviceId.toString(); + + if (!onboardingState.wifi?.ssid || !onboardingState.wifi?.password) + throw new Error('invalid state, wifi should be set'); + + const method = new SerialFirmwareUpdateT(); + method.deviceId = id; + method.ssid = onboardingState.wifi.ssid; + method.password = onboardingState.wifi.password; + method.needManualReboot = defaultConfig?.needManualReboot ?? false; + + method.firmwarePart = firmwareFiles.map(({ offset, url }) => { + const part = new FirmwarePartT(); + part.offset = offset; + part.url = url; + return part; + }); + + const req = new FirmwareUpdateRequestT(); + req.method = method; + req.methodType = FirmwareUpdateMethod.SerialFirmwareUpdate; + requests.push(req); + break; + } + default: { + throw new Error('unsupported flashing method'); + } + } + } + return requests; +}; diff --git a/gui/src/hooks/onboarding.ts b/gui/src/hooks/onboarding.ts index 74f6ff7806..0ee82895fd 100644 --- a/gui/src/hooks/onboarding.ts +++ b/gui/src/hooks/onboarding.ts @@ -16,7 +16,7 @@ interface OnboardingState { export interface OnboardingContext { state: OnboardingState; applyProgress: (value: number) => void; - setWifiCredentials: (ssid: string, password: string) => void; + setWifiCredentials: (ssid: string, password?: string) => void; skipSetup: () => void; } @@ -68,8 +68,8 @@ export function useProvideOnboarding(): OnboardingContext { dispatch({ type: 'progress', value }); }, []); }, - setWifiCredentials: (ssid: string, password: string) => { - dispatch({ type: 'wifi-creds', ssid, password }); + setWifiCredentials: (ssid: string, password?: string) => { + dispatch({ type: 'wifi-creds', ssid, password: password ?? '' }); }, skipSetup: () => { setConfig({ doneOnboarding: true }); diff --git a/gui/src/hooks/tracker.ts b/gui/src/hooks/tracker.ts index 530fc6bc07..4795e3f023 100644 --- a/gui/src/hooks/tracker.ts +++ b/gui/src/hooks/tracker.ts @@ -1,8 +1,8 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { BodyPart, TrackerDataT, TrackerStatus } from 'solarxr-protocol'; +import { BodyPart, TrackerDataT, TrackerInfoT, TrackerStatus } from 'solarxr-protocol'; import { QuaternionFromQuatT, QuaternionToEulerDegrees } from '@/maths/quaternion'; import { useAppContext } from './app'; -import { useLocalization } from '@fluent/react'; +import { ReactLocalization, useLocalization } from '@fluent/react'; import { useDataFeedConfig } from './datafeed-config'; import { Quaternion, Vector3 } from 'three'; import { Vector3FromVec3fT } from '@/maths/vector3'; @@ -36,18 +36,19 @@ export function useTrackers() { }; } +export function getTrackerName(l10n: ReactLocalization, info: TrackerInfoT | null) { + if (info?.customName) return info?.customName; + if (info?.bodyPart) return l10n.getString('body_part-' + BodyPart[info?.bodyPart]); + return info?.displayName || 'NONE'; +} + export function useTracker(tracker: TrackerDataT) { const { l10n } = useLocalization(); const { feedMaxTps } = useDataFeedConfig(); return { useName: () => - useMemo(() => { - if (tracker.info?.customName) return tracker.info?.customName; - if (tracker.info?.bodyPart) - return l10n.getString('body_part-' + BodyPart[tracker.info?.bodyPart]); - return tracker.info?.displayName || 'NONE'; - }, [tracker.info]), + useMemo(() => getTrackerName(l10n, tracker.info), [tracker.info, l10n]), useRawRotationEulerDegrees: () => useMemo(() => QuaternionToEulerDegrees(tracker?.rotation), [tracker.rotation]), useRefAdjRotationEulerDegrees: () => diff --git a/gui/src/index.scss b/gui/src/index.scss index a183ab9174..88b094d10f 100644 --- a/gui/src/index.scss +++ b/gui/src/index.scss @@ -90,7 +90,7 @@ body { } :root { - overflow: hidden; + // overflow: hidden; -- NEVER EVER BRING THIS BACK <3 background: theme('colors.background.20'); --navbar-w: 101px; @@ -388,6 +388,14 @@ body { background: theme('colors.background.60'); } +.bg-background-60::-webkit-scrollbar-thumb:hover { + background: theme('colors.background.40'); +} + +.bg-background-60 { + scrollbar-color: theme('colors.background.50') transparent; +} + .dropdown-scroll { scrollbar-color: theme('colors.background.40') theme('colors.background.50'); } diff --git a/gui/tailwind.config.ts b/gui/tailwind.config.ts index d612158bf7..34ab01a0bb 100644 --- a/gui/tailwind.config.ts +++ b/gui/tailwind.config.ts @@ -1,5 +1,6 @@ import plugin from 'tailwindcss/plugin'; import forms from '@tailwindcss/forms'; +import typography from '@tailwindcss/typography'; import gradient from 'tailwind-gradient-mask-image'; import type { Config } from 'tailwindcss'; @@ -150,7 +151,7 @@ const colors = { 700: '#b3b3b3', 900: '#d8d8d8', }, - 'asexual': { + asexual: { 100: '#000000', 200: '#A3A3A3', 300: '#FFFFFF', @@ -162,9 +163,11 @@ const config = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { screens: { + 'mobile-settings': { raw: 'not (min-width: 900px)' }, nsmol: { raw: 'not (min-width: 525px)' }, smol: '525px', mobile: { raw: 'not (min-width: 800px)' }, + 'xs-settings': '900px', xs: '800px', nsm: { raw: 'not (min-width: 900px)' }, sm: '900px', @@ -245,6 +248,7 @@ const config = { plugins: [ forms, gradient, + typography, plugin(function ({ addUtilities }) { const textConfig = (fontSize: any, fontWeight: any) => ({ fontSize, diff --git a/gui/vite.config.ts b/gui/vite.config.ts index c9eb27becc..a61b5476e1 100644 --- a/gui/vite.config.ts +++ b/gui/vite.config.ts @@ -10,7 +10,9 @@ const versionTag = execSync('git --no-pager tag --sort -taggerdate --points-at H .split('\n')[0] .trim(); // If not empty then it's not clean -const gitClean = execSync('git status --porcelain').toString() ? false : true; +const gitCleanString = execSync('git status --porcelain').toString(); +const gitClean = gitCleanString ? false : true; +if (!gitClean) console.log('Git is dirty because of:\n' + gitCleanString); console.log(`version is ${versionTag || commitHash}${gitClean ? '' : '-dirty'}`); @@ -21,7 +23,7 @@ export function i18nHotReload(): PluginOption { handleHotUpdate({ file, server }) { if (file.endsWith('.ftl')) { console.log('Fluent files updated'); - server.ws.send({ + server.hot.send({ type: 'custom', event: 'locales-update', }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76ca90d8b8..786a9a5635 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,12 +29,21 @@ importers: '@formatjs/intl-localematcher': specifier: ^0.2.32 version: 0.2.32 + '@hookform/resolvers': + specifier: ^3.6.0 + version: 3.6.0(react-hook-form@7.53.0(react@18.3.1)) '@react-three/drei': specifier: ^9.114.3 - version: 9.114.3(@react-three/fiber@8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0))(@types/react@18.3.11)(@types/three@0.163.0)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0) + version: 9.114.5(@react-three/fiber@8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0))(@types/react@18.3.11)(@types/three@0.163.0)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0) '@react-three/fiber': specifier: ^8.17.10 version: 8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0) + '@tailwindcss/typography': + specifier: ^0.5.15 + version: 0.5.15(tailwindcss@3.4.14(ts-node@9.1.1(typescript@5.6.3))) + '@tanstack/react-query': + specifier: ^5.48.0 + version: 5.48.0(react@18.3.1) '@tauri-apps/api': specifier: ^2.0.2 version: 2.0.2 @@ -86,6 +95,9 @@ importers: react-hook-form: specifier: ^7.53.0 version: 7.53.0(react@18.3.1) + react-markdown: + specifier: ^9.0.1 + version: 9.0.1(@types/react@18.3.11)(react@18.3.1) react-modal: specifier: ^3.16.1 version: 3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -94,35 +106,47 @@ importers: version: 10.0.0(react@18.3.1) react-router-dom: specifier: ^6.26.2 - version: 6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + remark-gfm: + specifier: ^4.0.0 + version: 4.0.0 semver: specifier: ^7.6.3 version: 7.6.3 solarxr-protocol: specifier: file:../solarxr-protocol - version: file:solarxr-protocol + version: link:../solarxr-protocol three: specifier: ^0.163.0 version: 0.163.0 ts-pattern: specifier: ^5.4.0 - version: 5.4.0 + version: 5.5.0 typescript: specifier: ^5.6.3 version: 5.6.3 use-double-tap: specifier: ^1.3.6 version: 1.3.6(react@18.3.1) + yup: + specifier: ^1.4.0 + version: 1.4.0 devDependencies: '@dword-design/eslint-plugin-import-alias': specifier: ^4.0.9 version: 4.0.9 + '@openapi-codegen/cli': + specifier: ^2.0.2 + version: 2.0.2(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@openapi-codegen/typescript': + specifier: ^8.0.2 + version: 8.0.2 '@tailwindcss/forms': specifier: ^0.5.9 - version: 0.5.9(tailwindcss@3.4.13(ts-node@9.1.1(typescript@5.6.3))) + version: 0.5.9(tailwindcss@3.4.14(ts-node@9.1.1(typescript@5.6.3))) '@tauri-apps/cli': specifier: ^2.0.2 - version: 2.0.2 + version: 2.0.3 '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -152,13 +176,16 @@ importers: version: 7.18.0(eslint@8.57.1)(typescript@5.6.3) '@vitejs/plugin-react': specifier: ^4.3.2 - version: 4.3.2(vite@5.4.8(@types/node@20.14.2)(sass@1.79.4)(terser@5.31.1)) + version: 4.3.2(vite@5.4.9(@types/node@20.14.2)(sass@1.80.2)(terser@5.31.1)) autoprefixer: specifier: ^10.4.20 - version: 10.4.20(postcss@8.4.47) + version: 10.4.20(postcss@8.4.38) cross-env: specifier: ^7.0.3 version: 7.0.3 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 eslint: specifier: ^8.57.1 version: 8.57.1 @@ -180,6 +207,9 @@ importers: eslint-plugin-react-hooks: specifier: ^4.6.2 version: 4.6.2(eslint@8.57.1) + globals: + specifier: ^15.10.0 + version: 15.10.0 prettier: specifier: ^3.3.3 version: 3.3.3 @@ -188,7 +218,7 @@ importers: version: 5.12.0(rollup@4.24.0) sass: specifier: ^1.79.4 - version: 1.79.4 + version: 1.80.2 spdx-satisfies: specifier: ^5.0.1 version: 5.0.1 @@ -197,19 +227,22 @@ importers: version: 1.2.0 tailwindcss: specifier: ^3.4.13 - version: 3.4.13(ts-node@9.1.1(typescript@5.6.3)) + version: 3.4.14(ts-node@9.1.1(typescript@5.6.3)) ts-xor: specifier: ^1.3.0 version: 1.3.0 + typescript-eslint: + specifier: ^8.8.0 + version: 8.8.0(eslint@8.57.1)(typescript@5.6.3) vite: specifier: ^5.4.8 - version: 5.4.8(@types/node@20.14.2)(sass@1.79.4)(terser@5.31.1) + version: 5.4.9(@types/node@20.14.2)(sass@1.80.2)(terser@5.31.1) solarxr-protocol: dependencies: flatbuffers: specifier: ^22.10.26 - version: 22.12.6 + version: 22.10.26 devDependencies: '@mgit-at/typescript-flatbuffers-codegen': specifier: ^0.1.3 @@ -228,95 +261,200 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@apollo/client@3.10.6': + resolution: {integrity: sha512-3lLFGJtzC1/mEnK11BRf+Bf8536kBQUSB1G9yMtcRsxmY+tCKdTPzsP3fMUKy10BPIE0sDUY1pux3iMPIn2vow==} + peerDependencies: + graphql: ^15.0.0 || ^16.0.0 + graphql-ws: ^5.5.5 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + subscriptions-transport-ws: ^0.9.0 || ^0.11.0 + peerDependenciesMeta: + graphql-ws: + optional: true + react: + optional: true + react-dom: + optional: true + subscriptions-transport-ws: + optional: true + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + '@babel/code-frame@7.25.7': resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.25.7': - resolution: {integrity: sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==} + '@babel/compat-data@7.24.7': + resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.25.8': + resolution: {integrity: sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.24.7': + resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.25.8': + resolution: {integrity: sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==} engines: {node: '>=6.9.0'} - '@babel/core@7.25.7': - resolution: {integrity: sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==} + '@babel/generator@7.24.7': + resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} engines: {node: '>=6.9.0'} '@babel/generator@7.25.7': resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.24.7': + resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.25.7': resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==} engines: {node: '>=6.9.0'} + '@babel/helper-environment-visitor@7.24.7': + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.24.7': + resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.24.7': + resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.7': + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.25.7': resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.24.7': + resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.25.7': resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.25.7': - resolution: {integrity: sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==} + '@babel/helper-plugin-utils@7.24.7': + resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-simple-access@7.24.7': + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} '@babel/helper-simple-access@7.25.7': resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} engines: {node: '>=6.9.0'} + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.7': + resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.7': resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.7': resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.7': + resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.25.7': resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.24.7': + resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.25.7': resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==} engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.25.7': resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.25.7': - resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==} + '@babel/parser@7.24.7': + resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.25.8': + resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.25.7': - resolution: {integrity: sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg==} + '@babel/plugin-transform-react-jsx-self@7.24.7': + resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.25.7': - resolution: {integrity: sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w==} + '@babel/plugin-transform-react-jsx-source@7.24.7': + resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.25.7': - resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} + '@babel/runtime@7.24.7': + resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.24.7': + resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} engines: {node: '>=6.9.0'} '@babel/template@7.25.7': resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.24.7': + resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.7': resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} engines: {node: '>=6.9.0'} - '@babel/types@7.25.7': - resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} + '@babel/types@7.24.7': + resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.25.8': + resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} engines: {node: '>=6.9.0'} '@dword-design/dedent@0.7.0': @@ -477,8 +615,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.1': - resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + '@eslint-community/regexpp@4.10.1': + resolution: {integrity: sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/eslintrc@2.1.4': @@ -489,6 +627,9 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@exodus/schemasafe@1.3.0': + resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@fluent/bundle@0.18.0': resolution: {integrity: sha512-8Wfwu9q8F9g2FNnv82g6Ch/E1AW1wwljsUOolH5NEtdJdv0sZTuWvfCM7c3teB9dzNaJA8rn4khpidpozHWYEA==} engines: {node: '>=14.0.0', npm: '>=7.0.0'} @@ -512,6 +653,16 @@ packages: '@formatjs/intl-localematcher@0.2.32': resolution: {integrity: sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==} + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@hookform/resolvers@3.6.0': + resolution: {integrity: sha512-UBcpyOX3+RR+dNnqBd0lchXpoL8p4xC21XP8H6Meb8uve5Br1GCnmg0PcBoKKqPKgGu9GHQ/oygcmPrQhetwqw==} + peerDependencies: + react-hook-form: ^7.0.0 + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -544,8 +695,8 @@ packages: '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -557,8 +708,8 @@ packages: resolution: {integrity: sha512-sf9vaoiR/SR0dpV568GhsoLbd6659StJ4Gl9jszZL/bsJJaF5VmLYbI57OSI4JDm+L6d3osVMl9mkchox9j6/g==} hasBin: true - '@monogrid/gainmap-js@3.0.6': - resolution: {integrity: sha512-ireqJg7cw0tUn/JePDG8rAL7RyXgUKSDbjYdiygkrnye1WuKGLAWDBwF/ICwCwJ9iZBAF5caU8gSu+c34HLGdQ==} + '@monogrid/gainmap-js@3.0.5': + resolution: {integrity: sha512-53sCTG4FaJBaAq/tcufARtVYDMDGqyBT9i7F453pWGhZ5LqubDHDWtYoHo9VhQqMcHTEexdJqSsR58y+9HVmQA==} peerDependencies: three: '>= 0.159.0' @@ -578,6 +729,89 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@openapi-codegen/cli@2.0.2': + resolution: {integrity: sha512-uBk6yOBSBIgGWA2ok/IjBS03UwVAIpnan0lKz2sk3tsSe8rVIjOnQPxGYvSuByfxzdIu+nrPom2meqtcjlMvDQ==} + hasBin: true + + '@openapi-codegen/typescript@8.0.2': + resolution: {integrity: sha512-7X9WR+qlIMcMxiBgheGzyQcChLSPVqNYf9SAFJdTOJQLWfy+gaXiDonUC8WC7p6Hpz7eM6OLU1i7f/h+2RlH1w==} + + '@parcel/watcher-android-arm64@2.4.1': + resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.4.1': + resolution: {integrity: sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.4.1': + resolution: {integrity: sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.4.1': + resolution: {integrity: sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.4.1': + resolution: {integrity: sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.4.1': + resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.4.1': + resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.4.1': + resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.4.1': + resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.4.1': + resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.4.1': + resolution: {integrity: sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.4.1': + resolution: {integrity: sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.4.1': + resolution: {integrity: sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==} + engines: {node: '>= 10.0.0'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -610,8 +844,8 @@ packages: '@react-spring/types@9.6.1': resolution: {integrity: sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==} - '@react-three/drei@9.114.3': - resolution: {integrity: sha512-hPKPYmxTb2P1mOdhkouJbKJVcfFK5JmThr/97i4zkweoNzWBHNde090A6r0SFFb4tGaTtHM4/kyfVx5PrzjTMw==} + '@react-three/drei@9.114.5': + resolution: {integrity: sha512-nXD/wOwQVaaKF1WXG5Ah3ief+Mojm5YInlk91tanzEYdG+5Vhno34AFn3xt0XKMAaHA+Lkjfi+BpqnVama+JPA==} peerDependencies: '@react-three/fiber': '>=8.0' react: '>=18.0' @@ -646,8 +880,8 @@ packages: react-native: optional: true - '@remix-run/router@1.19.2': - resolution: {integrity: sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==} + '@remix-run/router@1.20.0': + resolution: {integrity: sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==} engines: {node: '>=14.0.0'} '@rollup/rollup-android-arm-eabi@4.24.0': @@ -733,76 +967,172 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@sindresorhus/is@5.6.0': + resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} + engines: {node: '>=14.16'} + + '@swc/core-darwin-arm64@1.6.5': + resolution: {integrity: sha512-RGQhMdni2v1/ANQ/2K+F+QYdzaucekYBewZcX1ogqJ8G5sbPaBdYdDN1qQ4kHLCIkPtGP6qC7c71qPEqL2RidQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.6.5': + resolution: {integrity: sha512-/pSN0/Jtcbbb9+ovS9rKxR3qertpFAM3OEJr/+Dh/8yy7jK5G5EFPIrfsw/7Q5987ERPIJIH6BspK2CBB2tgcg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.6.5': + resolution: {integrity: sha512-B0g/dROCE747RRegs/jPHuKJgwXLracDhnqQa80kFdgWEMjlcb7OMCgs5OX86yJGRS4qcYbiMGD0Pp7Kbqn3yw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.6.5': + resolution: {integrity: sha512-W8meapgXTq8AOtSvDG4yKR8ant2WWD++yOjgzAleB5VAC+oC+aa8YJROGxj8HepurU8kurqzcialwoMeq5SZZQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.6.5': + resolution: {integrity: sha512-jyCKqoX50Fg8rJUQqh4u5PqnE7nqYKXHjVH2WcYr114/MU21zlsI+YL6aOQU1XP8bJQ2gPQ1rnlnGJdEHiKS/w==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.6.5': + resolution: {integrity: sha512-G6HmUn/RRIlXC0YYFfBz2qh6OZkHS/KUPkhoG4X9ADcgWXXjOFh6JrefwsYj8VBAJEnr5iewzjNfj+nztwHaeA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.6.5': + resolution: {integrity: sha512-AQpBjBnelQDSbeTJA50AXdS6+CP66LsXIMNTwhPSgUfE7Bx1ggZV11Fsi4Q5SGcs6a8Qw1cuYKN57ZfZC5QOuA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.6.5': + resolution: {integrity: sha512-MZTWM8kUwS30pVrtbzSGEXtek46aXNb/mT9D6rsS7NvOuv2w+qZhjR1rzf4LNbbn5f8VnR4Nac1WIOYZmfC5ng==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.6.5': + resolution: {integrity: sha512-WZdu4gISAr3yOm1fVwKhhk6+MrP7kVX0KMP7+ZQFTN5zXQEiDSDunEJKVgjMVj3vlR+6mnAqa/L0V9Qa8+zKlQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.6.5': + resolution: {integrity: sha512-ezXgucnMTzlFIxQZw7ls/5r2hseFaRoDL04cuXUOs97E8r+nJSmFsRQm/ygH5jBeXNo59nyZCalrjJAjwfgACA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.6.5': + resolution: {integrity: sha512-tyVvUK/HDOUUsK6/GmWvnqUtD9oDpPUA4f7f7JCOV8hXxtfjMtAZeBKf93yrB1XZet69TDR7EN0hFC6i4MF0Ig==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.9': + resolution: {integrity: sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg==} + + '@szmarczak/http-timer@5.0.1': + resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} + engines: {node: '>=14.16'} + '@tailwindcss/forms@0.5.9': resolution: {integrity: sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==} peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20' + '@tailwindcss/typography@0.5.15': + resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20' + + '@tanstack/query-core@5.48.0': + resolution: {integrity: sha512-lZAfPPeVIqXCswE9SSbG33B6/91XOWt/Iq41bFeWb/mnHwQSIfFRbkS4bfs+WhIk9abRArF9Id2fp0Mgo+hq6Q==} + + '@tanstack/react-query@5.48.0': + resolution: {integrity: sha512-GDExbjYWzvDokyRqMSWXdrPiYpp95Aig0oeMIrxTaruOJJgWiWfUP//OAaowm2RrRkGVsavSZdko/XmIrrV2Nw==} + peerDependencies: + react: ^18.0.0 + '@tauri-apps/api@2.0.2': resolution: {integrity: sha512-3wSwmG+1kr6WrgAFKK5ijkNFPp8TT3FLj3YHUb5EwMO+3FxX4uWlfSWkeeBy+Kc1RsKzugtYLuuya+98Flj+3w==} - '@tauri-apps/cli-darwin-arm64@2.0.2': - resolution: {integrity: sha512-B+/a8Q6wAqmB4A4HVeK0oQP5TdQGKW60ZLOI9O2ktH2HPr9ETr3XkwXPuJ2uAOuGEgtRZHBgFOIgG000vMnKlg==} + '@tauri-apps/cli-darwin-arm64@2.0.3': + resolution: {integrity: sha512-jIsbxGWS+As1ZN7umo90nkql/ZAbrDK0GBT6UsgHSz5zSwwArICsZFFwE1pLZip5yoiV5mn3TGG2c1+v+0puzQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tauri-apps/cli-darwin-x64@2.0.2': - resolution: {integrity: sha512-kaurhn6XT4gAVCPAQSSHl/CHFxTS0ljc47N7iGTSlYJ03sCWPRZeNuVa/bn6rolz9MA2JfnRnFqB1pUL6jzp9Q==} + '@tauri-apps/cli-darwin-x64@2.0.3': + resolution: {integrity: sha512-ROITHtLTA1muyrwgyuwyasmaLCGtT4as/Kd1kerXaSDtFcYrnxiM984ZD0+FDUEDl5BgXtYa/sKKkKQFjgmM0A==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tauri-apps/cli-linux-arm-gnueabihf@2.0.2': - resolution: {integrity: sha512-bVrofjlacMxmGMcqK18iBW05tsZXOd19/MnqruFFcHSVjvkGGIXHMtUbMXnZNXBPkHDsnfytNtkY9SZGfCFaBA==} + '@tauri-apps/cli-linux-arm-gnueabihf@2.0.3': + resolution: {integrity: sha512-bQ3EZwCFfrLg/ZQ2I8sLuifSxESz4TP56SleTkKsPtTIZgNnKpM88PRDz4neiRroHVOq8NK0X276qi9LjGcXPw==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tauri-apps/cli-linux-arm64-gnu@2.0.2': - resolution: {integrity: sha512-7XCBn0TTBVQGnV42dXcbHPLg/9W8kJoVzuliIozvNGyRWxfXqDbQYzpI48HUQG3LgHMabcw8+pVZAfGhevLrCA==} + '@tauri-apps/cli-linux-arm64-gnu@2.0.3': + resolution: {integrity: sha512-aLfAA8P9OTErVUk3sATxtXqpAtlfDPMPp4fGjDysEELG/MyekGhmh2k/kG/i32OdPeCfO+Nr37wJksARJKubGw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tauri-apps/cli-linux-arm64-musl@2.0.2': - resolution: {integrity: sha512-1xi2SreGVlpAL68MCsDUY63rdItUdPZreXIAcOVqvUehcJRYOa1XGSBhrV0YXRgZeh0AtKC19z6PRzcv4rosZA==} + '@tauri-apps/cli-linux-arm64-musl@2.0.3': + resolution: {integrity: sha512-I4MVD7nf6lLLRmNQPpe5beEIFM6q7Zkmh77ROA5BNu/+vHNL5kiTMD+bmd10ZL2r753A6pO7AvqkIxcBuIl0tg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tauri-apps/cli-linux-x64-gnu@2.0.2': - resolution: {integrity: sha512-WVjwYzPWFqZVg1fx6KSU5w47Q0VbMyaCp34qs5EcS8EIU0/RnofdzqUoOYqvgGVgNgoz7Pj5dXK2SkS8BHXMmA==} + '@tauri-apps/cli-linux-x64-gnu@2.0.3': + resolution: {integrity: sha512-C6Jkx2zZGKkoi+sg5FK9GoH/0EvAaOgrZfF5azV5EALGba46g7VpWcZgp9zFUd7K2IzTi+0OOY8TQ2OVfKZgew==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tauri-apps/cli-linux-x64-musl@2.0.2': - resolution: {integrity: sha512-h5miE2mctgaQNn/BbG9o1pnJcrx+VGBi2A6JFqGu934lFgSV5+s28M8Gc8AF2JgFH4hQV4IuMkeSw8Chu5Dodg==} + '@tauri-apps/cli-linux-x64-musl@2.0.3': + resolution: {integrity: sha512-qi4ghmTfSAl+EEUDwmwI9AJUiOLNSmU1RgiGgcPRE+7A/W+Am9UnxYySAiRbB/gJgTl9sj/pqH5Y9duP1/sqHg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tauri-apps/cli-win32-arm64-msvc@2.0.2': - resolution: {integrity: sha512-2b8oO0+dYonahG5PfA/zoq0zlafLclfmXgqoWDZ++UiPtQHJNpNeEQ8GWbSFKGHQ494Jo6jHvazOojGRE1kqAg==} + '@tauri-apps/cli-win32-arm64-msvc@2.0.3': + resolution: {integrity: sha512-UXxHkYmFesC97qVmZre4vY7oDxRDtC2OeKNv0bH+iSnuUp/ROxzJYGyaelnv9Ybvgl4YVqDCnxgB28qMM938TA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tauri-apps/cli-win32-ia32-msvc@2.0.2': - resolution: {integrity: sha512-axgICLunFi0To3EibdCBgbST5RocsSmtM4c04+CbcX8WQQosJ9ziWlCSrrOTRr+gJERAMSvEyVUS98f6bWMw9A==} + '@tauri-apps/cli-win32-ia32-msvc@2.0.3': + resolution: {integrity: sha512-D+xoaa35RGlkXDpnL5uDTpj29untuC5Wp6bN9snfgFDagD0wnFfC8+2ZQGu16bD0IteWqDI0OSoIXhNvy+F+wg==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@tauri-apps/cli-win32-x64-msvc@2.0.2': - resolution: {integrity: sha512-JR17cM6+DyExZRgpXr2/DdqvcFYi/EKvQt8dI5R1/uQoesWd8jeNnrU7c1FG1Zmw9+pTzDztsNqEKsrNq2sNIg==} + '@tauri-apps/cli-win32-x64-msvc@2.0.3': + resolution: {integrity: sha512-eWV9XWb4dSYHXl13OtYWLjX1JHphUEkHkkGwJrhr8qFBm7RbxXxQvrsUEprSi51ug/dwJenjJgM4zR8By4htfw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tauri-apps/cli@2.0.2': - resolution: {integrity: sha512-R4ontHZvXORArERAHIidp5zRfZEshZczTiK+poslBv7AGKpQZoMw+E49zns7mOmP64i2Cq9Ci0pJvi4Rm8Okzw==} + '@tauri-apps/cli@2.0.3': + resolution: {integrity: sha512-JwEyhc5BAVpn4E8kxzY/h7+bVOiXQdudR1r3ODMfyyumZBfgIWqpD/WuTcPq6Yjchju1BSS+80jAE/oYwI/RKg==} engines: {node: '>= 10'} hasBin: true @@ -821,8 +1151,8 @@ packages: '@tauri-apps/plugin-store@2.0.0': resolution: {integrity: sha512-l4xsbxAXrKGdBdYNNswrLfcRv3v1kOatdycOcVPYW+jKwkznCr1HEOrPXkPhXsZLSLyYmNXpgfOmdSZNmcykDg==} - '@tweenjs/tween.js@23.1.3': - resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} + '@tweenjs/tween.js@23.1.2': + resolution: {integrity: sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -839,26 +1169,44 @@ packages: '@types/debounce@1.2.4': resolution: {integrity: sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==} + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/draco3d@1.4.10': resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} '@types/file-saver@2.0.7': resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/node@20.14.2': resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} '@types/offscreencanvas@2019.7.3': resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} - '@types/prop-types@15.7.13': - resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} '@types/react-dom@18.3.0': resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} @@ -887,8 +1235,17 @@ packages: '@types/three@0.163.0': resolution: {integrity: sha512-uIdDhsXRpQiBUkflBS/i1l3JX14fW6Ot9csed60nfbZNXHDTRsnV2xnTVwXcgbvTiboAR4IW+t+lTL5f1rqIqA==} - '@types/webxr@0.5.20': - resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/webxr@0.5.16': + resolution: {integrity: sha512-0E0Cl84FECtzrB4qG19TNTqpunw0F1YF0QZZnFMF6pDw1kNKJtrlTKlVB34stGIsHbZsYQ7H0tNjPfZftkHHoA==} + + '@types/yoga-layout@1.9.2': + resolution: {integrity: sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==} '@typescript-eslint/eslint-plugin@7.18.0': resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} @@ -901,6 +1258,17 @@ packages: typescript: optional: true + '@typescript-eslint/eslint-plugin@8.8.0': + resolution: {integrity: sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/parser@7.18.0': resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -911,10 +1279,24 @@ packages: typescript: optional: true + '@typescript-eslint/parser@8.8.0': + resolution: {integrity: sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/scope-manager@7.18.0': resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.8.0': + resolution: {integrity: sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/type-utils@7.18.0': resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -925,10 +1307,23 @@ packages: typescript: optional: true + '@typescript-eslint/type-utils@8.8.0': + resolution: {integrity: sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/types@7.18.0': resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.8.0': + resolution: {integrity: sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@7.18.0': resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -938,16 +1333,35 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@8.8.0': + resolution: {integrity: sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/utils@7.18.0': resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 + '@typescript-eslint/utils@8.8.0': + resolution: {integrity: sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/visitor-keys@7.18.0': resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@8.8.0': + resolution: {integrity: sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -965,19 +1379,43 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 + '@wry/caches@1.0.1': + resolution: {integrity: sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==} + engines: {node: '>=8'} + + '@wry/context@0.7.4': + resolution: {integrity: sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==} + engines: {node: '>=8'} + + '@wry/equality@0.5.7': + resolution: {integrity: sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==} + engines: {node: '>=8'} + + '@wry/trie@0.4.3': + resolution: {integrity: sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==} + engines: {node: '>=8'} + + '@wry/trie@0.5.0': + resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==} + engines: {node: '>=8'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.12.0: + resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==} engines: {node: '>=0.4.0'} hasBin: true ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} @@ -1021,9 +1459,6 @@ packages: aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} - array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} - array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -1067,6 +1502,14 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + auto-bind@4.0.0: + resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} + engines: {node: '>=8'} + autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -1074,16 +1517,12 @@ packages: peerDependencies: postcss: ^8.1.0 - available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.10.0: - resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} + axe-core@4.10.1: + resolution: {integrity: sha512-qPC9o+kD8Tir0lzNGLeghbOrWMr3ZJpaRlCIb6Uobt/7N4FiEDvqUMnxzCHRHmg8vOg14kr5gVNyScRmbMaJ9g==} engines: {node: '>=4'} axobject-query@4.1.0: @@ -1096,6 +1535,9 @@ packages: babel-plugin-module-resolver@5.0.2: resolution: {integrity: sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1122,6 +1564,11 @@ packages: browser-fs-access@0.35.0: resolution: {integrity: sha512-sLoadumpRfsjprP8XzVjpQc0jK8yqHBx0PtUTGYj2fftT+P/t+uyDAQdMgGAPKD011in/O+YYGh7fIs0oG/viw==} + browserslist@4.23.1: + resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + browserslist@4.24.0: resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1133,6 +1580,14 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + cacheable-lookup@7.0.0: + resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} + engines: {node: '>=14.16'} + + cacheable-request@10.2.14: + resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} + engines: {node: '>=14.16'} + cached-iterable@0.3.0: resolution: {integrity: sha512-MDqM6TpBVebZD4UDtmlFp8EjVtRcsB6xt9aRdWymjk0fWVUUGgmt/V7o0H0gkI2Tkvv8B0ucjidZm4mLosdlWw==} engines: {node: '>=8.9.0'} @@ -1144,6 +1599,9 @@ packages: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1152,13 +1610,23 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - camera-controls@2.9.0: - resolution: {integrity: sha512-TpCujnP0vqPppTXXJRYpvIy0xq9Tro6jQf2iYUxlDpPCNxkvE/XGaTuwIxnhINOkVP/ob2CRYXtY3iVYXeMEzA==} + camera-controls@2.8.5: + resolution: {integrity: sha512-7VTwRk7Nu1nRKsY7bEt9HVBfKt8DETvzyYhLN4OW26OByBayMDB5fUaNcPI+z++vG23RH5yqn6ZRhZcgLQy2rA==} peerDependencies: three: '>=0.126.1' - caniuse-lite@1.0.30001667: - resolution: {integrity: sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==} + caniuse-lite@1.0.30001636: + resolution: {integrity: sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==} + + caniuse-lite@1.0.30001669: + resolution: {integrity: sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==} + + case@1.6.3: + resolution: {integrity: sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==} + engines: {node: '>= 0.8.0'} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -1172,6 +1640,18 @@ packages: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -1180,17 +1660,42 @@ packages: resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} engines: {node: '>= 14.16.0'} + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cli-truncate@2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + cli-truncate@4.0.0: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} + clipanion@3.2.1: + resolution: {integrity: sha512-dYFdjLb7y1ajfxQopN05mylEpK9ZX0sO1/RfMXdfmwjlIsPkbh4p7A682x++zFPLDCo1x3p82dtljHf5cW2LKA==} + peerDependencies: + typanion: '*' + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -1198,6 +1703,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + code-excerpt@3.0.0: + resolution: {integrity: sha512-VHNTVhd7KsLGOqfX3SyeO8RyYPMp1GJOg194VITk04WMYCv4plV68YWe6TJZxd9MhobjtpMRnVky01gqZsalaw==} + engines: {node: '>=10'} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -1214,6 +1723,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -1234,6 +1746,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + convert-to-spaces@1.0.2: + resolution: {integrity: sha512-cj09EBuObp9gZNQCzc7hByQyrs6jVGE+o9kSJmeUoj+GiPiJvi5LYqEH/Hmme4+MTLHM+Ejtq+FChpjjEnsPdQ==} + engines: {node: '>= 4'} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -1283,6 +1799,15 @@ packages: supports-color: optional: true + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -1292,8 +1817,12 @@ packages: supports-color: optional: true - deep-equal@2.2.2: - resolution: {integrity: sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==} + decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} @@ -1306,7 +1835,11 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - define-data-property@1.1.4: + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1322,8 +1855,20 @@ packages: resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} engines: {node: '>=10'} - detect-gpu@5.0.51: - resolution: {integrity: sha512-7P+5KDthVGXXWS06EuqBIq7YBijxfaNfm+BSFNTRAkZP26J97ASssh5KoR53diWNcBNOEb1ILfdsz2pzesSgYw==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-gpu@5.0.38: + resolution: {integrity: sha512-36QeGHSXYcJ/RfrnPEScR8GDprbXFG4ZhXsfVNVHztZr38+fRxgHnJl3CjYXXjbeRUhu3ZZBJh6Lg0A9v0Qd8A==} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1347,6 +1892,10 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + draco3d@1.5.7: resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} @@ -1360,11 +1909,14 @@ packages: resolution: {integrity: sha512-uW2UKSsuty9ANJ3YByIQE4ANkD8nqUPO7r6Fwcc1ADKPe9FRdcPpMl3VEput4JSvKBJ4J86npIC2MLP0pYkCuw==} hasBin: true - electron-to-chromium@1.5.34: - resolution: {integrity: sha512-/TZAiChbAflBNjCg+VvstbcwAtIL/VdMFO3NgRFIzBjpvPzWOTIbbO8kNb6RwU4bt9TP7K+3KqBKw/lOU+Y+GA==} + electron-to-chromium@1.4.803: + resolution: {integrity: sha512-61H9mLzGOCLLVsnLiRzCbc63uldP0AniRYPV3hbGVtONA1pI7qSGILdbofR7A8TMbOypDocEAjH/e+9k1QIe3g==} + + electron-to-chromium@1.5.41: + resolution: {integrity: sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==} - emoji-regex@10.4.0: - resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1375,8 +1927,8 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - enhanced-resolve@5.17.1: - resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + enhanced-resolve@5.17.0: + resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} engines: {node: '>=10.13.0'} environment@1.1.0: @@ -1398,8 +1950,8 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-iterator-helpers@1.1.0: - resolution: {integrity: sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==} + es-iterator-helpers@1.0.19: + resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} engines: {node: '>= 0.4'} es-object-atoms@1.0.0: @@ -1417,13 +1969,16 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} + es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} escalade@3.2.0: @@ -1434,10 +1989,18 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-config-airbnb-base@15.0.0: resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1538,8 +2101,8 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -1550,6 +2113,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -1568,6 +2134,9 @@ packages: exenv@1.2.2: resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1584,6 +2153,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -1601,8 +2173,8 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-babel-config@2.1.2: - resolution: {integrity: sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==} + find-babel-config@2.1.1: + resolution: {integrity: sha512-5Ji+EAysHGe1OipH7GN4qDjok5Z1uw5KAwDCbicU/4wyTZY7CqOCzcWbG7J5ad9mazq67k89fXlbc1MuIfl9uA==} find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} @@ -1622,22 +2194,27 @@ packages: flatbuffers@22.10.26: resolution: {integrity: sha512-sdO3emf/BlLfOogW6KwHuXg16APR/E86jNacDXfSInPzt8SSEzxlHcqDekfM/IJ1CGC5bvDksfNufNhS8h1FRA==} - flatbuffers@22.12.6: - resolution: {integrity: sha512-CEwO0TRo6Z2dQ9iIfVAUC+BipwUnP2g8paCNqnrSsTh/axisRgzOwWyqTiPJMh8Si9QwhROjZ/FsFMfoESP33A==} - flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.2.1: + resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} + form-data-encoder@2.1.4: + resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} + engines: {node: '>= 14.17'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1679,6 +2256,10 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -1687,8 +2268,8 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + get-tsconfig@4.7.5: + resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -1698,8 +2279,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@10.4.1: + resolution: {integrity: sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==} + engines: {node: '>=16 || 14 >=14.18'} hasBin: true glob@7.2.3: @@ -1718,6 +2300,10 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} + globals@15.10.0: + resolution: {integrity: sha512-tqFIbz83w4Y5TCbtgjZjApohbuh7K9BxGYFm7ifwDR240tvdb7P9x+/9VvUKlmkPoiknoJtanI8UOrqxS3a7lQ==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -1732,12 +2318,32 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + got-fetch@5.1.10: + resolution: {integrity: sha512-Gwj/A2htjvLEcY07PKDItv0WCPEs3dV2vWeZ+9TVBSKSTuWEZ4oXaMD0ZAOsajwx2orahQWN4HI0MfRyWSZsbg==} + engines: {node: '>=14.0.0'} + peerDependencies: + got: ^12.0.0 + + got@12.6.1: + resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} + engines: {node: '>=14.16'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql-tag@2.12.6: + resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + + graphql@15.9.0: + resolution: {integrity: sha512-GCOQdvm7XxV1S4U4CGrsdlEN37245eC8P9zaYCMr6K1BG0IPGy5lUwmJsEOGyl1GD6HXjOtl2keCP9asRBwNvA==} + engines: {node: '>= 10.x'} + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -1776,8 +2382,33 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hls.js@1.3.5: - resolution: {integrity: sha512-uybAvKS6uDe0MnWNEPnO0krWVr+8m2R0hJ/viql8H3MVK+itq8gGQuIYoFHL3rECkIpNH98Lw8YuuWMKZxp3Ew==} + hast-util-to-jsx-runtime@2.3.2: + resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hls.js@1.5.17: + resolution: {integrity: sha512-wA66nnYFvQa1o4DO/BFgLNRKnBTVXpNeldGRBJ2Y0SvFtdwvFKCbqa9zhHoZLoxHhZ+jYsj3aIBkWQQCPNOhMw==} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + http2-client@1.3.5: + resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==} + + http2-wrapper@2.2.1: + resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} + engines: {node: '>=10.19.0'} human-signals@1.1.1: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} @@ -1798,8 +2429,8 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} immediate@3.0.6: @@ -1808,8 +2439,8 @@ packages: immer@9.0.21: resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} - immutable@4.3.7: - resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + immutable@4.3.6: + resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==} import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -1819,6 +2450,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -1826,6 +2461,19 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ink@3.2.0: + resolution: {integrity: sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '>=16.8.0' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -1836,13 +2484,16 @@ packages: ip-num@1.5.1: resolution: {integrity: sha512-QziFxgxq3mjIf5CuwlzXFYscHxgLqdEdJKRo2UJ5GurL5zrSRMzT/O+nK0ABimoFH8MWF8YwIiwECYsHc1LpUQ==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} - is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} - is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -1869,6 +2520,13 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} + is-ci@2.0.0: + resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} + hasBin: true + + is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.15.1: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} @@ -1881,6 +2539,9 @@ packages: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -1913,8 +2574,8 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-map@2.0.2: - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} @@ -1936,6 +2597,10 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} @@ -1943,16 +2608,10 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} - is-set@2.0.2: - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} - is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - is-shared-array-buffer@1.0.3: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} @@ -1973,17 +2632,10 @@ packages: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} - is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} - engines: {node: '>= 0.4'} - is-typed-array@1.1.13: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} - is-weakmap@2.0.1: - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} - is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -1991,9 +2643,6 @@ packages: is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - is-weakset@2.0.2: - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} - is-weakset@2.0.3: resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} engines: {node: '>= 0.4'} @@ -2008,17 +2657,17 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - iterator.prototype@1.1.3: - resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} - engines: {node: '>= 0.4'} + iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} its-fine@1.2.5: resolution: {integrity: sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==} peerDependencies: react: '>=18.0' - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@3.4.0: + resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} + engines: {node: '>=14'} jiti@1.21.6: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} @@ -2031,6 +2680,11 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} engines: {node: '>=6'} @@ -2054,6 +2708,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -2107,6 +2764,12 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -2117,28 +2780,84 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lowercase-keys@3.0.0: + resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - maath@0.10.8: - resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==} + maath@0.10.7: + resolution: {integrity: sha512-zQ2xd7dNOIVTjAS+hj22fyj1EFYmOJX6tzKjZ92r6WDoq8hyFxjuGA2q950tmR4iC/EKXoMQdSipkaJVuUHDTg==} peerDependencies: - '@types/three': '>=0.134.0' - three: '>=0.134.0' + '@types/three': '>=0.144.0' + three: '>=0.144.0' make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + matchmediaquery@0.4.2: resolution: {integrity: sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==} + mdast-util-find-and-replace@3.0.1: + resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.0.0: + resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.0.0: + resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.1.3: + resolution: {integrity: sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -2154,6 +2873,94 @@ packages: meshoptimizer@0.18.1: resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} + micromark-core-commonmark@2.0.1: + resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.0: + resolution: {integrity: sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + + micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + + micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + + micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + + micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + + micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + + micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + + micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + + micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + + micromark-util-decode-numeric-character-reference@2.0.1: + resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + + micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + + micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + + micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + + micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + + micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + + micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + + micromark-util-subtokenize@2.0.1: + resolution: {integrity: sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==} + + micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + + micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + + micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2170,6 +2977,14 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + mimic-response@4.0.0: + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + mini-svg-data-uri@1.4.4: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true @@ -2181,8 +2996,8 @@ packages: resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: @@ -2196,6 +3011,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2210,6 +3028,28 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-fetch-h2@2.3.0: + resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==} + engines: {node: 4.x || >=6.0.0} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-readfiles@0.2.0: + resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} @@ -2221,6 +3061,10 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + normalize-url@8.0.1: + resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} + engines: {node: '>=14.16'} + npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -2229,6 +3073,22 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + oas-kit-common@1.0.8: + resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==} + + oas-linter@3.2.2: + resolution: {integrity: sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==} + + oas-resolver@2.5.6: + resolution: {integrity: sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==} + hasBin: true + + oas-schema-walker@1.1.5: + resolution: {integrity: sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==} + + oas-validator@5.0.8: + resolution: {integrity: sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2237,16 +3097,8 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - object-inspect@1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} - engines: {node: '>= 0.4'} - - object-is@1.1.5: - resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} - engines: {node: '>= 0.4'} + object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} object-is@1.1.6: resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} @@ -2298,10 +3150,20 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + openapi3-ts@2.0.2: + resolution: {integrity: sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==} + + optimism@0.18.0: + resolution: {integrity: sha512-tGn8+REwLRNFnb9WmcY5IfpOqeX2kpaYJ1s6Ae3mn12AeydLkR3j+jSCmVQFoXqU8D41PAJ1RG1rCRNWmNZVmQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + p-cancelable@3.0.0: + resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} + engines: {node: '>=12.20'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -2322,13 +3184,26 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@4.0.1: + resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + patch-console@1.0.0: + resolution: {integrity: sha512-nxl9nrnLQmh64iTzMfyylSlRozL7kAXIaxw1fVcLYdyhNkJCRUzirRZTikXGJsg+hc4fqpneTK6iU2H1Q8THSA==} + engines: {node: '>=10'} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -2365,8 +3240,11 @@ packages: engines: {node: '>=0.10'} hasBin: true - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -2389,6 +3267,10 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -2417,19 +3299,27 @@ packages: ts-node: optional: true - postcss-nested@6.2.0: - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + postcss-nested@6.0.1: + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss-selector-parser@6.1.0: + resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} engines: {node: '>=4'} postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.4.47: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} @@ -2456,6 +3346,12 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -2466,11 +3362,18 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + react-composer@5.0.3: resolution: {integrity: sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==} peerDependencies: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-devtools-core@4.28.5: + resolution: {integrity: sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==} + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -2501,6 +3404,12 @@ packages: react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} + react-markdown@9.0.1: + resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-modal@3.16.1: resolution: {integrity: sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==} engines: {node: '>=8'} @@ -2508,6 +3417,12 @@ packages: react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 + react-reconciler@0.26.2: + resolution: {integrity: sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^17.0.2 + react-reconciler@0.27.0: resolution: {integrity: sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==} engines: {node: '>=0.10.0'} @@ -2524,15 +3439,15 @@ packages: peerDependencies: react: '>=16.8.0' - react-router-dom@6.26.2: - resolution: {integrity: sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==} + react-router-dom@6.27.0: + resolution: {integrity: sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - react-router@6.26.2: - resolution: {integrity: sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==} + react-router@6.27.0: + resolution: {integrity: sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' @@ -2561,16 +3476,38 @@ packages: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} + reftools@1.1.9: + resolution: {integrity: sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - regexp.prototype.flags@1.5.1: - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} - regexp.prototype.flags@1.5.3: - resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} - engines: {node: '>= 0.4'} + rehackt@0.1.0: + resolution: {integrity: sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==} + peerDependencies: + '@types/react': '*' + react: '*' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + remark-gfm@4.0.0: + resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.1: + resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -2583,6 +3520,9 @@ packages: reselect@4.1.8: resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2598,6 +3538,18 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true + response-iterator@0.2.6: + resolution: {integrity: sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==} + engines: {node: '>=0.8'} + + responselike@3.0.0: + resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} + engines: {node: '>=14.16'} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -2632,6 +3584,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + safe-array-concat@1.1.2: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} @@ -2640,11 +3595,14 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} - sass@1.79.4: - resolution: {integrity: sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==} + sass@1.80.2: + resolution: {integrity: sha512-9wXY8cGBlUmoUoT+vwOZOFCiS+naiWVjqlreN9ar9PudXbGwlMTFwCR5K9kB4dFumJ6ib98wZyAObJKsWf1nAA==} engines: {node: '>=14.0.0'} hasBin: true + scheduler@0.20.2: + resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} + scheduler@0.21.0: resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==} @@ -2664,10 +3622,6 @@ packages: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} - set-function-name@2.0.1: - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} - engines: {node: '>= 0.4'} - set-function-name@2.0.2: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} @@ -2683,8 +3637,26 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + + should-equal@2.0.0: + resolution: {integrity: sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==} + + should-format@3.0.3: + resolution: {integrity: sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==} + + should-type-adaptors@1.1.0: + resolution: {integrity: sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==} + + should-type@1.4.0: + resolution: {integrity: sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==} + + should-util@1.0.1: + resolution: {integrity: sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==} + + should@13.2.3: + resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==} side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} @@ -2704,6 +3676,14 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + + slice-ansi@3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -2712,8 +3692,9 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} - solarxr-protocol@file:solarxr-protocol: - resolution: {directory: solarxr-protocol, type: directory} + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -2730,6 +3711,9 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-compare@1.0.0: resolution: {integrity: sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==} @@ -2739,8 +3723,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.20: - resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} + spdx-license-ids@3.0.18: + resolution: {integrity: sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==} spdx-ranges@2.1.1: resolution: {integrity: sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==} @@ -2748,6 +3732,10 @@ packages: spdx-satisfies@5.0.1: resolution: {integrity: sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw==} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stats-gl@2.2.8: resolution: {integrity: sha512-94G5nZvduDmzxBS7K0lYnynYwreZpkknD8g5dZmU6mpwIhy3caCrjAm11Qm1cbyx7mqix7Fp00RkbsonzKWnoQ==} @@ -2770,12 +3758,13 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + string-width@7.1.0: + resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} engines: {node: '>=18'} - string.prototype.includes@2.0.0: - resolution: {integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==} + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} string.prototype.matchall@4.0.11: resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} @@ -2795,6 +3784,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2819,6 +3811,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + style-to-object@1.0.8: + resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -2841,11 +3836,19 @@ packages: peerDependencies: react: '>=17.0' + swagger2openapi@7.0.8: + resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} + hasBin: true + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + tailwind-gradient-mask-image@1.2.0: resolution: {integrity: sha512-tUJaGhvqbJFiVKJu6EU5n//KvGdVvY3L3VOFNqjztk13+ifAk00pcSNHBTgHfUiBGOEzDn0gFRbSmsftUV1lXA==} - tailwindcss@3.4.13: - resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} + tailwindcss@3.4.14: + resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} hasBin: true @@ -2874,14 +3877,17 @@ packages: peerDependencies: three: '>= 0.151.0' - three-stdlib@2.33.0: - resolution: {integrity: sha512-V/uycBuqQOP/3Z+FBtpMdj2Ds5PyfJ3VDfMzktEmG4niOIzv7q1y5uMSbMcng0+057m1l0N147FQxsodQo9zBg==} + three-stdlib@2.30.3: + resolution: {integrity: sha512-rYr8PqMljMza+Ct8kQk90Y7y+YcWoPu1thfYv5YGCp0hytNRbxSQWXY4GpdTGymCj3bDggEBpxso53C3pPwhIw==} peerDependencies: three: '>=0.128.0' three@0.163.0: resolution: {integrity: sha512-HlMgCb2TF/dTLRtknBnjUTsR8FsDqBY43itYop2+Zg822I+Kd0Ua2vs8CvfBVefXkBdNDrLMoRTGCIIpfCuDew==} + tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} @@ -2893,6 +3899,15 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + troika-three-text@0.49.1: resolution: {integrity: sha512-lXGWxgjJP9kw4i4Wh+0k0Q/7cRfS6iOME4knKht/KozPu9GcFA9NnNpRvehIhrUawq9B0ZRw+0oiFHgRO+4Wig==} peerDependencies: @@ -2906,6 +3921,9 @@ packages: troika-worker-utils@0.49.0: resolution: {integrity: sha512-1xZHoJrG0HFfCvT/iyN41DvI/nRykiBtHqFkGaGgJwq5iXfIZFBiPPEHFpPpgyKM3Oo5ITHXP5wM2TNQszYdVg==} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -2915,6 +3933,10 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-invariant@0.10.3: + resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} + engines: {node: '>=8'} + ts-node@9.1.1: resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==} engines: {node: '>=10.0.0'} @@ -2922,8 +3944,8 @@ packages: peerDependencies: typescript: '>=2.7' - ts-pattern@5.4.0: - resolution: {integrity: sha512-hgfOMfjlrARCnYtGD/xEAkFHDXuSyuqjzFSltyQCbN689uNvoQL20TVN2XFcLMjfNuwSsQGU+xtH6MrjIwhwUg==} + ts-pattern@5.5.0: + resolution: {integrity: sha512-jqbIpTsa/KKTJYWgPNsFNbLVpwCgzXfFJ1ukNn4I8hMwyQzHMJnk/BqWzggB0xpkILuKzaO/aMYhS0SkaJyKXg==} ts-xor@1.3.0: resolution: {integrity: sha512-RLXVjliCzc1gfKQFLRpfeD0rrWmjnSTgj7+RFhoq3KRkUYa8LE/TIidYOzM5h+IdFBDSjjSgk9Lto9sdMfDFEA==} @@ -2931,20 +3953,44 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' tunnel-rat@0.1.2: resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} + typanion@3.14.0: + resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@0.12.0: + resolution: {integrity: sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==} + engines: {node: '>=10'} + type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -2961,6 +4007,20 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} + typescript-eslint@8.8.0: + resolution: {integrity: sha512-BjIT/VwJ8+0rVO01ZQ2ZVnjE1svFBiRczcpr1t1Yxt7sT25VSbPfrJtDsQ8uQTy2pilX5nI9gwxhUyLULNentw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + typescript@4.8.2: + resolution: {integrity: sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==} + engines: {node: '>=4.2.0'} + hasBin: true + typescript@4.8.4: resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} engines: {node: '>=4.2.0'} @@ -2977,8 +4037,36 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - update-browserslist-db@1.1.1: - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.0.16: + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2991,8 +4079,8 @@ packages: peerDependencies: react: '>=16.8.0' - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + use-sync-external-store@1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -3007,8 +4095,14 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - vite@5.4.8: - resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@5.4.9: + resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -3047,24 +4141,23 @@ packages: webgl-sdf-generator@1.1.1: resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - which-builtin-type@1.1.4: - resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} + which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} engines: {node: '>= 0.4'} - which-collection@1.0.1: - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} - which-collection@1.0.2: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.11: - resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} - engines: {node: '>= 0.4'} - which-typed-array@1.1.15: resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} @@ -3074,10 +4167,18 @@ packages: engines: {node: '>= 8'} hasBin: true + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3093,6 +4194,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + 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 + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -3100,6 +4213,15 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.4.5: + resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} + engines: {node: '>= 14'} + hasBin: true + yaml@2.5.1: resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} engines: {node: '>= 14'} @@ -3129,6 +4251,19 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoga-layout-prebuilt@1.10.0: + resolution: {integrity: sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==} + engines: {node: '>=8'} + + yup@1.4.0: + resolution: {integrity: sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==} + + zen-observable-ts@1.2.5: + resolution: {integrity: sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==} + + zen-observable@0.8.15: + resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} + zustand@3.7.2: resolution: {integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==} engines: {node: '>=12.7.0'} @@ -3138,8 +4273,8 @@ packages: react: optional: true - zustand@4.5.5: - resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} + zustand@4.5.2: + resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} engines: {node: '>=12.7.0'} peerDependencies: '@types/react': '>=16.8' @@ -3153,6 +4288,9 @@ packages: react: optional: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -3162,58 +4300,154 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@apollo/client@3.10.6(@types/react@18.3.11)(graphql@15.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@15.9.0) + '@wry/caches': 1.0.1 + '@wry/equality': 0.5.7 + '@wry/trie': 0.5.0 + graphql: 15.9.0 + graphql-tag: 2.12.6(graphql@15.9.0) + hoist-non-react-statics: 3.3.2 + optimism: 0.18.0 + prop-types: 15.8.1 + rehackt: 0.1.0(@types/react@18.3.11)(react@18.3.1) + response-iterator: 0.2.6 + symbol-observable: 4.0.0 + ts-invariant: 0.10.3 + tslib: 2.6.3 + zen-observable-ts: 1.2.5 + optionalDependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + '@babel/code-frame@7.25.7': dependencies: '@babel/highlight': 7.25.7 - picocolors: 1.1.0 + picocolors: 1.0.1 + + '@babel/compat-data@7.24.7': {} - '@babel/compat-data@7.25.7': {} + '@babel/compat-data@7.25.8': {} + + '@babel/core@7.24.7': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helpers': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + convert-source-map: 2.0.0 + debug: 4.3.5 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@babel/core@7.25.7': + '@babel/core@7.25.8': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.25.7 '@babel/generator': 7.25.7 '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.7) + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) '@babel/helpers': 7.25.7 - '@babel/parser': 7.25.7 + '@babel/parser': 7.25.8 '@babel/template': 7.25.7 '@babel/traverse': 7.25.7 - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 convert-source-map: 2.0.0 - debug: 4.3.7 + debug: 4.3.5 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color + '@babel/generator@7.24.7': + dependencies: + '@babel/types': 7.24.7 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + '@babel/generator@7.25.7': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 + '@babel/helper-compilation-targets@7.24.7': + dependencies: + '@babel/compat-data': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + browserslist: 4.23.1 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-compilation-targets@7.25.7': dependencies: - '@babel/compat-data': 7.25.7 + '@babel/compat-data': 7.25.8 '@babel/helper-validator-option': 7.25.7 browserslist: 4.24.0 lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-environment-visitor@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-function-name@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/helper-hoist-variables@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.25.7': dependencies: '@babel/traverse': 7.25.7 - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.7)': + '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': dependencies: - '@babel/core': 7.25.7 + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 '@babel/helper-module-imports': 7.25.7 '@babel/helper-simple-access': 7.25.7 '@babel/helper-validator-identifier': 7.25.7 @@ -3221,70 +4455,130 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.25.7': {} + '@babel/helper-plugin-utils@7.24.7': {} + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color '@babel/helper-simple-access@7.25.7': dependencies: '@babel/traverse': 7.25.7 - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 transitivePeerDependencies: - supports-color + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-string-parser@7.24.7': {} + '@babel/helper-string-parser@7.25.7': {} + '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-identifier@7.25.7': {} + '@babel/helper-validator-option@7.24.7': {} + '@babel/helper-validator-option@7.25.7': {} + '@babel/helpers@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + '@babel/helpers@7.25.7': dependencies: '@babel/template': 7.25.7 - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 '@babel/highlight@7.25.7': dependencies: '@babel/helper-validator-identifier': 7.25.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.1.0 + picocolors: 1.0.1 + + '@babel/parser@7.24.7': + dependencies: + '@babel/types': 7.24.7 - '@babel/parser@7.25.7': + '@babel/parser@7.25.8': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 - '@babel/plugin-transform-react-jsx-self@7.25.7(@babel/core@7.25.7)': + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-transform-react-jsx-source@7.25.7(@babel/core@7.25.7)': + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.7 - '@babel/runtime@7.25.7': + '@babel/runtime@7.24.7': dependencies: regenerator-runtime: 0.14.1 + '@babel/template@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + '@babel/template@7.25.7': dependencies: '@babel/code-frame': 7.25.7 - '@babel/parser': 7.25.7 - '@babel/types': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 + + '@babel/traverse@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + debug: 4.3.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color '@babel/traverse@7.25.7': dependencies: '@babel/code-frame': 7.25.7 '@babel/generator': 7.25.7 - '@babel/parser': 7.25.7 + '@babel/parser': 7.25.8 '@babel/template': 7.25.7 - '@babel/types': 7.25.7 - debug: 4.3.7 + '@babel/types': 7.25.8 + debug: 4.3.5 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.25.7': + '@babel/types@7.24.7': + dependencies: + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@babel/types@7.25.8': dependencies: '@babel/helper-string-parser': 7.25.7 '@babel/helper-validator-identifier': 7.25.7 @@ -3302,7 +4596,7 @@ snapshots: '@dword-design/eslint-plugin-import-alias@4.0.9': dependencies: - '@babel/core': 7.25.7 + '@babel/core': 7.24.7 '@dword-design/functions': 5.0.27 babel-plugin-module-resolver: 5.0.2 deepmerge: 4.3.1 @@ -3391,15 +4685,15 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.1': {} + '@eslint-community/regexpp@4.10.1': {} '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7 + debug: 4.3.5 espree: 9.6.1 globals: 13.24.0 - ignore: 5.3.2 + ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -3409,6 +4703,8 @@ snapshots: '@eslint/js@8.57.1': {} + '@exodus/schemasafe@1.3.0': {} + '@fluent/bundle@0.18.0': {} '@fluent/react@0.15.2(@fluent/bundle@0.18.0)(react@18.3.1)': @@ -3426,12 +4722,20 @@ snapshots: '@formatjs/intl-localematcher@0.2.32': dependencies: - tslib: 2.7.0 + tslib: 2.6.3 + + '@graphql-typed-document-node/core@3.2.0(graphql@15.9.0)': + dependencies: + graphql: 15.9.0 + + '@hookform/resolvers@3.6.0(react-hook-form@7.53.0(react@18.3.1))': + dependencies: + react-hook-form: 7.53.0(react@18.3.1) '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7 + debug: 4.3.5 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -3452,7 +4756,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} @@ -3465,18 +4769,18 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 optional: true - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.4.15': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.4.15 '@mediapipe/tasks-vision@0.10.8': {} '@mgit-at/typescript-flatbuffers-codegen@0.1.3': dependencies: - deep-equal: 2.2.2 + deep-equal: 2.2.3 ebnf: 1.9.1 ebnf-parser: 0.1.10 execa: 4.1.0 @@ -3486,7 +4790,7 @@ snapshots: typescript: 4.8.4 yargs: 16.2.0 - '@monogrid/gainmap-js@3.0.6(three@0.163.0)': + '@monogrid/gainmap-js@3.0.5(three@0.163.0)': dependencies: promise-worker-transferable: 1.0.4 three: 0.163.0 @@ -3505,6 +4809,105 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@openapi-codegen/cli@2.0.2(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@apollo/client': 3.10.6(@types/react@18.3.11)(graphql@15.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@swc/core': 1.6.5 + case: 1.6.3 + chalk: 5.3.0 + cli-highlight: 2.1.11 + clipanion: 3.2.1(typanion@3.14.0) + fs-extra: 10.1.0 + got: 12.6.1 + got-fetch: 5.1.10(got@12.6.1) + graphql: 15.9.0 + ink: 3.2.0(@types/react@18.3.11)(react@18.3.1) + js-yaml: 4.1.0 + openapi3-ts: 2.0.2 + prettier: 3.3.3 + rxjs: 7.8.1 + slash: 4.0.0 + swagger2openapi: 7.0.8 + tslib: 2.6.3 + typanion: 3.14.0 + typescript: 4.8.2 + transitivePeerDependencies: + - '@swc/helpers' + - '@types/react' + - bufferutil + - encoding + - graphql-ws + - react + - react-dom + - subscriptions-transport-ws + - utf-8-validate + + '@openapi-codegen/typescript@8.0.2': + dependencies: + case: 1.6.3 + lodash: 4.17.21 + openapi3-ts: 2.0.2 + pluralize: 8.0.0 + tslib: 2.6.3 + tsutils: 3.21.0(typescript@4.8.2) + typescript: 4.8.2 + + '@parcel/watcher-android-arm64@2.4.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.4.1': + optional: true + + '@parcel/watcher-darwin-x64@2.4.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.4.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.4.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.4.1': + optional: true + + '@parcel/watcher-win32-arm64@2.4.1': + optional: true + + '@parcel/watcher-win32-ia32@2.4.1': + optional: true + + '@parcel/watcher-win32-x64@2.4.1': + optional: true + + '@parcel/watcher@2.4.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.7 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.4.1 + '@parcel/watcher-darwin-arm64': 2.4.1 + '@parcel/watcher-darwin-x64': 2.4.1 + '@parcel/watcher-freebsd-x64': 2.4.1 + '@parcel/watcher-linux-arm-glibc': 2.4.1 + '@parcel/watcher-linux-arm64-glibc': 2.4.1 + '@parcel/watcher-linux-arm64-musl': 2.4.1 + '@parcel/watcher-linux-x64-glibc': 2.4.1 + '@parcel/watcher-linux-x64-musl': 2.4.1 + '@parcel/watcher-win32-arm64': 2.4.1 + '@parcel/watcher-win32-ia32': 2.4.1 + '@parcel/watcher-win32-x64': 2.4.1 + '@pkgjs/parseargs@0.11.0': optional: true @@ -3542,20 +4945,20 @@ snapshots: '@react-spring/types@9.6.1': {} - '@react-three/drei@9.114.3(@react-three/fiber@8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0))(@types/react@18.3.11)(@types/three@0.163.0)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0)': + '@react-three/drei@9.114.5(@react-three/fiber@8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0))(@types/react@18.3.11)(@types/three@0.163.0)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0)': dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.24.7 '@mediapipe/tasks-vision': 0.10.8 - '@monogrid/gainmap-js': 3.0.6(three@0.163.0) + '@monogrid/gainmap-js': 3.0.5(three@0.163.0) '@react-spring/three': 9.6.1(@react-three/fiber@8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0))(react@18.3.1)(three@0.163.0) '@react-three/fiber': 8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0) '@use-gesture/react': 10.3.1(react@18.3.1) - camera-controls: 2.9.0(three@0.163.0) + camera-controls: 2.8.5(three@0.163.0) cross-env: 7.0.3 - detect-gpu: 5.0.51 + detect-gpu: 5.0.38 glsl-noise: 0.0.0 - hls.js: 1.3.5 - maath: 0.10.8(@types/three@0.163.0)(three@0.163.0) + hls.js: 1.5.17 + maath: 0.10.7(@types/three@0.163.0)(three@0.163.0) meshline: 3.3.1(three@0.163.0) react: 18.3.1 react-composer: 5.0.3(react@18.3.1) @@ -3564,7 +4967,7 @@ snapshots: suspend-react: 0.1.3(react@18.3.1) three: 0.163.0 three-mesh-bvh: 0.7.8(three@0.163.0) - three-stdlib: 2.33.0(three@0.163.0) + three-stdlib: 2.30.3(three@0.163.0) troika-three-text: 0.49.1(three@0.163.0) tunnel-rat: 0.1.2(@types/react@18.3.11)(immer@9.0.21)(react@18.3.1) utility-types: 3.11.0 @@ -3579,10 +4982,10 @@ snapshots: '@react-three/fiber@8.17.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.163.0)': dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.24.7 '@types/debounce': 1.2.4 '@types/react-reconciler': 0.26.7 - '@types/webxr': 0.5.20 + '@types/webxr': 0.5.16 base64-js: 1.5.1 buffer: 6.0.3 debounce: 1.2.1 @@ -3596,7 +4999,7 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) - '@remix-run/router@1.19.2': {} + '@remix-run/router@1.20.0': {} '@rollup/rollup-android-arm-eabi@4.24.0': optional: true @@ -3648,55 +5051,128 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@tailwindcss/forms@0.5.9(tailwindcss@3.4.13(ts-node@9.1.1(typescript@5.6.3)))': + '@sindresorhus/is@5.6.0': {} + + '@swc/core-darwin-arm64@1.6.5': + optional: true + + '@swc/core-darwin-x64@1.6.5': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.6.5': + optional: true + + '@swc/core-linux-arm64-gnu@1.6.5': + optional: true + + '@swc/core-linux-arm64-musl@1.6.5': + optional: true + + '@swc/core-linux-x64-gnu@1.6.5': + optional: true + + '@swc/core-linux-x64-musl@1.6.5': + optional: true + + '@swc/core-win32-arm64-msvc@1.6.5': + optional: true + + '@swc/core-win32-ia32-msvc@1.6.5': + optional: true + + '@swc/core-win32-x64-msvc@1.6.5': + optional: true + + '@swc/core@1.6.5': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.9 + optionalDependencies: + '@swc/core-darwin-arm64': 1.6.5 + '@swc/core-darwin-x64': 1.6.5 + '@swc/core-linux-arm-gnueabihf': 1.6.5 + '@swc/core-linux-arm64-gnu': 1.6.5 + '@swc/core-linux-arm64-musl': 1.6.5 + '@swc/core-linux-x64-gnu': 1.6.5 + '@swc/core-linux-x64-musl': 1.6.5 + '@swc/core-win32-arm64-msvc': 1.6.5 + '@swc/core-win32-ia32-msvc': 1.6.5 + '@swc/core-win32-x64-msvc': 1.6.5 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.9': + dependencies: + '@swc/counter': 0.1.3 + + '@szmarczak/http-timer@5.0.1': + dependencies: + defer-to-connect: 2.0.1 + + '@tailwindcss/forms@0.5.9(tailwindcss@3.4.14(ts-node@9.1.1(typescript@5.6.3)))': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.13(ts-node@9.1.1(typescript@5.6.3)) + tailwindcss: 3.4.14(ts-node@9.1.1(typescript@5.6.3)) + + '@tailwindcss/typography@0.5.15(tailwindcss@3.4.14(ts-node@9.1.1(typescript@5.6.3)))': + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.14(ts-node@9.1.1(typescript@5.6.3)) + + '@tanstack/query-core@5.48.0': {} + + '@tanstack/react-query@5.48.0(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.48.0 + react: 18.3.1 '@tauri-apps/api@2.0.2': {} - '@tauri-apps/cli-darwin-arm64@2.0.2': + '@tauri-apps/cli-darwin-arm64@2.0.3': optional: true - '@tauri-apps/cli-darwin-x64@2.0.2': + '@tauri-apps/cli-darwin-x64@2.0.3': optional: true - '@tauri-apps/cli-linux-arm-gnueabihf@2.0.2': + '@tauri-apps/cli-linux-arm-gnueabihf@2.0.3': optional: true - '@tauri-apps/cli-linux-arm64-gnu@2.0.2': + '@tauri-apps/cli-linux-arm64-gnu@2.0.3': optional: true - '@tauri-apps/cli-linux-arm64-musl@2.0.2': + '@tauri-apps/cli-linux-arm64-musl@2.0.3': optional: true - '@tauri-apps/cli-linux-x64-gnu@2.0.2': + '@tauri-apps/cli-linux-x64-gnu@2.0.3': optional: true - '@tauri-apps/cli-linux-x64-musl@2.0.2': + '@tauri-apps/cli-linux-x64-musl@2.0.3': optional: true - '@tauri-apps/cli-win32-arm64-msvc@2.0.2': + '@tauri-apps/cli-win32-arm64-msvc@2.0.3': optional: true - '@tauri-apps/cli-win32-ia32-msvc@2.0.2': + '@tauri-apps/cli-win32-ia32-msvc@2.0.3': optional: true - '@tauri-apps/cli-win32-x64-msvc@2.0.2': + '@tauri-apps/cli-win32-x64-msvc@2.0.3': optional: true - '@tauri-apps/cli@2.0.2': + '@tauri-apps/cli@2.0.3': optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.0.2 - '@tauri-apps/cli-darwin-x64': 2.0.2 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.2 - '@tauri-apps/cli-linux-arm64-gnu': 2.0.2 - '@tauri-apps/cli-linux-arm64-musl': 2.0.2 - '@tauri-apps/cli-linux-x64-gnu': 2.0.2 - '@tauri-apps/cli-linux-x64-musl': 2.0.2 - '@tauri-apps/cli-win32-arm64-msvc': 2.0.2 - '@tauri-apps/cli-win32-ia32-msvc': 2.0.2 - '@tauri-apps/cli-win32-x64-msvc': 2.0.2 + '@tauri-apps/cli-darwin-arm64': 2.0.3 + '@tauri-apps/cli-darwin-x64': 2.0.3 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.3 + '@tauri-apps/cli-linux-arm64-gnu': 2.0.3 + '@tauri-apps/cli-linux-arm64-musl': 2.0.3 + '@tauri-apps/cli-linux-x64-gnu': 2.0.3 + '@tauri-apps/cli-linux-x64-musl': 2.0.3 + '@tauri-apps/cli-win32-arm64-msvc': 2.0.3 + '@tauri-apps/cli-win32-ia32-msvc': 2.0.3 + '@tauri-apps/cli-win32-x64-msvc': 2.0.3 '@tauri-apps/plugin-dialog@2.0.0': dependencies: @@ -3718,39 +5194,59 @@ snapshots: dependencies: '@tauri-apps/api': 2.0.2 - '@tweenjs/tween.js@23.1.3': {} + '@tweenjs/tween.js@23.1.2': {} '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.25.7 - '@babel/types': 7.25.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.24.7 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.25.7 - '@babel/types': 7.25.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.24.7 '@types/debounce@1.2.4': {} + '@types/debug@4.1.12': + dependencies: + '@types/ms': 0.7.34 + '@types/draco3d@1.4.10': {} + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.6 + '@types/estree@1.0.6': {} '@types/file-saver@2.0.7': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/http-cache-semantics@4.0.4': {} + '@types/json5@0.0.29': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@0.7.34': {} + '@types/node@20.14.2': dependencies: undici-types: 5.26.5 @@ -3758,7 +5254,7 @@ snapshots: '@types/offscreencanvas@2019.7.3': {} - '@types/prop-types@15.7.13': {} + '@types/prop-types@15.7.12': {} '@types/react-dom@18.3.0': dependencies: @@ -3782,7 +5278,7 @@ snapshots: '@types/react@18.3.11': dependencies: - '@types/prop-types': 15.7.13 + '@types/prop-types': 15.7.12 csstype: 3.1.3 '@types/semver@7.5.8': {} @@ -3791,17 +5287,23 @@ snapshots: '@types/three@0.163.0': dependencies: - '@tweenjs/tween.js': 23.1.3 + '@tweenjs/tween.js': 23.1.2 '@types/stats.js': 0.17.3 - '@types/webxr': 0.5.20 + '@types/webxr': 0.5.16 fflate: 0.8.2 meshoptimizer: 0.18.1 - '@types/webxr@0.5.20': {} + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/webxr@0.5.16': {} + + '@types/yoga-layout@1.9.2': {} '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/regexpp': 4.10.1 '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) @@ -3809,7 +5311,25 @@ snapshots: '@typescript-eslint/visitor-keys': 7.18.0 eslint: 8.57.1 graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/eslint-plugin@8.8.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.10.1 + '@typescript-eslint/parser': 8.8.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.8.0 + '@typescript-eslint/type-utils': 8.8.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.8.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.8.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.1 natural-compare: 1.4.0 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: @@ -3823,7 +5343,20 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7 + debug: 4.3.5 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.8.0 + '@typescript-eslint/types': 8.8.0 + '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.8.0 + debug: 4.3.5 eslint: 8.57.1 optionalDependencies: typescript: 5.6.3 @@ -3835,11 +5368,16 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/scope-manager@8.8.0': + dependencies: + '@typescript-eslint/types': 8.8.0 + '@typescript-eslint/visitor-keys': 8.8.0 + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - debug: 4.3.7 + debug: 4.3.5 eslint: 8.57.1 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: @@ -3847,16 +5385,45 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.8.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.8.0(eslint@8.57.1)(typescript@5.6.3) + debug: 4.3.5 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color + '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/types@8.8.0': {} + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7 + debug: 4.3.5 globby: 11.1.0 is-glob: 4.0.3 - minimatch: 9.0.5 + minimatch: 9.0.4 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.8.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.8.0 + '@typescript-eslint/visitor-keys': 8.8.0 + debug: 4.3.5 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.4 semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: @@ -3875,11 +5442,27 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@8.8.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.8.0 + '@typescript-eslint/types': 8.8.0 + '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.6.3) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/visitor-keys@7.18.0': dependencies: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.8.0': + dependencies: + '@typescript-eslint/types': 8.8.0 + eslint-visitor-keys: 3.4.3 + '@ungap/structured-clone@1.2.0': {} '@use-gesture/core@10.3.1': {} @@ -3889,22 +5472,42 @@ snapshots: '@use-gesture/core': 10.3.1 react: 18.3.1 - '@vitejs/plugin-react@4.3.2(vite@5.4.8(@types/node@20.14.2)(sass@1.79.4)(terser@5.31.1))': + '@vitejs/plugin-react@4.3.2(vite@5.4.9(@types/node@20.14.2)(sass@1.80.2)(terser@5.31.1))': dependencies: - '@babel/core': 7.25.7 - '@babel/plugin-transform-react-jsx-self': 7.25.7(@babel/core@7.25.7) - '@babel/plugin-transform-react-jsx-source': 7.25.7(@babel/core@7.25.7) + '@babel/core': 7.25.8 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.8) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.8(@types/node@20.14.2)(sass@1.79.4)(terser@5.31.1) + vite: 5.4.9(@types/node@20.14.2)(sass@1.80.2)(terser@5.31.1) transitivePeerDependencies: - supports-color - acorn-jsx@5.3.2(acorn@8.12.1): + '@wry/caches@1.0.1': + dependencies: + tslib: 2.6.3 + + '@wry/context@0.7.4': + dependencies: + tslib: 2.6.3 + + '@wry/equality@0.5.7': + dependencies: + tslib: 2.6.3 + + '@wry/trie@0.4.3': dependencies: - acorn: 8.12.1 + tslib: 2.6.3 - acorn@8.12.1: {} + '@wry/trie@0.5.0': + dependencies: + tslib: 2.6.3 + + acorn-jsx@5.3.2(acorn@8.12.0): + dependencies: + acorn: 8.12.0 + + acorn@8.12.0: {} ajv@6.12.6: dependencies: @@ -3913,6 +5516,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-escapes@7.0.0: dependencies: environment: 1.1.0 @@ -3948,11 +5555,6 @@ snapshots: dependencies: deep-equal: 2.2.3 - array-buffer-byte-length@1.0.0: - dependencies: - call-bind: 1.0.2 - is-array-buffer: 3.0.2 - array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -4024,23 +5626,25 @@ snapshots: ast-types-flow@0.0.8: {} - autoprefixer@10.4.20(postcss@8.4.47): + astral-regex@2.0.0: {} + + auto-bind@4.0.0: {} + + autoprefixer@10.4.20(postcss@8.4.38): dependencies: browserslist: 4.24.0 - caniuse-lite: 1.0.30001667 + caniuse-lite: 1.0.30001669 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.1.0 - postcss: 8.4.47 + picocolors: 1.0.1 + postcss: 8.4.38 postcss-value-parser: 4.2.0 - available-typed-arrays@1.0.5: {} - available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 - axe-core@4.10.0: {} + axe-core@4.10.1: {} axobject-query@4.1.0: {} @@ -4048,12 +5652,14 @@ snapshots: babel-plugin-module-resolver@5.0.2: dependencies: - find-babel-config: 2.1.2 + find-babel-config: 2.1.1 glob: 9.3.5 pkg-up: 3.1.0 reselect: 4.1.8 resolve: 1.22.8 + bail@2.0.2: {} + balanced-match@1.0.2: {} base64-js@1.5.1: {} @@ -4079,10 +5685,17 @@ snapshots: browser-fs-access@0.35.0: {} + browserslist@4.23.1: + dependencies: + caniuse-lite: 1.0.30001636 + electron-to-chromium: 1.4.803 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.1) + browserslist@4.24.0: dependencies: - caniuse-lite: 1.0.30001667 - electron-to-chromium: 1.5.34 + caniuse-lite: 1.0.30001669 + electron-to-chromium: 1.5.41 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.0) @@ -4093,6 +5706,18 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + cacheable-lookup@7.0.0: {} + + cacheable-request@10.2.14: + dependencies: + '@types/http-cache-semantics': 4.0.4 + get-stream: 6.0.1 + http-cache-semantics: 4.1.1 + keyv: 4.5.4 + mimic-response: 4.0.0 + normalize-url: 8.0.1 + responselike: 3.0.0 + cached-iterable@0.3.0: {} call-bind@1.0.2: @@ -4108,15 +5733,23 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + call-me-maybe@1.0.2: {} + callsites@3.1.0: {} camelcase-css@2.0.1: {} - camera-controls@2.9.0(three@0.163.0): - dependencies: - three: 0.163.0 + camera-controls@2.8.5(three@0.163.0): + dependencies: + three: 0.163.0 + + caniuse-lite@1.0.30001636: {} + + caniuse-lite@1.0.30001669: {} + + case@1.6.3: {} - caniuse-lite@1.0.30001667: {} + ccount@2.0.1: {} chalk@2.4.2: dependencies: @@ -4131,6 +5764,14 @@ snapshots: chalk@5.3.0: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -4147,16 +5788,42 @@ snapshots: dependencies: readdirp: 4.0.2 + ci-info@2.0.0: {} + classnames@2.5.1: {} + cli-boxes@2.2.1: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + cli-truncate@4.0.0: dependencies: slice-ansi: 5.0.0 - string-width: 7.2.0 + string-width: 7.1.0 + + clipanion@3.2.1(typanion@3.14.0): + dependencies: + typanion: 3.14.0 cliui@7.0.4: dependencies: @@ -4170,6 +5837,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + code-excerpt@3.0.0: + dependencies: + convert-to-spaces: 1.0.2 + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -4184,6 +5855,8 @@ snapshots: colorette@2.0.20: {} + comma-separated-tokens@2.0.3: {} + commander@12.1.0: {} commander@2.20.3: @@ -4197,6 +5870,8 @@ snapshots: convert-source-map@2.0.0: {} + convert-to-spaces@1.0.2: {} + create-require@1.1.1: {} cross-env@7.0.3: @@ -4241,30 +5916,21 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.5: + dependencies: + ms: 2.1.2 + debug@4.3.7: dependencies: ms: 2.1.3 - deep-equal@2.2.2: + decode-named-character-reference@1.0.2: dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.2 - es-get-iterator: 1.1.3 - get-intrinsic: 1.2.1 - is-arguments: 1.1.1 - is-array-buffer: 3.0.2 - is-date-object: 1.0.5 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - isarray: 2.0.5 - object-is: 1.1.5 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.1 - side-channel: 1.0.4 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.1 - which-typed-array: 1.1.11 + character-entities: 2.0.2 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 deep-equal@2.2.3: dependencies: @@ -4281,7 +5947,7 @@ snapshots: object-is: 1.1.6 object-keys: 1.1.1 object.assign: 4.1.5 - regexp.prototype.flags: 1.5.3 + regexp.prototype.flags: 1.5.2 side-channel: 1.0.6 which-boxed-primitive: 1.0.2 which-collection: 1.0.2 @@ -4291,6 +5957,8 @@ snapshots: deepmerge@4.3.1: {} + defer-to-connect@2.0.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 @@ -4307,10 +5975,18 @@ snapshots: delay@5.0.0: {} - detect-gpu@5.0.51: + dequal@2.0.3: {} + + detect-gpu@5.0.38: dependencies: webgl-constants: 1.1.1 + detect-libc@1.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + didyoumean@1.2.2: {} diff@4.0.2: {} @@ -4329,6 +6005,8 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv@16.4.5: {} + draco3d@1.5.7: {} eastasianwidth@0.2.0: {} @@ -4337,9 +6015,11 @@ snapshots: ebnf@1.9.1: {} - electron-to-chromium@1.5.34: {} + electron-to-chromium@1.4.803: {} - emoji-regex@10.4.0: {} + electron-to-chromium@1.5.41: {} + + emoji-regex@10.3.0: {} emoji-regex@8.0.0: {} @@ -4349,7 +6029,7 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.17.1: + enhanced-resolve@5.17.0: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 @@ -4389,10 +6069,10 @@ snapshots: is-string: 1.0.7 is-typed-array: 1.1.13 is-weakref: 1.0.2 - object-inspect: 1.13.2 + object-inspect: 1.13.1 object-keys: 1.1.1 object.assign: 4.1.5 - regexp.prototype.flags: 1.5.3 + regexp.prototype.flags: 1.5.2 safe-array-concat: 1.1.2 safe-regex-test: 1.0.3 string.prototype.trim: 1.2.9 @@ -4413,17 +6093,17 @@ snapshots: es-get-iterator@1.1.3: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 has-symbols: 1.0.3 is-arguments: 1.1.1 - is-map: 2.0.2 - is-set: 2.0.2 + is-map: 2.0.3 + is-set: 2.0.3 is-string: 1.0.7 isarray: 2.0.5 stop-iteration-iterator: 1.0.0 - es-iterator-helpers@1.1.0: + es-iterator-helpers@1.0.19: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -4437,7 +6117,7 @@ snapshots: has-proto: 1.0.3 has-symbols: 1.0.3 internal-slot: 1.0.7 - iterator.prototype: 1.1.3 + iterator.prototype: 1.1.2 safe-array-concat: 1.1.2 es-object-atoms@1.0.0: @@ -4460,6 +6140,8 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 + es6-promise@3.3.1: {} + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -4486,14 +6168,18 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - escalade@3.1.1: {} + escalade@3.1.2: {} escalade@3.2.0: {} escape-string-regexp@1.0.5: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: confusing-browser-globals: 1.0.11 @@ -4525,12 +6211,12 @@ snapshots: eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.3.7 - enhanced-resolve: 5.17.1 + debug: 4.3.5 + enhanced-resolve: 5.17.0 eslint: 8.57.1 eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 - get-tsconfig: 4.8.1 + get-tsconfig: 4.7.5 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: @@ -4587,11 +6273,11 @@ snapshots: array-includes: 3.1.8 array.prototype.flatmap: 1.3.2 ast-types-flow: 0.0.8 - axe-core: 4.10.0 + axe-core: 4.10.1 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - es-iterator-helpers: 1.1.0 + es-iterator-helpers: 1.0.19 eslint: 8.57.1 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -4599,7 +6285,7 @@ snapshots: minimatch: 3.1.2 object.fromentries: 2.0.8 safe-regex-test: 1.0.3 - string.prototype.includes: 2.0.0 + string.prototype.includes: 2.0.1 eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): dependencies: @@ -4612,7 +6298,7 @@ snapshots: array.prototype.flatmap: 1.3.2 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.1.0 + es-iterator-helpers: 1.0.19 eslint: 8.57.1 estraverse: 5.3.0 hasown: 2.0.2 @@ -4637,7 +6323,7 @@ snapshots: eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/regexpp': 4.10.1 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 '@humanwhocodes/config-array': 0.13.0 @@ -4647,13 +6333,13 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7 + debug: 4.3.5 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - esquery: 1.6.0 + esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 @@ -4661,7 +6347,7 @@ snapshots: glob-parent: 6.0.2 globals: 13.24.0 graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 5.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -4679,11 +6365,11 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.12.0 + acorn-jsx: 5.3.2(acorn@8.12.0) eslint-visitor-keys: 3.4.3 - esquery@1.6.0: + esquery@1.5.0: dependencies: estraverse: 5.3.0 @@ -4693,6 +6379,8 @@ snapshots: estraverse@5.3.0: {} + estree-util-is-identifier-name@3.0.0: {} + esutils@2.0.3: {} eventemitter3@5.0.1: {} @@ -4723,6 +6411,8 @@ snapshots: exenv@1.2.2: {} + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -4731,7 +6421,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.8 + micromatch: 4.0.7 fast-json-parse@1.0.3: {} @@ -4739,6 +6429,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -4755,9 +6447,10 @@ snapshots: dependencies: to-regex-range: 5.0.1 - find-babel-config@2.1.2: + find-babel-config@2.1.1: dependencies: json5: 2.2.3 + path-exists: 4.0.0 find-up@3.0.0: dependencies: @@ -4778,21 +6471,27 @@ snapshots: flatbuffers@22.10.26: {} - flatbuffers@22.12.6: {} - flatted@3.3.1: {} for-each@0.3.3: dependencies: is-callable: 1.2.7 - foreground-child@3.3.0: + foreground-child@3.2.1: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data-encoder@2.1.4: {} + fraction.js@4.3.7: {} + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -4834,6 +6533,8 @@ snapshots: dependencies: pump: 3.0.0 + get-stream@6.0.1: {} + get-stream@8.0.1: {} get-symbol-description@1.0.2: @@ -4842,7 +6543,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 - get-tsconfig@4.8.1: + get-tsconfig@4.7.5: dependencies: resolve-pkg-maps: 1.0.0 @@ -4854,13 +6555,12 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: + glob@10.4.1: dependencies: - foreground-child: 3.3.0 - jackspeak: 3.4.3 - minimatch: 9.0.5 + foreground-child: 3.2.1 + jackspeak: 3.4.0 + minimatch: 9.0.4 minipass: 7.1.2 - package-json-from-dist: 1.0.1 path-scurry: 1.11.1 glob@7.2.3: @@ -4885,6 +6585,8 @@ snapshots: dependencies: type-fest: 0.20.2 + globals@15.10.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -4895,7 +6597,7 @@ snapshots: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.3.2 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 @@ -4905,10 +6607,35 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + got-fetch@5.1.10(got@12.6.1): + dependencies: + got: 12.6.1 + + got@12.6.1: + dependencies: + '@sindresorhus/is': 5.6.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.14 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + graceful-fs@4.2.11: {} graphemer@1.4.0: {} + graphql-tag@2.12.6(graphql@15.9.0): + dependencies: + graphql: 15.9.0 + tslib: 2.6.3 + + graphql@15.9.0: {} + has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -4939,7 +6666,48 @@ snapshots: dependencies: function-bind: 1.1.2 - hls.js@1.3.5: {} + hast-util-to-jsx-runtime@2.3.2: + dependencies: + '@types/estree': 1.0.6 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.1.3 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + style-to-object: 1.0.8 + unist-util-position: 5.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + highlight.js@10.7.3: {} + + hls.js@1.5.17: {} + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + html-url-attributes@3.0.1: {} + + http-cache-semantics@4.1.1: {} + + http2-client@1.3.5: {} + + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 human-signals@1.1.1: {} @@ -4951,14 +6719,14 @@ snapshots: ieee754@1.2.1: {} - ignore@5.3.2: {} + ignore@5.3.1: {} immediate@3.0.6: {} immer@9.0.21: optional: true - immutable@4.3.7: {} + immutable@4.3.6: {} import-fresh@3.3.0: dependencies: @@ -4967,6 +6735,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -4974,6 +6744,40 @@ snapshots: inherits@2.0.4: {} + ink@3.2.0(@types/react@18.3.11)(react@18.3.1): + dependencies: + ansi-escapes: 4.3.2 + auto-bind: 4.0.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + cli-cursor: 3.1.0 + cli-truncate: 2.1.0 + code-excerpt: 3.0.0 + indent-string: 4.0.0 + is-ci: 2.0.0 + lodash: 4.17.21 + patch-console: 1.0.0 + react: 18.3.1 + react-devtools-core: 4.28.5 + react-reconciler: 0.26.2(react@18.3.1) + scheduler: 0.20.2 + signal-exit: 3.0.7 + slice-ansi: 3.0.0 + stack-utils: 2.0.6 + string-width: 4.2.3 + type-fest: 0.12.0 + widest-line: 3.1.0 + wrap-ansi: 6.2.0 + ws: 7.5.10 + yoga-layout-prebuilt: 1.10.0 + optionalDependencies: + '@types/react': 18.3.11 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + inline-style-parser@0.2.4: {} + internal-slot@1.0.7: dependencies: es-errors: 1.3.0 @@ -4984,16 +6788,17 @@ snapshots: ip-num@1.5.1: {} - is-arguments@1.1.1: + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 - is-array-buffer@3.0.2: + is-arguments@1.1.1: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-typed-array: 1.1.12 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 is-array-buffer@3.0.4: dependencies: @@ -5023,6 +6828,14 @@ snapshots: is-callable@1.2.7: {} + is-ci@2.0.0: + dependencies: + ci-info: 2.0.0 + + is-core-module@2.13.1: + dependencies: + hasown: 2.0.2 + is-core-module@2.15.1: dependencies: hasown: 2.0.2 @@ -5035,6 +6848,8 @@ snapshots: dependencies: has-tostringtag: 1.0.0 + is-decimal@2.0.1: {} + is-docker@2.2.1: {} is-extglob@2.1.1: {} @@ -5059,7 +6874,7 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-map@2.0.2: {} + is-hexadecimal@2.0.1: {} is-map@2.0.3: {} @@ -5073,6 +6888,8 @@ snapshots: is-path-inside@3.0.3: {} + is-plain-obj@4.1.0: {} + is-promise@2.2.2: {} is-regex@1.1.4: @@ -5080,14 +6897,8 @@ snapshots: call-bind: 1.0.2 has-tostringtag: 1.0.0 - is-set@2.0.2: {} - is-set@2.0.3: {} - is-shared-array-buffer@1.0.2: - dependencies: - call-bind: 1.0.2 - is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 @@ -5104,27 +6915,16 @@ snapshots: dependencies: has-symbols: 1.0.3 - is-typed-array@1.1.12: - dependencies: - which-typed-array: 1.1.11 - is-typed-array@1.1.13: dependencies: which-typed-array: 1.1.15 - is-weakmap@2.0.1: {} - is-weakmap@2.0.2: {} is-weakref@1.0.2: dependencies: call-bind: 1.0.7 - is-weakset@2.0.2: - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-weakset@2.0.3: dependencies: call-bind: 1.0.7 @@ -5138,7 +6938,7 @@ snapshots: isexe@2.0.0: {} - iterator.prototype@1.1.3: + iterator.prototype@1.1.2: dependencies: define-properties: 1.2.1 get-intrinsic: 1.2.4 @@ -5151,7 +6951,7 @@ snapshots: '@types/react-reconciler': 0.28.8 react: 18.3.1 - jackspeak@3.4.3: + jackspeak@3.4.0: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: @@ -5165,6 +6965,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@2.5.2: {} + jsesc@3.0.2: {} json-buffer@3.0.1: {} @@ -5179,6 +6981,12 @@ snapshots: json5@2.2.3: {} + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -5237,57 +7045,416 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.0 - locate-path@3.0.0: + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.castarray@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lowercase-keys@3.0.0: {} + + lru-cache@10.2.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + maath@0.10.7(@types/three@0.163.0)(three@0.163.0): + dependencies: + '@types/three': 0.163.0 + three: 0.163.0 + + make-error@1.3.6: {} + + markdown-table@3.0.4: {} + + matchmediaquery@0.4.2: + dependencies: + css-mediaquery: 0.1.2 + + mdast-util-find-and-replace@3.0.1: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.1 + micromark-util-character: 2.1.0 + + mdast-util-gfm-footnote@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.0.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.1.3: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.1 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-decode-string: 2.0.0 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + meshline@3.3.1(three@0.163.0): + dependencies: + three: 0.163.0 + + meshoptimizer@0.18.1: {} + + micromark-core-commonmark@2.0.1: + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-table@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.0 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.0 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-destination@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-label@2.0.0: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-space@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-types: 2.0.0 + + micromark-factory-title@2.0.0: dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - locate-path@6.0.0: + micromark-factory-whitespace@2.0.0: dependencies: - p-locate: 5.0.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - lodash.merge@4.6.2: {} + micromark-util-character@2.1.0: + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - lodash@4.17.21: {} + micromark-util-chunked@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 - log-update@6.1.0: + micromark-util-classify-character@2.0.0: dependencies: - ansi-escapes: 7.0.0 - cli-cursor: 5.0.0 - slice-ansi: 7.1.0 - strip-ansi: 7.1.0 - wrap-ansi: 9.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - loose-envify@1.4.0: + micromark-util-combine-extensions@2.0.0: dependencies: - js-tokens: 4.0.0 + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 - lru-cache@10.4.3: {} + micromark-util-decode-numeric-character-reference@2.0.1: + dependencies: + micromark-util-symbol: 2.0.0 - lru-cache@5.1.1: + micromark-util-decode-string@2.0.0: dependencies: - yallist: 3.1.1 + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-symbol: 2.0.0 + + micromark-util-encode@2.0.0: {} - maath@0.10.8(@types/three@0.163.0)(three@0.163.0): + micromark-util-html-tag-name@2.0.0: {} + + micromark-util-normalize-identifier@2.0.0: dependencies: - '@types/three': 0.163.0 - three: 0.163.0 + micromark-util-symbol: 2.0.0 - make-error@1.3.6: {} + micromark-util-resolve-all@2.0.0: + dependencies: + micromark-util-types: 2.0.0 - matchmediaquery@0.4.2: + micromark-util-sanitize-uri@2.0.0: dependencies: - css-mediaquery: 0.1.2 + micromark-util-character: 2.1.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 - merge-stream@2.0.0: {} + micromark-util-subtokenize@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 - merge2@1.4.1: {} + micromark-util-symbol@2.0.0: {} - meshline@3.3.1(three@0.163.0): + micromark-util-types@2.0.0: {} + + micromark@4.0.0: dependencies: - three: 0.163.0 + '@types/debug': 4.1.12 + debug: 4.3.7 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + transitivePeerDependencies: + - supports-color - meshoptimizer@0.18.1: {} + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 micromatch@4.0.8: dependencies: @@ -5300,6 +7467,10 @@ snapshots: mimic-function@5.0.1: {} + mimic-response@3.1.0: {} + + mimic-response@4.0.0: {} + mini-svg-data-uri@1.4.4: {} minimatch@3.1.2: @@ -5310,7 +7481,7 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimatch@9.0.5: + minimatch@9.0.4: dependencies: brace-expansion: 2.0.1 @@ -5320,6 +7491,8 @@ snapshots: minipass@7.1.2: {} + ms@2.1.2: {} + ms@2.1.3: {} mz@2.7.0: @@ -5332,12 +7505,30 @@ snapshots: natural-compare@1.4.0: {} + node-addon-api@7.1.1: {} + + node-fetch-h2@2.3.0: + dependencies: + http2-client: 1.3.5 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-readfiles@0.2.0: + dependencies: + es6-promise: 3.3.1 + + node-releases@2.0.14: {} + node-releases@2.0.18: {} normalize-path@3.0.0: {} normalize-range@0.1.2: {} + normalize-url@8.0.1: {} + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -5346,18 +7537,42 @@ snapshots: dependencies: path-key: 4.0.0 - object-assign@4.1.1: {} + oas-kit-common@1.0.8: + dependencies: + fast-safe-stringify: 2.1.1 - object-hash@3.0.0: {} + oas-linter@3.2.2: + dependencies: + '@exodus/schemasafe': 1.3.0 + should: 13.2.3 + yaml: 1.10.2 - object-inspect@1.12.3: {} + oas-resolver@2.5.6: + dependencies: + node-fetch-h2: 2.3.0 + oas-kit-common: 1.0.8 + reftools: 1.1.9 + yaml: 1.10.2 + yargs: 17.7.2 - object-inspect@1.13.2: {} + oas-schema-walker@1.1.5: {} - object-is@1.1.5: + oas-validator@5.0.8: dependencies: - call-bind: 1.0.2 - define-properties: 1.2.1 + call-me-maybe: 1.0.2 + oas-kit-common: 1.0.8 + oas-linter: 3.2.2 + oas-resolver: 2.5.6 + oas-schema-walker: 1.1.5 + reftools: 1.1.9 + should: 13.2.3 + yaml: 1.10.2 + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.1: {} object-is@1.1.6: dependencies: @@ -5422,6 +7637,17 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + openapi3-ts@2.0.2: + dependencies: + yaml: 1.10.2 + + optimism@0.18.0: + dependencies: + '@wry/caches': 1.0.1 + '@wry/context': 0.7.4 + '@wry/trie': 0.4.3 + tslib: 2.6.3 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5431,6 +7657,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + p-cancelable@3.0.0: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -5449,12 +7677,31 @@ snapshots: p-try@2.2.0: {} - package-json-from-dist@1.0.1: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-entities@4.0.1: + dependencies: + '@types/unist': 2.0.11 + character-entities: 2.0.2 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.0.2 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + + patch-console@1.0.0: {} + path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -5469,14 +7716,16 @@ snapshots: path-scurry@1.11.1: dependencies: - lru-cache: 10.4.3 + lru-cache: 10.2.2 minipass: 7.1.2 path-type@4.0.0: {} pegjs@0.10.0: {} - picocolors@1.1.0: {} + picocolors@1.0.1: {} + + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5490,44 +7739,57 @@ snapshots: dependencies: find-up: 3.0.0 + pluralize@8.0.0: {} + possible-typed-array-names@1.0.0: {} - postcss-import@15.1.0(postcss@8.4.47): + postcss-import@15.1.0(postcss@8.4.38): dependencies: - postcss: 8.4.47 + postcss: 8.4.38 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.47): + postcss-js@4.0.1(postcss@8.4.38): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.47 + postcss: 8.4.38 - postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@9.1.1(typescript@5.6.3)): + postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@9.1.1(typescript@5.6.3)): dependencies: lilconfig: 3.1.2 - yaml: 2.5.1 + yaml: 2.4.5 optionalDependencies: - postcss: 8.4.47 + postcss: 8.4.38 ts-node: 9.1.1(typescript@5.6.3) - postcss-nested@6.2.0(postcss@8.4.47): + postcss-nested@6.0.1(postcss@8.4.38): dependencies: - postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss: 8.4.38 + postcss-selector-parser: 6.1.0 + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 - postcss-selector-parser@6.1.2: + postcss-selector-parser@6.1.0: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 postcss-value-parser@4.2.0: {} + postcss@8.4.38: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.1.0 + picocolors: 1.1.1 source-map-js: 1.2.1 potpack@1.0.2: {} @@ -5552,6 +7814,10 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-expr@2.0.6: {} + + property-information@6.5.0: {} + pump@3.0.0: dependencies: end-of-stream: 1.4.4 @@ -5561,11 +7827,21 @@ snapshots: queue-microtask@1.2.3: {} + quick-lru@5.1.1: {} + react-composer@5.0.3(react@18.3.1): dependencies: prop-types: 15.8.1 react: 18.3.1 + react-devtools-core@4.28.5: + dependencies: + shell-quote: 1.8.1 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -5574,7 +7850,7 @@ snapshots: react-error-boundary@4.0.13(react@18.3.1): dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.24.7 react: 18.3.1 react-fast-compare@3.2.2: {} @@ -5595,6 +7871,23 @@ snapshots: react-lifecycles-compat@3.0.4: {} + react-markdown@9.0.1(@types/react@18.3.11)(react@18.3.1): + dependencies: + '@types/hast': 3.0.4 + '@types/react': 18.3.11 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.2 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.0 + react: 18.3.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.1 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-modal@3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: exenv: 1.2.2 @@ -5604,6 +7897,13 @@ snapshots: react-lifecycles-compat: 3.0.4 warning: 4.0.3 + react-reconciler@0.26.2(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react: 18.3.1 + scheduler: 0.20.2 + react-reconciler@0.27.0(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -5620,16 +7920,16 @@ snapshots: react: 18.3.1 shallow-equal: 3.1.0 - react-router-dom@6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router-dom@6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@remix-run/router': 1.19.2 + '@remix-run/router': 1.20.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-router: 6.26.2(react@18.3.1) + react-router: 6.27.0(react@18.3.1) - react-router@6.26.2(react@18.3.1): + react-router@6.27.0(react@18.3.1): dependencies: - '@remix-run/router': 1.19.2 + '@remix-run/router': 1.20.0 react: 18.3.1 react-side-effect@2.1.2(react@18.3.1): @@ -5658,45 +7958,93 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 globalthis: 1.0.4 - which-builtin-type: 1.1.4 + which-builtin-type: 1.1.3 - regenerator-runtime@0.14.1: {} + reftools@1.1.9: {} - regexp.prototype.flags@1.5.1: - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.1 - set-function-name: 2.0.1 + regenerator-runtime@0.14.1: {} - regexp.prototype.flags@1.5.3: + regexp.prototype.flags@1.5.2: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 es-errors: 1.3.0 set-function-name: 2.0.2 + rehackt@0.1.0(@types/react@18.3.11)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.11 + react: 18.3.1 + + remark-gfm@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.0.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-directory@2.1.1: {} require-from-string@2.0.2: {} reselect@4.1.8: {} + resolve-alpn@1.2.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} resolve@1.22.8: dependencies: - is-core-module: 2.15.1 + is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 resolve@2.0.0-next.5: dependencies: - is-core-module: 2.15.1 + is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + response-iterator@0.2.6: {} + + responselike@3.0.0: + dependencies: + lowercase-keys: 3.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -5745,6 +8093,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rxjs@7.8.1: + dependencies: + tslib: 2.6.3 + safe-array-concat@1.1.2: dependencies: call-bind: 1.0.7 @@ -5758,11 +8110,17 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 - sass@1.79.4: + sass@1.80.2: dependencies: + '@parcel/watcher': 2.4.1 chokidar: 4.0.1 - immutable: 4.3.7 - source-map-js: 1.2.1 + immutable: 4.3.6 + source-map-js: 1.2.0 + + scheduler@0.20.2: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 scheduler@0.21.0: dependencies: @@ -5785,12 +8143,6 @@ snapshots: gopd: 1.0.1 has-property-descriptors: 1.0.2 - set-function-name@2.0.1: - dependencies: - define-data-property: 1.1.4 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - set-function-name@2.0.2: dependencies: define-data-property: 1.1.4 @@ -5806,18 +8158,40 @@ snapshots: shebang-regex@3.0.0: {} - side-channel@1.0.4: + shell-quote@1.8.1: {} + + should-equal@2.0.0: dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - object-inspect: 1.12.3 + should-type: 1.4.0 + + should-format@3.0.3: + dependencies: + should-type: 1.4.0 + should-type-adaptors: 1.1.0 + + should-type-adaptors@1.1.0: + dependencies: + should-type: 1.4.0 + should-util: 1.0.1 + + should-type@1.4.0: {} + + should-util@1.0.1: {} + + should@13.2.3: + dependencies: + should-equal: 2.0.0 + should-format: 3.0.3 + should-type: 1.4.0 + should-type-adaptors: 1.1.0 + should-util: 1.0.1 side-channel@1.0.6: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 - object-inspect: 1.13.2 + object-inspect: 1.13.1 signal-exit@3.0.7: {} @@ -5827,6 +8201,14 @@ snapshots: slash@3.0.0: {} + slash@4.0.0: {} + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + slice-ansi@5.0.0: dependencies: ansi-styles: 6.2.1 @@ -5837,9 +8219,7 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 - solarxr-protocol@file:solarxr-protocol: - dependencies: - flatbuffers: 22.10.26 + source-map-js@1.2.0: {} source-map-js@1.2.1: {} @@ -5852,6 +8232,8 @@ snapshots: source-map@0.7.4: {} + space-separated-tokens@2.0.2: {} + spdx-compare@1.0.0: dependencies: array-find-index: 1.0.2 @@ -5863,9 +8245,9 @@ snapshots: spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.20 + spdx-license-ids: 3.0.18 - spdx-license-ids@3.0.20: {} + spdx-license-ids@3.0.18: {} spdx-ranges@2.1.1: {} @@ -5875,6 +8257,10 @@ snapshots: spdx-expression-parse: 3.0.1 spdx-ranges: 2.1.1 + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stats-gl@2.2.8: dependencies: '@types/three': 0.163.0 @@ -5899,14 +8285,15 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string-width@7.2.0: + string-width@7.1.0: dependencies: - emoji-regex: 10.4.0 + emoji-regex: 10.3.0 get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 - string.prototype.includes@2.0.0: + string.prototype.includes@2.0.1: dependencies: + call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 @@ -5921,7 +8308,7 @@ snapshots: gopd: 1.0.1 has-symbols: 1.0.3 internal-slot: 1.0.7 - regexp.prototype.flags: 1.5.3 + regexp.prototype.flags: 1.5.2 set-function-name: 2.0.2 side-channel: 1.0.6 @@ -5949,6 +8336,11 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -5965,11 +8357,15 @@ snapshots: strip-json-comments@3.1.1: {} + style-to-object@1.0.8: + dependencies: + inline-style-parser: 0.2.4 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 - glob: 10.4.5 + glob: 10.4.1 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.6 @@ -5989,9 +8385,27 @@ snapshots: dependencies: react: 18.3.1 + swagger2openapi@7.0.8: + dependencies: + call-me-maybe: 1.0.2 + node-fetch: 2.7.0 + node-fetch-h2: 2.3.0 + node-readfiles: 0.2.0 + oas-kit-common: 1.0.8 + oas-resolver: 2.5.6 + oas-schema-walker: 1.1.5 + oas-validator: 5.0.8 + reftools: 1.1.9 + yaml: 1.10.2 + yargs: 17.7.2 + transitivePeerDependencies: + - encoding + + symbol-observable@4.0.0: {} + tailwind-gradient-mask-image@1.2.0: {} - tailwindcss@3.4.13(ts-node@9.1.1(typescript@5.6.3)): + tailwindcss@3.4.14(ts-node@9.1.1(typescript@5.6.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -6003,16 +8417,16 @@ snapshots: is-glob: 4.0.3 jiti: 1.21.6 lilconfig: 2.1.0 - micromatch: 4.0.8 + micromatch: 4.0.7 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.1.0 - postcss: 8.4.47 - postcss-import: 15.1.0(postcss@8.4.47) - postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@9.1.1(typescript@5.6.3)) - postcss-nested: 6.2.0(postcss@8.4.47) - postcss-selector-parser: 6.1.2 + picocolors: 1.0.1 + postcss: 8.4.38 + postcss-import: 15.1.0(postcss@8.4.38) + postcss-js: 4.0.1(postcss@8.4.38) + postcss-load-config: 4.0.2(postcss@8.4.38)(ts-node@9.1.1(typescript@5.6.3)) + postcss-nested: 6.0.1(postcss@8.4.38) + postcss-selector-parser: 6.1.0 resolve: 1.22.8 sucrase: 3.35.0 transitivePeerDependencies: @@ -6023,7 +8437,7 @@ snapshots: terser@5.31.1: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.12.0 commander: 2.20.3 source-map-support: 0.5.21 optional: true @@ -6042,11 +8456,11 @@ snapshots: dependencies: three: 0.163.0 - three-stdlib@2.33.0(three@0.163.0): + three-stdlib@2.30.3(three@0.163.0): dependencies: '@types/draco3d': 1.4.10 '@types/offscreencanvas': 2019.7.3 - '@types/webxr': 0.5.20 + '@types/webxr': 0.5.16 draco3d: 1.5.7 fflate: 0.6.10 potpack: 1.0.2 @@ -6054,6 +8468,8 @@ snapshots: three@0.163.0: {} + tiny-case@1.0.3: {} + tinycolor2@1.6.0: {} to-fast-properties@2.0.0: {} @@ -6062,6 +8478,12 @@ snapshots: dependencies: is-number: 7.0.0 + toposort@2.0.2: {} + + tr46@0.0.3: {} + + trim-lines@3.0.1: {} + troika-three-text@0.49.1(three@0.163.0): dependencies: bidi-js: 1.0.3 @@ -6076,12 +8498,18 @@ snapshots: troika-worker-utils@0.49.0: {} + trough@2.2.0: {} + ts-api-utils@1.3.0(typescript@5.6.3): dependencies: typescript: 5.6.3 ts-interface-checker@0.1.13: {} + ts-invariant@0.10.3: + dependencies: + tslib: 2.6.3 + ts-node@9.1.1(typescript@4.8.4): dependencies: arg: 4.1.3 @@ -6103,7 +8531,7 @@ snapshots: yn: 3.1.1 optional: true - ts-pattern@5.4.0: {} + ts-pattern@5.5.0: {} ts-xor@1.3.0: {} @@ -6114,22 +8542,37 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tslib@2.7.0: {} + tslib@1.14.1: {} + + tslib@2.6.3: {} + + tsutils@3.21.0(typescript@4.8.2): + dependencies: + tslib: 1.14.1 + typescript: 4.8.2 tunnel-rat@0.1.2(@types/react@18.3.11)(immer@9.0.21)(react@18.3.1): dependencies: - zustand: 4.5.5(@types/react@18.3.11)(immer@9.0.21)(react@18.3.1) + zustand: 4.5.2(@types/react@18.3.11)(immer@9.0.21)(react@18.3.1) transitivePeerDependencies: - '@types/react' - immer - react + typanion@3.14.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 + type-fest@0.12.0: {} + type-fest@0.20.2: {} + type-fest@0.21.3: {} + + type-fest@2.19.0: {} + typed-array-buffer@1.0.2: dependencies: call-bind: 1.0.7 @@ -6162,6 +8605,19 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 + typescript-eslint@8.8.0(eslint@8.57.1)(typescript@5.6.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.8.0(@typescript-eslint/parser@8.8.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 8.8.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.8.0(eslint@8.57.1)(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color + + typescript@4.8.2: {} + typescript@4.8.4: {} typescript@5.6.3: {} @@ -6176,11 +8632,52 @@ snapshots: undici-types@5.26.5: optional: true + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + universalify@2.0.1: {} + + update-browserslist-db@1.0.16(browserslist@4.23.1): + dependencies: + browserslist: 4.23.1 + escalade: 3.1.2 + picocolors: 1.0.1 + update-browserslist-db@1.1.1(browserslist@4.24.0): dependencies: browserslist: 4.24.0 escalade: 3.2.0 - picocolors: 1.1.0 + picocolors: 1.1.1 uri-js@4.4.1: dependencies: @@ -6190,7 +8687,7 @@ snapshots: dependencies: react: 18.3.1 - use-sync-external-store@1.2.2(react@18.3.1): + use-sync-external-store@1.2.0(react@18.3.1): dependencies: react: 18.3.1 @@ -6200,7 +8697,17 @@ snapshots: uuid@9.0.1: {} - vite@5.4.8(@types/node@20.14.2)(sass@1.79.4)(terser@5.31.1): + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + + vite@5.4.9(@types/node@20.14.2)(sass@1.80.2)(terser@5.31.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 @@ -6208,7 +8715,7 @@ snapshots: optionalDependencies: '@types/node': 20.14.2 fsevents: 2.3.3 - sass: 1.79.4 + sass: 1.80.2 terser: 5.31.1 warning@4.0.3: @@ -6219,6 +8726,13 @@ snapshots: webgl-sdf-generator@1.1.1: {} + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -6227,7 +8741,7 @@ snapshots: is-string: 1.0.7 is-symbol: 1.0.4 - which-builtin-type@1.1.4: + which-builtin-type@1.1.3: dependencies: function.prototype.name: 1.1.6 has-tostringtag: 1.0.2 @@ -6242,13 +8756,6 @@ snapshots: which-collection: 1.0.2 which-typed-array: 1.1.15 - which-collection@1.0.1: - dependencies: - is-map: 2.0.2 - is-set: 2.0.2 - is-weakmap: 2.0.1 - is-weakset: 2.0.2 - which-collection@1.0.2: dependencies: is-map: 2.0.3 @@ -6256,14 +8763,6 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.3 - which-typed-array@1.1.11: - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - which-typed-array@1.1.15: dependencies: available-typed-arrays: 1.0.7 @@ -6276,8 +8775,18 @@ snapshots: dependencies: isexe: 2.0.0 + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -6293,15 +8802,21 @@ snapshots: wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 - string-width: 7.2.0 + string-width: 7.1.0 strip-ansi: 7.1.0 wrappy@1.0.2: {} + ws@7.5.10: {} + y18n@5.0.8: {} yallist@3.1.1: {} + yaml@1.10.2: {} + + yaml@2.4.5: {} + yaml@2.5.1: {} yargs-parser@20.2.9: {} @@ -6311,7 +8826,7 @@ snapshots: yargs@16.2.0: dependencies: cliui: 7.0.4 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -6321,7 +8836,7 @@ snapshots: yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.2.0 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -6332,14 +8847,33 @@ snapshots: yocto-queue@0.1.0: {} + yoga-layout-prebuilt@1.10.0: + dependencies: + '@types/yoga-layout': 1.9.2 + + yup@1.4.0: + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + + zen-observable-ts@1.2.5: + dependencies: + zen-observable: 0.8.15 + + zen-observable@0.8.15: {} + zustand@3.7.2(react@18.3.1): optionalDependencies: react: 18.3.1 - zustand@4.5.5(@types/react@18.3.11)(immer@9.0.21)(react@18.3.1): + zustand@4.5.2(@types/react@18.3.11)(immer@9.0.21)(react@18.3.1): dependencies: - use-sync-external-store: 1.2.2(react@18.3.1) + use-sync-external-store: 1.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.11 immer: 9.0.21 react: 18.3.1 + + zwitch@2.0.4: {} diff --git a/server/android/build.gradle.kts b/server/android/build.gradle.kts index 7dd94c33aa..bf9e942c14 100644 --- a/server/android/build.gradle.kts +++ b/server/android/build.gradle.kts @@ -57,12 +57,8 @@ tasks.withType { options.encoding = "UTF-8" } -allprojects { - repositories { - google() - mavenCentral() - maven(url = "https://jitpack.io") - } +repositories { + google() } dependencies { diff --git a/server/android/src/main/java/dev/slimevr/android/serial/AndroidSerialHandler.kt b/server/android/src/main/java/dev/slimevr/android/serial/AndroidSerialHandler.kt index 229620566f..ea4ee4448a 100644 --- a/server/android/src/main/java/dev/slimevr/android/serial/AndroidSerialHandler.kt +++ b/server/android/src/main/java/dev/slimevr/android/serial/AndroidSerialHandler.kt @@ -90,10 +90,16 @@ class AndroidSerialHandler(val activity: AppCompatActivity) : listeners.forEach { it.onNewSerialDevice(port) } } + private fun onDeviceDel(port: SerialPortWrapper) { + listeners.forEach { it.onSerialDeviceDeleted(port) } + } + private fun detectNewPorts() { - val differences = knownPorts.asSequence() - lastKnownPorts + val addDifferences = knownPorts.asSequence() - lastKnownPorts + val delDifferences = lastKnownPorts - knownPorts.asSequence().toSet() lastKnownPorts = knownPorts.asSequence().toSet() - differences.forEach { onNewDevice(it) } + addDifferences.forEach { onNewDevice(it) } + delDifferences.forEach { onDeviceDel(it) } } override fun addListener(channel: SerialListener) { @@ -226,12 +232,18 @@ class AndroidSerialHandler(val activity: AppCompatActivity) : } } + override fun write(buff: ByteArray) { + usbIoManager?.writeAsync(buff) + } + @Synchronized override fun setWifi(ssid: String, passwd: String) { writeSerial("SET WIFI \"${ssid}\" \"${passwd}\"") addLog("-> SET WIFI \"$ssid\" \"${passwd.replace(".".toRegex(), "*")}\"\n") } + override fun getCurrentPort(): SlimeSerialPort? = this.currentPort + private fun addLog(str: String) { LogManager.info("[Serial] $str") listeners.forEach { it.onSerialLog(str) } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 645f9c3831..11ddef897f 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -2,8 +2,11 @@ plugins { id("com.diffplug.spotless") } -repositories { - mavenCentral() +allprojects { + repositories { + mavenCentral() + maven("https://jitpack.io") + } } configure { @@ -35,7 +38,7 @@ configure { "ktlint_standard_property-naming" to "disabled", "ij_kotlin_packages_to_use_import_on_demand" to "java.util.*,kotlin.math.*,dev.slimevr.autobone.errors.*" + - ",io.github.axisangles.ktmath.*,kotlinx.atomicfu.*" + + ",io.github.axisangles.ktmath.*,kotlinx.atomicfu.*,kotlinx.coroutines.*" + ",dev.slimevr.tracking.trackers.*,dev.slimevr.desktop.platform.ProtobufMessages.*" + ",solarxr_protocol.rpc.*,kotlinx.coroutines.*,com.illposed.osc.*,android.app.*", "ij_kotlin_allow_trailing_comma" to true, diff --git a/server/core/build.gradle.kts b/server/core/build.gradle.kts index cc400bc2ef..251d16bb57 100644 --- a/server/core/build.gradle.kts +++ b/server/core/build.gradle.kts @@ -77,6 +77,12 @@ dependencies { implementation("com.melloware:jintellitype:1.+") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") + implementation("com.mayakapps.kache:kache:2.1.0") + + api("com.github.loucass003:EspflashKotlin:v0.10.0") + + // Allow the use of reflection + implementation(kotlin("reflect")) // Jitpack implementation("com.github.SlimeVR:oscquery-kt:566a0cba58") @@ -87,6 +93,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.platform:junit-platform-launcher") } + tasks.test { useJUnitPlatform() } diff --git a/server/core/src/main/java/dev/slimevr/VRServer.kt b/server/core/src/main/java/dev/slimevr/VRServer.kt index 6ae919fe8e..ab43a62987 100644 --- a/server/core/src/main/java/dev/slimevr/VRServer.kt +++ b/server/core/src/main/java/dev/slimevr/VRServer.kt @@ -5,6 +5,8 @@ import dev.slimevr.autobone.AutoBoneHandler import dev.slimevr.bridge.Bridge import dev.slimevr.bridge.ISteamVRBridge import dev.slimevr.config.ConfigManager +import dev.slimevr.firmware.FirmwareUpdateHandler +import dev.slimevr.firmware.SerialFlashingHandler import dev.slimevr.osc.OSCHandler import dev.slimevr.osc.OSCRouter import dev.slimevr.osc.VMCHandler @@ -47,9 +49,12 @@ class VRServer @JvmOverloads constructor( driverBridgeProvider: SteamBridgeProvider = { _, _ -> null }, feederBridgeProvider: (VRServer) -> ISteamVRBridge? = { _ -> null }, serialHandlerProvider: (VRServer) -> SerialHandler = { _ -> SerialHandlerStub() }, + flashingHandlerProvider: (VRServer) -> SerialFlashingHandler? = { _ -> null }, acquireMulticastLock: () -> Any? = { null }, + // configPath is used by VRWorkout, do not remove! configPath: String, ) : Thread("VRServer") { + @JvmField val configManager: ConfigManager @@ -60,6 +65,7 @@ class VRServer @JvmOverloads constructor( private val bridges: MutableList = FastList() private val tasks: Queue = LinkedBlockingQueue() private val newTrackersConsumers: MutableList> = FastList() + private val trackerStatusListeners: MutableList = FastList() private val onTick: MutableList = FastList() private val lock = acquireMulticastLock() val oSCRouter: OSCRouter @@ -77,6 +83,10 @@ class VRServer @JvmOverloads constructor( @JvmField val serialHandler: SerialHandler + var serialFlashingHandler: SerialFlashingHandler? + + val firmwareUpdateHandler: FirmwareUpdateHandler + @JvmField val autoBoneHandler: AutoBoneHandler @@ -106,12 +116,14 @@ class VRServer @JvmOverloads constructor( configManager.loadConfig() deviceManager = DeviceManager(this) serialHandler = serialHandlerProvider(this) + serialFlashingHandler = flashingHandlerProvider(this) provisioningHandler = ProvisioningHandler(this) resetHandler = ResetHandler() tapSetupHandler = TapSetupHandler() humanPoseManager = HumanPoseManager(this) // AutoBone requires HumanPoseManager first autoBoneHandler = AutoBoneHandler(this) + firmwareUpdateHandler = FirmwareUpdateHandler(this) protocolAPI = ProtocolAPI(this) val computedTrackers = humanPoseManager.computedTrackers @@ -409,6 +421,18 @@ class VRServer @JvmOverloads constructor( } } + fun trackerStatusChanged(tracker: Tracker, oldStatus: TrackerStatus, newStatus: TrackerStatus) { + trackerStatusListeners.forEach { it.onTrackerStatusChanged(tracker, oldStatus, newStatus) } + } + + fun addTrackerStatusListener(listener: TrackerStatusListener) { + trackerStatusListeners.add(listener) + } + + fun removeTrackerStatusListener(listener: TrackerStatusListener) { + trackerStatusListeners.removeIf { listener == it } + } + companion object { private val nextLocalTrackerId = AtomicInteger() lateinit var instance: VRServer diff --git a/server/core/src/main/java/dev/slimevr/autobone/AutoBone.kt b/server/core/src/main/java/dev/slimevr/autobone/AutoBone.kt index d8f4f2868b..faf714f2d2 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/AutoBone.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/AutoBone.kt @@ -6,6 +6,7 @@ import dev.slimevr.autobone.errors.* import dev.slimevr.config.AutoBoneConfig import dev.slimevr.poseframeformat.PoseFrameIO import dev.slimevr.poseframeformat.PoseFrames +import dev.slimevr.tracking.processor.BoneType import dev.slimevr.tracking.processor.HumanPoseManager import dev.slimevr.tracking.processor.config.SkeletonConfigManager import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets @@ -94,38 +95,73 @@ class AutoBone(server: VRServer) { } } - fun getBoneDirection( + /** + * Computes the local tail position of the bone after rotation. + */ + fun getBoneLocalTail( skeleton: HumanPoseManager, - configOffset: SkeletonConfigOffsets, - rightSide: Boolean, + boneType: BoneType, ): Vector3 { - // IMPORTANT: This assumption for acquiring BoneType only works if - // SkeletonConfigOffsets is set up to only affect one BoneType, make sure no - // changes to SkeletonConfigOffsets goes against this assumption, please! - val boneType = when (configOffset) { - SkeletonConfigOffsets.HIPS_WIDTH, SkeletonConfigOffsets.SHOULDERS_WIDTH, - SkeletonConfigOffsets.SHOULDERS_DISTANCE, SkeletonConfigOffsets.UPPER_ARM, - SkeletonConfigOffsets.LOWER_ARM, SkeletonConfigOffsets.UPPER_LEG, - SkeletonConfigOffsets.LOWER_LEG, SkeletonConfigOffsets.FOOT_LENGTH, - -> - if (rightSide) configOffset.affectedOffsets[1] else configOffset.affectedOffsets[0] - - else -> configOffset.affectedOffsets[0] - } - return skeleton.getBone(boneType).getGlobalRotation().toRotationVector() + val bone = skeleton.getBone(boneType) + return bone.getTailPosition() - bone.getPosition() + } + + /** + * Computes the direction of the bone tail's movement between skeletons 1 and 2. + */ + fun getBoneLocalTailDir( + skeleton1: HumanPoseManager, + skeleton2: HumanPoseManager, + boneType: BoneType, + ): Vector3? { + val boneOff = getBoneLocalTail(skeleton2, boneType) - getBoneLocalTail(skeleton1, boneType) + val boneOffLen = boneOff.len() + return if (boneOffLen > MIN_SLIDE_DIST) boneOff / boneOffLen else null } - fun getDotProductDiff( + /** + * Predicts how much the provided config should be affecting the slide offsets + * of the left and right ankles. + */ + fun getSlideDot( skeleton1: HumanPoseManager, skeleton2: HumanPoseManager, - configOffset: SkeletonConfigOffsets, - rightSide: Boolean, - offset: Vector3, + config: SkeletonConfigOffsets, + slideL: Vector3?, + slideR: Vector3?, ): Float { - val normalizedOffset = offset.unit() - val dot1 = normalizedOffset.dot(getBoneDirection(skeleton1, configOffset, rightSide)) - val dot2 = normalizedOffset.dot(getBoneDirection(skeleton2, configOffset, rightSide)) - return dot2 - dot1 + var slideDot = 0f + // Used for right offset if not a symmetric bone + var boneOffL: Vector3? = null + + if (slideL != null) { + boneOffL = getBoneLocalTailDir(skeleton1, skeleton2, config.affectedOffsets[0]) + + if (boneOffL != null) { + slideDot += slideL.dot(boneOffL) + } + } + + if (slideR != null) { + // IMPORTANT: This assumption for acquiring BoneType only works if + // SkeletonConfigOffsets is set up to only affect one BoneType, make sure no + // changes to SkeletonConfigOffsets goes against this assumption, please! + val boneOffR = if (SYMM_CONFIGS.contains(config)) { + getBoneLocalTailDir(skeleton1, skeleton2, config.affectedOffsets[1]) + } else if (slideL != null) { + // Use cached offset if slideL was used + boneOffL + } else { + // Compute offset if missing because of slideL + getBoneLocalTailDir(skeleton1, skeleton2, config.affectedOffsets[0]) + } + + if (boneOffR != null) { + slideDot += slideR.dot(boneOffR) + } + } + + return slideDot / 2f } fun applyConfig( @@ -488,13 +524,15 @@ class AutoBone(server: VRServer) { return } - val slideLeft = skeleton2 - .getComputedTracker(TrackerRole.LEFT_FOOT).position - + val slideL = skeleton2.getComputedTracker(TrackerRole.LEFT_FOOT).position - skeleton1.getComputedTracker(TrackerRole.LEFT_FOOT).position + val slideLLen = slideL.len() + val slideLUnit: Vector3? = if (slideLLen > MIN_SLIDE_DIST) slideL / slideLLen else null - val slideRight = skeleton2 - .getComputedTracker(TrackerRole.RIGHT_FOOT).position - + val slideR = skeleton2.getComputedTracker(TrackerRole.RIGHT_FOOT).position - skeleton1.getComputedTracker(TrackerRole.RIGHT_FOOT).position + val slideRLen = slideR.len() + val slideRUnit: Vector3? = if (slideRLen > MIN_SLIDE_DIST) slideR / slideRLen else null val intermediateOffsets = EnumMap(offsets) for (entry in intermediateOffsets.entries) { @@ -505,28 +543,23 @@ class AutoBone(server: VRServer) { } val originalLength = entry.value - val leftDotProduct = getDotProductDiff( - skeleton1, - skeleton2, - entry.key, - false, - slideLeft, - ) - val rightDotProduct = getDotProductDiff( + // Calculate the total effect of the bone based on change in rotation + val slideDot = getSlideDot( skeleton1, skeleton2, entry.key, - true, - slideRight, + slideLUnit, + slideRUnit, ) - - // Calculate the total effect of the bone based on change in rotation - val dotLength = originalLength * ((leftDotProduct + rightDotProduct) / 2f) + val dotLength = originalLength * slideDot // Scale by the total effect of the bone val curAdjustVal = adjustVal * -dotLength - val newLength = originalLength + curAdjustVal + if (curAdjustVal == 0f) { + continue + } + val newLength = originalLength + curAdjustVal // No small or negative numbers!!! Bad algorithm! if (newLength < 0.01f) { continue @@ -754,6 +787,7 @@ class AutoBone(server: VRServer) { companion object { const val MIN_HEIGHT = 0.4f + const val MIN_SLIDE_DIST = 0.002f const val AUTOBONE_FOLDER = "AutoBone Recordings" const val LOADAUTOBONE_FOLDER = "Load AutoBone Recordings" @@ -773,5 +807,16 @@ class AutoBone(server: VRServer) { private fun errorFunc(errorDeriv: Float): Float = 0.5f * (errorDeriv * errorDeriv) private fun decayFunc(initialAdjustRate: Float, adjustRateDecay: Float, epoch: Int): Float = if (epoch >= 0) initialAdjustRate / (1 + (adjustRateDecay * epoch)) else 0.0f + + private val SYMM_CONFIGS = arrayOf( + SkeletonConfigOffsets.HIPS_WIDTH, + SkeletonConfigOffsets.SHOULDERS_WIDTH, + SkeletonConfigOffsets.SHOULDERS_DISTANCE, + SkeletonConfigOffsets.UPPER_ARM, + SkeletonConfigOffsets.LOWER_ARM, + SkeletonConfigOffsets.UPPER_LEG, + SkeletonConfigOffsets.LOWER_LEG, + SkeletonConfigOffsets.FOOT_LENGTH, + ) } } diff --git a/server/core/src/main/java/dev/slimevr/autobone/errors/PositionError.kt b/server/core/src/main/java/dev/slimevr/autobone/errors/PositionError.kt index 498379875f..f438f1bf2e 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/errors/PositionError.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/errors/PositionError.kt @@ -39,10 +39,14 @@ class PositionError : IAutoBoneError { val position = trackerFrame.tryGetPosition() ?: continue val trackerRole = trackerFrame.tryGetTrackerPosition()?.trackerRole ?: continue - val computedTracker = skeleton.getComputedTracker(trackerRole) ?: continue + try { + val computedTracker = skeleton.getComputedTracker(trackerRole) - offset += (position - computedTracker.position).len() - offsetCount++ + offset += (position - computedTracker.position).len() + offsetCount++ + } catch (_: Exception) { + // Ignore unsupported positions + } } return if (offsetCount > 0) offset / offsetCount else 0f } diff --git a/server/core/src/main/java/dev/slimevr/autobone/errors/PositionOffsetError.kt b/server/core/src/main/java/dev/slimevr/autobone/errors/PositionOffsetError.kt index d514b517ee..953c022c81 100644 --- a/server/core/src/main/java/dev/slimevr/autobone/errors/PositionOffsetError.kt +++ b/server/core/src/main/java/dev/slimevr/autobone/errors/PositionOffsetError.kt @@ -37,13 +37,17 @@ class PositionOffsetError : IAutoBoneError { val position2 = trackerFrame2.tryGetPosition() ?: continue val trackerRole2 = trackerFrame2.tryGetTrackerPosition()?.trackerRole ?: continue - val computedTracker1 = skeleton1.getComputedTracker(trackerRole1) ?: continue - val computedTracker2 = skeleton2.getComputedTracker(trackerRole2) ?: continue + try { + val computedTracker1 = skeleton1.getComputedTracker(trackerRole1) + val computedTracker2 = skeleton2.getComputedTracker(trackerRole2) - val dist1 = (position1 - computedTracker1.position).len() - val dist2 = (position2 - computedTracker2.position).len() - offset += abs(dist2 - dist1) - offsetCount++ + val dist1 = (position1 - computedTracker1.position).len() + val dist2 = (position2 - computedTracker2.position).len() + offset += abs(dist2 - dist1) + offsetCount++ + } catch (_: Exception) { + // Ignore unsupported positions + } } return if (offsetCount > 0) offset / offsetCount else 0f } diff --git a/server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt b/server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt index 7c6c136720..3c7cf20d94 100644 --- a/server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt +++ b/server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt @@ -4,14 +4,14 @@ class AutoBoneConfig { var cursorIncrement = 2 var minDataDistance = 1 var maxDataDistance = 1 - var numEpochs = 100 + var numEpochs = 50 var printEveryNumEpochs = 25 var initialAdjustRate = 10.0f var adjustRateDecay = 1.0f - var slideErrorFactor = 0.0f - var offsetSlideErrorFactor = 1.0f + var slideErrorFactor = 1.0f + var offsetSlideErrorFactor = 0.0f var footHeightOffsetErrorFactor = 0.0f - var bodyProportionErrorFactor = 0.25f + var bodyProportionErrorFactor = 0.05f var heightErrorFactor = 0.0f var positionErrorFactor = 0.0f var positionOffsetErrorFactor = 0.0f diff --git a/server/core/src/main/java/dev/slimevr/config/CurrentVRConfigConverter.java b/server/core/src/main/java/dev/slimevr/config/CurrentVRConfigConverter.java index 479df1f6da..ce6e436b2d 100644 --- a/server/core/src/main/java/dev/slimevr/config/CurrentVRConfigConverter.java +++ b/server/core/src/main/java/dev/slimevr/config/CurrentVRConfigConverter.java @@ -304,6 +304,32 @@ public ObjectNode convert( } } } + + if (version < 14) { + // Update AutoBone defaults + ObjectNode autoBoneNode = (ObjectNode) modelData.get("autoBone"); + if (autoBoneNode != null) { + JsonNode offsetSlideNode = autoBoneNode.get("offsetSlideErrorFactor"); + JsonNode slideNode = autoBoneNode.get("slideErrorFactor"); + if ( + offsetSlideNode != null + && slideNode != null + && offsetSlideNode.floatValue() == 1.0f + && slideNode.floatValue() == 0.0f + ) { + autoBoneNode.set("offsetSlideErrorFactor", new FloatNode(0.0f)); + autoBoneNode.set("slideErrorFactor", new FloatNode(1.0f)); + } + JsonNode bodyProportionsNode = autoBoneNode.get("bodyProportionErrorFactor"); + if (bodyProportionsNode != null && bodyProportionsNode.floatValue() == 0.25f) { + autoBoneNode.set("bodyProportionErrorFactor", new FloatNode(0.05f)); + } + JsonNode numEpochsNode = autoBoneNode.get("numEpochs"); + if (numEpochsNode != null && numEpochsNode.intValue() == 100) { + autoBoneNode.set("numEpochs", new IntNode(50)); + } + } + } } catch (Exception e) { LogManager.severe("Error during config migration: " + e); } diff --git a/server/core/src/main/java/dev/slimevr/config/VRConfig.kt b/server/core/src/main/java/dev/slimevr/config/VRConfig.kt index 873ce37e61..65487f07de 100644 --- a/server/core/src/main/java/dev/slimevr/config/VRConfig.kt +++ b/server/core/src/main/java/dev/slimevr/config/VRConfig.kt @@ -10,8 +10,8 @@ import dev.slimevr.tracking.trackers.Tracker import dev.slimevr.tracking.trackers.TrackerRole @JsonVersionedModel( - currentVersion = "13", - defaultDeserializeToVersion = "13", + currentVersion = "14", + defaultDeserializeToVersion = "14", toCurrentConverterClass = CurrentVRConfigConverter::class, ) class VRConfig { diff --git a/server/core/src/main/java/dev/slimevr/filtering/QuaternionMovingAverage.kt b/server/core/src/main/java/dev/slimevr/filtering/QuaternionMovingAverage.kt index f86c4525d8..09517e692b 100644 --- a/server/core/src/main/java/dev/slimevr/filtering/QuaternionMovingAverage.kt +++ b/server/core/src/main/java/dev/slimevr/filtering/QuaternionMovingAverage.kt @@ -23,7 +23,7 @@ class QuaternionMovingAverage( ) { private var smoothFactor = 0f private var predictFactor = 0f - private lateinit var rotBuffer: CircularArrayList + private var rotBuffer: CircularArrayList? = null private var latestQuaternion = IDENTITY private var smoothingQuaternion = IDENTITY private val fpsTimer = if (VRServer.instanceInitialized) VRServer.instance.fpsTimer else NanoTimer() @@ -57,11 +57,11 @@ class QuaternionMovingAverage( @Synchronized fun update() { if (type == TrackerFilters.PREDICTION) { - if (rotBuffer.size > 0) { + if (rotBuffer!!.size > 0) { var quatBuf = latestQuaternion // Applies the past rotations to the current rotation - rotBuffer.forEach { quatBuf *= it } + rotBuffer?.forEach { quatBuf *= it } // Calculate how much to slerp val amt = predictFactor * fpsTimer.timePerFrame @@ -98,12 +98,12 @@ class QuaternionMovingAverage( @Synchronized fun addQuaternion(q: Quaternion) { if (type == TrackerFilters.PREDICTION) { - if (rotBuffer.size == rotBuffer.capacity()) { - rotBuffer.removeLast() + if (rotBuffer!!.size == rotBuffer!!.capacity()) { + rotBuffer?.removeLast() } // Gets and stores the rotation between the last 2 quaternions - rotBuffer.add(latestQuaternion.inv().times(q)) + rotBuffer?.add(latestQuaternion.inv().times(q)) } else if (type == TrackerFilters.SMOOTHING) { frameCounter = 0 lastAmt = 0f @@ -116,8 +116,11 @@ class QuaternionMovingAverage( } fun resetQuats(q: Quaternion) { + if (type == TrackerFilters.PREDICTION) { + rotBuffer?.clear() + latestQuaternion = q + } filteredQuaternion = q - latestQuaternion = q - smoothingQuaternion = q + addQuaternion(q) } } diff --git a/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateHandler.kt b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateHandler.kt new file mode 100644 index 0000000000..50fda19674 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateHandler.kt @@ -0,0 +1,498 @@ +package dev.slimevr.firmware + +import com.mayakapps.kache.InMemoryKache +import com.mayakapps.kache.KacheStrategy +import dev.llelievr.espflashkotlin.Flasher +import dev.llelievr.espflashkotlin.FlashingProgressListener +import dev.slimevr.VRServer +import dev.slimevr.serial.ProvisioningListener +import dev.slimevr.serial.ProvisioningStatus +import dev.slimevr.serial.SerialPort +import dev.slimevr.tracking.trackers.Tracker +import dev.slimevr.tracking.trackers.TrackerStatus +import dev.slimevr.tracking.trackers.TrackerStatusListener +import dev.slimevr.tracking.trackers.udp.UDPDevice +import io.eiren.util.logging.LogManager +import kotlinx.coroutines.* +import solarxr_protocol.rpc.FirmwarePartT +import solarxr_protocol.rpc.FirmwareUpdateRequestT +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.net.URL +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList +import kotlin.concurrent.scheduleAtFixedRate + +data class DownloadedFirmwarePart( + val firmware: ByteArray, + val offset: Long?, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DownloadedFirmwarePart + + if (!firmware.contentEquals(other.firmware)) return false + if (offset != other.offset) return false + + return true + } + + override fun hashCode(): Int { + var result = firmware.contentHashCode() + result = 31 * result + (offset?.hashCode() ?: 0) + return result + } +} + +class FirmwareUpdateHandler(private val server: VRServer) : + TrackerStatusListener, + ProvisioningListener, + SerialRebootListener { + + private val updateTickTimer = Timer("StatusUpdateTimer") + private val runningJobs: MutableList = CopyOnWriteArrayList() + private val watchRestartQueue: MutableList, () -> Unit>> = + CopyOnWriteArrayList() + private val updatingDevicesStatus: MutableMap, UpdateStatusEvent<*>> = + ConcurrentHashMap() + private val listeners: MutableList = CopyOnWriteArrayList() + private val firmwareCache = + InMemoryKache>(maxSize = 5 * 1024 * 1024) { + strategy = KacheStrategy.LRU + sizeCalculator = { _, parts -> parts.sumOf { it.firmware.size }.toLong() } + } + private val mainScope: CoroutineScope = CoroutineScope(SupervisorJob()) + private var clearJob: Deferred? = null + + private var serialRebootHandler: SerialRebootHandler = SerialRebootHandler(watchRestartQueue, server, this) + + fun addListener(channel: FirmwareUpdateListener) { + listeners.add(channel) + } + + fun removeListener(channel: FirmwareUpdateListener) { + listeners.removeIf { channel == it } + } + + init { + server.addTrackerStatusListener(this) + server.provisioningHandler.addListener(this) + server.serialHandler.addListener(serialRebootHandler) + + this.updateTickTimer.scheduleAtFixedRate(0, 1000) { + checkUpdateTimeout() + } + } + + private fun startOtaUpdate( + part: DownloadedFirmwarePart, + deviceId: UpdateDeviceId, + ) { + val udpDevice: UDPDevice? = + (this.server.deviceManager.devices.find { device -> device is UDPDevice && device.id == deviceId.id }) as UDPDevice? + + if (udpDevice == null) { + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.ERROR_DEVICE_NOT_FOUND, + ), + ) + return + } + OTAUpdateTask( + part.firmware, + deviceId, + udpDevice.ipAddress, + this::onStatusChange, + ).run() + } + + private fun startSerialUpdate( + firmwares: Array, + deviceId: UpdateDeviceId, + needManualReboot: Boolean, + ssid: String, + password: String, + ) { + val serialPort = this.server.serialHandler.knownPorts.toList() + .find { port -> deviceId.id == port.portLocation } + + if (serialPort == null) { + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.ERROR_DEVICE_NOT_FOUND, + ), + ) + return + } + + val flashingHandler = this.server.serialFlashingHandler + + if (flashingHandler == null) { + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.ERROR_UNSUPPORTED_METHOD, + ), + ) + return + } + + try { + val flasher = Flasher(flashingHandler) + + for (part in firmwares) { + if (part.offset == null) { + error("Offset is empty") + } + flasher.addBin(part.firmware, part.offset.toInt()) + } + + flasher.addProgressListener(object : FlashingProgressListener { + override fun progress(progress: Float) { + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.UPLOADING, + (progress * 100).toInt(), + ), + ) + } + }) + + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.SYNCING_WITH_MCU, + ), + ) + flasher.flash(serialPort) + if (needManualReboot) { + if (watchRestartQueue.find { it.first == deviceId } != null) { + LogManager.info("[FirmwareUpdateHandler] Device is already updating, skipping") + } + + onStatusChange(UpdateStatusEvent(deviceId, FirmwareUpdateStatus.NEED_MANUAL_REBOOT)) + server.serialHandler.openSerial(deviceId.id, false) + watchRestartQueue.add( + Pair(deviceId) { + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.REBOOTING, + ), + ) + server.provisioningHandler.start( + ssid, + password, + serialPort.portLocation, + ) + }, + ) + } else { + onStatusChange(UpdateStatusEvent(deviceId, FirmwareUpdateStatus.REBOOTING)) + server.provisioningHandler.start(ssid, password, serialPort.portLocation) + } + } catch (e: Exception) { + LogManager.severe("[FirmwareUpdateHandler] Upload failed", e) + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.ERROR_UPLOAD_FAILED, + ), + ) + } + } + + fun queueFirmwareUpdate( + request: FirmwareUpdateRequestT, + deviceId: UpdateDeviceId<*>, + ) = mainScope.launch { + val method = FirmwareUpdateMethod.getById(request.method.type) ?: error("Unknown method") + + clearJob?.await() + if (method == FirmwareUpdateMethod.OTA) { + if (watchRestartQueue.find { it.first == deviceId } != null) { + LogManager.info("[FirmwareUpdateHandler] Device is already updating, skipping") + } + + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.NEED_MANUAL_REBOOT, + ), + ) + watchRestartQueue.add( + Pair(deviceId) { + mainScope.launch { + startFirmwareUpdateJob( + request, + deviceId, + ) + } + }, + ) + } else { + if (updatingDevicesStatus[deviceId] != null) { + LogManager.info("[FirmwareUpdateHandler] Device is already updating, skipping") + return@launch + } + + startFirmwareUpdateJob( + request, + deviceId, + ) + } + } + + fun cancelUpdates() { + val oldClearJob = clearJob + clearJob = mainScope.async { + oldClearJob?.await() + watchRestartQueue.clear() + runningJobs.forEach { it.cancelAndJoin() } + runningJobs.clear() + } + } + + private fun getFirmwareParts(request: FirmwareUpdateRequestT): ArrayList { + val parts = ArrayList() + val method = FirmwareUpdateMethod.getById(request.method.type) ?: error("Unknown method") + when (method) { + FirmwareUpdateMethod.OTA -> { + val updateReq = request.method.asOTAFirmwareUpdate() + parts.add(updateReq.firmwarePart) + } + + FirmwareUpdateMethod.SERIAL -> { + val updateReq = request.method.asSerialFirmwareUpdate() + parts.addAll(updateReq.firmwarePart) + } + + FirmwareUpdateMethod.NONE -> error("Method should not be NONE") + } + return parts + } + + private suspend fun startFirmwareUpdateJob( + request: FirmwareUpdateRequestT, + deviceId: UpdateDeviceId<*>, + ) = coroutineScope { + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.DOWNLOADING, + ), + ) + + try { + // We add the firmware to an LRU cache + val toDownloadParts = getFirmwareParts(request) + val firmwareParts = + firmwareCache.getOrPut(toDownloadParts.joinToString("|") { "${it.url}#${it.offset}" }) { + withTimeoutOrNull(30_000) { + toDownloadParts.map { + val firmware = downloadFirmware(it.url) + ?: error("unable to download firmware part") + DownloadedFirmwarePart( + firmware, + it.offset, + ) + }.toTypedArray() + } + } + + val job = launch { + withTimeout(2 * 60 * 1000) { + if (firmwareParts.isNullOrEmpty()) { + onStatusChange( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.ERROR_DOWNLOAD_FAILED, + ), + ) + return@withTimeout + } + + val method = FirmwareUpdateMethod.getById(request.method.type) ?: error("Unknown method") + when (method) { + FirmwareUpdateMethod.NONE -> error("unsupported method") + + FirmwareUpdateMethod.OTA -> { + if (deviceId.id !is Int) { + error("invalid state, the device id is not an int") + } + if (firmwareParts.size > 1) { + error("invalid state, ota only use one firmware file") + } + startOtaUpdate( + firmwareParts.first(), + UpdateDeviceId( + FirmwareUpdateMethod.OTA, + deviceId.id, + ), + ) + } + + FirmwareUpdateMethod.SERIAL -> { + val req = request.method.asSerialFirmwareUpdate() + if (deviceId.id !is String) { + error("invalid state, the device id is not a string") + } + startSerialUpdate( + firmwareParts, + UpdateDeviceId( + FirmwareUpdateMethod.SERIAL, + deviceId.id, + ), + req.needManualReboot, + req.ssid, + req.password, + ) + } + } + } + } + runningJobs.add(job) + } catch (e: Exception) { + onStatusChange( + UpdateStatusEvent( + deviceId, + if (e is TimeoutCancellationException) FirmwareUpdateStatus.ERROR_TIMEOUT else FirmwareUpdateStatus.ERROR_UNKNOWN, + ), + ) + if (e !is TimeoutCancellationException) { + LogManager.severe("[FirmwareUpdateHandler] Update process timed out", e) + e.printStackTrace() + } + return@coroutineScope + } + } + + private fun onStatusChange(event: UpdateStatusEvent) { + this.updatingDevicesStatus[event.deviceId] = event + + if (event.status == FirmwareUpdateStatus.DONE || event.status.isError()) { + this.updatingDevicesStatus.remove(event.deviceId) + + // we remove the device from the restart queue + val queuedDevice = watchRestartQueue.find { it.first.id == event.deviceId } + if (queuedDevice != null) { + watchRestartQueue.remove(queuedDevice) + if (event.deviceId.type == FirmwareUpdateMethod.SERIAL && server.serialHandler.isConnected) { + server.serialHandler.closeSerial() + } + } + + // We make sure to stop the provisioning routine if the tracker is done + // flashing + if (event.deviceId.type == FirmwareUpdateMethod.SERIAL) { + this.server.provisioningHandler.stop() + } + } + listeners.forEach { l -> l.onUpdateStatusChange(event) } + } + + private fun checkUpdateTimeout() { + updatingDevicesStatus.forEach { (id, device) -> + // if more than 30s between two events, consider the update as stuck + // We do not timeout on the Downloading step as it has it own timeout + // We do not timeout on the Done step as it is the end of the update process + if (!device.status.isError() && + !intArrayOf(FirmwareUpdateStatus.DONE.id, FirmwareUpdateStatus.DOWNLOADING.id).contains(device.status.id) && + System.currentTimeMillis() - device.time > 30 * 1000 + ) { + onStatusChange( + UpdateStatusEvent( + id, + FirmwareUpdateStatus.ERROR_TIMEOUT, + ), + ) + } + } + } + + // this only works for OTA trackers as the device id + // only exists when the usb connection is created + override fun onTrackerStatusChanged( + tracker: Tracker, + oldStatus: TrackerStatus, + newStatus: TrackerStatus, + ) { + val device = tracker.device + if (device !is UDPDevice) return + + if (oldStatus == TrackerStatus.DISCONNECTED && newStatus == TrackerStatus.OK) { + val queuedDevice = watchRestartQueue.find { it.first.id == device.id } + + if (queuedDevice != null) { + queuedDevice.second() // we start the queued update task + watchRestartQueue.remove(queuedDevice) // then we remove it from the queue + return + } + + // We can only filter OTA method here as the device id is only provided when using Wi-Fi + val deviceStatusKey = + updatingDevicesStatus.keys.find { it.type == FirmwareUpdateMethod.OTA && it.id == device.id } + ?: return + val updateStatus = updatingDevicesStatus[deviceStatusKey] ?: return + // We check for the reconnection of the tracker, once the tracker reconnected we notify the user that the update is completed + if (updateStatus.status == FirmwareUpdateStatus.REBOOTING) { + onStatusChange( + UpdateStatusEvent( + updateStatus.deviceId, + FirmwareUpdateStatus.DONE, + ), + ) + } + } + } + + override fun onProvisioningStatusChange( + status: ProvisioningStatus, + port: SerialPort?, + ) { + fun update(s: FirmwareUpdateStatus) { + val deviceStatusKey = + updatingDevicesStatus.keys.find { it.type == FirmwareUpdateMethod.SERIAL && it.id == port?.portLocation } + ?: return + val updateStatus = updatingDevicesStatus[deviceStatusKey] ?: return + onStatusChange(UpdateStatusEvent(updateStatus.deviceId, s)) + } + + when (status) { + ProvisioningStatus.PROVISIONING -> update(FirmwareUpdateStatus.PROVISIONING) + ProvisioningStatus.DONE -> update(FirmwareUpdateStatus.DONE) + ProvisioningStatus.CONNECTION_ERROR, ProvisioningStatus.COULD_NOT_FIND_SERVER -> update(FirmwareUpdateStatus.ERROR_PROVISIONING_FAILED) + else -> {} + } + } + + override fun onSerialDeviceReconnect(deviceHandle: Pair, () -> Unit>) { + deviceHandle.second() + watchRestartQueue.remove(deviceHandle) + } +} + +fun downloadFirmware(url: String): ByteArray? { + val outputStream = ByteArrayOutputStream() + + try { + val chunk = ByteArray(4096) + var bytesRead: Int + val stream: InputStream = URL(url).openStream() + while (stream.read(chunk).also { bytesRead = it } > 0) { + outputStream.write(chunk, 0, bytesRead) + } + } catch (e: IOException) { + error("Cant download firmware $url") + } + + return outputStream.toByteArray() +} diff --git a/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateListener.kt b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateListener.kt new file mode 100644 index 0000000000..3f048828ac --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateListener.kt @@ -0,0 +1,5 @@ +package dev.slimevr.firmware + +interface FirmwareUpdateListener { + fun onUpdateStatusChange(event: UpdateStatusEvent<*>) +} diff --git a/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateMethod.kt b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateMethod.kt new file mode 100644 index 0000000000..7aaddf974e --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateMethod.kt @@ -0,0 +1,14 @@ +package dev.slimevr.firmware + +enum class FirmwareUpdateMethod(val id: Byte) { + NONE(solarxr_protocol.rpc.FirmwareUpdateMethod.NONE), + OTA(solarxr_protocol.rpc.FirmwareUpdateMethod.OTAFirmwareUpdate), + SERIAL(solarxr_protocol.rpc.FirmwareUpdateMethod.SerialFirmwareUpdate), + ; + + companion object { + fun getById(id: Byte): FirmwareUpdateMethod? = byId[id] + } +} + +private val byId = FirmwareUpdateMethod.entries.associateBy { it.id } diff --git a/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateStatus.kt b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateStatus.kt new file mode 100644 index 0000000000..4edb320b44 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateStatus.kt @@ -0,0 +1,29 @@ +package dev.slimevr.firmware + +enum class FirmwareUpdateStatus(val id: Int) { + DOWNLOADING(solarxr_protocol.rpc.FirmwareUpdateStatus.DOWNLOADING), + AUTHENTICATING(solarxr_protocol.rpc.FirmwareUpdateStatus.AUTHENTICATING), + UPLOADING(solarxr_protocol.rpc.FirmwareUpdateStatus.UPLOADING), + SYNCING_WITH_MCU(solarxr_protocol.rpc.FirmwareUpdateStatus.SYNCING_WITH_MCU), + REBOOTING(solarxr_protocol.rpc.FirmwareUpdateStatus.REBOOTING), + NEED_MANUAL_REBOOT(solarxr_protocol.rpc.FirmwareUpdateStatus.NEED_MANUAL_REBOOT), + PROVISIONING(solarxr_protocol.rpc.FirmwareUpdateStatus.PROVISIONING), + DONE(solarxr_protocol.rpc.FirmwareUpdateStatus.DONE), + ERROR_DEVICE_NOT_FOUND(solarxr_protocol.rpc.FirmwareUpdateStatus.ERROR_DEVICE_NOT_FOUND), + ERROR_TIMEOUT(solarxr_protocol.rpc.FirmwareUpdateStatus.ERROR_TIMEOUT), + ERROR_DOWNLOAD_FAILED(solarxr_protocol.rpc.FirmwareUpdateStatus.ERROR_DOWNLOAD_FAILED), + ERROR_AUTHENTICATION_FAILED(solarxr_protocol.rpc.FirmwareUpdateStatus.ERROR_AUTHENTICATION_FAILED), + ERROR_UPLOAD_FAILED(solarxr_protocol.rpc.FirmwareUpdateStatus.ERROR_UPLOAD_FAILED), + ERROR_PROVISIONING_FAILED(solarxr_protocol.rpc.FirmwareUpdateStatus.ERROR_PROVISIONING_FAILED), + ERROR_UNSUPPORTED_METHOD(solarxr_protocol.rpc.FirmwareUpdateStatus.ERROR_UNSUPPORTED_METHOD), + ERROR_UNKNOWN(solarxr_protocol.rpc.FirmwareUpdateStatus.ERROR_UNKNOWN), + ; + + fun isError(): Boolean = id in ERROR_DEVICE_NOT_FOUND.id..ERROR_UNKNOWN.id + + companion object { + fun getById(id: Int): FirmwareUpdateStatus? = byId[id] + } +} + +private val byId = FirmwareUpdateStatus.entries.associateBy { it.id } diff --git a/server/core/src/main/java/dev/slimevr/firmware/OTAUpdateTask.kt b/server/core/src/main/java/dev/slimevr/firmware/OTAUpdateTask.kt new file mode 100644 index 0000000000..9b47305494 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/OTAUpdateTask.kt @@ -0,0 +1,181 @@ +package dev.slimevr.firmware + +import io.eiren.util.logging.LogManager +import java.io.DataInputStream +import java.io.DataOutputStream +import java.net.DatagramPacket +import java.net.DatagramSocket +import java.net.InetAddress +import java.net.ServerSocket +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.util.* +import java.util.function.Consumer +import kotlin.math.min + +class OTAUpdateTask( + private val firmware: ByteArray, + private val deviceId: UpdateDeviceId, + private val deviceIp: InetAddress, + private val statusCallback: Consumer>, +) { + private val receiveBuffer: ByteArray = ByteArray(38) + + @Throws(NoSuchAlgorithmException::class) + private fun bytesToMd5(bytes: ByteArray): String { + val md5 = MessageDigest.getInstance("MD5") + md5.update(bytes) + val digest = md5.digest() + val md5str = StringBuilder() + for (b in digest) { + md5str.append(String.format("%02x", b)) + } + return md5str.toString() + } + + private fun authenticate(localPort: Int): Boolean { + try { + DatagramSocket().use { socket -> + statusCallback.accept(UpdateStatusEvent(deviceId, FirmwareUpdateStatus.AUTHENTICATING)) + LogManager.info("[OTAUpdate] Sending OTA invitation to: $deviceIp") + + val fileMd5 = bytesToMd5(firmware) + val message = "$FLASH $localPort ${firmware.size} $fileMd5\n" + + socket.send(DatagramPacket(message.toByteArray(), message.length, deviceIp, PORT)) + socket.soTimeout = 10000 + + val authPacket = DatagramPacket(receiveBuffer, receiveBuffer.size) + socket.receive(authPacket) + + val data = String(authPacket.data, 0, authPacket.length) + + // if we received OK directly from the MCU, we do not need to authenticate + if (data == "OK") return true + + val args = data.split(" ") + + // The expected auth payload should look like "AUTH AUTH_TOKEN" + // if we have less than those two args it means that we are in an invalid state + if (args.size != 2 || args[0] != "AUTH") return false + + LogManager.info("[OTAUpdate] Authenticating...") + + val authToken = args[1] + val signature = bytesToMd5(UUID.randomUUID().toString().toByteArray()) + val hashedPassword = bytesToMd5(PASSWORD.toByteArray()) + val resultText = "$hashedPassword:$authToken:$signature" + val payload = bytesToMd5(resultText.toByteArray()) + + val authMessage = "$AUTH $signature $payload\n" + + socket.soTimeout = 10000 + socket.send( + DatagramPacket( + authMessage.toByteArray(), + authMessage.length, + deviceIp, + PORT, + ), + ) + + val authResponsePacket = DatagramPacket(receiveBuffer, receiveBuffer.size) + socket.receive(authResponsePacket) + + val authResponse = String(authResponsePacket.data, 0, authResponsePacket.length) + + return authResponse == "OK" + } + } catch (e: Exception) { + LogManager.severe("OTA Authentication exception", e) + return false + } + } + + private fun upload(serverSocket: ServerSocket): Boolean { + try { + LogManager.info("[OTAUpdate] Starting on: ${serverSocket.localPort}") + LogManager.info("[OTAUpdate] Waiting for device...") + + val connection = serverSocket.accept() + connection.setSoTimeout(1000) + + val dos = DataOutputStream(connection.getOutputStream()) + val dis = DataInputStream(connection.getInputStream()) + + LogManager.info("[OTAUpdate] Upload size: ${firmware.size} bytes") + var offset = 0 + val chunkSize = 2048 + while (offset != firmware.size) { + statusCallback.accept( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.UPLOADING, + ((offset.toDouble() / firmware.size) * 100).toInt(), + ), + ) + + val chunkLen = min(chunkSize, (firmware.size - offset)) + dos.write(firmware, offset, chunkLen) + dos.flush() + offset += chunkLen + + // Those skipped bytes are the size written to the MCU. We do not really need that information, + // so we simply skip it. + // The reason those bytes are skipped here is to not have to skip all of them when checking + // for the OK response. Saving time + dis.skipNBytes(4) + } + + LogManager.info("[OTAUpdate] Waiting for result...") + // We set the timeout of the connection bigger as it can take some time for the MCU + // to confirm that everything is ok + connection.setSoTimeout(10000) + val responseBytes = dis.readAllBytes() + val response = String(responseBytes) + + return response.contains("OK") + } catch (e: Exception) { + LogManager.severe("Unable to upload the firmware using ota", e) + return false + } + } + + fun run() { + ServerSocket(0).use { serverSocket -> + if (!authenticate(serverSocket.localPort)) { + statusCallback.accept( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.ERROR_AUTHENTICATION_FAILED, + ), + ) + return + } + + if (!upload(serverSocket)) { + statusCallback.accept( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.ERROR_UPLOAD_FAILED, + ), + ) + return + } + + statusCallback.accept( + UpdateStatusEvent( + deviceId, + FirmwareUpdateStatus.REBOOTING, + ), + ) + } + } + + companion object { + private const val FLASH = 0 + private const val PORT = 8266 + private const val PASSWORD = "SlimeVR-OTA" + private const val AUTH = 200 + } +} diff --git a/server/core/src/main/java/dev/slimevr/firmware/SerialFlashingHandler.kt b/server/core/src/main/java/dev/slimevr/firmware/SerialFlashingHandler.kt new file mode 100644 index 0000000000..1564ab3341 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/SerialFlashingHandler.kt @@ -0,0 +1,5 @@ +package dev.slimevr.firmware + +import dev.llelievr.espflashkotlin.FlasherSerialInterface + +interface SerialFlashingHandler : FlasherSerialInterface diff --git a/server/core/src/main/java/dev/slimevr/firmware/SerialRebootHandler.kt b/server/core/src/main/java/dev/slimevr/firmware/SerialRebootHandler.kt new file mode 100644 index 0000000000..0d801facfe --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/SerialRebootHandler.kt @@ -0,0 +1,66 @@ +package dev.slimevr.firmware + +import dev.slimevr.VRServer +import dev.slimevr.serial.SerialListener +import dev.slimevr.serial.SerialPort +import java.util.concurrent.CopyOnWriteArrayList + +interface SerialRebootListener { + fun onSerialDeviceReconnect(deviceHandle: Pair, () -> Unit>) +} + +/** + * This class watch for a serial device to disconnect then reconnect. + * This is used to watch the user progress through the firmware update process + */ +class SerialRebootHandler( + private val watchRestartQueue: MutableList, () -> Unit>>, + private val server: VRServer, + // Could be moved to a list of listeners later + private val serialRebootListener: SerialRebootListener, +) : SerialListener { + + private var currentPort: SerialPort? = null + private val disconnectedDevices: MutableList = CopyOnWriteArrayList() + + override fun onSerialConnected(port: SerialPort) { + currentPort = port + } + + override fun onSerialDisconnected() { + currentPort = null + } + + override fun onSerialLog(str: String) { + if (str.contains("starting up...")) { + val foundPort = watchRestartQueue.find { it.first.id == currentPort?.portLocation } + if (foundPort != null) { + disconnectedDevices.remove(currentPort) + serialRebootListener.onSerialDeviceReconnect(foundPort) + // once the restart detected we close the connection + if (server.serialHandler.isConnected) { + server.serialHandler.closeSerial() + } + } + } + } + + override fun onNewSerialDevice(port: SerialPort) { + val foundPort = watchRestartQueue.find { it.first.id == port.portLocation } + if (foundPort != null && disconnectedDevices.contains(port)) { + disconnectedDevices.remove(port) + serialRebootListener.onSerialDeviceReconnect(foundPort) + // once the restart detected we close the connection + if (server.serialHandler.isConnected) { + server.serialHandler.closeSerial() + } + } + } + + override fun onSerialDeviceDeleted(port: SerialPort) { + val foundPort = watchRestartQueue.find { it.first.id == port.portLocation } + if (foundPort != null) { + disconnectedDevices.add(port) + } + } +} diff --git a/server/core/src/main/java/dev/slimevr/firmware/UpdateDeviceId.kt b/server/core/src/main/java/dev/slimevr/firmware/UpdateDeviceId.kt new file mode 100644 index 0000000000..5bc6dc5899 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/UpdateDeviceId.kt @@ -0,0 +1,24 @@ +package dev.slimevr.firmware + +data class UpdateDeviceId( + val type: FirmwareUpdateMethod, + val id: T, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UpdateDeviceId<*> + + if (type != other.type) return false + if (id != other.id) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + (id?.hashCode() ?: 0) + return result + } +} diff --git a/server/core/src/main/java/dev/slimevr/firmware/UpdateStatusEvent.kt b/server/core/src/main/java/dev/slimevr/firmware/UpdateStatusEvent.kt new file mode 100644 index 0000000000..85b485a95b --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/UpdateStatusEvent.kt @@ -0,0 +1,8 @@ +package dev.slimevr.firmware + +data class UpdateStatusEvent( + val deviceId: UpdateDeviceId, + val status: FirmwareUpdateStatus, + val progress: Int = 0, + val time: Long = System.currentTimeMillis(), +) diff --git a/server/core/src/main/java/dev/slimevr/poseframeformat/trackerdata/TrackerFrames.kt b/server/core/src/main/java/dev/slimevr/poseframeformat/trackerdata/TrackerFrames.kt index 2aae21d4d1..d81c2dc09e 100644 --- a/server/core/src/main/java/dev/slimevr/poseframeformat/trackerdata/TrackerFrames.kt +++ b/server/core/src/main/java/dev/slimevr/poseframeformat/trackerdata/TrackerFrames.kt @@ -41,6 +41,7 @@ data class TrackerFrames(val name: String = "", val frames: FastList) { + val fbb = FlatBufferBuilder(32) + + val dataUnion = FirmwareUpdateDeviceIdUnion() + dataUnion.type = event.deviceId.type.id + dataUnion.value = createUpdateDeviceId(event.deviceId) + + val deviceIdOffset = FirmwareUpdateDeviceIdUnion.pack(fbb, dataUnion) + + FirmwareUpdateStatusResponse.startFirmwareUpdateStatusResponse(fbb) + FirmwareUpdateStatusResponse.addStatus(fbb, event.status.id) + FirmwareUpdateStatusResponse.addDeviceIdType(fbb, dataUnion.type) + FirmwareUpdateStatusResponse.addDeviceId(fbb, deviceIdOffset) + FirmwareUpdateStatusResponse.addProgress(fbb, event.progress.toByte()) + + val update = FirmwareUpdateStatusResponse.endFirmwareUpdateStatusResponse(fbb) + val outbound = rpcHandler.createRPCMessage( + fbb, + RpcMessage.FirmwareUpdateStatusResponse, + update, + ) + fbb.finish(outbound) + + api + .apiServers.forEach { server -> + server.apiConnections.forEach { conn -> + conn.send(fbb.dataBuffer()) + } + } + } + + private fun buildUpdateDeviceId(req: FirmwareUpdateRequestT): UpdateDeviceId? { + when (req.method.type) { + FirmwareUpdateDeviceId.solarxr_protocol_datatypes_DeviceIdTable -> { + return UpdateDeviceId( + FirmwareUpdateMethod.OTA, + req.method.asOTAFirmwareUpdate().deviceId.id, + ) + } + + FirmwareUpdateDeviceId.SerialDevicePort -> { + return UpdateDeviceId( + FirmwareUpdateMethod.SERIAL, + req.method.asSerialFirmwareUpdate().deviceId.port, + ) + } + } + return null + } + + private fun createUpdateDeviceId(data: UpdateDeviceId<*>): Any = when (data.type) { + FirmwareUpdateMethod.NONE -> error("Unsupported method") + + FirmwareUpdateMethod.OTA -> { + if (data.id !is Int) { + error("Invalid state, the id type should be Int") + } + DeviceIdTableT().apply { + id = DeviceIdT().apply { + id = data.id + } + } + } + + FirmwareUpdateMethod.SERIAL -> { + if (data.id !is String) { + error("Invalid state, the id type should be String") + } + SerialDevicePortT().apply { + port = data.id + } + } + } +} diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCProvisioningHandler.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCProvisioningHandler.java index fa58bbc519..97ae898398 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCProvisioningHandler.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCProvisioningHandler.java @@ -6,6 +6,7 @@ import dev.slimevr.protocol.rpc.RPCHandler; import dev.slimevr.serial.ProvisioningListener; import dev.slimevr.serial.ProvisioningStatus; +import dev.slimevr.serial.SerialPort; import solarxr_protocol.rpc.*; import java.util.function.Consumer; @@ -59,7 +60,7 @@ public void onStopWifiProvisioningRequest( } @Override - public void onProvisioningStatusChange(ProvisioningStatus status) { + public void onProvisioningStatusChange(ProvisioningStatus status, SerialPort port) { FlatBufferBuilder fbb = new FlatBufferBuilder(32); diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCSerialHandler.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCSerialHandler.java index 2e1a42ff0f..56496c6c29 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCSerialHandler.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/serial/RPCSerialHandler.java @@ -7,6 +7,7 @@ import dev.slimevr.serial.SerialListener; import dev.slimevr.serial.SerialPort; import io.eiren.util.logging.LogManager; +import org.jetbrains.annotations.NotNull; import solarxr_protocol.rpc.*; import java.util.ArrayList; @@ -274,4 +275,7 @@ public void forAllListeners(Consumer action) { ); } + @Override + public void onSerialDeviceDeleted(@NotNull SerialPort port) { + } } diff --git a/server/core/src/main/java/dev/slimevr/serial/ProvisioningHandler.java b/server/core/src/main/java/dev/slimevr/serial/ProvisioningHandler.java index b65a9da7f3..c6b0a526d7 100644 --- a/server/core/src/main/java/dev/slimevr/serial/ProvisioningHandler.java +++ b/server/core/src/main/java/dev/slimevr/serial/ProvisioningHandler.java @@ -2,6 +2,7 @@ import dev.slimevr.VRServer; import io.eiren.util.logging.LogManager; +import kotlin.text.Regex; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -80,6 +81,10 @@ public void initSerial(String port) { } + public void tryObtainMacAddress() { + this.changeStatus(ProvisioningStatus.OBTAINING_MAC_ADDRESS); + vrServer.serialHandler.infoRequest(); + } public void tryProvisioning() { this.changeStatus(ProvisioningStatus.PROVISIONING); @@ -97,12 +102,16 @@ public void provisioningTick() { if (System.currentTimeMillis() - this.lastStatusChange > 10000) { - if (this.provisioningStatus == ProvisioningStatus.NONE) + if ( + this.provisioningStatus == ProvisioningStatus.NONE + || this.provisioningStatus == ProvisioningStatus.SERIAL_INIT + ) this.initSerial(this.preferredPort); - else if (this.provisioningStatus == ProvisioningStatus.SERIAL_INIT) - initSerial(this.preferredPort); - else if (this.provisioningStatus == ProvisioningStatus.PROVISIONING) - this.tryProvisioning(); + else if ( + this.provisioningStatus == ProvisioningStatus.OBTAINING_MAC_ADDRESS + || this.provisioningStatus == ProvisioningStatus.PROVISIONING + ) + this.tryObtainMacAddress(); else if (this.provisioningStatus == ProvisioningStatus.LOOKING_FOR_SERVER) this.changeStatus(ProvisioningStatus.COULD_NOT_FIND_SERVER); } @@ -113,7 +122,7 @@ else if (this.provisioningStatus == ProvisioningStatus.LOOKING_FOR_SERVER) public void onSerialConnected(@NotNull SerialPort port) { if (!isRunning) return; - this.tryProvisioning(); + this.tryObtainMacAddress(); } @Override @@ -129,6 +138,23 @@ public void onSerialLog(@NotNull String str) { if (!isRunning) return; + if ( + provisioningStatus == ProvisioningStatus.OBTAINING_MAC_ADDRESS && str.contains("mac:") + ) { + var match = new Regex("mac: (?([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})), ") + .find(str, str.indexOf("mac:")); + + if (match != null) { + var b = match.getGroups().get(1); + if (b != null) { + vrServer.configManager.getVrConfig().addKnownDevice(b.getValue()); + vrServer.configManager.saveConfig(); + this.tryProvisioning(); + } + } + + } + if ( provisioningStatus == ProvisioningStatus.PROVISIONING && str.contains("New wifi credentials set") @@ -166,7 +192,11 @@ public void onSerialLog(@NotNull String str) { public void changeStatus(ProvisioningStatus status) { this.lastStatusChange = System.currentTimeMillis(); if (this.provisioningStatus != status) { - this.listeners.forEach((l) -> l.onProvisioningStatusChange(status)); + this.listeners + .forEach( + (l) -> l + .onProvisioningStatusChange(status, vrServer.serialHandler.getCurrentPort()) + ); this.provisioningStatus = status; } } @@ -186,4 +216,7 @@ public void removeListener(ProvisioningListener l) { listeners.removeIf(listener -> l == listener); } + @Override + public void onSerialDeviceDeleted(@NotNull SerialPort port) { + } } diff --git a/server/core/src/main/java/dev/slimevr/serial/ProvisioningListener.java b/server/core/src/main/java/dev/slimevr/serial/ProvisioningListener.java index 636d62c5c6..a1cf17a03d 100644 --- a/server/core/src/main/java/dev/slimevr/serial/ProvisioningListener.java +++ b/server/core/src/main/java/dev/slimevr/serial/ProvisioningListener.java @@ -2,5 +2,5 @@ public interface ProvisioningListener { - void onProvisioningStatusChange(ProvisioningStatus status); + void onProvisioningStatusChange(ProvisioningStatus status, SerialPort port); } diff --git a/server/core/src/main/java/dev/slimevr/serial/ProvisioningStatus.java b/server/core/src/main/java/dev/slimevr/serial/ProvisioningStatus.java index 9038550241..cf897cc9f7 100644 --- a/server/core/src/main/java/dev/slimevr/serial/ProvisioningStatus.java +++ b/server/core/src/main/java/dev/slimevr/serial/ProvisioningStatus.java @@ -1,15 +1,19 @@ package dev.slimevr.serial; +import solarxr_protocol.rpc.WifiProvisioningStatus; + + public enum ProvisioningStatus { - NONE(0), - SERIAL_INIT(1), - PROVISIONING(2), - CONNECTING(3), - CONNECTION_ERROR(4), - LOOKING_FOR_SERVER(5), - COULD_NOT_FIND_SERVER(6), - DONE(7); + NONE(WifiProvisioningStatus.NONE), + SERIAL_INIT(WifiProvisioningStatus.SERIAL_INIT), + PROVISIONING(WifiProvisioningStatus.PROVISIONING), + OBTAINING_MAC_ADDRESS(WifiProvisioningStatus.OBTAINING_MAC_ADDRESS), + CONNECTING(WifiProvisioningStatus.CONNECTING), + CONNECTION_ERROR(WifiProvisioningStatus.CONNECTION_ERROR), + LOOKING_FOR_SERVER(WifiProvisioningStatus.LOOKING_FOR_SERVER), + COULD_NOT_FIND_SERVER(WifiProvisioningStatus.COULD_NOT_FIND_SERVER), + DONE(WifiProvisioningStatus.DONE); public final int id; diff --git a/server/core/src/main/java/dev/slimevr/serial/SerialHandler.kt b/server/core/src/main/java/dev/slimevr/serial/SerialHandler.kt index 99346cf5fe..bbd173779e 100644 --- a/server/core/src/main/java/dev/slimevr/serial/SerialHandler.kt +++ b/server/core/src/main/java/dev/slimevr/serial/SerialHandler.kt @@ -15,7 +15,9 @@ abstract class SerialHandler { abstract fun infoRequest() abstract fun wifiScanRequest() abstract fun closeSerial() + abstract fun write(buff: ByteArray) abstract fun setWifi(ssid: String, passwd: String) + abstract fun getCurrentPort(): SerialPort? companion object { val supportedSerial: Set> = setOf( @@ -65,5 +67,9 @@ class SerialHandlerStub : SerialHandler() { override fun closeSerial() {} + override fun write(buff: ByteArray) {} + override fun setWifi(ssid: String, passwd: String) {} + + override fun getCurrentPort(): SerialPort? = null } diff --git a/server/core/src/main/java/dev/slimevr/serial/SerialListener.kt b/server/core/src/main/java/dev/slimevr/serial/SerialListener.kt index 861e770a9e..32fe72087b 100644 --- a/server/core/src/main/java/dev/slimevr/serial/SerialListener.kt +++ b/server/core/src/main/java/dev/slimevr/serial/SerialListener.kt @@ -25,4 +25,7 @@ interface SerialListener { fun onSerialDisconnected() fun onSerialLog(str: String) fun onNewSerialDevice(port: SerialPort) + + // This is called when the serial diver does not see the device anymore + fun onSerialDeviceDeleted(port: SerialPort) } diff --git a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt index adbe0cfced..c500ea2632 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt @@ -785,11 +785,6 @@ class HumanSkeleton( var hipRot = it.getRotation() var chestRot = chest.getRotation() - // Get the rotation relative to where we expect the hip to be - if (chestRot.times(FORWARD_QUATERNION).dot(hipRot) < 0.0f) { - hipRot = hipRot.unaryMinus() - } - // Interpolate between the chest and the hip chestRot = chestRot.interpQ(hipRot, waistFromChestHipAveraging) @@ -802,15 +797,6 @@ class HumanSkeleton( var rightLegRot = rightUpperLegTracker?.getRotation() ?: IDENTITY var chestRot = chest.getRotation() - // Get the rotation relative to where we expect the upper legs to be - val expectedUpperLegsRot = chestRot.times(FORWARD_QUATERNION) - if (expectedUpperLegsRot.dot(leftLegRot) < 0.0f) { - leftLegRot = leftLegRot.unaryMinus() - } - if (expectedUpperLegsRot.dot(rightLegRot) < 0.0f) { - rightLegRot = rightLegRot.unaryMinus() - } - // Interpolate between the pelvis, averaged from the legs, and the chest chestRot = chestRot.interpQ(leftLegRot.lerpQ(rightLegRot, 0.5f), waistFromChestLegsAveraging).unit() @@ -827,15 +813,6 @@ class HumanSkeleton( var rightLegRot = rightUpperLegTracker?.getRotation() ?: IDENTITY var waistRot = it.getRotation() - // Get the rotation relative to where we expect the upper legs to be - val expectedUpperLegsRot = waistRot.times(FORWARD_QUATERNION) - if (expectedUpperLegsRot.dot(leftLegRot) < 0.0f) { - leftLegRot = leftLegRot.unaryMinus() - } - if (expectedUpperLegsRot.dot(rightLegRot) < 0.0f) { - rightLegRot = rightLegRot.unaryMinus() - } - // Interpolate between the pelvis, averaged from the legs, and the chest waistRot = waistRot.interpQ(leftLegRot.lerpQ(rightLegRot, 0.5f), hipFromWaistLegsAveraging).unit() @@ -849,15 +826,6 @@ class HumanSkeleton( var rightLegRot = rightUpperLegTracker?.getRotation() ?: IDENTITY var chestRot = it.getRotation() - // Get the rotation relative to where we expect the upper legs to be - val expectedUpperLegsRot = chestRot.times(FORWARD_QUATERNION) - if (expectedUpperLegsRot.dot(leftLegRot) < 0.0f) { - leftLegRot = leftLegRot.unaryMinus() - } - if (expectedUpperLegsRot.dot(rightLegRot) < 0.0f) { - rightLegRot = rightLegRot.unaryMinus() - } - // Interpolate between the pelvis, averaged from the legs, and the chest chestRot = chestRot.interpQ(leftLegRot.lerpQ(rightLegRot, 0.5f), hipFromChestLegsAveraging).unit() @@ -1110,24 +1078,11 @@ class HumanSkeleton( rightKnee: Quaternion, hip: Quaternion, ): Quaternion { - // Get the knees' rotation relative to where we expect them to be. - // The angle between your knees and hip can be over 180 degrees... - var leftKneeRot = leftKnee - var rightKneeRot = rightKnee - - val kneeRot = hip.times(FORWARD_QUATERNION) - if (kneeRot.dot(leftKneeRot) < 0.0f) { - leftKneeRot = leftKneeRot.unaryMinus() - } - if (kneeRot.dot(rightKneeRot) < 0.0f) { - rightKneeRot = rightKneeRot.unaryMinus() - } - // R = InverseHip * (LeftLeft + RightLeg) // C = Quaternion(R.w, -R.x, 0, 0) // Pelvis = Hip * R * C // normalize(Pelvis) - val r = hip.inv() * (leftKneeRot + rightKneeRot) + val r = hip.inv() * (leftKnee + rightKnee) val c = Quaternion(r.w, -r.x, 0f, 0f) return (hip * r * c).unit() } diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/Device.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/Device.kt index 4fca49688a..79d8c79f1f 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/Device.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/Device.kt @@ -20,7 +20,7 @@ open class Device(val magSupport: Boolean = false) { * Implement toString() to return a string that uniquely identifies the board type * SHOULDN'T RETURN NULL WHEN toString() IS CALLED */ - open val boardType: Any = BoardType.UNKNOWN + open val boardType: BoardType = BoardType.UNKNOWN open val mcuType: MCUType = MCUType.UNKNOWN open val hardwareIdentifier: String = "Unknown" diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt index b0d60d4756..16ff5193dc 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt @@ -67,6 +67,12 @@ class Tracker @JvmOverloads constructor( val needsReset: Boolean = false, val needsMounting: Boolean = false, val isHmd: Boolean = false, + /** + * Whether to track the direction of the tracker's rotation + * (positive vs negative rotation). This needs to be disabled for AutoBone and + * unit tests, where the rotation is absolute and not temporal. + */ + val trackRotDirection: Boolean = true, magStatus: MagnetometerStatus = MagnetometerStatus.NOT_SUPPORTED, /** * Rotation by default. @@ -117,6 +123,8 @@ class Tracker @JvmOverloads constructor( } checkReportErrorStatus() checkReportRequireReset() + + VRServer.instance.trackerStatusChanged(this, old, new) } } @@ -308,7 +316,9 @@ class Tracker @JvmOverloads constructor( fun dataTick() { timer.update() timeAtLastUpdate = System.currentTimeMillis() - filteringHandler.dataTick(_rotation) + if (trackRotDirection) { + filteringHandler.dataTick(_rotation) + } } /** @@ -318,6 +328,13 @@ class Tracker @JvmOverloads constructor( timeAtLastUpdate = System.currentTimeMillis() } + private fun getFilteredRotation(): Quaternion = if (trackRotDirection) { + filteringHandler.getFilteredRotation() + } else { + // Get raw rotation + _rotation + } + /** * Gets the adjusted tracker rotation after all corrections * (filtering, reset, mounting and drift compensation). @@ -326,13 +343,7 @@ class Tracker @JvmOverloads constructor( * it too much should be avoided for performance reasons. */ fun getRotation(): Quaternion { - var rot = if (allowFiltering && filteringHandler.filteringEnabled) { - // Get filtered rotation - filteringHandler.getFilteredRotation() - } else { - // Get unfiltered rotation - filteringHandler.getTrackedRotation() - } + var rot = getFilteredRotation() // Reset if needed and is not computed and internal if (needsReset && !(isComputed && isInternal) && trackerDataType == TrackerDataType.ROTATION) { @@ -358,13 +369,7 @@ class Tracker @JvmOverloads constructor( * This is used for debugging/visualizing tracker data */ fun getIdentityAdjustedRotation(): Quaternion { - var rot = if (filteringHandler.filteringEnabled) { - // Get filtered rotation - filteringHandler.getFilteredRotation() - } else { - // Get unfiltered rotation - filteringHandler.getTrackedRotation() - } + var rot = getFilteredRotation() // Reset if needed or is a computed tracker besides head if (needsReset && !(isComputed && trackerPosition != TrackerPosition.HEAD) && trackerDataType == TrackerDataType.ROTATION) { @@ -422,6 +427,6 @@ class Tracker @JvmOverloads constructor( * Call when doing a full reset to reset the tracking of rotations >180 degrees */ fun resetFilteringQuats() { - filteringHandler.resetQuats(_rotation) + filteringHandler.resetMovingAverage(_rotation) } } diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerFilteringHandler.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerFilteringHandler.kt index e00f06b8a4..87a6e0a805 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerFilteringHandler.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerFilteringHandler.kt @@ -11,9 +11,8 @@ import io.github.axisangles.ktmath.Quaternion * See QuaternionMovingAverage.kt for the quaternion math. */ class TrackerFilteringHandler { - - private var filteringMovingAverage: QuaternionMovingAverage? = null - private var trackingMovingAverage = QuaternionMovingAverage(TrackerFilters.NONE) + // Instantiated by default in case config doesn't get read (if tracker doesn't support filtering) + private var movingAverage = QuaternionMovingAverage(TrackerFilters.NONE) var filteringEnabled = false /** @@ -22,14 +21,14 @@ class TrackerFilteringHandler { fun readFilteringConfig(config: FiltersConfig, currentRawRotation: Quaternion) { val type = TrackerFilters.getByConfigkey(config.type) if (type == TrackerFilters.SMOOTHING || type == TrackerFilters.PREDICTION) { - filteringMovingAverage = QuaternionMovingAverage( + movingAverage = QuaternionMovingAverage( type, config.amount, currentRawRotation, ) filteringEnabled = true } else { - filteringMovingAverage = null + movingAverage = QuaternionMovingAverage(TrackerFilters.NONE) filteringEnabled = false } } @@ -38,33 +37,25 @@ class TrackerFilteringHandler { * Update the moving average to make it smooth */ fun update() { - trackingMovingAverage.update() - filteringMovingAverage?.update() + movingAverage.update() } /** * Updates the latest rotation */ fun dataTick(currentRawRotation: Quaternion) { - trackingMovingAverage.addQuaternion(currentRawRotation) - filteringMovingAverage?.addQuaternion(currentRawRotation) + movingAverage.addQuaternion(currentRawRotation) } /** * Call when doing a full reset to reset the tracking of rotations >180 degrees */ - fun resetQuats(currentRawRotation: Quaternion) { - trackingMovingAverage.resetQuats(currentRawRotation) - filteringMovingAverage?.resetQuats(currentRawRotation) + fun resetMovingAverage(currentRawRotation: Quaternion) { + movingAverage.resetQuats(currentRawRotation) } /** - * Gets the tracked rotation from the moving average (allows >180 degrees) - */ - fun getTrackedRotation() = trackingMovingAverage.filteredQuaternion - - /** - * Get the filtered rotation from the moving average + * Get the filtered rotation from the moving average (either prediction/smoothing or just >180 degs) */ - fun getFilteredRotation() = filteringMovingAverage?.filteredQuaternion ?: Quaternion.IDENTITY + fun getFilteredRotation() = movingAverage.filteredQuaternion } diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerStatusListener.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerStatusListener.kt new file mode 100644 index 0000000000..4f4069be67 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerStatusListener.kt @@ -0,0 +1,6 @@ +package dev.slimevr.tracking.trackers + +interface TrackerStatusListener { + + fun onTrackerStatusChanged(tracker: Tracker, oldStatus: TrackerStatus, newStatus: TrackerStatus) +} diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/FirmwareConstants.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/FirmwareConstants.kt index 57f0a47d06..5deae18379 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/FirmwareConstants.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/FirmwareConstants.kt @@ -42,7 +42,7 @@ enum class BoardType(val id: UInt) { ESP01(8u), SLIMEVR(9u), LOLIN_C3_MINI(10u), - BEETLE32C32(11u), + BEETLE32C3(11u), ES32C3DEVKITM1(12u), OWOTRACK(13u), WRANGLER(14u), @@ -53,6 +53,8 @@ enum class BoardType(val id: UInt) { DEV_RESERVED(250u), ; + fun getSolarType(): Int = this.id.toInt() + override fun toString(): String = when (this) { UNKNOWN -> "Unknown" SLIMEVR_LEGACY -> "SlimeVR Legacy" @@ -65,7 +67,7 @@ enum class BoardType(val id: UInt) { ESP01 -> "ESP-01" SLIMEVR -> "SlimeVR" LOLIN_C3_MINI -> "Lolin C3 Mini" - BEETLE32C32 -> "Beetle ESP32-C3" + BEETLE32C3 -> "Beetle ESP32-C3" ES32C3DEVKITM1 -> "Espressif ESP32-C3 DevKitM-1" OWOTRACK -> "owoTrack" WRANGLER -> "Wrangler Joycons" diff --git a/server/core/src/test/java/dev/slimevr/unit/MountingResetTests.kt b/server/core/src/test/java/dev/slimevr/unit/MountingResetTests.kt index c693097d20..1ca49463eb 100644 --- a/server/core/src/test/java/dev/slimevr/unit/MountingResetTests.kt +++ b/server/core/src/test/java/dev/slimevr/unit/MountingResetTests.kt @@ -46,6 +46,7 @@ class MountingResetTests { imuType = IMUType.UNKNOWN, needsReset = true, needsMounting = true, + trackRotDirection = false, ) // Apply full reset and mounting @@ -130,6 +131,7 @@ class MountingResetTests { imuType = IMUType.UNKNOWN, needsReset = true, needsMounting = true, + trackRotDirection = false, ) // Apply full reset and mounting diff --git a/server/desktop/build.gradle.kts b/server/desktop/build.gradle.kts index 9ebb84a73e..17614f7ddc 100644 --- a/server/desktop/build.gradle.kts +++ b/server/desktop/build.gradle.kts @@ -50,6 +50,7 @@ allprojects { // You can declare any Maven/Ivy/file repository here. mavenCentral() maven(url = "https://jitpack.io") + maven(url = "https://oss.sonatype.org/content/repositories/snapshots") } } @@ -62,7 +63,7 @@ dependencies { implementation("com.google.protobuf:protobuf-java:3.21.12") implementation("net.java.dev.jna:jna:5.+") implementation("net.java.dev.jna:jna-platform:5.+") - implementation("com.fazecast:jSerialComm:2.11.0") + implementation("com.fazecast:jSerialComm:2.11.1-SNAPSHOT") implementation("org.hid4java:hid4java:0.8.0") } diff --git a/server/desktop/src/main/java/dev/slimevr/desktop/Main.kt b/server/desktop/src/main/java/dev/slimevr/desktop/Main.kt index cc4662d70c..e1719a53d0 100644 --- a/server/desktop/src/main/java/dev/slimevr/desktop/Main.kt +++ b/server/desktop/src/main/java/dev/slimevr/desktop/Main.kt @@ -6,6 +6,7 @@ import dev.slimevr.Keybinding import dev.slimevr.SLIMEVR_IDENTIFIER import dev.slimevr.VRServer import dev.slimevr.bridge.ISteamVRBridge +import dev.slimevr.desktop.firmware.DesktopSerialFlashingHandler import dev.slimevr.desktop.platform.SteamVRBridge import dev.slimevr.desktop.platform.linux.UnixSocketBridge import dev.slimevr.desktop.platform.windows.WindowsNamedPipeBridge @@ -121,6 +122,7 @@ fun main(args: Array) { ::provideSteamVRBridge, ::provideFeederBridge, { _ -> DesktopSerialHandler() }, + { _ -> DesktopSerialFlashingHandler() }, configPath = configDir, ) vrServer.start() diff --git a/server/desktop/src/main/java/dev/slimevr/desktop/firmware/DesktopSerialFlashingHandler.kt b/server/desktop/src/main/java/dev/slimevr/desktop/firmware/DesktopSerialFlashingHandler.kt new file mode 100644 index 0000000000..1d626bf6e4 --- /dev/null +++ b/server/desktop/src/main/java/dev/slimevr/desktop/firmware/DesktopSerialFlashingHandler.kt @@ -0,0 +1,88 @@ +package dev.slimevr.desktop.firmware + +import com.fazecast.jSerialComm.SerialPort +import dev.slimevr.firmware.SerialFlashingHandler +import io.eiren.util.logging.LogManager +import dev.slimevr.serial.SerialPort as SerialPortWrapper + +class DesktopSerialFlashingHandler : SerialFlashingHandler { + private var port: SerialPort? = null + + override fun openSerial(port: Any) { + if (port !is SerialPortWrapper) { + error("Not a serial port") + } + val ports = SerialPort.getCommPorts() + val comPort = ports.find { it.portLocation == port.portLocation } + ?: error("Unable to find port ${port.portLocation}") + if (comPort.isOpen) { + comPort.closePort() + } + if (!comPort.openPort(1000)) { + error("unable to open port") + } + this.port = comPort + } + + override fun closeSerial() { + val p = port ?: error("no port to close") + try { + p.closePort() + LogManager.info("Port closed") + } catch (e: Exception) { + error("unable to close port") + } + } + + override fun setDTR(value: Boolean) { + val p = port ?: error("no port to set DTR") + if (value) { + p.setDTR() + } else { + p.clearDTR() + } + } + + override fun setRTS(value: Boolean) { + val p = port ?: error("no port to set RTS") + if (value) { + p.setRTS() + } else { + p.clearRTS() + } + } + + override fun write(data: ByteArray) { + val p = port ?: error("no port to write") + p.writeBytes(data, data.size) + } + + override fun read(length: Int): ByteArray { + val p = port ?: error("no port to read") + val data = ByteArray(length) + p.readBytes(data, length) + return data + } + + override fun changeBaud(baud: Int) { + val p = port ?: error("no port to set the baud") + if (!p.setBaudRate(baud)) { + error("Unable to change baudrate") + } + } + + override fun setReadTimeout(timeout: Long) { + val p = port ?: error("no port to set the timeout") + p.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, timeout.toInt(), 0) + } + + override fun availableBytes(): Int { + val p = port ?: error("no port to check available bytes") + return p.bytesAvailable() + } + + override fun flushIOBuffers() { + val p = port ?: error("no port to flush") + p.flushIOBuffers() + } +} diff --git a/server/desktop/src/main/java/dev/slimevr/desktop/serial/DesktopSerialHandler.kt b/server/desktop/src/main/java/dev/slimevr/desktop/serial/DesktopSerialHandler.kt index 716d4781d5..5e44b32a9e 100644 --- a/server/desktop/src/main/java/dev/slimevr/desktop/serial/DesktopSerialHandler.kt +++ b/server/desktop/src/main/java/dev/slimevr/desktop/serial/DesktopSerialHandler.kt @@ -71,10 +71,14 @@ class DesktopSerialHandler : getDevicesTimer.purge() } - fun onNewDevice(port: SerialPort) { + private fun onNewDevice(port: SerialPort) { listeners.forEach { it.onNewSerialDevice(SerialPortWrapper(port)) } } + private fun onDeviceDel(port: SerialPort) { + listeners.forEach { it.onSerialDeviceDeleted(SerialPortWrapper(port)) } + } + override fun addListener(channel: SerialListener) { listeners.add(channel) } @@ -181,6 +185,11 @@ class DesktopSerialHandler : } } + override fun write(buff: ByteArray) { + LogManager.info("[SerialHandler] WRITING $buff") + currentPort?.outputStream?.write(buff) + } + @Synchronized override fun setWifi(ssid: String, passwd: String) { val os = currentPort?.outputStream ?: return @@ -236,13 +245,20 @@ class DesktopSerialHandler : private fun detectNewPorts() { try { - val differences = knownPorts.asSequence() - lastKnownPorts + val addDifferences = knownPorts.asSequence() - lastKnownPorts + val delDifferences = lastKnownPorts - knownPorts.asSequence().toSet() lastKnownPorts = SerialPort.getCommPorts().map { SerialPortWrapper(it) }.toSet() - differences.forEach { onNewDevice(it.port) } + addDifferences.forEach { onNewDevice(it.port) } + delDifferences.forEach { onDeviceDel(it.port) } } catch (e: Throwable) { LogManager .severe("[SerialHandler] Using serial ports is not supported on this platform", e) throw RuntimeException("Serial unsupported") } } + + override fun getCurrentPort(): dev.slimevr.serial.SerialPort? { + val port = this.currentPort ?: return null + return SerialPortWrapper(port) + } }