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/.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 ad39075d7f..ae77c44501 100644 --- a/gui/package.json +++ b/gui/package.json @@ -2,13 +2,16 @@ "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", + "@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", @@ -34,7 +37,8 @@ "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 +50,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 +72,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", @@ -77,6 +86,8 @@ "spdx-satisfies": "^5.0.1", "tailwind-gradient-mask-image": "^1.2.0", "tailwindcss": "^3.4.13", - "vite": "^5.4.8" + "vite": "^5.4.8", + "globals": "^15.10.0", + "typescript-eslint": "^8.8.0" } } diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl index a4d4739020..d6db7a015f 100644 --- a/gui/public/i18n/en/translation.ftl +++ b/gui/public/i18n/en/translation.ftl @@ -45,6 +45,19 @@ body_part-LEFT_UPPER_LEG = Left thigh body_part-LEFT_LOWER_LEG = Left ankle body_part-LEFT_FOOT = Left foot +## 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 @@ -294,6 +307,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 @@ -672,6 +686,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 @@ -721,6 +736,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-optaining_mac_address = Optaining 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 @@ -1013,6 +1029,157 @@ 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 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 a 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-optional-tracker = + .label = Optional tracker +firmware-tool-add-imus-show-less = Show Less +firmware-tool-add-imus-show-more = Show More +firmware-tool-add-imus-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 = Press the flash button on the pcb before inserting turning on the tracker. + If the tracker was already on, simply turn it off and back on while pressing the button or shorting the flash pads. + Here are a few pictures on how to do it according to the different revisions of the slimevr tracker +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 + ## 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/App.tsx b/gui/src/App.tsx index 9865a08b91..7923d8a1be 100644 --- a/gui/src/App.tsx +++ b/gui/src/App.tsx @@ -51,10 +51,12 @@ 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'; export const GH_REPO = 'SlimeVR/SlimeVR-Server'; @@ -105,6 +107,7 @@ function Layout() { } > + } /> } /> } /> } /> @@ -272,19 +275,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/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/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..42ce1e3f02 --- /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-show-less' + : 'firmware-tool-add-imus-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..844731a040 --- /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..d299a575b7 --- /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..07505077f5 --- /dev/null +++ b/gui/src/components/firmware-tool/DeviceCard.tsx @@ -0,0 +1,107 @@ +import { Control, Controller } from 'react-hook-form'; +import { Typography } from '@/components/commons/Typography'; +import { ProgressBar } from '@/components/commons/ProgressBar'; +import { CHECKBOX_CLASSES } from '@/components/commons/Checkbox'; +import classNames from 'classnames'; +import { FirmwareUpdateStatus } from 'solarxr-protocol'; +import { useLocalization } from '@fluent/react'; +import { firmwareUpdateErrorStatus } from '@/hooks/firmware-tool'; + +interface DeviceCardProps { + deviceNames: string[]; + status?: FirmwareUpdateStatus; +} + +interface DeviceCardControlProps { + control?: Control; + name?: string; + progress?: number; +} + +export function DeviceCardContent({ deviceNames, status }: DeviceCardProps) { + const { l10n } = useLocalization(); + + return ( +
    +
    + {deviceNames.map((name) => ( + + {name} + + ))} +
    + {status && ( + + {l10n.getString( + 'firmware-update-status-' + FirmwareUpdateStatus[status] + )} + + )} +
    + ); +} + +export function DeviceCardControl({ + control, + name, + progress = undefined, + ...props +}: DeviceCardControlProps & DeviceCardProps) { + return ( +
    + {control && name ? ( + ( + + )} + > + ) : ( +
    + +
    + )} +
    + +
    +
    + ); +} diff --git a/gui/src/components/firmware-tool/FirmwareTool.tsx b/gui/src/components/firmware-tool/FirmwareTool.tsx new file mode 100644 index 0000000000..17b1b28892 --- /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]); + + 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..43c5d345d4 --- /dev/null +++ b/gui/src/components/firmware-tool/FlashBtnStep.tsx @@ -0,0 +1,86 @@ +import { 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..6acb3aa09b --- /dev/null +++ b/gui/src/components/firmware-tool/FlashingMethodStep.tsx @@ -0,0 +1,405 @@ +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) { + 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); + } 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 }) => + trackers.length > 0 && + boardTypeToFirmwareToolBoardType[ + (hardwareInfo?.officialBoardType !== BoardType.SLIMEVR_LEGACY && + hardwareInfo?.officialBoardType !== BoardType.SLIMEVR_DEV && + hardwareInfo?.officialBoardType !== BoardType.CUSTOM && + hardwareInfo?.officialBoardType) || + BoardType.UNKNOWN + ] == newConfig?.boardConfig?.type && + trackers.every(({ status }) => status == TrackerStatus.OK) + ) || []; + + 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..94f5fd1b3f --- /dev/null +++ b/gui/src/components/firmware-tool/FlashingStep.tsx @@ -0,0 +1,269 @@ +import { Localized, useLocalization } from '@fluent/react'; +import { Typography } from '@/components/commons/Typography'; +import { + SelectedDevice, + firmwareUpdateErrorStatus, + useFirmwareTool, +} from '@/hooks/firmware-tool'; +import { useEffect, useMemo, useState } from 'react'; +import { useWebsocketAPI } from '@/hooks/websocket-api'; +import { + DeviceIdT, + DeviceIdTableT, + FirmwarePartT, + FirmwareUpdateMethod, + FirmwareUpdateRequestT, + FirmwareUpdateStatus, + FirmwareUpdateStatusResponseT, + FirmwareUpdateStopQueuesRequestT, + OTAFirmwareUpdateT, + RpcMessage, + SerialDevicePortT, + SerialFirmwareUpdateT, +} from 'solarxr-protocol'; +import { firmwareToolS3BaseUrl } from '@/firmware-tool-api/firmwareToolFetcher'; +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'; + +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 = (devices: SelectedDevice[]) => { + clear(); + + if (!buildStatus.firmwareFiles) + throw new Error('invalid state - no firmware files'); + + const firmware = buildStatus.firmwareFiles.find( + ({ isFirmware }) => isFirmware + ); + if (!firmware) throw new Error('invalid state - no firmware to find'); + + 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 = firmwareToolS3BaseUrl + '/' + firmware.url; + + const method = new OTAFirmwareUpdateT(); + method.deviceId = dId; + method.firmwarePart = part; + + const req = new FirmwareUpdateRequestT(); + req.method = method; + req.methodType = FirmwareUpdateMethod.OTAFirmwareUpdate; + sendRPCPacket(RpcMessage.FirmwareUpdateRequest, 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 = buildStatus.firmwareFiles.map( + ({ offset, url }) => { + const part = new FirmwarePartT(); + part.offset = offset; + part.url = firmwareToolS3BaseUrl + '/' + url; + return part; + } + ); + + const req = new FirmwareUpdateRequestT(); + req.method = method; + req.methodType = FirmwareUpdateMethod.SerialFirmwareUpdate; + sendRPCPacket(RpcMessage.FirmwareUpdateRequest, req); + break; + } + default: { + throw new Error('unsupported flashing method'); + } + } + } + }; + + 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 = Object.keys(status).filter((id) => + firmwareUpdateErrorStatus.includes(status[id].status) + ); + + 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.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..0c4aa72cc3 --- /dev/null +++ b/gui/src/components/firmware-tool/SelectBoardStep.tsx @@ -0,0 +1,91 @@ +import { 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 && ( +
    + + Loading ... +
    + )} +
    +
    + + ); +} diff --git a/gui/src/components/firmware-tool/SelectFirmwareStep.tsx b/gui/src/components/firmware-tool/SelectFirmwareStep.tsx new file mode 100644 index 0000000000..7d547870ba --- /dev/null +++ b/gui/src/components/firmware-tool/SelectFirmwareStep.tsx @@ -0,0 +1,117 @@ +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((firmwares) => ( +
    { + selectVersion(firmwares.name); + }} + > + {getName(firmwares.name)} +
    + ))} +
    +
    +
    + + + + + + +
    +
    + )} + {isFetching && ( +
    + + + + +
    + )} +
    +
    + + ); +} 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/TrackerSettings.tsx b/gui/src/components/tracker/TrackerSettings.tsx index 8a1f1f7a57..72de235265 100644 --- a/gui/src/components/tracker/TrackerSettings.tsx +++ b/gui/src/components/tracker/TrackerSettings.tsx @@ -6,6 +6,7 @@ import { useForm } from 'react-hook-form'; import { useParams } from 'react-router-dom'; import { AssignTrackerRequestT, + BoardType, BodyPart, ForgetDeviceRequestT, ImuType, @@ -148,6 +149,25 @@ export function TrackerSettingsPage() { } }, [firstLoad]); + const boarType = 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, + ]); + const macAddress = useMemo(() => { if ( /(?:[a-zA-Z\d]{2}:){5}[a-zA-Z\d]{2}/.test( @@ -274,9 +294,7 @@ export function TrackerSettingsPage() { {l10n.getString('tracker-infos-board_type')} - - {tracker?.device?.hardwareInfo?.boardType || '--'} - + {boarType}
    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/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/firmware-tool.ts b/gui/src/hooks/firmware-tool.ts new file mode 100644 index 0000000000..638646105d --- /dev/null +++ b/gui/src/hooks/firmware-tool.ts @@ -0,0 +1,179 @@ +import { createContext, useContext, useState } from 'react'; +import { + fetchGetFirmwaresDefaultConfigBoard, + useGetHealth, + useGetIsCompatibleVersion, +} from '@/firmware-tool-api/firmwareToolComponents'; +import { + BuildResponseDTO, + CreateBoardConfigDTO, + CreateBuildFirmwareDTO, + DefaultBuildConfigDTO, +} from '@/firmware-tool-api/firmwareToolSchemas'; +import { BoardPinsForm } from '@/components/firmware-tool/BoardPinsStep'; +import { DeepPartial } from 'react-hook-form'; +import { + BoardType, + FirmwareUpdateMethod, + FirmwareUpdateStatus, +} from 'solarxr-protocol'; + +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__ || __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), + }; +} 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..93488cb316 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,18 @@ 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]), + useName: () => useMemo(() => getTrackerName(l10n, tracker.info), [tracker.info]), useRawRotationEulerDegrees: () => useMemo(() => QuaternionToEulerDegrees(tracker?.rotation), [tracker.rotation]), useRefAdjRotationEulerDegrees: () => diff --git a/gui/src/index.scss b/gui/src/index.scss index 354e141bdc..8ed7c77c73 100644 --- a/gui/src/index.scss +++ b/gui/src/index.scss @@ -84,7 +84,7 @@ body { } :root { - overflow: hidden; + // overflow: hidden; -- NEVER EVER BRING THIS BACK <3 background: theme('colors.background.20'); --navbar-w: 101px; diff --git a/gui/tailwind.config.ts b/gui/tailwind.config.ts index d612158bf7..5753f72134 100644 --- a/gui/tailwind.config.ts +++ b/gui/tailwind.config.ts @@ -162,9 +162,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', diff --git a/gui/vite.config.ts b/gui/vite.config.ts index c9eb27becc..0d17119671 100644 --- a/gui/vite.config.ts +++ b/gui/vite.config.ts @@ -21,7 +21,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 91e8dc5e79..41c4f11312 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,12 +29,18 @@ 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) + '@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 @@ -94,35 +100,44 @@ 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) 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 +167,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 +198,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 +209,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,16 +218,19 @@ 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)) + 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 @@ -225,95 +249,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': @@ -474,8 +603,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': @@ -486,6 +615,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'} @@ -509,6 +641,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'} @@ -541,8 +683,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==} @@ -554,8 +696,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' @@ -575,6 +717,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'} @@ -607,8 +832,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' @@ -643,8 +868,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': @@ -730,76 +955,167 @@ 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' + '@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 @@ -818,8 +1134,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==} @@ -845,6 +1161,9 @@ packages: '@types/file-saver@2.0.7': resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} + '@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==} @@ -854,8 +1173,8 @@ packages: '@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==} @@ -884,8 +1203,11 @@ 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/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==} @@ -898,6 +1220,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} @@ -908,10 +1241,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} @@ -922,10 +1269,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} @@ -935,16 +1295,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==} @@ -962,19 +1341,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'} @@ -1018,9 +1421,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'} @@ -1064,6 +1464,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} @@ -1071,16 +1479,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: @@ -1119,6 +1523,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} @@ -1130,6 +1539,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'} @@ -1141,6 +1558,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'} @@ -1149,13 +1569,20 @@ 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'} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -1177,17 +1604,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==} @@ -1195,6 +1647,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==} @@ -1231,6 +1687,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==} @@ -1280,6 +1740,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'} @@ -1289,8 +1758,9 @@ packages: supports-color: optional: true - deep-equal@2.2.2: - resolution: {integrity: sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==} + 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==} @@ -1303,6 +1773,10 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + 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'} @@ -1319,8 +1793,13 @@ packages: resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} engines: {node: '>=10'} - detect-gpu@5.0.51: - resolution: {integrity: sha512-7P+5KDthVGXXWS06EuqBIq7YBijxfaNfm+BSFNTRAkZP26J97ASssh5KoR53diWNcBNOEb1ILfdsz2pzesSgYw==} + 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 didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1344,6 +1823,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==} @@ -1357,11 +1840,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==} @@ -1372,8 +1858,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: @@ -1395,8 +1881,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: @@ -1414,13 +1900,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: @@ -1431,6 +1920,10 @@ 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'} @@ -1489,12 +1982,33 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-import@2.31.0: - resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + eslint-module-utils@2.8.1: + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 peerDependenciesMeta: '@typescript-eslint/parser': optional: true @@ -1535,8 +2049,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: @@ -1581,6 +2095,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==} @@ -1598,8 +2115,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==} @@ -1619,22 +2136,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==} @@ -1676,6 +2198,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'} @@ -1684,8 +2210,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==} @@ -1695,8 +2221,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: @@ -1715,6 +2242,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'} @@ -1729,12 +2260,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==} @@ -1773,8 +2324,24 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hls.js@1.3.5: - resolution: {integrity: sha512-uybAvKS6uDe0MnWNEPnO0krWVr+8m2R0hJ/viql8H3MVK+itq8gGQuIYoFHL3rECkIpNH98Lw8YuuWMKZxp3Ew==} + 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==} + + 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==} @@ -1795,8 +2362,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: @@ -1805,8 +2372,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==} @@ -1816,6 +2383,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. @@ -1823,6 +2394,16 @@ 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 + internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -1837,9 +2418,6 @@ packages: 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'} @@ -1866,6 +2444,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'} @@ -1910,9 +2495,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-map@2.0.2: - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} - is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -1940,16 +2522,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'} @@ -1970,17 +2546,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'} @@ -1988,9 +2557,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'} @@ -2005,17 +2571,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==} @@ -2028,6 +2594,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'} @@ -2051,6 +2622,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'} @@ -2118,17 +2692,22 @@ packages: 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==} @@ -2151,6 +2730,10 @@ packages: meshoptimizer@0.18.1: resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} + 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'} @@ -2167,6 +2750,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 @@ -2178,8 +2769,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: @@ -2193,6 +2784,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==} @@ -2207,6 +2801,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==} @@ -2218,6 +2834,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'} @@ -2226,6 +2846,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'} @@ -2234,16 +2870,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==} @@ -2295,10 +2923,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'} @@ -2319,13 +2957,23 @@ 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'} + 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'} @@ -2362,8 +3010,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==} @@ -2386,6 +3037,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'} @@ -2414,19 +3069,23 @@ 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.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} @@ -2453,6 +3112,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -2463,11 +3125,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: @@ -2505,6 +3174,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'} @@ -2521,15 +3196,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' @@ -2558,16 +3233,26 @@ 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 require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -2580,6 +3265,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'} @@ -2595,6 +3283,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'} @@ -2629,6 +3329,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'} @@ -2637,11 +3340,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==} @@ -2661,10 +3367,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'} @@ -2680,8 +3382,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==} @@ -2701,6 +3421,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'} @@ -2709,8 +3437,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==} @@ -2736,8 +3465,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==} @@ -2745,6 +3474,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==} @@ -2767,12 +3500,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==} @@ -2838,11 +3572,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 @@ -2871,14 +3613,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==} @@ -2890,6 +3635,12 @@ 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==} + troika-three-text@0.49.1: resolution: {integrity: sha512-lXGWxgjJP9kw4i4Wh+0k0Q/7cRfS6iOME4knKht/KozPu9GcFA9NnNpRvehIhrUawq9B0ZRw+0oiFHgRO+4Wig==} peerDependencies: @@ -2912,6 +3663,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'} @@ -2919,26 +3674,50 @@ 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==} 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'} @@ -2955,6 +3734,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'} @@ -2971,6 +3764,16 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + 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 @@ -2985,8 +3788,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 @@ -3001,8 +3804,8 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - vite@5.4.8: - resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} + vite@5.4.9: + resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -3041,24 +3844,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'} @@ -3068,10 +3870,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'} @@ -3087,6 +3897,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'} @@ -3094,6 +3916,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'} @@ -3123,6 +3954,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'} @@ -3132,8 +3976,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' @@ -3156,58 +4000,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.25.7': + '@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.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.24.7(@babel/core@7.24.7)': + dependencies: + '@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.7)': + '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.7 + '@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 @@ -3215,70 +4155,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.25.7': + '@babel/parser@7.24.7': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.24.7 - '@babel/plugin-transform-react-jsx-self@7.25.7(@babel/core@7.25.7)': + '@babel/parser@7.25.8': dependencies: - '@babel/core': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/types': 7.25.8 - '@babel/plugin-transform-react-jsx-source@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/runtime@7.25.7': + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.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 @@ -3296,7 +4296,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 @@ -3385,15 +4385,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 @@ -3403,6 +4403,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)': @@ -3420,12 +4422,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 @@ -3446,7 +4456,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': {} @@ -3459,18 +4469,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 @@ -3480,7 +4490,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 @@ -3499,6 +4509,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 @@ -3536,20 +4645,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) @@ -3558,7 +4667,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 @@ -3573,10 +4682,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 @@ -3590,7 +4699,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 @@ -3642,55 +4751,120 @@ 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)) + + '@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: @@ -3712,28 +4886,28 @@ 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': {} @@ -3743,6 +4917,8 @@ snapshots: '@types/file-saver@2.0.7': {} + '@types/http-cache-semantics@4.0.4': {} + '@types/json5@0.0.29': {} '@types/node@20.14.2': @@ -3752,7 +4928,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: @@ -3776,7 +4952,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': {} @@ -3785,17 +4961,19 @@ 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/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) @@ -3803,7 +4981,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: @@ -3817,7 +5013,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 @@ -3829,11 +5038,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: @@ -3841,16 +5055,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: @@ -3869,11 +5112,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': {} @@ -3883,22 +5142,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: + tslib: 2.6.3 + + '@wry/trie@0.5.0': + dependencies: + tslib: 2.6.3 + + acorn-jsx@5.3.2(acorn@8.12.0): dependencies: - acorn: 8.12.1 + acorn: 8.12.0 - acorn@8.12.1: {} + acorn@8.12.0: {} ajv@6.12.6: dependencies: @@ -3907,6 +5186,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 @@ -3942,11 +5225,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 @@ -4018,23 +5296,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: {} @@ -4042,7 +5322,7 @@ 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 @@ -4073,10 +5353,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) @@ -4087,6 +5374,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: @@ -4102,15 +5401,21 @@ 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): + camera-controls@2.8.5(three@0.163.0): dependencies: three: 0.163.0 - caniuse-lite@1.0.30001667: {} + caniuse-lite@1.0.30001636: {} + + caniuse-lite@1.0.30001669: {} + + case@1.6.3: {} chalk@2.4.2: dependencies: @@ -4141,16 +5446,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: @@ -4164,6 +5495,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 @@ -4191,6 +5526,8 @@ snapshots: convert-source-map@2.0.0: {} + convert-to-spaces@1.0.2: {} + create-require@1.1.1: {} cross-env@7.0.3: @@ -4235,30 +5572,17 @@ 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: + decompress-response@6.0.0: 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 + mimic-response: 3.1.0 deep-equal@2.2.3: dependencies: @@ -4275,7 +5599,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 @@ -4285,6 +5609,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 @@ -4301,10 +5627,12 @@ snapshots: delay@5.0.0: {} - detect-gpu@5.0.51: + detect-gpu@5.0.38: dependencies: webgl-constants: 1.1.1 + detect-libc@1.0.3: {} + didyoumean@1.2.2: {} diff@4.0.2: {} @@ -4323,6 +5651,8 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv@16.4.5: {} + draco3d@1.5.7: {} eastasianwidth@0.2.0: {} @@ -4331,9 +5661,11 @@ snapshots: ebnf@1.9.1: {} - electron-to-chromium@1.5.34: {} + electron-to-chromium@1.4.803: {} + + electron-to-chromium@1.5.41: {} - emoji-regex@10.4.0: {} + emoji-regex@10.3.0: {} emoji-regex@8.0.0: {} @@ -4343,7 +5675,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 @@ -4383,10 +5715,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 @@ -4407,17 +5739,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 @@ -4431,7 +5763,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: @@ -4454,6 +5786,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 @@ -4480,12 +5814,14 @@ 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: {} eslint-config-airbnb-base@15.0.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1))(eslint@8.57.1): @@ -4519,12 +5855,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(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(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))(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: @@ -4546,6 +5882,16 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(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))(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + eslint: 8.57.1 + 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) + transitivePeerDependencies: + - supports-color + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 @@ -4581,11 +5927,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 @@ -4593,7 +5939,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: @@ -4606,7 +5952,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 @@ -4631,7 +5977,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 @@ -4641,13 +5987,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 @@ -4655,7 +6001,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 @@ -4673,11 +6019,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 @@ -4725,7 +6071,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: {} @@ -4733,6 +6079,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -4749,9 +6097,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: @@ -4772,21 +6121,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: @@ -4828,6 +6183,8 @@ snapshots: dependencies: pump: 3.0.0 + get-stream@6.0.1: {} + get-stream@8.0.1: {} get-symbol-description@1.0.2: @@ -4836,7 +6193,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 @@ -4848,13 +6205,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: @@ -4879,6 +6235,8 @@ snapshots: dependencies: type-fest: 0.20.2 + globals@15.10.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -4889,7 +6247,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 @@ -4899,10 +6257,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: {} @@ -4933,7 +6316,22 @@ snapshots: dependencies: function-bind: 1.1.2 - hls.js@1.3.5: {} + highlight.js@10.7.3: {} + + hls.js@1.5.17: {} + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.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: {} @@ -4945,14 +6343,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: @@ -4961,6 +6359,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -4968,6 +6368,38 @@ 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 + internal-slot@1.0.7: dependencies: es-errors: 1.3.0 @@ -4980,14 +6412,8 @@ snapshots: is-arguments@1.1.1: dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - - is-array-buffer@3.0.2: - 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: @@ -5017,6 +6443,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 @@ -5053,8 +6487,6 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-map@2.0.2: {} - is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -5074,14 +6506,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 @@ -5098,27 +6524,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 @@ -5132,7 +6547,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 @@ -5145,7 +6560,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: @@ -5159,6 +6574,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@2.5.2: {} + jsesc@3.0.2: {} json-buffer@3.0.1: {} @@ -5173,6 +6590,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 @@ -5256,13 +6679,15 @@ snapshots: dependencies: js-tokens: 4.0.0 - lru-cache@10.4.3: {} + lowercase-keys@3.0.0: {} + + lru-cache@10.2.2: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - maath@0.10.8(@types/three@0.163.0)(three@0.163.0): + maath@0.10.7(@types/three@0.163.0)(three@0.163.0): dependencies: '@types/three': 0.163.0 three: 0.163.0 @@ -5283,6 +6708,11 @@ snapshots: meshoptimizer@0.18.1: {} + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -5294,6 +6724,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: @@ -5304,7 +6738,7 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimatch@9.0.5: + minimatch@9.0.4: dependencies: brace-expansion: 2.0.1 @@ -5314,6 +6748,8 @@ snapshots: minipass@7.1.2: {} + ms@2.1.2: {} + ms@2.1.3: {} mz@2.7.0: @@ -5326,12 +6762,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 @@ -5340,18 +6794,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: @@ -5416,6 +6894,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 @@ -5425,6 +6914,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 @@ -5443,12 +6934,20 @@ snapshots: p-try@2.2.0: {} - package-json-from-dist@1.0.1: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 + 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: {} @@ -5463,14 +6962,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: {} @@ -5484,44 +6985,52 @@ 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.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: {} @@ -5546,6 +7055,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-expr@2.0.6: {} + pump@3.0.0: dependencies: end-of-stream: 1.4.4 @@ -5555,11 +7066,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 @@ -5568,7 +7089,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: {} @@ -5598,6 +7119,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 @@ -5614,16 +7142,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): @@ -5652,45 +7180,59 @@ 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 + 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 @@ -5739,6 +7281,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 @@ -5752,11 +7298,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: @@ -5779,12 +7331,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 @@ -5800,18 +7346,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: {} @@ -5821,6 +7389,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 @@ -5831,9 +7407,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: {} @@ -5857,9 +7431,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: {} @@ -5869,6 +7443,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 @@ -5893,14 +7471,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 @@ -5915,7 +7494,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 @@ -5963,7 +7542,7 @@ snapshots: 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 @@ -5983,9 +7562,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 @@ -5997,16 +7594,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: @@ -6017,7 +7614,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 @@ -6036,11 +7633,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 @@ -6048,6 +7645,8 @@ snapshots: three@0.163.0: {} + tiny-case@1.0.3: {} + tinycolor2@1.6.0: {} to-fast-properties@2.0.0: {} @@ -6056,6 +7655,10 @@ snapshots: dependencies: is-number: 7.0.0 + toposort@2.0.2: {} + + tr46@0.0.3: {} + troika-three-text@0.49.1(three@0.163.0): dependencies: bidi-js: 1.0.3 @@ -6076,6 +7679,10 @@ snapshots: 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 @@ -6097,7 +7704,7 @@ snapshots: yn: 3.1.1 optional: true - ts-pattern@5.4.0: {} + ts-pattern@5.5.0: {} tsconfig-paths@3.15.0: dependencies: @@ -6106,22 +7713,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 @@ -6154,6 +7776,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: {} @@ -6168,11 +7803,19 @@ snapshots: undici-types@5.26.5: optional: true + 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: @@ -6182,7 +7825,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 @@ -6192,7 +7835,7 @@ snapshots: uuid@9.0.1: {} - 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): dependencies: esbuild: 0.21.5 postcss: 8.4.47 @@ -6200,7 +7843,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: @@ -6211,6 +7854,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 @@ -6219,7 +7869,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 @@ -6234,13 +7884,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 @@ -6248,14 +7891,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 @@ -6268,8 +7903,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 @@ -6285,15 +7930,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: {} @@ -6303,7 +7954,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 @@ -6313,7 +7964,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 @@ -6324,13 +7975,30 @@ 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 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..d52baa6e87 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,19 @@ class AndroidSerialHandler(val activity: AppCompatActivity) : } } + override fun write(buff: ByteArray) { + LogManager.info("[SerialHandler] WRITING $buff") + 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..63938783c4 100644 --- a/server/core/build.gradle.kts +++ b/server/core/build.gradle.kts @@ -76,7 +76,14 @@ dependencies { implementation("org.java-websocket:Java-WebSocket:1.+") implementation("com.melloware:jintellitype:1.+") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") + implementation("it.unimi.dsi:fastutil:8.5.12") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") + implementation("com.mayakapps.kache:kache:2.0.0-rc02") + + 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 +94,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 4bd35390e3..1f253231c8 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 @@ -108,12 +118,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 @@ -410,6 +422,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/firmware/FirmwareUpdateHandler.kt b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateHandler.kt new file mode 100644 index 0000000000..fc6bdf206f --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateHandler.kt @@ -0,0 +1,506 @@ +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, this) + + fun addListener(channel: FirmwareUpdateListener) { + listeners.add(channel) + } + + fun removeListener(channel: FirmwareUpdateListener) { + listeners.removeIf { channel == it } + } + + fun destroy() { + mainScope.cancel() + } + + 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) { + e.printStackTrace() + 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) { + 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)) + } + + if (status == ProvisioningStatus.PROVISIONING) { + update(FirmwareUpdateStatus.PROVISIONING) + } + + if (status == ProvisioningStatus.DONE) { + update(FirmwareUpdateStatus.DONE) + } + + if (status == ProvisioningStatus.CONNECTION_ERROR || status == ProvisioningStatus.COULD_NOT_FIND_SERVER) { + update(FirmwareUpdateStatus.ERROR_PROVISIONING_FAILED) + } + } + + override fun onSerialDeviceReconnect(deviceHandle: Pair, () -> Unit>) { + deviceHandle.second() + watchRestartQueue.remove(deviceHandle) + if (server.serialHandler.isConnected) { + server.serialHandler.closeSerial() + } + } +} + +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..ab014038e8 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/OTAUpdateTask.kt @@ -0,0 +1,180 @@ +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) { + 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) { + e.printStackTrace() + 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..28cc87c6e8 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/firmware/SerialRebootHandler.kt @@ -0,0 +1,56 @@ +package dev.slimevr.firmware + +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>>, + // 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) + } + } + } + + 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) + } + } + + 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/protocol/ProtocolAPI.java b/server/core/src/main/java/dev/slimevr/protocol/ProtocolAPI.java index 7bb090e643..7908ecc3a5 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/ProtocolAPI.java +++ b/server/core/src/main/java/dev/slimevr/protocol/ProtocolAPI.java @@ -65,4 +65,5 @@ public void registerAPIServer(ProtocolAPIServer server) { public void removeAPIServer(ProtocolAPIServer server) { this.servers.remove(server); } + } diff --git a/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedBuilder.java b/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedBuilder.java index 3eb480af0d..86c2429625 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedBuilder.java +++ b/server/core/src/main/java/dev/slimevr/protocol/datafeed/DataFeedBuilder.java @@ -38,8 +38,6 @@ public static int createHardwareInfo(FlatBufferBuilder fbb, Device device) { ? fbb.createString(device.getManufacturer()) : 0; - int boardTypeOffset = fbb.createString(device.getBoardType().toString()); - int hardwareIdentifierOffset = fbb.createString(device.getHardwareIdentifier()); HardwareInfo.startHardwareInfo(fbb); @@ -68,7 +66,7 @@ public static int createHardwareInfo(FlatBufferBuilder fbb, Device device) { // TODO need support: HardwareInfo.addDisplayName(fbb, de); HardwareInfo.addMcuId(fbb, device.getMcuType().getSolarType()); - HardwareInfo.addBoardType(fbb, boardTypeOffset); + HardwareInfo.addOfficialBoardType(fbb, device.getBoardType().getSolarType()); return HardwareInfo.endHardwareInfo(fbb); } @@ -349,7 +347,7 @@ public static int createDevicesData( for (int i = 0; i < devices.size(); i++) { Device device = devices.get(i); devicesDataOffsets[i] = DataFeedBuilder - .createDeviceData(fbb, i, deviceDataMaskT, device); + .createDeviceData(fbb, device.getId(), deviceDataMaskT, device); } return DataFeedUpdate.createDevicesVector(fbb, devicesDataOffsets); diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.kt b/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.kt index d9995f647e..850644ea1e 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.kt +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/RPCHandler.kt @@ -8,6 +8,7 @@ import dev.slimevr.protocol.ProtocolAPI import dev.slimevr.protocol.ProtocolHandler import dev.slimevr.protocol.datafeed.DataFeedBuilder import dev.slimevr.protocol.rpc.autobone.RPCAutoBoneHandler +import dev.slimevr.protocol.rpc.firmware.RPCFirmwareUpdateHandler import dev.slimevr.protocol.rpc.reset.RPCResetHandler import dev.slimevr.protocol.rpc.serial.RPCProvisioningHandler import dev.slimevr.protocol.rpc.serial.RPCSerialHandler @@ -41,6 +42,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler) { + 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..b87c08e5e6 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 tryOptainMacAddress() { + 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.tryOptainMacAddress(); 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.tryOptainMacAddress(); } @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/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 e5d8ab2f58..1902287d50 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 @@ -110,6 +110,8 @@ class Tracker @JvmOverloads constructor( } checkReportErrorStatus() checkReportRequireReset() + + VRServer.instance.trackerStatusChanged(this, old, new) } } 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 164d7d4a64..a82a5f26e9 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/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..7adc3d4ed3 --- /dev/null +++ b/server/desktop/src/main/java/dev/slimevr/desktop/firmware/DesktopSerialFlashingHandler.kt @@ -0,0 +1,87 @@ +package dev.slimevr.desktop.firmware + +import com.fazecast.jSerialComm.SerialPort +import dev.slimevr.firmware.SerialFlashingHandler +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() + println("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) + } }