diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d14c3ea..80273e7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,5 +2,15 @@ "name": "neptune", "build": { "dockerfile": "../Docker/Dockerfile" + }, + + "runArgs": ["--privileged", "--add-host=host.docker.internal:host-gateway" ], + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools-extension-pack" + ] } -} \ No newline at end of file +}, +"onCreateCommand": "pipx run pre-commit install" +} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..851a235 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,31 @@ +name: Lint + +on: + push: + pull_request: + branches: [main, master] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install frontend dependencies + working-directory: web_app/frontend_gui + run: npm ci + + - name: Install clang-format + run: | + sudo apt-get update + sudo apt-get install -y clang-format-18 + + - uses: pre-commit/action@v3.0.1 diff --git a/.gitignore b/.gitignore index 1d46c1d..ad41229 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ **/.DS_Store **/node_modules **/.venv -<<<<<<< HEAD **/.vscode **/__pycache__ @@ -9,7 +8,4 @@ auv/data/local/* !auv/data/local/.gitkeep web_app/data/local/* !web_app/data/local/.gitkeep -======= -*.vscode -__pycache__ ->>>>>>> main +build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a997106 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,58 @@ +# Pre-commit hooks for Neptune repository (C++ and Frontend only) +# Install: pip install pre-commit && pre-commit install +# Run manually: pre-commit run --all-files + +exclude: ^auv/ + +repos: + # General file checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + args: [--unsafe] + - id: check-json + exclude: tsconfig\.json$ + - id: check-added-large-files + args: ["--maxkb=1000"] + - id: check-merge-conflict + + # C++ clangformat + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v18.1.8 + hooks: + - id: clang-format + files: ^auv_cpp/.*\.(cpp|hpp|h)$ + args: [--style=file] + + # TypeScript/JavaScript -ESLint + Prettier + - repo: local + hooks: + - id: eslint + name: ESLint + entry: bash -c 'stripped=(); for f in "$@"; do stripped+=("${f#web_app/frontend_gui/}"); done; cd web_app/frontend_gui && ./node_modules/.bin/eslint --fix "${stripped[@]}"' -- + language: system + files: ^web_app/frontend_gui/src/.*\.(ts|tsx|js|jsx)$ + pass_filenames: true + + - id: prettier + name: Prettier + entry: bash -c 'stripped=(); for f in "$@"; do stripped+=("${f#web_app/frontend_gui/}"); done; cd web_app/frontend_gui && ./node_modules/.bin/prettier --write "${stripped[@]}"' -- + language: system + files: ^web_app/frontend_gui/.*\.(ts|tsx|js|jsx|css|json)$ + exclude: tsconfig\.json$ + pass_filenames: true + + # YAML validation + - repo: https://github.com/adrienverge/yamllint + rev: v1.35.1 + hooks: + - id: yamllint + args: [-c, .yamllint.yml] + files: \.(yaml|yml)$ + +ci: + autofix_commit_msg: "style: auto-fix by pre-commit hooks" + autoupdate_commit_msg: "chore: update pre-commit hooks" diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..8ae80bb --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,15 @@ +--- +extends: default + +rules: + line-length: + max: 200 + level: warning + truthy: + check-keys: false + comments: + min-spaces-from-content: 1 + document-start: disable + indentation: + spaces: 2 + indent-sequences: true diff --git a/Docker/Dockerfile b/Docker/Dockerfile index 9b0851e..53ba434 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -1,11 +1,21 @@ -FROM debian:bookworm +FROM ubuntu:latest -RUN apt-get update -y +RUN apt-get update -y -# install cmake and ninja +# install dev dependcies RUN apt-get install -y cmake build-essential RUN apt-get install -y ninja-build -RUN apt-get install -y git - +RUN apt-get update -y +RUN apt-get install -y git gdb curl clang-format +#Get project dependencies +RUN apt-get install -y gpiod libgpiod-dev liblgpio-dev libserial-dev gpsd libgps-dev libeigen3-dev +#dev dependencies +RUN apt-get install -y python3 python3-pip pipx +WORKDIR /workspace/Neptune +RUN pipx install pre-commit +WORKDIR /usr/local/include +#install httplib and serial lib TODO - this is unstable, we should choose a specfic version to fix +RUN curl -LO https://github.com/yhirose/cpp-httplib/raw/refs/tags/latest/httplib.h > httplib.h +RUN curl -LO https://github.com/CLIUtils/CLI11/releases/download/v2.6.2/CLI11.hpp > CLI11.hpp diff --git a/Docs/Docker.md b/Docs/Docker.md index 610bba9..95c33ce 100644 --- a/Docs/Docker.md +++ b/Docs/Docker.md @@ -4,11 +4,10 @@ Docker allows us to containerize our app to ensure the environment is the same a # Launching the project in the container -* Clone the repository and open it in vscode -* Make sure the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension by microsoft is installed +* Clone the repository and open it in vscode +* Make sure the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension by microsoft is installed * From you command palete (ctrl(cmd on mac) + shift + p) run the `Dev containers rebuild and reopen in container` command. * The first time, this may take awhile to build You can tell the container is open if the bar at the top of vscode shows `Neptune [Dev Container: neptune]` Later, you can relaunch directly in the contaier by choosing the [dev container] option when you select your workspace - diff --git a/Docs/Running.md b/Docs/Running.md index 7569061..c0ede66 100644 --- a/Docs/Running.md +++ b/Docs/Running.md @@ -14,5 +14,3 @@ build the project, then run it cmake --build neptune ./neptune ``` - - diff --git a/README.md b/README.md new file mode 100644 index 0000000..6158ff5 --- /dev/null +++ b/README.md @@ -0,0 +1,119 @@ +# Neptune +Yonder Deep 2026 + +## To Get Started: +READ DOCS/docker.md + +## Branch Management + +### Main Branch +- `main` is the primary working branch and should always contain stable, tested code +- All code in `main` must pass automated tests and peer review +- Do not force push to `main`, create your own branch before working on your issue. + +### Development Branches +- Create feature branches for individual or subteam work: `feature/sensor-integration`, `feature/navigation-system` +- Use descriptive branch names: `andytgarcia/pressure-sensor-calibration`, `yonderdeep/motor-controller-pid` +- Branch naming convention: `yourgithubusername/brief-descriptive-name` +- Delete branches after merging to keep the repository clean + +### Merge Process +1. Create a pull request from your branch to `main` +2. Ensure all tests pass (or write your own) +3. Request review from at least one team member +4. Address review comments +5. Merge only after approval + +## Code Style Guidelines + +### Indentation +- Nested blocks should be indented one level deeper + +### Line Lengths +- Make lines entirely readable on screen +- Break long lines logically at operators, commas, or before opening parentheses +- Comments should also respect the line length limit + +### Brace Style + +```cpp +if (condition) { + // code here +} +else { + // alternative code +} + +void functionName() { + // function body +} +``` + +### Naming Conventions + +#### Variables +- Use `camelCase` for local variables: `sensorData`, `motorSpeed` +- Use `UPPER_SNAKE_CASE` for constants: `MAX_DEPTH`, `SENSOR_TIMEOUT_MS` +- Use descriptive names that convey purpose: `currentDepth` instead of `d` + +#### Functions +- Use `camelCase` for function names: `calculateBuoyancy()`, `updateMotorController()` +- Use verb-noun pairs for clarity: `readPressureSensor()`, `setThrottle()` + +#### Classes +- Use `PascalCase` for class names: `NavigationSystem`, `PressureSensor` +- Use nouns or noun phrases: `MotorController`, `DataLogger` + +#### Files +- Use `snake_case` for filenames: `motor_controller.cpp`, `pressure_sensor.h` +- Header files use `.h` or `.hpp` extension +- Implementation files use `.cpp` or `.c` extension + +## Documentation Standards + +### Header Files +Use '#pragma once' for all .h and .hpp files + +```cpp + +#pragma once + +class ClassName + +``` + +### Class Headers +Document every class with its purpose and usage: + +```cpp +/** + * @class PressureSensor + * @brief Interface for MS5837 pressure/temperature sensor + * + * Provides depth and temperature readings using I2C communication. + * Handles calibration and conversion to engineering units. + */ +class PressureSensor { + // class definition +}; +``` + +### Function Headers +Document all public functions and complex private functions: + + +### Inline Comments +- Use inline comments for complex logic or non-obvious decisions +- Explain the "why," not the "what" +- Keep comments up-to-date with code changes + + +### First Time Contributors +1. Read this entire README +2. Review the existing codebase to understand project structure +3. Set up your development environment +4. Start with a small issue labeled "good first issue" +5. Ask questions in Discord + + +For questions or suggestions about these guidelines, please open an issue or contact the project maintainer. diff --git a/auv_cpp/.clang-format b/auv_cpp/.clang-format index 3d8f02d..1bcfcff 100644 --- a/auv_cpp/.clang-format +++ b/auv_cpp/.clang-format @@ -5,4 +5,4 @@ PointerAlignment: Left AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: Never BreakBeforeBraces: Attach -SortIncludes: true \ No newline at end of file +SortIncludes: true diff --git a/auv_cpp/.editorconfig b/auv_cpp/.editorconfig index e925a43..19ba592 100644 --- a/auv_cpp/.editorconfig +++ b/auv_cpp/.editorconfig @@ -9,4 +9,4 @@ trim_trailing_whitespace = true insert_final_newline = true [*.{yml,yaml}] -indent_size = 2 \ No newline at end of file +indent_size = 2 diff --git a/auv_cpp/.gitignore b/auv_cpp/.gitignore index 50df32b..8df5e2f 100644 --- a/auv_cpp/.gitignore +++ b/auv_cpp/.gitignore @@ -26,4 +26,4 @@ logs/ *.log # OS -.DS_Store \ No newline at end of file +.DS_Store diff --git a/auv_cpp/CMakeLists.txt b/auv_cpp/CMakeLists.txt index 508578b..05f0668 100644 --- a/auv_cpp/CMakeLists.txt +++ b/auv_cpp/CMakeLists.txt @@ -21,5 +21,9 @@ find_package(Eigen3 3.3 REQUIRED NO_MODULE) include_directories(${CMAKE_SOURCE_DIR}/src) include_directories(${CMAKE_SOURCE_DIR}/include) -#if we make core subdirectory uncomment this -#add_subdirectory(src/core) \ No newline at end of file +add_subdirectory(src/core) + +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/auv_cpp/api/motor_controller.hpp b/auv_cpp/api/motor_controller.hpp index 96e8d43..098bee4 100644 --- a/auv_cpp/api/motor_controller.hpp +++ b/auv_cpp/api/motor_controller.hpp @@ -1,5 +1,5 @@ -//object that manages and abstracts all interactions with the motor array -// for the auv +// object that manages and abstracts all interactions with the motor array +// for the auv #pragma once diff --git a/auv_cpp/tests/CMakeLists.txt b/auv_cpp/tests/CMakeLists.txt new file mode 100644 index 0000000..db81165 --- /dev/null +++ b/auv_cpp/tests/CMakeLists.txt @@ -0,0 +1,5 @@ +# ============================================================================= +# tests/CMakeLists.txt - Test suite +# ============================================================================= + +add_subdirectory(core) diff --git a/auv_cpp/tests/core/test_config b/auv_cpp/tests/core/test_config new file mode 100755 index 0000000..70c8e37 Binary files /dev/null and b/auv_cpp/tests/core/test_config differ diff --git a/motor_client.py b/motor_client.py new file mode 100644 index 0000000..5425d6b --- /dev/null +++ b/motor_client.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +""" +motor_client.py — Base station client for motor_server + +Connects to the Pi's motor_server over TCP (WiFi HaLow, Ethernet, USB-gadget, +or localhost) and provides an interactive command line to control motors. + +For HolyBro Telemetry Radio: + ensure that radios are connected to Pi and Base Station + on Pi: sudo pppd /dev/ttyUSB0 57600 lock local noauth 192.168.100.11:192.168.100.10 + on base: sudo pppd /dev/ttyUSB0 57600 lock local noauth nodetach 192.168.100.10:192.168.100.11 + +Usage: + python3 motor_client.py # localhost:9000 (both sides local) + python3 motor_client.py 192.168.100.11 # Pi via static Ethernet/HaLow + python3 motor_client.py 192.168.100.11 9000 # explicit port + +Commands (once connected): + motors Set forward, turn, front, back [-1.0 to 1.0] + pwm Set one motor by raw PWM (1100–1900 µs) + stop Zero all motors + status Query current normalized speed for all motors + quit Shut down the server and exit + help Show this message + Ctrl+C or exit Disconnect and exit client only +""" + +import json +import socket +import sys +import readline # noqa: F401 — gives line editing / history for free + + +DEFAULT_HOST = "localhost" +DEFAULT_PORT = 9000 + + +def send_cmd(sock: socket.socket, obj: dict) -> dict | None: + """Send a JSON command and return the parsed response.""" + try: + msg = json.dumps(obj) + "\n" + sock.sendall(msg.encode()) + # Read until newline + buf = b"" + while not buf.endswith(b"\n"): + chunk = sock.recv(4096) + if not chunk: + return None + buf += chunk + return json.loads(buf.strip()) + except (OSError, json.JSONDecodeError) as e: + print(f"[ERR] {e}") + return None + + +def print_response(resp: dict | None): + if resp is None: + print("[ERR] No response (disconnected?)") + return + ok = resp.get("ok", False) + prefix = "[ OK]" if ok else "[ERR]" + + # Status response has speeds array (normalized -1.0 to 1.0) + if "speeds" in resp: + speeds = resp["speeds"] + labels = ["forward", "turn ", "front ", "back "] + print(f"{prefix} Motor speeds (normalized):") + for i, (label, speed) in enumerate(zip(labels, speeds)): + bar_len = int(abs(speed) * 20) + direction = "►" if speed >= 0 else "◄" + bar = direction * bar_len + print(f" [{i}] {label}: {speed:+.3f} {bar}") + else: + print(f"{prefix} {resp.get('msg', resp)}") + + +def print_help(): + print(""" +Commands: + motors Normalized values, -1.0 to 1.0 + fwd = forward/reverse thruster + turn = yaw thruster + front = front vertical thruster + back = back vertical thruster + + pwm Raw PWM for one motor (1100–1900 µs) + 1500 = neutral/stop + 1900 = full forward + 1100 = full reverse + + stop Stop all motors + status Show current normalized speed for all motors + quit Shut down the server then exit + help Show this message + exit / Ctrl+C Disconnect client (server keeps running) +""") + + +def main(): + host = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_HOST + port = int(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_PORT + + print(f"[CLIENT] Connecting to {host}:{port} ...") + try: + sock = socket.create_connection((host, port), timeout=5) + sock.settimeout(None) # blocking after connect + except OSError as e: + print(f"[ERR] Could not connect: {e}") + sys.exit(1) + + # Read the greeting + buf = b"" + while not buf.endswith(b"\n"): + buf += sock.recv(4096) + greeting = json.loads(buf.strip()) + print_response(greeting) + print_help() + + try: + while True: + try: + raw = input("motor> ").strip() + except EOFError: + break + + if not raw: + continue + + parts = raw.split() + cmd = parts[0].lower() + + if cmd in ("exit", "disconnect"): + print("[CLIENT] Disconnecting (server still running).") + break + + elif cmd == "help": + print_help() + + elif cmd == "stop": + resp = send_cmd(sock, {"command": "stop"}) + print_response(resp) + + elif cmd == "status": + resp = send_cmd(sock, {"command": "status"}) + print_response(resp) + + elif cmd == "quit": + resp = send_cmd(sock, {"command": "quit"}) + print_response(resp) + break + + elif cmd == "motors": + if len(parts) != 5: + print("[ERR] Usage: motors ") + continue + try: + vals = [float(p) for p in parts[1:]] + except ValueError: + print("[ERR] Values must be floats in [-1.0, 1.0]") + continue + if any(abs(v) > 1.0 for v in vals): + print("[WARN] Values outside [-1, 1] will be clamped by the server.") + resp = send_cmd(sock, {"command": "motors", "values": vals}) + print_response(resp) + + elif cmd == "pwm": + if len(parts) != 3: + print("[ERR] Usage: pwm ") + continue + try: + idx = int(parts[1]) + val = int(parts[2]) + except ValueError: + print("[ERR] Index and value must be integers.") + continue + resp = send_cmd(sock, {"command": "pwm", "motor": idx, "value": val}) + print_response(resp) + + else: + print(f"[ERR] Unknown command '{cmd}'. Type 'help' for options.") + + except KeyboardInterrupt: + print("\n[CLIENT] Interrupted.") + finally: + sock.close() + print("[CLIENT] Connection closed.") + + +if __name__ == "__main__": + main() diff --git a/src/.gitignore b/src/.gitignore index 07ed706..5dd7ff8 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1,5 @@ -build/* \ No newline at end of file +build/* +CMakeFiles/* +CMakeCache.txt +.cache +.gazebo diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 72fecaa..257b6f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,11 +5,22 @@ project(neptune C CXX) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() +find_package(Threads REQUIRED) #required for pthread_setaffinity_np / CPU_SET (GNU extensions) add_compile_definitions(_GNU_SOURCE) add_executable(neptune main.cpp) +add_executable(neptune_sim sim_main.cpp) +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + #BUILD_SIMULATION option #this isn't a physics engine its to mimic motor.py to simulate the motor @@ -38,39 +49,103 @@ else() set(LGPIO_LINK_LIBS "") endif() -#create motor library (just header file) -add_library(motor INTERFACE) -target_include_directories(motor INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/drivers) -#add lgpio include directory to motor target + +# Eigen(linear algebra lib) +find_package(Eigen3 REQUIRED) + +target_include_directories(neptune PRIVATE ${EIGEN3_INCLUDE_DIRS}) +target_include_directories(neptune_sim PRIVATE ${EIGEN3_INCLUDE_DIRS}) + +link_directories(drivers) +add_subdirectory(drivers) +add_subdirectory(types) +add_subdirectory(states) + +target_link_libraries(motors ${LGPIO_LINK_LIBS}) + +#main target +target_include_directories(motors PRIVATE ${LGPIO_INCLUDE_DIR}) +target_link_libraries(neptune_sim motors) +target_link_libraries(neptune motors) + +target_link_libraries(neptune_sim gps_drivers) +target_link_libraries(neptune gps_drivers) + + +# Everything below here is not part of the main target + +#Testing +enable_testing() + +add_executable( + thruster_test + tests/thruster.cc +) + +target_link_libraries(thruster_test + GTest::gtest_main + motors +) + +include(GoogleTest) +gtest_discover_tests(thruster_test) + +add_library(thruster STATIC + drivers/motors/thruster.cpp + drivers/motors/motor.cpp +) +target_include_directories(thruster PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/drivers +) if(NOT BUILD_SIMULATION) - target_include_directories(motor INTERFACE ${LGPIO_INCLUDE_DIR}) - target_link_libraries(motor INTERFACE ${LGPIO_LINK_LIBS}) + target_link_libraries(thruster PUBLIC ${LGPIO_LINK_LIBS}) + target_include_directories(thruster PUBLIC ${LGPIO_INCLUDE_DIR}) endif() -#need pthreads for RT thread -find_package(Threads REQUIRED) +add_library(sim_motor STATIC + drivers/motors/motor.cpp +) +target_include_directories(sim_motor PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/drivers +) -#executable for testing motor(separate from library) -add_executable(motor_test drivers/main.cpp) -target_link_libraries(motor_test motor) -target_link_libraries(motor_test Threads::Threads) +add_executable(motor_server + drivers/server/motor_server.cpp +) -#executable for pool test -add_executable(pool_test drivers/pool_test.cpp) -target_link_libraries(pool_test motor) -target_link_libraries(pool_test Threads::Threads) +target_include_directories(motor_server PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/comms + ${CMAKE_CURRENT_SOURCE_DIR}/drivers +) -#define BUILD SIMULATION -if(BUILD_SIMULATION) - target_compile_definitions(motor_test PRIVATE BUILD_SIMULATION) - target_compile_definitions(pool_test PRIVATE BUILD_SIMULATION) -endif() + +target_link_libraries(motor_server PRIVATE Threads::Threads) if(NOT BUILD_SIMULATION) - target_link_libraries(motor_test ${LGPIO_LINK_LIBS}) - target_link_libraries(pool_test ${LGPIO_LINK_LIBS}) + target_link_libraries(motor_server PRIVATE thruster) + target_include_directories(motor_server PRIVATE ${LGPIO_INCLUDE_DIR}) +else() + target_link_libraries(motor_server PRIVATE sim_motor) + target_compile_definitions(motor_server PRIVATE BUILD_SIMULATION) endif() -#to use the motor library elsewhere -#target_link_libraries(your_target motor) + +#linter +find_program(PIPX NAMES "pipx") +if(PIPX) + add_custom_target( + lint + COMMAND ${PIPX} run pre-commit run + + COMMENT "Runing precommit checks" + ) + add_custom_target( + lint_all + COMMAND ${PIPX} run pre-commit run --all-files + + COMMENT "Runing precommit checks" + ) +else() +message(WARNING "Failed to find pipx for precommit checks") +endif() diff --git a/src/comms/tcp_server.hpp b/src/comms/tcp_server.hpp new file mode 100644 index 0000000..4b495ad --- /dev/null +++ b/src/comms/tcp_server.hpp @@ -0,0 +1,136 @@ +#pragma once + +/** + * @file tcp_server.hpp + * @brief Minimal single-client TCP server using POSIX sockets. + * + * Accepts one connection at a time. Each call to recv_line() blocks until a + * newline-terminated message arrives. The server automatically re-accepts after + * a client disconnects. + * + * No external dependencies — POSIX only. + * + * For HolyBro Telemetry Radio: + ensure that radios are connected to Pi and Base Station + on Pi: sudo pppd /dev/ttyUSB0 57600 lock local noauth + 192.168.100.11:192.168.100.10 on base: sudo pppd /dev/ttyUSB0 57600 lock local + noauth nodetach 192.168.100.10:192.168.100.11 + + IPs should be ordered relative to the device you are running the command on + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +class TcpServer { +public: + explicit TcpServer(int port) : port_(port), server_fd_(-1), client_fd_(-1) {} + + ~TcpServer() { close_all(); } + + /** + * Bind and start listening. Call once before the main loop. + */ + void start() { + server_fd_ = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd_ < 0) + throw std::runtime_error("socket() failed: " + err()); + + // Allow rapid restart without "Address already in use" + int opt = 1; + setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(static_cast(port_)); + + if (bind(server_fd_, reinterpret_cast(&addr), sizeof(addr)) < 0) + throw std::runtime_error("bind() failed: " + err()); + + if (listen(server_fd_, 1) < 0) + throw std::runtime_error("listen() failed: " + err()); + + std::cout << "[TCP] Listening on port " << port_ << std::endl; + } + + /** + * Block until a client connects. Closes any existing client first. + */ + void accept_client() { + if (client_fd_ >= 0) { + close(client_fd_); + client_fd_ = -1; + } + sockaddr_in client_addr{}; + socklen_t len = sizeof(client_addr); + std::cout << "[TCP] Waiting for connection..." << std::endl; + client_fd_ = + accept(server_fd_, reinterpret_cast(&client_addr), &len); + if (client_fd_ < 0) + throw std::runtime_error("accept() failed: " + err()); + + char ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip)); + std::cout << "[TCP] Client connected from " << ip << std::endl; + } + + /** + * Read one newline-terminated line from the current client. + * Returns false if the client disconnected (caller should call accept_client + * again). + */ + bool recv_line(std::string &out) { + out.clear(); + char ch; + while (true) { + ssize_t n = recv(client_fd_, &ch, 1, 0); + if (n <= 0) { + std::cout << "[TCP] Client disconnected." << std::endl; + return false; + } + if (ch == '\n') + return true; + if (ch != '\r') + out += ch; + } + } + + /** + * Send a newline-terminated string to the current client. + * Returns false if the send failed. + */ + bool send_line(const std::string &msg) { + std::string out = msg + "\n"; + ssize_t n = send(client_fd_, out.c_str(), out.size(), MSG_NOSIGNAL); + return n == static_cast(out.size()); + } + + bool has_client() const { return client_fd_ >= 0; } + +private: + int port_; + int server_fd_; + int client_fd_; + + void close_all() { + if (client_fd_ >= 0) { + close(client_fd_); + client_fd_ = -1; + } + if (server_fd_ >= 0) { + close(server_fd_); + server_fd_ = -1; + } + } + + static std::string err() { return std::string(strerror(errno)); } +}; diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt new file mode 100644 index 0000000..5c4a757 --- /dev/null +++ b/src/drivers/CMakeLists.txt @@ -0,0 +1,17 @@ + +add_library(motors motors/motor.cpp motors/thruster.cpp) + + +find_package(PkgConfig REQUIRED) +pkg_check_modules(GPSD REQUIRED libgps) +add_library(gps_drivers INTERFACE) +target_include_directories(gps_drivers INTERFACE ${GPSD_INCLUDE_DIRS}) + +target_include_directories(motors PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/motors) +target_include_directories(gps_drivers INTERFACE d ${CMAKE_CURRENT_SOURCE_DIR}/gps) +target_link_libraries(gps_drivers INTERFACE ${GPSD_LIBRARIES}) + + + +add_library(imu INTERFACE) +target_include_directories(imu INTERFACE d ${CMAKE_CURRENT_SOURCE_DIR}/imu) diff --git a/src/drivers/common/sim.hpp b/src/drivers/common/sim.hpp new file mode 100644 index 0000000..6c369c1 --- /dev/null +++ b/src/drivers/common/sim.hpp @@ -0,0 +1,8 @@ +#pragma once +#include + +#include + + +const std::string SIM_ADDR = "http://host.docker.internal:6767"; +inline httplib::Client *sim_connection = nullptr; diff --git a/src/drivers/gps/gps.hpp b/src/drivers/gps/gps.hpp new file mode 100644 index 0000000..3558fa7 --- /dev/null +++ b/src/drivers/gps/gps.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "../../types/GPSCoordinate.hpp" +#include +/** + * An abstract class that represents a gps that can gather cordinate location + */ +class GPS { +public: + + virtual bool await_lock(int interval, int tries) { + std::cout << "Dont call this" << std::endl; + throw std::logic_error("Tried to use a gps parent class"); + } + virtual GPSCoordinate location() { + std::cout << "Dont call this" << std::endl; + throw std::logic_error("Tried to use a gps parent class"); + } +}; diff --git a/src/drivers/gps/hardware_gps.hpp b/src/drivers/gps/hardware_gps.hpp new file mode 100644 index 0000000..c9b1599 --- /dev/null +++ b/src/drivers/gps/hardware_gps.hpp @@ -0,0 +1,99 @@ +#pragma once +#include "gps.hpp" +#include "../../types/GPSCoordinate.hpp" +#include +#include +#include +#include + +/** + * Note - its likely that only 1 gps can function at a time + * Reprsents a GPS attached by usb to the pi. Uses the gpsd daemon to gather information + */ +class HardwareGPS : public GPS { +public: + ~HardwareGPS() { delete this->gps_rec; } + + + /** + * Get the current GPS location once a valid fix is obtained. + * No alt is gotten. + * Maybe block for awhile + * @return GPSCoordinate containing latitude and longitude. + */ + GPSCoordinate location() override { + while (true) { + struct gps_data_t *data; + + if (!(this->gps_rec->waiting(10000))) { + continue; + } + + if ((data = this->gps_rec->read()) == NULL) { + std::cerr << "Read error." << std::endl; + // TODO - what to do here? + throw std::runtime_error("Failed to read gps"); + } else { + if (data->fix.mode >= MODE_2D) { + // Connection gotten + GPSCoordinate out = GPSCoordinate(); + out.latitude = data->fix.latitude; + out.longitude = data->fix.longitude; + return out; + } else { + //TODO - order + std::cout << "No gps fix yet" << std::endl; + } + } + } + } + HardwareGPS(std::string location, const char *gpsd_port = DEFAULT_GPSD_PORT) { + std::string out = "gpsd " + location; + int val = system(out.c_str()); + if (val != 0) { + throw new std::runtime_error("GPS failed to connect - gpsd didn't start"); + } + std::cout << "GPS loaded" << std::endl; + this->gps_rec = new gpsmm("localhost", gpsd_port); + } + /** + * Awaits a gps lock. Note that a lock could be lost if the gps is blocked again, in which case the next location call will re-obtain the lock + * @param interval Wait time between tries + * @param tries Number of times to try before returning false + * @returns the fix status + */ + bool await_lock(int interval, int tries) override { + if (this->gps_rec->stream(WATCH_ENABLE | WATCH_JSON) == NULL) { + std::cerr << "No GPSD running." << std::endl; + return 1; + } + int attempt = 0; + while (true) { + struct gps_data_t *data; + + if (!(this->gps_rec->waiting(interval * 10000))) { + continue; + } + + if ((data = this->gps_rec->read()) == NULL) { + std::cerr << "Read error." << std::endl; + // TODO - what to do here? + return false; + } else { + if (data->fix.mode >= MODE_2D) { + // Connection gotten + return true; + } else { + if (++attempt > tries) { + return false; + } + std::cout << "No gps fix" << std::endl; + } + } + } + return true; + } + +private: + gpsmm *gps_rec; +}; diff --git a/src/drivers/gps/simulated_gps.hpp b/src/drivers/gps/simulated_gps.hpp new file mode 100644 index 0000000..102f874 --- /dev/null +++ b/src/drivers/gps/simulated_gps.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "../../types/GPSCoordinate.hpp" +#include "gps.hpp" +#include +#include "../common/sim.hpp" + +class Simulated_GPS : public GPS { +public: + + Simulated_GPS() { + if (sim_connection == 0) { + sim_connection = new httplib::Client(SIM_ADDR); + httplib::Result out = sim_connection->Get("/heartbeat"); + if (out == nullptr) { + throw std::runtime_error("No simulation connection found"); + } + std::cout << "Status code from sim:" << out->status << std::endl; + } + } + bool await_lock(int interval, int tries) override { return true; } + GPSCoordinate location() override { + auto out = sim_connection->Get("/gps"); + if (out == nullptr) { + std::cout << "Error getting GPS sim info" << std::endl; + //throw? + } + GPSCoordinate s = GPSCoordinate(); + sscanf(out->body.c_str(), "[%lf,%lf,", &s.latitude, &s.longitude); + return s; + } +}; diff --git a/src/drivers/imu/compass.hpp b/src/drivers/imu/compass.hpp new file mode 100644 index 0000000..39c8988 --- /dev/null +++ b/src/drivers/imu/compass.hpp @@ -0,0 +1,22 @@ +#pragma once +#include + +#include + +using namespace Eigen; +/* +TODO: this is a placeholder until the commit w/ imu gets made + +*/ +class Compass +{ +public: + /** + * Filler sim version, should convert to abstract once we are done with the IMU + * Returns the true north vector in the context of the world + */ + std::array get() + { + return {0,0,0}; + } +}; diff --git a/src/drivers/motors/motor.cpp b/src/drivers/motors/motor.cpp new file mode 100644 index 0000000..62222eb --- /dev/null +++ b/src/drivers/motors/motor.cpp @@ -0,0 +1,16 @@ +#include "motor.hpp" +#include + +// All of these should probably not be here or throw execptions but I need to +// put them here for the linker +int Motor::setSpeed(float speed) { + std::cout << "Dont call me" << std::endl; + return 0; +} + +int Motor::setFrequency(float cycle) { + std::cout << "Dont call me" << std::endl; + return 0; +} + +void Motor::armMotor() {} diff --git a/src/drivers/motors/motor.hpp b/src/drivers/motors/motor.hpp new file mode 100644 index 0000000..a82f76c --- /dev/null +++ b/src/drivers/motors/motor.hpp @@ -0,0 +1,38 @@ +#pragma once +/** + * @brief The abstract class that represents a motor on the robot + * + */ +class Motor { +public: + /** + * @brief sets the motor to run at speed until this or another method is + * called + * @param speed A value between -1.0 and 1.0, with -1.0 being max reverse + * thrust, 0.0 being still, and 1.0 being max forward thrust + * @return The return code. Negative indicates errors, otherwise the meaning + * depends on the impelmenting class + */ + virtual int setSpeed(float speed); + /** + * @brief Sets the frequency of the motor pwm, ie how quick cycles are sent to + * the ESC + * @param cycle The value, in hz, for the new cycle + * @return The return code. Negative indicates errors, otherwise the meaning + * depends on the impelmenting class + */ + virtual int setFrequency(float cycle); + + /** + * @brief arms the motor. Likely a blocking method for a few seconds + */ + virtual void armMotor(); + float speed; + float cycle; + +protected: + Motor(int speed, int cycle) : speed(speed), cycle(cycle) {}; + +protected: + Motor() {}; +}; diff --git a/src/drivers/motors/sim_motor.hpp b/src/drivers/motors/sim_motor.hpp new file mode 100644 index 0000000..9108b15 --- /dev/null +++ b/src/drivers/motors/sim_motor.hpp @@ -0,0 +1,76 @@ +#pragma once +#include "motor.hpp" +#include +#include "../../types/MotorLocation.hpp" + +class SimulatedMotor : public Motor +{ + +public: + MotorLocation ml; + bool armed; + + SimulatedMotor(MotorLocation ml, int speed, + float cycle) : Motor(speed, cycle) + { + if (sim_connection == 0) + { + sim_connection = new httplib::Client(SIM_ADDR); + httplib::Result out = sim_connection->Get("/heartbeat"); + if (out == nullptr) + { + throw std::runtime_error("No simulation connection found"); + } + std::cout << "Status code from sim:" << out->status << std::endl; + } + this->ml = ml; + } + int setSpeed(float speed) override { + if (!this->armed) { + throw std::logic_error("Tried to set the speed of an unarmed motor"); + } + this->speed = speed; + // In the long run we should add a json lib + std::ostringstream oss; + oss << "{\"speed\":" << speed << "}"; + std::string s = oss.str(); + auto out = sim_connection->Post( + this->GetMotorPath().c_str(), s.c_str(), "application/json"); + if (out == nullptr) { + std::cout << "Error sending sim info" << std::endl; + } + + return 0; +} +int setFrequency(float cycle) override { + // idk if this will be called at all + return 0; +} + +void armMotor()override { this->armed = true; } + + +private: + std::string GetMotorPath() + { + switch (this->ml) + { + case FrontLeft: + return "/motor/top_left/set"; + break; + case FrontRight: + return "/motor/top_right/set"; + break; + case BackLeft: + return "/motor/bottom_left/set"; + break; + case BackRight: + return "/motor/bottom_right/set"; + break; + default: + // Error? + break; + } + return ""; + } +}; diff --git a/src/drivers/motors/thruster.cpp b/src/drivers/motors/thruster.cpp new file mode 100644 index 0000000..1143043 --- /dev/null +++ b/src/drivers/motors/thruster.cpp @@ -0,0 +1,55 @@ +#include "thruster.hpp" +#include +#include +#include +#include + +Thruster::Thruster(int pin, int handle, int speed /*= 0*/, int cycle /*= 50*/) + : pin(pin), handle(handle), Motor(speed, cycle) { + if (lgGpioClaimOutput(handle, 0, pin, 0) != LG_OKAY) { + throw new std::runtime_error("Failed to claim a pin for a motor"); + } +} + +Thruster::~Thruster() { + if (lgGpioFree(handle, pin) < 0) { + std::cerr << "Error code freeing pin " << pin << std::endl; + } +} +float Thruster::getDutyCycle() { + // Convert to nanoseconds + float cycle_width = 1 / (this->cycle * (1e-6)); + float goal_interval = ((MAX_PWM_DIST * this->speed) + STOP_PWM); + return (goal_interval / cycle_width) * 100; +} + +int Thruster::sendMotorPWM() { + if (!this->armed) { + throw new std::logic_error( + "Tried to affect motor before arming. Always ensure motors are armed " + "before activating them"); + } + return lgTxPwm(this->handle, this->pin, this->cycle, getDutyCycle(), 0, 0); +} + +int Thruster::setSpeed(float newSpeed) { + this->speed = newSpeed; + if (newSpeed < -1.0 || newSpeed > 1.0) { + // TODO clamp here instead ? + throw new std::logic_error("Motor Speed out of range"); + } + return sendMotorPWM(); +} + +int Thruster::setFrequency(float cycle) { + this->cycle = cycle; + return sendMotorPWM(); +} +void Thruster::armMotor() { + this->speed = 0.0; + this->armed = true; + if (this->sendMotorPWM() < 0) { + // Maybe error here? + } + lguSleep(ARM_SECONDS); +} diff --git a/src/drivers/motors/thruster.hpp b/src/drivers/motors/thruster.hpp new file mode 100644 index 0000000..1fa2f1f --- /dev/null +++ b/src/drivers/motors/thruster.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "motor.hpp" +#include +/** + * @brief Represents one of the blue robotics thrusters on the robot + */ +class Thruster : public Motor { +public: + int pin; + int handle; + bool armed; + + Thruster(int pin, int handle, int speed = 0, int cycle = 50); + ~Thruster(); + int setSpeed(float speed) override; + + int setFrequency(float cycle) override; + + void armMotor() override; + /** + * @brief gets the duty cycle to send to the motor in order to move the motor + * at the current speed + */ + float getDutyCycle(); + +protected: + explicit Thruster() {}; + +private: + const int STOP_PWM = 1500; + const int MAX_PWM_DIST = 400; + const int ARM_SECONDS = 3; // I dont think this actually has to be 7 seconds, + // like 3 works but better safe than sorry + + /** + * @brief updates the pwm values being sent. Must be called after any updates + * to class variables that will affect this value + * @return the return value of the lgTxPwm call + */ + int sendMotorPWM(); +}; diff --git a/src/drivers/server/motor_server.cpp b/src/drivers/server/motor_server.cpp new file mode 100644 index 0000000..f1ad075 --- /dev/null +++ b/src/drivers/server/motor_server.cpp @@ -0,0 +1,389 @@ +/** + * @file motor_server.cpp + * @brief Remote motor control server over TCP. + * + * Uses the Thruster class (real hardware) or SimulatedMotor (BUILD_SIMULATION). + * + * Motor API: + * - Thruster(int pin, int handle) -- constructor claims pin; destructor + * frees it + * - motor.armMotor() -- blocks ~3 s, sets neutral + * - motor.setSpeed(float speed) -- -1.0 (full reverse) .. 0.0 (stop) + * .. 1.0 (full forward) + * + * ## Protocol (newline-terminated JSON) + * + * Set all four motors (normalized -1.0 to 1.0): + * {"command":"motors","values":[0.5, 0.0, -0.3, 0.2]} + * + * Set one motor by raw PWM microseconds (1100-1900): + * {"command":"pwm","motor":0,"value":1600} + * (converted internally to normalized speed before calling setSpeed) + * + * Stop all motors: + * {"command":"stop"} + * + * Query current normalized speeds: + * {"command":"status"} + * + * Graceful server shutdown: + * {"command":"quit"} + * + * ## Build & run on Pi + * cmake -B build && cmake --build build --target motor_server + * sudo ./build/motor_server [port] (default 9000) + * + * ## Simulate (routes motor commands to Gazebo sim over HTTP) + * cmake -B build -DBUILD_SIMULATION=ON + * ./build/motor_server + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef BUILD_SIMULATION +#include "../motors/thruster.hpp" +#include +#else +#include "../motors/sim_motor.hpp" +#endif + +#include "../../comms/tcp_server.hpp" +// --------------------------------------------------------------------------- +// Constants -- mirrors Thruster internals for the "pwm" command converter +// --------------------------------------------------------------------------- +static constexpr float STOP_SPEED = 0.0f; +static constexpr int NEUTRAL_PWM = 1500; +static constexpr int MIN_PWM = 1100; +static constexpr int MAX_PWM = 1900; +static constexpr int MAX_PWM_DIST = 400; // microseconds from neutral to limit + +// --------------------------------------------------------------------------- +// Motor GPIO pin assignments (matches pool_test.cpp / motor_controller.py) +// --------------------------------------------------------------------------- +static constexpr int NUM_MOTORS = 4; +static constexpr int MOTOR_PINS[NUM_MOTORS] = { + 4, // forward (FORWARD_GPIO_PIN) + 11, // turn (TURN_GPIO_PIN) + 18, // front (FRONT_GPIO_PIN) + 24, // back (BACK_GPIO_PIN) +}; + +#ifdef BUILD_SIMULATION +// Map each motor slot to a SimulatedMotor::MotorLocation for the HTTP sim API. +// Mapping is approximate -- adjust once the sim model is finalised. +static constexpr SimulatedMotor::MotorLocation MOTOR_LOCATIONS[NUM_MOTORS] = { + SimulatedMotor::FrontLeft, // motor 0 - forward + SimulatedMotor::FrontRight, // motor 1 - turn + SimulatedMotor::BackLeft, // motor 2 - front + SimulatedMotor::BackRight, // motor 3 - back +}; +#endif + +// Current normalized speed for each motor (source of truth for "status" +// replies) +static std::array g_speeds = {STOP_SPEED, STOP_SPEED, + STOP_SPEED, STOP_SPEED}; + +// --------------------------------------------------------------------------- +// Minimal JSON helpers (no external lib) +// --------------------------------------------------------------------------- +static std::string make_response(bool ok, const std::string &msg) { + std::ostringstream ss; + ss << "{\"ok\":" << (ok ? "true" : "false") << ",\"msg\":\"" << msg << "\"}"; + return ss.str(); +} + +static std::string make_status(const std::array &speeds) { + std::ostringstream ss; + ss << "{\"ok\":true,\"speeds\":[" << speeds[0] << "," << speeds[1] << "," + << speeds[2] << "," << speeds[3] << "]}"; + return ss.str(); +} + +static std::string parse_string(const std::string &json, + const std::string &key) { + std::string search = "\"" + key + "\""; + size_t pos = json.find(search); + if (pos == std::string::npos) + return ""; + pos = json.find(':', pos); + if (pos == std::string::npos) + return ""; + pos = json.find('"', pos); + if (pos == std::string::npos) + return ""; + size_t end = json.find('"', pos + 1); + if (end == std::string::npos) + return ""; + return json.substr(pos + 1, end - pos - 1); +} + +static bool parse_int(const std::string &json, const std::string &key, + int &out) { + std::string search = "\"" + key + "\""; + size_t pos = json.find(search); + if (pos == std::string::npos) + return false; + pos = json.find(':', pos); + if (pos == std::string::npos) + return false; + pos++; + while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t')) + pos++; + try { + size_t consumed = 0; + out = std::stoi(json.substr(pos), &consumed); + return consumed > 0; + } catch (...) { + return false; + } +} + +static bool parse_float_array4(const std::string &json, const std::string &key, + std::array &out) { + std::string search = "\"" + key + "\""; + size_t pos = json.find(search); + if (pos == std::string::npos) + return false; + pos = json.find('[', pos); + if (pos == std::string::npos) + return false; + pos++; + for (int i = 0; i < 4; i++) { + while (pos < json.size() && + (json[pos] == ' ' || json[pos] == '\t' || json[pos] == ',')) + pos++; + if (pos >= json.size() || json[pos] == ']') + return false; + try { + size_t consumed = 0; + out[i] = std::stof(json.substr(pos), &consumed); + pos += consumed; + } catch (...) { + return false; + } + } + return true; +} + +// Convert PWM microseconds (1100-1900) to normalized speed (-1.0 to 1.0). +// Mirrors Thruster::getDutyCycle() in reverse. +static float pwm_to_speed(int pwm) { + return static_cast(pwm - NEUTRAL_PWM) / + static_cast(MAX_PWM_DIST); +} + +static void zero_motors(std::array &motors) { + for (int i = 0; i < NUM_MOTORS; i++) { + motors[i]->setSpeed(STOP_SPEED); + g_speeds[i] = STOP_SPEED; + } +} + +// --------------------------------------------------------------------------- +// Signal handling +// --------------------------------------------------------------------------- +static volatile bool g_running = true; +static void handle_signal(int) { g_running = false; } + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- +int main(int argc, char *argv[]) { + int port = 9000; + if (argc >= 2) { + try { + port = std::stoi(argv[1]); + } catch (...) { + std::cerr << "Usage: " << argv[0] << " [port]\n"; + return 1; + } + } + + std::signal(SIGINT, handle_signal); + std::signal(SIGTERM, handle_signal); + std::signal(SIGPIPE, SIG_IGN); + + // ----------------------------------------------------------------------- + // Open lgpio chip (real hardware only) + // ----------------------------------------------------------------------- +#ifndef BUILD_SIMULATION + int handle = lgGpiochipOpen(0); + if (handle < 0) { + std::cerr << "[INIT] lgGpiochipOpen failed: " << handle << std::endl; + return 1; + } + std::cout << "[INIT] GPIO chip opened." << std::endl; +#else + std::cout << "[SIM] Simulation mode -- routing to Gazebo sim over HTTP." + << std::endl; +#endif + + // ----------------------------------------------------------------------- + // Construct motors + // + // Thruster: constructor claims the GPIO pin; destructor frees it. + // SimulatedMotor: constructor connects to the HTTP sim server. + // Both throw on failure, so we catch here and bail cleanly. + // ----------------------------------------------------------------------- + std::array, NUM_MOTORS> motor_storage; + + std::cout << "[INIT] Constructing motors..." << std::endl; + for (int i = 0; i < NUM_MOTORS; i++) { + try { +#ifndef BUILD_SIMULATION + motor_storage[i] = std::make_unique(MOTOR_PINS[i], handle); +#else + motor_storage[i] = std::make_unique(MOTOR_LOCATIONS[i]); +#endif + } catch (const std::exception &e) { + std::cerr << "[INIT] Failed to construct motor " << i << ": " << e.what() + << std::endl; +#ifndef BUILD_SIMULATION + lgGpiochipClose(handle); +#endif + return 1; + } + } + + // Raw pointer array for convenience in helper functions + std::array motors = { + motor_storage[0].get(), motor_storage[1].get(), motor_storage[2].get(), + motor_storage[3].get()}; + + // ----------------------------------------------------------------------- + // Arm ESCs -- armMotor() sets neutral and blocks for the required delay + // ----------------------------------------------------------------------- + std::cout << "[INIT] Arming ESCs..." << std::endl; + for (auto *m : motors) + m->armMotor(); + std::cout << "[INIT] Motors ready." << std::endl; + + // ----------------------------------------------------------------------- + // TCP server + // ----------------------------------------------------------------------- + TcpServer server(port); + try { + server.start(); + } catch (const std::exception &e) { + std::cerr << "[TCP] " << e.what() << std::endl; +#ifndef BUILD_SIMULATION + lgGpiochipClose(handle); +#endif + return 1; + } + + // ----------------------------------------------------------------------- + // Main accept loop + // ----------------------------------------------------------------------- + while (g_running) { + try { + server.accept_client(); + } catch (const std::exception &e) { + std::cerr << "[TCP] accept failed: " << e.what() << std::endl; + break; + } + + server.send_line(make_response(true, "connected -- motor_server ready")); + + std::string line; + while (g_running && server.recv_line(line)) { + if (line.empty()) + continue; + std::cout << "[CMD] " << line << std::endl; + + std::string cmd = parse_string(line, "command"); + + if (cmd == "stop") { + zero_motors(motors); + server.send_line(make_response(true, "all motors stopped")); + + } else if (cmd == "motors") { + std::array vals{}; + if (!parse_float_array4(line, "values", vals)) { + server.send_line(make_response(false, "bad values array")); + continue; + } + bool ok = true; + for (int i = 0; i < NUM_MOTORS; i++) { + // Clamp to [-1, 1] before passing to setSpeed + float speed = vals[i]; + if (speed > 1.0f) + speed = 1.0f; + if (speed < -1.0f) + speed = -1.0f; + + if (motors[i]->setSpeed(speed) < 0) { + server.send_line(make_response(false, "motor " + std::to_string(i) + + " setSpeed failed")); + ok = false; + break; + } + g_speeds[i] = speed; + } + if (ok) + server.send_line(make_response(true, "motors updated")); + + } else if (cmd == "pwm") { + // Accept legacy PWM microseconds (1100-1900) and convert to speed + int motor_idx = -1, pwm_value = -1; + if (!parse_int(line, "motor", motor_idx) || + !parse_int(line, "value", pwm_value) || motor_idx < 0 || + motor_idx >= NUM_MOTORS || pwm_value < MIN_PWM || + pwm_value > MAX_PWM) { + server.send_line( + make_response(false, "bad args -- motor 0-3, value 1100-1900")); + continue; + } + float speed = pwm_to_speed(pwm_value); + if (motors[motor_idx]->setSpeed(speed) < 0) { + server.send_line(make_response(false, "setSpeed failed")); + continue; + } + g_speeds[motor_idx] = speed; + server.send_line( + make_response(true, "motor " + std::to_string(motor_idx) + " -> " + + std::to_string(pwm_value) + + " us" + " (speed " + + std::to_string(speed) + ")")); + + } else if (cmd == "status") { + server.send_line(make_status(g_speeds)); + + } else if (cmd == "quit") { + server.send_line(make_response(true, "shutting down")); + g_running = false; + break; + + } else { + server.send_line(make_response( + false, "unknown command: " + (cmd.empty() ? "(empty)" : cmd))); + } + } + + std::cout << "[SAFE] Client gone -- zeroing motors." << std::endl; + zero_motors(motors); + } + + // ----------------------------------------------------------------------- + // Cleanup + // Thruster destructors free GPIO pins automatically when motor_storage + // goes out of scope; no manual freePin() calls needed. + // ----------------------------------------------------------------------- + std::cout << "[SHUTDOWN] Stopping motors." << std::endl; + for (auto *m : motors) + m->setSpeed(STOP_SPEED); + + // motor_storage destructors run here, freeing all pins +#ifndef BUILD_SIMULATION + lgGpiochipClose(handle); +#endif + return 0; +} diff --git a/src/main.cpp b/src/main.cpp index c68b0a3..95363c4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,52 @@ // main.cpp +#include "CLI11.hpp" +#include "drivers/gps/hardware_gps.hpp" +#include "drivers/gps/gps.hpp" +#include "drivers/motors/motor.hpp" +#include "neptune.hpp" +#include "states/navigate.hpp" +#include "drivers/motors/thruster.hpp" +#include "states/menu.hpp" +#include "lgpio.h" #include +#include +#include +#include +void handle_quit(int signum){ + Neptune::instance->stop(); + exit(0); +} +int main(int argc, char **argv) +{ + CLI::App app; + std::string gps_port; + app.add_option("--gps", gps_port, + "The string path to the gps. Likely, /dev/ttyACM0") + ->required(); + + std::vector pins; + app.add_option("--motors", pins, "The 4 pins for motors (space delimited) in the form: front_left front_right back_left back_right"); + CLI11_PARSE(app, argc, argv); + GPS *gps = new HardwareGPS(gps_port); + //Hardcoded since I dont think this will ever change on the pi -int main() { - std::cout << "Hello World" << std::endl; - return 0; + if(pins.size() != 4){ + std::cout << "Wrong number of input pins for motors:" << pins.size() <<"\n"; + return 1; + } + int handle = lgGpiochipOpen(0); + Motor *front_left = new Thruster(pins[0], handle); + Motor *front_right = new Thruster(pins[1], handle); + Motor *back_left = new Thruster(pins[2], handle); + Motor *back_right = new Thruster(pins[3], handle); + Neptune *neptune = new Neptune(); + neptune->gps = gps; + neptune->back_left = back_left; + neptune->back_right = back_right; + neptune->front_left = front_left; + neptune->front_right = front_right; + neptune->compass = new Compass(); + neptune->state = new Menu(); + std::signal(SIGINT,handle_quit); + neptune->start(); } diff --git a/src/neptune.hpp b/src/neptune.hpp new file mode 100644 index 0000000..3da81d4 --- /dev/null +++ b/src/neptune.hpp @@ -0,0 +1,251 @@ +#pragma once +#include "drivers/motors/motor.hpp" +#include "drivers/gps/gps.hpp" + +#include "drivers/imu/compass.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "types/MotorLocation.hpp" +#include +#include + +#include "states/state.hpp" + +class Neptune +{ +public: + State *state; + inline static Neptune *instance = nullptr; + GPS *gps; + Motor *front_left; + Motor *front_right; + Motor *back_left; + Motor *back_right; + Motor *all_motors[4]; + Compass *compass; + /** + * Prints all of the sensor data to std out in a nice format + */ + void dispData() + { + // GPS + try + { + auto loc = gps->location(); + std::cout << loc << "\n"; + } + catch (...) + { + std::cout << "GPS Location: \n"; + } + // Compass + try + { + auto c = compass->get(); + std::cout << "Compass: [" << c[0] << ", " << c[1] << ", " << c[2] << "] Heading(rad): " << get_heading() << "\n"; + } + catch (...) + { + std::cout << "Compass: \n"; + } + // Motors + for (int i = 0; i < 4; ++i) + { + Motor *m = all_motors[i]; + if (m) + { + double s = 0; + try + { + s = m->speed; + } + catch (...) + { + } + std::cout << "Motor[" << i << "] speed: " << s << "\n"; + } + else + { + std::cout << "Motor[" << i << "]: \n"; + } + } + // State + if (state) + { + try + { + std::cout << "State tick(ms): " << typeid(state).name() << "\n"; + } + catch (...) + { + std::cout << "State: \n"; + } + } + else + { + std::cout << "State: \n"; + } + } + // Helper to power motors for forward movement + void forwards(double max = 0.75) + { + front_left->setSpeed(max); + front_right->setSpeed(max); + back_left->setSpeed(max); + back_right->setSpeed(max); + } + // Helper to power motors for backwards movement + void backwards(double max = 0.75) + { + front_left->setSpeed(-max); + front_right->setSpeed(-max); + back_left->setSpeed(-max); + back_right->setSpeed(-max); + } + + // Helper to halt all motors. Note this stops the motor, and does not account for drifting + void stop() + { + for (Motor *m : all_motors) + { + m->setSpeed(0); + } + } + + /** + * Gets the heading, in radians of the robot. 0 indicates north, pi indicates south + * Heading is a **counterclockwise** rotation about the upward axsis + * @return The heading + */ + double get_heading() + { + auto c = compass->get(); // north in the format: [nx, ny, nz] of the compass + // We know true north in ENU is [0,0,1], so we need to measure our north directions difference from that + // Ignore z(upwards) cause if we get rotated too far upward it impacts nav we're fucked anyway. Thus our axis of rotation is +z (0,0,1) + + // Flip x/y here because atan will give from x axsis but we want from +y + double out = atan2(c[1], c[0]); + + return out; + } + const double MAX_MOTOR_ACTIVATION = 0.75; // TODO move to some config or smth + /** + * Activates the motors to travel towards the target location. + * Uses MAX_MOTOR_ACTIVATION to scale the output + * TODO change tolerance, 5.0 is crazy high for gps coords + * @return if the location is reached + */ + bool travel(GPSCoordinate goal, double dist_tolerance = 1.0, double rot_tolerance = (0.174533 / 2)) // 5 deg + { + Vector2d dif = goal - gps->location(); + if (dif.norm() < dist_tolerance) + { + stop(); + return true; + } + // Find a way to move along dif with our motors + + double heading = get_heading(); + double goal_heading = atan2(dif[1], dif[0]); + double angle_error = atan2( + sin(goal_heading - heading), + cos(goal_heading - heading)); + + std::cout << "Heading: " << heading << "goal" << goal_heading << "dif: " << angle_error << std::endl; + if (angle_error < rot_tolerance) + { + // move forward + forwards(); + } + else + { + // rotate self + rotate((angle_error < 0), 0.3); + } + return false; + } + /** + * Starts neptune. This method will functionally take over the thread it is called from + */ + void start() + { + Neptune::instance = this; + //gps->await_lock(5, 500); + std::cout << "Arming motors" << std::endl; + front_left->armMotor(); + front_right->armMotor(); + back_left->armMotor(); + back_right->armMotor(); + + all_motors[0] = front_left; + all_motors[1] = front_right; + all_motors[2] = back_left; + all_motors[3] = back_right; + + while (true) + { + std::this_thread::sleep_for(this->state->tick()); + } + } + // Rotates + void rotate(bool clockwise, double max = 0.75) + { + if (clockwise) + { + // rights on backwards, lefts on forward + front_left->setSpeed(max); + front_right->setSpeed(-max); + back_left->setSpeed(max); + back_right->setSpeed(-max); + } + else + { + // inverse + front_left->setSpeed(-max); + front_right->setSpeed(max); + back_left->setSpeed(-max); + back_right->setSpeed(max); + } + } + +private: + // Helper to build a matrix of the motors thrust vectors, rotate by the heading + Matrix motor_mat() + { + Matrix out; + std::cout << "heading" << get_heading() << std::endl; + Eigen::Rotation2D rot(get_heading()); + for (int m = static_cast(MotorLocation::FrontRight); m <= static_cast(MotorLocation::BackLeft); m++) + { + out.col(m) = rot * thrust_vector(static_cast(m)); // rotate by heading + } + return out; + } + // Helper to activate motors based on a vector + bool set_motors(Vector4d v) + { + for (int m = static_cast(MotorLocation::FrontRight); m <= static_cast(MotorLocation::BackLeft); m++) + { + all_motors[m]->setSpeed(v[m]); + } + return true; // TODO error checking? + } + // Wraps an angle to [0, 2pi] + double normalizeToPositiveAngle(double angle) + { + while (angle < 0) + { + angle += M_PI * 2; + } + while (angle > 2 * M_PI) + { + angle -= M_PI * 2; + } + return angle; + } +}; diff --git a/src/sim_main.cpp b/src/sim_main.cpp new file mode 100644 index 0000000..36d32a6 --- /dev/null +++ b/src/sim_main.cpp @@ -0,0 +1,34 @@ +#include "drivers/gps/gps.hpp" +#include "drivers/gps/simulated_gps.hpp" +#include "drivers/motors/sim_motor.hpp" +#include "drivers/motors/motor.hpp" +#include "types/MotorLocation.hpp" +#include "neptune.hpp" +#include "states/navigate.hpp" +#include "states/menu.hpp" +#include +#include +#include + +int main() + +{ + sim_connection = new httplib::Client(SIM_ADDR); + GPS *g = new Simulated_GPS(); + + Motor *front_left = new SimulatedMotor(MotorLocation::FrontLeft, 0, 50); + Motor *front_right = new SimulatedMotor(MotorLocation::FrontRight, 0, 50); + Motor *back_left = new SimulatedMotor(MotorLocation::BackLeft, 0, 50); + Motor *back_right = new SimulatedMotor(MotorLocation::BackRight, 0, 50); + Neptune *neptune = new Neptune(); + neptune->gps = g; + neptune->back_left = back_left; + neptune->back_right = back_right; + neptune->front_left = front_left; + neptune->front_right = front_right; + neptune->compass = new Compass(); + neptune->state = new Menu(); + neptune->start(); + + return 0; +} diff --git a/src/states/CMakeLists.txt b/src/states/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/states/idle.hpp b/src/states/idle.hpp new file mode 100644 index 0000000..6ecca7f --- /dev/null +++ b/src/states/idle.hpp @@ -0,0 +1,11 @@ + +#pragma once +#include "state.hpp" +class Idle : public State { + public: + + std::chrono::milliseconds tick() override { + std::cout << "Idling" << std::endl; + return std::chrono::milliseconds(5000); + } +}; diff --git a/src/states/menu.hpp b/src/states/menu.hpp new file mode 100644 index 0000000..531e81c --- /dev/null +++ b/src/states/menu.hpp @@ -0,0 +1,76 @@ +#pragma once +#include "state.hpp" +#include "../../neptune.hpp" +#include +#include +#include +#include + +/** + * Rep + */ +class Menu : public State +{ +public: + bool finished; + int wait = 500; + double speed = 0.5; + Menu() + { + } + + std::chrono::milliseconds tick() override + { + Neptune::instance->stop(); + std::cout << "Current duration(ms): " << wait << "Current Speed:" << speed << "\nOptions:\n[0] Rotate Clockwise \n[1] Rotate Counter-Clockwise \n[2] Move Forwards \n[3] Move Backwards \n[4]Set Duration \n[5]Change Speed \n[6]Read Sensors" << std::endl; + std::cout << ":"; + int out; + while (!(std::cin >> out) && (0 <= out) && (out <= 6)) + { + std::cin.clear(); + std::cin.ignore(std::numeric_limits::max(), '\n'); + std::cout << "Invalid input. Please enter a number: "; + } + switch (out) + { + case 0: + Neptune::instance->rotate(true, speed); + return std::chrono::milliseconds(wait); + case 1: + Neptune::instance->rotate(false, speed); + return std::chrono::milliseconds(wait); + case 2: + Neptune::instance->forwards(speed); + return std::chrono::milliseconds(wait); + case 3: + Neptune::instance->backwards(speed); + return std::chrono::milliseconds(wait); + case 4: + int wait; + std::cout << "New duration(ms):"; + while (!(std::cin >> wait)) + { + std::cin.clear(); + std::cin.ignore(std::numeric_limits::max(), '\n'); + std::cout << "Invalid input. Please enter a number: "; + } + this->wait = wait; + return std::chrono::milliseconds(10); + case 5: + double speed; + std::cout << "New speed(0.01 to 1):"; + while (!(std::cin >> speed) && (0.01 <= speed) && (speed <= 1)) + { + std::cin.clear(); + std::cin.ignore(std::numeric_limits::max(), '\n'); + std::cout << "Invalid input. Please enter a number: "; + } + this->speed = speed; + return std::chrono::milliseconds(10); + case 6: + Neptune::instance->dispData(); + return std::chrono::milliseconds(10); + } + return std::chrono::milliseconds(wait); + } +}; diff --git a/src/states/navigate.hpp b/src/states/navigate.hpp new file mode 100644 index 0000000..206eb23 --- /dev/null +++ b/src/states/navigate.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "state.hpp" +#include "../../neptune.hpp" +#include +#include +#include +#include + +class Navigate : public State +{ +public: + bool finished; + GPSCoordinate goal; + Navigate(GPSCoordinate goal) : goal(goal) + { + } + + std::chrono::milliseconds tick() override + { + //std::cout << Neptune::instance->get_heading() << std::endl; + Neptune::instance->travel(goal); + return std::chrono::milliseconds(50); + } +}; diff --git a/src/states/state.hpp b/src/states/state.hpp new file mode 100644 index 0000000..795b956 --- /dev/null +++ b/src/states/state.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "../neptune.hpp" +#include +//Represents a state the usv can be in +class State{ + public: + State(){ + + } + + /** + * @brief The driving logic for this tick + * @returns The time, in ms, to wait before ticking again + */ + virtual std::chrono::milliseconds tick(){ + return std::chrono::milliseconds(5000); + } +}; diff --git a/src/tests/thruster.cc b/src/tests/thruster.cc new file mode 100644 index 0000000..8ab10e6 --- /dev/null +++ b/src/tests/thruster.cc @@ -0,0 +1,24 @@ +#include +#include "../drivers/motors/thruster.hpp" +#include +class ThrusterTest : public Thruster { +public: + // Use the protected constructor in the fixture's initializer list + ThrusterTest() : Thruster() {} + +}; + +TEST(Thruster, DutyCycle){ + ThrusterTest* thruster = new ThrusterTest(); + thruster->armed = true; + thruster->cycle = 50; + thruster->speed = 0.0; + EXPECT_FLOAT_EQ(7.5f, thruster->getDutyCycle()); + +} + +TEST(Thruster, UnarmedFailure){ + Thruster* thruster = new ThrusterTest(); + thruster->armed = false; + ASSERT_ANY_THROW(thruster->setSpeed(0.0)); +} diff --git a/src/types/CMakeLists.txt b/src/types/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/types/GPSCoordinate.hpp b/src/types/GPSCoordinate.hpp new file mode 100644 index 0000000..5a58ada --- /dev/null +++ b/src/types/GPSCoordinate.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include + +using namespace Eigen; +struct GPSCoordinate { + double latitude; + double longitude; +}; + +// Equality operators +inline bool operator==(GPSCoordinate const& lhs, GPSCoordinate const& rhs) { + return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude; +} + +inline bool operator!=(GPSCoordinate const& lhs, GPSCoordinate const& rhs) { + return !(lhs == rhs); +} + +// Comparison operators (lexicographical) +inline bool operator<(GPSCoordinate const& lhs, GPSCoordinate const& rhs) { + if (lhs.latitude != rhs.latitude) return lhs.latitude < rhs.latitude; + return lhs.longitude < rhs.longitude; +} + +inline bool operator>(GPSCoordinate const& lhs, GPSCoordinate const& rhs) { + return rhs < lhs; +} + +inline bool operator<=(GPSCoordinate const& lhs, GPSCoordinate const& rhs) { + return !(rhs < lhs); +} + +inline bool operator>=(GPSCoordinate const& lhs, GPSCoordinate const& rhs) { + return !(lhs < rhs); +} + +// Arithmetic operators +inline GPSCoordinate operator+(GPSCoordinate const& lhs, GPSCoordinate const& rhs) { + return {lhs.latitude + rhs.latitude, lhs.longitude + rhs.longitude}; +} + +inline Vector2d operator-(GPSCoordinate const& lhs, GPSCoordinate const& rhs) { + return Vector2d{lhs.latitude - rhs.latitude, lhs.longitude - rhs.longitude}; +} + +inline GPSCoordinate operator*(GPSCoordinate const& coord, double scalar) { + return {coord.latitude * scalar, coord.longitude * scalar}; +} + +inline GPSCoordinate operator*(double scalar, GPSCoordinate const& coord) { + return coord * scalar; +} + +inline GPSCoordinate operator/(GPSCoordinate const& coord, double scalar) { + return {coord.latitude / scalar, coord.longitude / scalar}; +} + +// Unary minus +inline GPSCoordinate operator-(GPSCoordinate const& coord) { + return {-coord.latitude, -coord.longitude}; +} + +// Compound assignment +inline GPSCoordinate& operator+=(GPSCoordinate& lhs, GPSCoordinate const& rhs) { + lhs.latitude += rhs.latitude; + lhs.longitude += rhs.longitude; + return lhs; +} + +inline GPSCoordinate& operator-=(GPSCoordinate& lhs, GPSCoordinate const& rhs) { + lhs.latitude -= rhs.latitude; + lhs.longitude -= rhs.longitude; + return lhs; +} + +inline GPSCoordinate& operator*=(GPSCoordinate& coord, double scalar) { + coord.latitude *= scalar; + coord.longitude *= scalar; + return coord; +} + +inline GPSCoordinate& operator/=(GPSCoordinate& coord, double scalar) { + coord.latitude /= scalar; + coord.longitude /= scalar; + return coord; +} + +inline std::ostream &operator<<(std::ostream &os, GPSCoordinate const &coord) { + return os << "GPSCoordinate{" << coord.latitude << ", " << coord.longitude + << "}"; +} diff --git a/src/types/MotorLocation.hpp b/src/types/MotorLocation.hpp new file mode 100644 index 0000000..c2879c3 --- /dev/null +++ b/src/types/MotorLocation.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "GPSCoordinate.hpp" +#include + +#include +using namespace Eigen; + +//IMPORTANT: If the robot is placed facing north, the front motors should be facing north +enum MotorLocation { FrontRight, FrontLeft, BackRight, BackLeft }; + +constexpr double SQRT2 = 1.41421356237309504880; +//TODO make sure these stay up to date +Vector2d thrust_vector(MotorLocation m){ + switch(m){ + case FrontLeft: + return {0.5 * SQRT2, 0.5 * SQRT2}; + case FrontRight: + return {0.5* SQRT2, -0.5* SQRT2}; + case BackRight: + return {-0.5* SQRT2, 0.5* SQRT2}; + case BackLeft: + return {-0.5* SQRT2, -0.5* SQRT2}; + } + throw std::logic_error("Invalid type"); +} diff --git a/web_app/__init__.py b/web_app/__init__.py index 187e97a..50ad4a3 100644 --- a/web_app/__init__.py +++ b/web_app/__init__.py @@ -15,7 +15,7 @@ from pathlib import Path from time import sleep -from backend import socket_thread +from backend import socket_thread from pydantic_yaml import parse_yaml_file_as from ruamel.yaml import YAML @@ -73,7 +73,7 @@ def log(message: str): def start_backend(): """ If the backend websocket thread is not alive, start it. - If it is alive, do nothing. + If it is alive, do nothing. """ if backend_thread.is_alive() == False: backend_thread.start() diff --git a/web_app/backend.py b/web_app/backend.py index 66d4734..8360b19 100644 --- a/web_app/backend.py +++ b/web_app/backend.py @@ -59,7 +59,7 @@ def socket_handler( ) queue_to_frontend.put(json.dumps(asdict(ping_result))) active_pings.remove(ping) - + if time() - last_ping > ping_interval: active_pings.append( Ping( diff --git a/web_app/build.sh b/web_app/build.sh index efae4d7..6454f5d 100644 --- a/web_app/build.sh +++ b/web_app/build.sh @@ -10,7 +10,7 @@ python3 -m pip install --upgrade pip python3 -m pip install -r requirements.txt # Run npm script to build react-vite app & generate static bundle -cd frontend_gui +cd frontend_gui npm install npm run build diff --git a/web_app/build_win.bat b/web_app/build_win.bat index 2faa4c2..65032b6 100644 --- a/web_app/build_win.bat +++ b/web_app/build_win.bat @@ -5,12 +5,12 @@ python -m pip install --upgrade pip python -m pip install -r requirements.txt :: Run npm script to build react-vite app & generate static bundle -cd frontend_gui +cd frontend_gui call npm install call npm run build :: Return to original directory cd .. -:: At the end of this script, you will no longer be in the virtual environment. +:: At the end of this script, you will no longer be in the virtual environment. :: To get back in, run '.\.venv\Scripts\activate' from the command line diff --git a/web_app/data/gui_layouts.json b/web_app/data/gui_layouts.json index 2b26e04..87f2439 100644 --- a/web_app/data/gui_layouts.json +++ b/web_app/data/gui_layouts.json @@ -1 +1 @@ -{"lg":[{"w":8,"h":6,"x":0,"y":0,"i":"0/.0","minW":2,"minH":2,"moved":false,"static":false},{"w":7,"h":6,"x":8,"y":12,"i":"1/.1","minW":2,"minH":2,"moved":false,"static":false},{"w":3,"h":3,"x":4,"y":10,"i":"2/.2","minW":2,"minH":2,"moved":false,"static":false},{"w":3,"h":3,"x":0,"y":13,"i":"3/.3","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":11,"y":6,"i":"4/.4","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":7,"y":6,"i":"5/.5","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":7,"y":9,"i":"6/.6","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":0,"y":10,"i":"7/.7","minW":2,"minH":2,"moved":false,"static":false},{"w":5,"h":11,"x":15,"y":0,"i":"8/.8","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":2,"x":11,"y":9,"i":"9/.9","moved":false,"static":false},{"w":7,"h":4,"x":0,"y":6,"i":"10/.a","moved":false,"static":false},{"w":7,"h":6,"x":8,"y":0,"i":"11/.b","moved":false,"static":false}],"md":[{"w":8,"h":6,"x":0,"y":0,"i":"0/.0","minW":2,"minH":2,"moved":false,"static":false},{"w":7,"h":6,"x":7,"y":6,"i":"1/.1","minW":2,"minH":2,"moved":false,"static":false},{"w":3,"h":3,"x":0,"y":9,"i":"2/.2","minW":2,"minH":2,"moved":false,"static":false},{"w":3,"h":3,"x":0,"y":6,"i":"3/.3","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":5,"x":10,"y":12,"i":"4/.4","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":4,"x":7,"y":17,"i":"5/.5","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":10,"y":0,"i":"6/.6","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":8,"y":3,"i":"7/.7","minW":2,"minH":2,"moved":false,"static":false},{"w":5,"h":11,"x":9,"y":21,"i":"8/.8","minW":2,"minH":2,"moved":false,"static":false}]} \ No newline at end of file +{"lg":[{"w":8,"h":6,"x":0,"y":0,"i":"0/.0","minW":2,"minH":2,"moved":false,"static":false},{"w":7,"h":6,"x":8,"y":12,"i":"1/.1","minW":2,"minH":2,"moved":false,"static":false},{"w":3,"h":3,"x":4,"y":10,"i":"2/.2","minW":2,"minH":2,"moved":false,"static":false},{"w":3,"h":3,"x":0,"y":13,"i":"3/.3","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":11,"y":6,"i":"4/.4","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":7,"y":6,"i":"5/.5","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":7,"y":9,"i":"6/.6","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":0,"y":10,"i":"7/.7","minW":2,"minH":2,"moved":false,"static":false},{"w":5,"h":11,"x":15,"y":0,"i":"8/.8","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":2,"x":11,"y":9,"i":"9/.9","moved":false,"static":false},{"w":7,"h":4,"x":0,"y":6,"i":"10/.a","moved":false,"static":false},{"w":7,"h":6,"x":8,"y":0,"i":"11/.b","moved":false,"static":false}],"md":[{"w":8,"h":6,"x":0,"y":0,"i":"0/.0","minW":2,"minH":2,"moved":false,"static":false},{"w":7,"h":6,"x":7,"y":6,"i":"1/.1","minW":2,"minH":2,"moved":false,"static":false},{"w":3,"h":3,"x":0,"y":9,"i":"2/.2","minW":2,"minH":2,"moved":false,"static":false},{"w":3,"h":3,"x":0,"y":6,"i":"3/.3","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":5,"x":10,"y":12,"i":"4/.4","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":4,"x":7,"y":17,"i":"5/.5","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":10,"y":0,"i":"6/.6","minW":2,"minH":2,"moved":false,"static":false},{"w":4,"h":3,"x":8,"y":3,"i":"7/.7","minW":2,"minH":2,"moved":false,"static":false},{"w":5,"h":11,"x":9,"y":21,"i":"8/.8","minW":2,"minH":2,"moved":false,"static":false}]} diff --git a/web_app/frontend_gui/eslint.config.js b/web_app/frontend_gui/eslint.config.js index 238d2e4..2cf9713 100644 --- a/web_app/frontend_gui/eslint.config.js +++ b/web_app/frontend_gui/eslint.config.js @@ -1,38 +1,38 @@ -import js from '@eslint/js' -import globals from 'globals' -import react from 'eslint-plugin-react' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' +import js from "@eslint/js"; +import globals from "globals"; +import react from "eslint-plugin-react"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; export default [ - { ignores: ['dist'] }, + { ignores: ["dist"] }, { - files: ['**/*.{js,jsx}'], + files: ["**/*.{js,jsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, parserOptions: { - ecmaVersion: 'latest', + ecmaVersion: "latest", ecmaFeatures: { jsx: true }, - sourceType: 'module', + sourceType: "module", }, }, - settings: { react: { version: '18.3' } }, + settings: { react: { version: "18.3" } }, plugins: { react, - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, }, rules: { ...js.configs.recommended.rules, ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, + ...react.configs["jsx-runtime"].rules, ...reactHooks.configs.recommended.rules, - 'react/jsx-no-target-blank': 'off', - 'react-refresh/only-export-components': [ - 'warn', + "react/jsx-no-target-blank": "off", + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], }, }, -] +]; diff --git a/web_app/frontend_gui/package-lock.json b/web_app/frontend_gui/package-lock.json index 7dd5b5f..975ef3e 100644 --- a/web_app/frontend_gui/package-lock.json +++ b/web_app/frontend_gui/package-lock.json @@ -18,12 +18,19 @@ "three": "^0.174.0" }, "devDependencies": { + "@eslint/js": "^9.39.2", "@types/leaflet": "^1.9.17", "@types/react": "^19.1.6", "@types/react-dom": "^19.1.2", "@types/react-grid-layout": "^1.3.5", "@types/three": "^0.175.0", "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.39.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.26", + "globals": "^15.15.0", + "prettier": "^3.8.1", "typescript": "^5.8.3", "vite": "^6.2.6" } @@ -297,6 +304,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/types": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", @@ -731,6 +747,215 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -1239,6 +1464,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/leaflet": { "version": "1.9.17", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.17.tgz", @@ -1352,11 +1584,11 @@ "license": "BSD-3-Clause" }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "devOptional": true, "license": "MIT", - "optional": true, "bin": { "acorn": "bin/acorn" }, @@ -1364,6 +1596,227 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1390,6 +1843,17 @@ "integrity": "sha512-2E8U2h/3ZltRwxfVkT1c2DqWUjgIGFRg8cD1qQDJ0m7YOJCpxqI87s+vaaEzvIxRw4MJOUVJ3OZ6K904UNX+iw==", "license": "MIT" }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -1446,47 +1910,220 @@ "ieee754": "^1.2.1" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001713", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", - "integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001713", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", + "integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1504,12 +2141,260 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.136", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz", "integrity": "sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==", "license": "ISC" }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", @@ -1559,106 +2444,1132 @@ "node": ">=6" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-equals": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", - "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, "peerDependencies": { - "picomatch": "^3 || ^4" + "jiti": "*" }, "peerDependenciesMeta": { - "picomatch": { + "jiti": { "optional": true } } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, "license": "MIT", "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/its-fine": { "version": "2.0.0", @@ -1678,6 +3589,19 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -1690,6 +3614,27 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -1702,6 +3647,32 @@ "node": ">=6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -1717,6 +3688,43 @@ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "license": "BSD-2-Clause" }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1738,62 +3746,298 @@ "yallist": "^3.0.2" } }, - "node_modules/meshoptimizer": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", - "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "license": "MIT" - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "callsites": "^3.0.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=6" } }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "license": "MIT" + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1856,6 +4100,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", @@ -1895,6 +4149,32 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -1906,6 +4186,16 @@ "react-is": "^16.13.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -2098,80 +4388,355 @@ } } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", "license": "MIT" }, - "node_modules/resolve-pkg-maps": { + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rollup": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", - "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", - "devOptional": true, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" - }, - "bin": { - "rollup": "dist/bin/rollup" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.0", - "@rollup/rollup-android-arm64": "4.40.0", - "@rollup/rollup-darwin-arm64": "4.40.0", - "@rollup/rollup-darwin-x64": "4.40.0", - "@rollup/rollup-freebsd-arm64": "4.40.0", - "@rollup/rollup-freebsd-x64": "4.40.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", - "@rollup/rollup-linux-arm-musleabihf": "4.40.0", - "@rollup/rollup-linux-arm64-gnu": "4.40.0", - "@rollup/rollup-linux-arm64-musl": "4.40.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-musl": "4.40.0", - "@rollup/rollup-linux-s390x-gnu": "4.40.0", - "@rollup/rollup-linux-x64-gnu": "4.40.0", - "@rollup/rollup-linux-x64-musl": "4.40.0", - "@rollup/rollup-win32-arm64-msvc": "4.40.0", - "@rollup/rollup-win32-ia32-msvc": "4.40.0", - "@rollup/rollup-win32-x64-msvc": "4.40.0", - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/sisteransi": { @@ -2190,6 +4755,157 @@ "node": ">=0.10.0" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/suspend-react": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", @@ -2241,6 +4957,97 @@ "fsevents": "~2.3.3" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -2255,6 +5062,25 @@ "node": ">=14.17" } }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -2305,6 +5131,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", @@ -2396,12 +5232,140 @@ "license": "MIT", "optional": true }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zustand": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", diff --git a/web_app/frontend_gui/package.json b/web_app/frontend_gui/package.json index 2928cdb..80efc76 100644 --- a/web_app/frontend_gui/package.json +++ b/web_app/frontend_gui/package.json @@ -20,12 +20,19 @@ "three": "^0.174.0" }, "devDependencies": { + "@eslint/js": "^9.39.2", "@types/leaflet": "^1.9.17", "@types/react": "^19.1.6", "@types/react-dom": "^19.1.2", "@types/react-grid-layout": "^1.3.5", "@types/three": "^0.175.0", "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.39.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.26", + "globals": "^15.15.0", + "prettier": "^3.8.1", "typescript": "^5.8.3", "vite": "^6.2.6" } diff --git a/web_app/frontend_gui/public/fonts.css b/web_app/frontend_gui/public/fonts.css index 39b2122..3342640 100644 --- a/web_app/frontend_gui/public/fonts.css +++ b/web_app/frontend_gui/public/fonts.css @@ -1,6 +1,6 @@ @font-face { - font-family: "Open Sans"; - font-weight: 400; - font-style: normal; - src: url('fonts/OpenSans.ttf') format('truetype'); + font-family: "Open Sans"; + font-weight: 400; + font-style: normal; + src: url("fonts/OpenSans.ttf") format("truetype"); } diff --git a/web_app/frontend_gui/public/leaflet.css b/web_app/frontend_gui/public/leaflet.css index 9ade8dc..2dd1c58 100644 --- a/web_app/frontend_gui/public/leaflet.css +++ b/web_app/frontend_gui/public/leaflet.css @@ -10,233 +10,248 @@ .leaflet-zoom-box, .leaflet-image-layer, .leaflet-layer { - position: absolute; - left: 0; - top: 0; - } + position: absolute; + left: 0; + top: 0; +} .leaflet-container { - overflow: hidden; - } + overflow: hidden; +} .leaflet-tile, .leaflet-marker-icon, .leaflet-marker-shadow { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - -webkit-user-drag: none; - } + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; +} /* Prevents IE11 from highlighting tiles in blue */ .leaflet-tile::selection { - background: transparent; + background: transparent; } /* Safari renders non-retina tile on retina better with this, but Chrome is worse */ .leaflet-safari .leaflet-tile { - image-rendering: -webkit-optimize-contrast; - } + image-rendering: -webkit-optimize-contrast; +} /* hack that prevents hw layers "stretching" when loading new tiles */ .leaflet-safari .leaflet-tile-container { - width: 1600px; - height: 1600px; - -webkit-transform-origin: 0 0; - } + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; +} .leaflet-marker-icon, .leaflet-marker-shadow { - display: block; - } + display: block; +} /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ .leaflet-container .leaflet-overlay-pane svg { - max-width: none !important; - max-height: none !important; - } + max-width: none !important; + max-height: none !important; +} .leaflet-container .leaflet-marker-pane img, .leaflet-container .leaflet-shadow-pane img, .leaflet-container .leaflet-tile-pane img, .leaflet-container img.leaflet-image-layer, .leaflet-container .leaflet-tile { - max-width: none !important; - max-height: none !important; - width: auto; - padding: 0; - } + max-width: none !important; + max-height: none !important; + width: auto; + padding: 0; +} .leaflet-container img.leaflet-tile { - /* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */ - mix-blend-mode: plus-lighter; + /* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */ + mix-blend-mode: plus-lighter; } .leaflet-container.leaflet-touch-zoom { - -ms-touch-action: pan-x pan-y; - touch-action: pan-x pan-y; - } + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; +} .leaflet-container.leaflet-touch-drag { - -ms-touch-action: pinch-zoom; - /* Fallback for FF which doesn't support pinch-zoom */ - touch-action: none; - touch-action: pinch-zoom; + -ms-touch-action: pinch-zoom; + /* Fallback for FF which doesn't support pinch-zoom */ + touch-action: none; + touch-action: pinch-zoom; } .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { - -ms-touch-action: none; - touch-action: none; + -ms-touch-action: none; + touch-action: none; } .leaflet-container { - -webkit-tap-highlight-color: transparent; + -webkit-tap-highlight-color: transparent; } .leaflet-container a { - -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); } .leaflet-tile { - filter: inherit; - visibility: hidden; - } + filter: inherit; + visibility: hidden; +} .leaflet-tile-loaded { - visibility: inherit; - } + visibility: inherit; +} .leaflet-zoom-box { - width: 0; - height: 0; - -moz-box-sizing: border-box; - box-sizing: border-box; - z-index: 800; - } + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; +} /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ .leaflet-overlay-pane svg { - -moz-user-select: none; - } + -moz-user-select: none; +} -.leaflet-pane { z-index: 400; } +.leaflet-pane { + z-index: 400; +} -.leaflet-tile-pane { z-index: 200; } -.leaflet-overlay-pane { z-index: 400; } -.leaflet-shadow-pane { z-index: 500; } -.leaflet-marker-pane { z-index: 600; } -.leaflet-tooltip-pane { z-index: 650; } -.leaflet-popup-pane { z-index: 700; } +.leaflet-tile-pane { + z-index: 200; +} +.leaflet-overlay-pane { + z-index: 400; +} +.leaflet-shadow-pane { + z-index: 500; +} +.leaflet-marker-pane { + z-index: 600; +} +.leaflet-tooltip-pane { + z-index: 650; +} +.leaflet-popup-pane { + z-index: 700; +} -.leaflet-map-pane canvas { z-index: 100; } -.leaflet-map-pane svg { z-index: 200; } +.leaflet-map-pane canvas { + z-index: 100; +} +.leaflet-map-pane svg { + z-index: 200; +} .leaflet-vml-shape { - width: 1px; - height: 1px; - } + width: 1px; + height: 1px; +} .lvml { - behavior: url(#default#VML); - display: inline-block; - position: absolute; - } - + behavior: url(#default#VML); + display: inline-block; + position: absolute; +} /* control positioning */ .leaflet-control { - position: relative; - z-index: 800; - pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ - pointer-events: auto; - } + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; +} .leaflet-top, .leaflet-bottom { - position: absolute; - z-index: 1000; - pointer-events: none; - } + position: absolute; + z-index: 1000; + pointer-events: none; +} .leaflet-top { - top: 0; - } + top: 0; +} .leaflet-right { - right: 0; - } + right: 0; +} .leaflet-bottom { - bottom: 0; - } + bottom: 0; +} .leaflet-left { - left: 0; - } + left: 0; +} .leaflet-control { - float: left; - clear: both; - } + float: left; + clear: both; +} .leaflet-right .leaflet-control { - float: right; - } + float: right; +} .leaflet-top .leaflet-control { - margin-top: 10px; - } + margin-top: 10px; +} .leaflet-bottom .leaflet-control { - margin-bottom: 10px; - } + margin-bottom: 10px; +} .leaflet-left .leaflet-control { - margin-left: 10px; - } + margin-left: 10px; +} .leaflet-right .leaflet-control { - margin-right: 10px; - } - + margin-right: 10px; +} /* zoom and fade animations */ .leaflet-fade-anim .leaflet-popup { - opacity: 0; - -webkit-transition: opacity 0.2s linear; - -moz-transition: opacity 0.2s linear; - transition: opacity 0.2s linear; - } + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; +} .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { - opacity: 1; - } + opacity: 1; +} .leaflet-zoom-animated { - -webkit-transform-origin: 0 0; - -ms-transform-origin: 0 0; - transform-origin: 0 0; - } + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; +} svg.leaflet-zoom-animated { - will-change: transform; + will-change: transform; } .leaflet-zoom-anim .leaflet-zoom-animated { - -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); - -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); - transition: transform 0.25s cubic-bezier(0,0,0.25,1); - } + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1); + transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1); +} .leaflet-zoom-anim .leaflet-tile, .leaflet-pan-anim .leaflet-tile { - -webkit-transition: none; - -moz-transition: none; - transition: none; - } + -webkit-transition: none; + -moz-transition: none; + transition: none; +} .leaflet-zoom-anim .leaflet-zoom-hide { - visibility: hidden; - } - + visibility: hidden; +} /* cursors */ .leaflet-interactive { - cursor: pointer; - } + cursor: pointer; +} .leaflet-grab { - cursor: -webkit-grab; - cursor: -moz-grab; - cursor: grab; - } + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} .leaflet-crosshair, .leaflet-crosshair .leaflet-interactive { - cursor: crosshair; - } + cursor: crosshair; +} .leaflet-popup-pane, .leaflet-control { - cursor: auto; - } + cursor: auto; +} .leaflet-dragging .leaflet-grab, .leaflet-dragging .leaflet-grab .leaflet-interactive, .leaflet-dragging .leaflet-marker-draggable { - cursor: move; - cursor: -webkit-grabbing; - cursor: -moz-grabbing; - cursor: grabbing; - } + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} /* marker & overlays interactivity */ .leaflet-marker-icon, @@ -244,418 +259,419 @@ svg.leaflet-zoom-animated { .leaflet-image-layer, .leaflet-pane > svg path, .leaflet-tile-container { - pointer-events: none; - } + pointer-events: none; +} .leaflet-marker-icon.leaflet-interactive, .leaflet-image-layer.leaflet-interactive, .leaflet-pane > svg path.leaflet-interactive, svg.leaflet-image-layer.leaflet-interactive path { - pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ - pointer-events: auto; - } + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; +} /* visual tweaks */ .leaflet-container { - background: #ddd; - outline-offset: 1px; - } + background: #ddd; + outline-offset: 1px; +} .leaflet-container a { - color: #0078A8; - } + color: #0078a8; +} .leaflet-zoom-box { - border: 2px dotted #38f; - background: rgba(255,255,255,0.5); - } - + border: 2px dotted #38f; + background: rgba(255, 255, 255, 0.5); +} /* general typography */ .leaflet-container { - font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; - font-size: 12px; - font-size: 0.75rem; - line-height: 1.5; - } - + font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; + font-size: 12px; + font-size: 0.75rem; + line-height: 1.5; +} /* general toolbar styles */ .leaflet-bar { - box-shadow: 0 1px 5px rgba(0,0,0,0.65); - border-radius: 4px; - } + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); + border-radius: 4px; +} .leaflet-bar a { - background-color: #fff; - border-bottom: 1px solid #ccc; - width: 26px; - height: 26px; - line-height: 26px; - display: block; - text-align: center; - text-decoration: none; - color: black; - } + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; +} .leaflet-bar a, .leaflet-control-layers-toggle { - background-position: 50% 50%; - background-repeat: no-repeat; - display: block; - } + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; +} .leaflet-bar a:hover, .leaflet-bar a:focus { - background-color: #f4f4f4; - } + background-color: #f4f4f4; +} .leaflet-bar a:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; - } + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} .leaflet-bar a:last-child { - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-bottom: none; - } + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; +} .leaflet-bar a.leaflet-disabled { - cursor: default; - background-color: #f4f4f4; - color: #bbb; - } + cursor: default; + background-color: #f4f4f4; + color: #bbb; +} .leaflet-touch .leaflet-bar a { - width: 30px; - height: 30px; - line-height: 30px; - } + width: 30px; + height: 30px; + line-height: 30px; +} .leaflet-touch .leaflet-bar a:first-child { - border-top-left-radius: 2px; - border-top-right-radius: 2px; - } + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} .leaflet-touch .leaflet-bar a:last-child { - border-bottom-left-radius: 2px; - border-bottom-right-radius: 2px; - } + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} /* zoom control */ .leaflet-control-zoom-in, .leaflet-control-zoom-out { - font: bold 18px 'Lucida Console', Monaco, monospace; - text-indent: 1px; - } - -.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { - font-size: 22px; - } + font: + bold 18px "Lucida Console", + Monaco, + monospace; + text-indent: 1px; +} +.leaflet-touch .leaflet-control-zoom-in, +.leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; +} /* layers control */ .leaflet-control-layers { - box-shadow: 0 1px 5px rgba(0,0,0,0.4); - background: #fff; - border-radius: 5px; - } + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); + background: #fff; + border-radius: 5px; +} .leaflet-control-layers-toggle { - background-image: url(images/layers.png); - width: 36px; - height: 36px; - } + background-image: url(images/layers.png); + width: 36px; + height: 36px; +} .leaflet-retina .leaflet-control-layers-toggle { - background-image: url(images/layers-2x.png); - background-size: 26px 26px; - } + background-image: url(images/layers-2x.png); + background-size: 26px 26px; +} .leaflet-touch .leaflet-control-layers-toggle { - width: 44px; - height: 44px; - } + width: 44px; + height: 44px; +} .leaflet-control-layers .leaflet-control-layers-list, .leaflet-control-layers-expanded .leaflet-control-layers-toggle { - display: none; - } + display: none; +} .leaflet-control-layers-expanded .leaflet-control-layers-list { - display: block; - position: relative; - } + display: block; + position: relative; +} .leaflet-control-layers-expanded { - padding: 6px 10px 6px 6px; - color: #333; - background: #fff; - } + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; +} .leaflet-control-layers-scrollbar { - overflow-y: scroll; - overflow-x: hidden; - padding-right: 5px; - } + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; +} .leaflet-control-layers-selector { - margin-top: 2px; - position: relative; - top: 1px; - } + margin-top: 2px; + position: relative; + top: 1px; +} .leaflet-control-layers label { - display: block; - font-size: 13px; - font-size: 1.08333em; - } + display: block; + font-size: 13px; + font-size: 1.08333em; +} .leaflet-control-layers-separator { - height: 0; - border-top: 1px solid #ddd; - margin: 5px -10px 5px -6px; - } + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; +} /* Default icon URLs */ -.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */ - background-image: url(images/marker-icon.png); - } - +.leaflet-default-icon-path { + /* used only in path-guessing heuristic, see L.Icon.Default */ + background-image: url(images/marker-icon.png); +} /* attribution and scale controls */ .leaflet-container .leaflet-control-attribution { - background: #fff; - background: rgba(255, 255, 255, 0.8); - margin: 0; - } + background: #fff; + background: rgba(255, 255, 255, 0.8); + margin: 0; +} .leaflet-control-attribution, .leaflet-control-scale-line { - padding: 0 5px; - color: #333; - line-height: 1.4; - } + padding: 0 5px; + color: #333; + line-height: 1.4; +} .leaflet-control-attribution a { - text-decoration: none; - } + text-decoration: none; +} .leaflet-control-attribution a:hover, .leaflet-control-attribution a:focus { - text-decoration: underline; - } + text-decoration: underline; +} .leaflet-attribution-flag { - display: inline !important; - vertical-align: baseline !important; - width: 1em; - height: 0.6669em; - } + display: inline !important; + vertical-align: baseline !important; + width: 1em; + height: 0.6669em; +} .leaflet-left .leaflet-control-scale { - margin-left: 5px; - } + margin-left: 5px; +} .leaflet-bottom .leaflet-control-scale { - margin-bottom: 5px; - } + margin-bottom: 5px; +} .leaflet-control-scale-line { - border: 2px solid #777; - border-top: none; - line-height: 1.1; - padding: 2px 5px 1px; - white-space: nowrap; - -moz-box-sizing: border-box; - box-sizing: border-box; - background: rgba(255, 255, 255, 0.8); - text-shadow: 1px 1px #fff; - } + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + white-space: nowrap; + -moz-box-sizing: border-box; + box-sizing: border-box; + background: rgba(255, 255, 255, 0.8); + text-shadow: 1px 1px #fff; +} .leaflet-control-scale-line:not(:first-child) { - border-top: 2px solid #777; - border-bottom: none; - margin-top: -2px; - } + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; +} .leaflet-control-scale-line:not(:first-child):not(:last-child) { - border-bottom: 2px solid #777; - } + border-bottom: 2px solid #777; +} .leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar { - box-shadow: none; - } + box-shadow: none; +} .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar { - border: 2px solid rgba(0,0,0,0.2); - background-clip: padding-box; - } - + border: 2px solid rgba(0, 0, 0, 0.2); + background-clip: padding-box; +} /* popup */ .leaflet-popup { - position: absolute; - text-align: center; - margin-bottom: 20px; - } + position: absolute; + text-align: center; + margin-bottom: 20px; +} .leaflet-popup-content-wrapper { - padding: 1px; - text-align: left; - border-radius: 12px; - } + padding: 1px; + text-align: left; + border-radius: 12px; +} .leaflet-popup-content { - margin: 13px 24px 13px 20px; - line-height: 1.3; - font-size: 13px; - font-size: 1.08333em; - min-height: 1px; - } + margin: 13px 24px 13px 20px; + line-height: 1.3; + font-size: 13px; + font-size: 1.08333em; + min-height: 1px; +} .leaflet-popup-content p { - margin: 17px 0; - margin: 1.3em 0; - } + margin: 17px 0; + margin: 1.3em 0; +} .leaflet-popup-tip-container { - width: 40px; - height: 20px; - position: absolute; - left: 50%; - margin-top: -1px; - margin-left: -20px; - overflow: hidden; - pointer-events: none; - } + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-top: -1px; + margin-left: -20px; + overflow: hidden; + pointer-events: none; +} .leaflet-popup-tip { - width: 17px; - height: 17px; - padding: 1px; - - margin: -10px auto 0; - pointer-events: auto; - - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); - } + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + pointer-events: auto; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} .leaflet-popup-content-wrapper, .leaflet-popup-tip { - background: white; - color: #333; - box-shadow: 0 3px 14px rgba(0,0,0,0.4); - } + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4); +} .leaflet-container a.leaflet-popup-close-button { - position: absolute; - top: 0; - right: 0; - border: none; - text-align: center; - width: 24px; - height: 24px; - font: 16px/24px Tahoma, Verdana, sans-serif; - color: #757575; - text-decoration: none; - background: transparent; - } + position: absolute; + top: 0; + right: 0; + border: none; + text-align: center; + width: 24px; + height: 24px; + font: + 16px/24px Tahoma, + Verdana, + sans-serif; + color: #757575; + text-decoration: none; + background: transparent; +} .leaflet-container a.leaflet-popup-close-button:hover, .leaflet-container a.leaflet-popup-close-button:focus { - color: #585858; - } + color: #585858; +} .leaflet-popup-scrolled { - overflow: auto; - } + overflow: auto; +} .leaflet-oldie .leaflet-popup-content-wrapper { - -ms-zoom: 1; - } + -ms-zoom: 1; +} .leaflet-oldie .leaflet-popup-tip { - width: 24px; - margin: 0 auto; + width: 24px; + margin: 0 auto; - -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; - filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); - } + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); +} .leaflet-oldie .leaflet-control-zoom, .leaflet-oldie .leaflet-control-layers, .leaflet-oldie .leaflet-popup-content-wrapper, .leaflet-oldie .leaflet-popup-tip { - border: 1px solid #999; - } - + border: 1px solid #999; +} /* div icon */ .leaflet-div-icon { - background: #fff; - border: 1px solid #666; - } - + background: #fff; + border: 1px solid #666; +} /* Tooltip */ /* Base styles for the element that has a tooltip */ .leaflet-tooltip { - position: absolute; - padding: 6px; - background-color: #fff; - border: 1px solid #fff; - border-radius: 3px; - color: #222; - white-space: nowrap; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - pointer-events: none; - box-shadow: 0 1px 3px rgba(0,0,0,0.4); - } + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); +} .leaflet-tooltip.leaflet-interactive { - cursor: pointer; - pointer-events: auto; - } + cursor: pointer; + pointer-events: auto; +} .leaflet-tooltip-top:before, .leaflet-tooltip-bottom:before, .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { - position: absolute; - pointer-events: none; - border: 6px solid transparent; - background: transparent; - content: ""; - } + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; +} /* Directions */ .leaflet-tooltip-bottom { - margin-top: 6px; + margin-top: 6px; } .leaflet-tooltip-top { - margin-top: -6px; + margin-top: -6px; } .leaflet-tooltip-bottom:before, .leaflet-tooltip-top:before { - left: 50%; - margin-left: -6px; - } + left: 50%; + margin-left: -6px; +} .leaflet-tooltip-top:before { - bottom: 0; - margin-bottom: -12px; - border-top-color: #fff; - } + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; +} .leaflet-tooltip-bottom:before { - top: 0; - margin-top: -12px; - margin-left: -6px; - border-bottom-color: #fff; - } + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; +} .leaflet-tooltip-left { - margin-left: -6px; + margin-left: -6px; } .leaflet-tooltip-right { - margin-left: 6px; + margin-left: 6px; } .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { - top: 50%; - margin-top: -6px; - } + top: 50%; + margin-top: -6px; +} .leaflet-tooltip-left:before { - right: 0; - margin-right: -12px; - border-left-color: #fff; - } + right: 0; + margin-right: -12px; + border-left-color: #fff; +} .leaflet-tooltip-right:before { - left: 0; - margin-left: -12px; - border-right-color: #fff; - } + left: 0; + margin-left: -12px; + border-right-color: #fff; +} /* Printing */ @media print { - /* Prevent printers from removing background-images of controls. */ - .leaflet-control { - -webkit-print-color-adjust: exact; - print-color-adjust: exact; - } - } + /* Prevent printers from removing background-images of controls. */ + .leaflet-control { + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } +} diff --git a/web_app/frontend_gui/public/main.css b/web_app/frontend_gui/public/main.css index 3c1ed0d..d57014b 100644 --- a/web_app/frontend_gui/public/main.css +++ b/web_app/frontend_gui/public/main.css @@ -1,109 +1,111 @@ -html, body { - font-family: "Open Sans", system-ui; - padding: 0; - margin: 0; - height: 100vh; - background: #CEF3FF; - font-size: 1.2; +html, +body { + font-family: "Open Sans", system-ui; + padding: 0; + margin: 0; + height: 100vh; + background: #cef3ff; + font-size: 1.2; } :root { - /* Generated with https://www.joshwcomeau.com/shadow-palette/*/ - --shadow-color: 195deg 39% 56%; /* Same as #CEF3FF */ - --shadow-elevation-low: - 0px 0.3px 0.3px hsl(var(--shadow-color) / 0.52), - 0px 0.5px 0.5px -1.9px hsl(var(--shadow-color) / 0.4), - 0px 1.3px 1.2px -3.7px hsl(var(--shadow-color) / 0.29); - --shadow-elevation-medium: - 0px 0.3px 0.3px hsl(var(--shadow-color) / 0.43), - 0px 0.6px 0.6px -0.9px hsl(var(--shadow-color) / 0.38), - 0px 1.3px 1.2px -1.9px hsl(var(--shadow-color) / 0.32), - 0px 3.1px 2.9px -2.8px hsl(var(--shadow-color) / 0.26), - 0px 6.3px 5.9px -3.7px hsl(var(--shadow-color) / 0.2); - --shadow-elevation-high: - 0px 0.2px 0.2px hsl(var(--shadow-color) / 0.32), - 0px 0.8px 0.8px -0.5px hsl(var(--shadow-color) / 0.3), - 0px 1.4px 1.4px -1px hsl(var(--shadow-color) / 0.28), - 0px 2.5px 2.4px -1.5px hsl(var(--shadow-color) / 0.25), - -0.1px 4.3px 4.2px -2px hsl(var(--shadow-color) / 0.23), - -0.1px 7.1px 6.9px -2.5px hsl(var(--shadow-color) / 0.21), - -0.2px 11.3px 11px -3px hsl(var(--shadow-color) / 0.19), - -0.3px 17.1px 16.7px -3.5px hsl(var(--shadow-color) / 0.16); + /* Generated with https://www.joshwcomeau.com/shadow-palette/*/ + --shadow-color: 195deg 39% 56%; /* Same as #CEF3FF */ + --shadow-elevation-low: + 0px 0.3px 0.3px hsl(var(--shadow-color) / 0.52), + 0px 0.5px 0.5px -1.9px hsl(var(--shadow-color) / 0.4), + 0px 1.3px 1.2px -3.7px hsl(var(--shadow-color) / 0.29); + --shadow-elevation-medium: + 0px 0.3px 0.3px hsl(var(--shadow-color) / 0.43), + 0px 0.6px 0.6px -0.9px hsl(var(--shadow-color) / 0.38), + 0px 1.3px 1.2px -1.9px hsl(var(--shadow-color) / 0.32), + 0px 3.1px 2.9px -2.8px hsl(var(--shadow-color) / 0.26), + 0px 6.3px 5.9px -3.7px hsl(var(--shadow-color) / 0.2); + --shadow-elevation-high: + 0px 0.2px 0.2px hsl(var(--shadow-color) / 0.32), + 0px 0.8px 0.8px -0.5px hsl(var(--shadow-color) / 0.3), + 0px 1.4px 1.4px -1px hsl(var(--shadow-color) / 0.28), + 0px 2.5px 2.4px -1.5px hsl(var(--shadow-color) / 0.25), + -0.1px 4.3px 4.2px -2px hsl(var(--shadow-color) / 0.23), + -0.1px 7.1px 6.9px -2.5px hsl(var(--shadow-color) / 0.21), + -0.2px 11.3px 11px -3px hsl(var(--shadow-color) / 0.19), + -0.3px 17.1px 16.7px -3.5px hsl(var(--shadow-color) / 0.16); } * { - box-sizing: border-box; + box-sizing: border-box; } h1 { - margin: -0.25rem auto 0.25rem auto; - width: fit-content; - background-color: white; - padding: 0.75rem; - border-radius: 1rem; - box-shadow: var(--shadow-elevation-medium); + margin: -0.25rem auto 0.25rem auto; + width: fit-content; + background-color: white; + padding: 0.75rem; + border-radius: 1rem; + box-shadow: var(--shadow-elevation-medium); } h2 { - margin: 0 0 0.5rem; - line-height: 1; - text-align: center; + margin: 0 0 0.5rem; + line-height: 1; + text-align: center; } h3 { - margin: 0; + margin: 0; } input, select { - font-family: inherit; + font-family: inherit; } ul { - margin-top: 0; - list-style: none; - padding-left: 0; + margin-top: 0; + list-style: none; + padding-left: 0; } -ul>li { - margin-bottom: 0.75rem; +ul > li { + margin-bottom: 0.75rem; } -pre { /* For the gui console output*/ - text-wrap: wrap; +pre { + /* For the gui console output*/ + text-wrap: wrap; } button { - cursor: pointer; + cursor: pointer; } .parent-container { - padding: 1rem; + padding: 1rem; } .top-right-buttons { - position: absolute; - top: 1rem; - right: 1.5rem; - display: flex; + position: absolute; + top: 1rem; + right: 1.5rem; + display: flex; } .top-right-buttons > button { - margin: 0.25rem; - border-radius: 0.75rem; - border: none; - padding: 0.5rem 0.75rem; - display: flex; - align-items: center; - justify-content: center; - gap: 0.2rem; - background: white; - box-shadow: var(--shadow-elevation-medium); - font-size: 0.9rem; + margin: 0.25rem; + border-radius: 0.75rem; + border: none; + padding: 0.5rem 0.75rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.2rem; + background: white; + box-shadow: var(--shadow-elevation-medium); + font-size: 0.9rem; } .top-right-buttons > button:hover { - transform: translateY(-0.1rem); + transform: translateY(-0.1rem); } /* This section has `react-grid-layout` package class names that are not found @@ -111,76 +113,77 @@ button { * future, it is due to react-grid-layout changing their class names. Change * them to match, or if that is impossible simply track the grid state manually * and apply custom class names. */ -.react-grid-item { /* Assigned to grid items by react-grid-layout*/ - padding: 1rem; - background-color: white; - border-radius: 1.5rem; - box-shadow: var(--shadow-elevation-high); +.react-grid-item { + /* Assigned to grid items by react-grid-layout*/ + padding: 1rem; + background-color: white; + border-radius: 1.5rem; + box-shadow: var(--shadow-elevation-high); } .react-grid-layout button { - border: none; - border-radius: 0.75rem; - margin: 0.5rem; - padding: 0.75rem 1rem; - background: white; - filter: brightness(95%); - font-weight: bold; + border: none; + border-radius: 0.75rem; + margin: 0.5rem; + padding: 0.75rem 1rem; + background: white; + filter: brightness(95%); + font-weight: bold; } .react-grid-layout button:hover { - filter: brightness(85%); + filter: brightness(85%); } /* For cursor when hovering in drag mode*/ .react-grid-item.react-draggable { - cursor: grab; + cursor: grab; } /* For cursor when actively dragging*/ .react-grid-item.react-draggable-dragging { - cursor: grabbing; + cursor: grabbing; } /* END of weird section with react-grid-layout classes*/ .map-handle { - position: absolute; - top: 0; - left: 1.5rem; - right: 1.5rem; - height: 2rem; - cursor: grab; + position: absolute; + top: 0; + left: 1.5rem; + right: 1.5rem; + height: 2rem; + cursor: grab; } .map-holder { - flex-grow: 1; - display: flex; - flex-direction: column; + flex-grow: 1; + display: flex; + flex-direction: column; } .map { - flex-grow: 1; - border-radius: 1rem; + flex-grow: 1; + border-radius: 1rem; } .status-section { - display: flex; - flex-direction: column; - align-items: flex-start; - padding-inline: 1rem; + display: flex; + flex-direction: column; + align-items: flex-start; + padding-inline: 1rem; } .status-item { - width: 20ch; + width: 20ch; } .testing-section { - flex-grow: 1; - display: flex; - justify-content: space-evenly; - align-items: flex-start; + flex-grow: 1; + display: flex; + justify-content: space-evenly; + align-items: flex-start; } .testing-section > div { - padding-inline: 1rem; + padding-inline: 1rem; } diff --git a/web_app/frontend_gui/src/App.tsx b/web_app/frontend_gui/src/App.tsx index 972d874..ff4ba9e 100644 --- a/web_app/frontend_gui/src/App.tsx +++ b/web_app/frontend_gui/src/App.tsx @@ -2,125 +2,129 @@ import React, { useEffect, useState, useRef } from "react"; import { Layouts } from "react-grid-layout"; import { - ParametersForm, - MotorTestForm, - HeadingTestForm, - StartMission, + ParametersForm, + MotorTestForm, + HeadingTestForm, + StartMission, } from "./inputs/Forms"; -import { SocketHealth } from "./inputs/SocketHealth" -import { TaskManager} from "./inputs/TaskManager"; +import { SocketHealth } from "./inputs/SocketHealth"; +import { TaskManager } from "./inputs/TaskManager"; import { StatusItem, StatusMessages } from "./outputs/status"; import { Simulation } from "./outputs/sim"; -import { Map } from "./outputs/map" -import { VideoStream } from "./outputs/video" -import { - Command, - isCommand, - State, - isState, - Data, - isData -} from "./types" +import { Map } from "./outputs/map"; +import { VideoStream } from "./outputs/video"; +import { Command, isCommand, State, isState, Data, isData } from "./types"; import { Grid } from "./Grid"; import { GridSvg } from "./utils/icons"; const tryJson = (raw: string): any => { - try { - return JSON.parse(raw) - } - catch { - return undefined - } -} + try { + return JSON.parse(raw); + } catch { + return undefined; + } +}; export default function App() { - // These are the only state variables that need full scope - const [imuData, setImuData] = useState([ - { title: 'Magnetometer', value: '', id: 1 }, - { title: 'Accelerometer', value: '', id: 2 }, - { title: 'Gyroscope', value: '', id: 3 } - ]); - const [insData, setInsData] = useState([ - { title: 'Heading', value: '', id: 1 }, - { title: 'Roll', value: '', id: 2 }, - { title: 'Pitch', value: '', id: 3 } - ]); - const [websocket, setWebsocket]: any = useState(); - const [statusMessages, setStatusMessages]: any = useState([]); - const [attitude, setAttitude] = useState([0,0,0]); + // These are the only state variables that need full scope + const [imuData, setImuData] = useState([ + { title: "Magnetometer", value: "", id: 1 }, + { title: "Accelerometer", value: "", id: 2 }, + { title: "Gyroscope", value: "", id: 3 }, + ]); + const [insData, setInsData] = useState([ + { title: "Heading", value: "", id: 1 }, + { title: "Roll", value: "", id: 2 }, + { title: "Pitch", value: "", id: 3 }, + ]); + const [websocket, setWebsocket]: any = useState(); + const [statusMessages, setStatusMessages]: any = useState([]); + const [attitude, setAttitude] = useState([0, 0, 0]); - const handleSocketData = (event: MessageEvent) => { - console.log("Socket data arrived: " + event.data); - const data: Command | Data = tryJson(event.data) - if (isData(data) && data.type == "state" && isState(data.content)) { - setAttitude(data.content.attitude) - } else { - setStatusMessages((prev: any) => [...prev, event.data]); - } + const handleSocketData = (event: MessageEvent) => { + console.log("Socket data arrived: " + event.data); + const data: Command | Data = tryJson(event.data); + if (isData(data) && data.type == "state" && isState(data.content)) { + setAttitude(data.content.attitude); + } else { + setStatusMessages((prev: any) => [...prev, event.data]); } + }; - // Register socket handler for server-sent data - const connection: any = useRef(null); - useEffect(() => { - const socket: WebSocket = new WebSocket("ws://localhost:6543/api/websocket"); + // Register socket handler for server-sent data + const connection: any = useRef(null); + useEffect(() => { + const socket: WebSocket = new WebSocket( + "ws://localhost:6543/api/websocket", + ); - socket.addEventListener("open", () => setWebsocket(socket)); - socket.addEventListener("message", handleSocketData); + socket.addEventListener("open", () => setWebsocket(socket)); + socket.addEventListener("message", handleSocketData); - connection.current = socket; + connection.current = socket; - return () => connection.current.close(); - }, [useState]); + return () => connection.current.close(); + }, [useState]); - // Grid Layout Section - const [gridEnabled, setGridEnabled] = useState(false); - const [layouts, setLayouts] = useState(); - // On load try to get saved layout - useEffect(() => { - const fetchLayout = async () => { - const response = await fetch("http://" + window.location.host + "/api/layouts"); - const initialLayout: Layouts = await response.json(); - setLayouts(initialLayout); - } - fetchLayout(); - }, []); - // Save layouts at server - const saveLayout = () => { - fetch("http://" + window.location.host + "/api/layouts", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(layouts) - }); - } + // Grid Layout Section + const [gridEnabled, setGridEnabled] = useState(false); + const [layouts, setLayouts] = useState(); + // On load try to get saved layout + useEffect(() => { + const fetchLayout = async () => { + const response = await fetch( + "http://" + window.location.host + "/api/layouts", + ); + const initialLayout: Layouts = await response.json(); + setLayouts(initialLayout); + }; + fetchLayout(); + }, []); + // Save layouts at server + const saveLayout = () => { + fetch("http://" + window.location.host + "/api/layouts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(layouts), + }); + }; - return ( -
-

Yonder Deep Nautilus Dashboard

-
- - -
- - - - - - - - - - - - - - -
- ); + return ( +
+

Yonder Deep Nautilus Dashboard

+
+ + +
+ + + + + + + + + + + + + + +
+ ); } diff --git a/web_app/frontend_gui/src/Grid.tsx b/web_app/frontend_gui/src/Grid.tsx index 84f5847..5564a16 100644 --- a/web_app/frontend_gui/src/Grid.tsx +++ b/web_app/frontend_gui/src/Grid.tsx @@ -1,61 +1,59 @@ -import React from 'react'; -import { Responsive, WidthProvider, Layout, Layouts } from 'react-grid-layout'; +import React from "react"; +import { Responsive, WidthProvider, Layout, Layouts } from "react-grid-layout"; // Import resizable styles -import 'react-grid-layout/css/styles.css'; -import 'react-resizable/css/styles.css'; +import "react-grid-layout/css/styles.css"; +import "react-resizable/css/styles.css"; const ResponsiveGridLayout = WidthProvider(Responsive); export const Grid = ({ - children, - enabled, - layouts, - setLayouts, -}:{ - children: any, // TODO: Figure out typing for react children of children - enabled: boolean, - layouts: Layouts | undefined, - setLayouts: React.Dispatch> - }) => { - - return ( - { - setLayouts(allLayouts); - }} - draggableHandle=".drag-handle" - isDraggable={enabled} - isResizable={enabled} - // The breakpoints & cols chosen for 50px increments - breakpoints={{ lg: 1200, md: 700, sm:500, xs:200, xxs:0 }} - cols={{ lg: 20, md: 14, sm: 10, xs: 4, xxs: 2 }} - rowHeight={50} - resizeHandles={['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne']} - > - {React.Children.map(children, (child, index) => { - if (child.props.coordinates) { - /** The only one w/ coordinates as props is the map - * The map needs to have a defined separate handle to allow - * for movement dragging to conflict with map dragging - * The extra class "map-flex-fix" is for the map to expand correctly - **/ - return ( -
- {child} -
- ) - } - return ( -
- {child} -
- ) - })} - {children} -
- ); + children, + enabled, + layouts, + setLayouts, +}: { + children: any; // TODO: Figure out typing for react children of children + enabled: boolean; + layouts: Layouts | undefined; + setLayouts: React.Dispatch>; +}) => { + return ( + { + setLayouts(allLayouts); + }} + draggableHandle=".drag-handle" + isDraggable={enabled} + isResizable={enabled} + // The breakpoints & cols chosen for 50px increments + breakpoints={{ lg: 1200, md: 700, sm: 500, xs: 200, xxs: 0 }} + cols={{ lg: 20, md: 14, sm: 10, xs: 4, xxs: 2 }} + rowHeight={50} + resizeHandles={["s", "w", "e", "n", "sw", "nw", "se", "ne"]} + > + {React.Children.map(children, (child, index) => { + if (child.props.coordinates) { + /** The only one w/ coordinates as props is the map + * The map needs to have a defined separate handle to allow + * for movement dragging to conflict with map dragging + * The extra class "map-flex-fix" is for the map to expand correctly + **/ + return ( +
+ {child} +
+ ); + } + return ( +
+ {child} +
+ ); + })} + {children} +
+ ); }; - diff --git a/web_app/frontend_gui/src/cssModules.d.ts b/web_app/frontend_gui/src/cssModules.d.ts index 8b00847..f2d12bb 100644 --- a/web_app/frontend_gui/src/cssModules.d.ts +++ b/web_app/frontend_gui/src/cssModules.d.ts @@ -1,4 +1,4 @@ -declare module '*.module.css' { - const classes: { [key: string]: string }; - export default classes; +declare module "*.module.css" { + const classes: { [key: string]: string }; + export default classes; } diff --git a/web_app/frontend_gui/src/index.tsx b/web_app/frontend_gui/src/index.tsx index ec92126..679f993 100644 --- a/web_app/frontend_gui/src/index.tsx +++ b/web_app/frontend_gui/src/index.tsx @@ -1,14 +1,14 @@ -import { scan } from "react-scan" -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' +import { scan } from "react-scan"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; import App from "./App.js"; scan({ - enabled: true, - }); + enabled: true, +}); -createRoot(document.getElementById('root')!).render( - - - , -) +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/web_app/frontend_gui/src/inputs/Forms.tsx b/web_app/frontend_gui/src/inputs/Forms.tsx index 795ef19..9a362a9 100644 --- a/web_app/frontend_gui/src/inputs/Forms.tsx +++ b/web_app/frontend_gui/src/inputs/Forms.tsx @@ -1,184 +1,191 @@ -import React, { useState, useEffect, FC } from 'react'; -import styles from "./forms.module.css" +import React, { useState, useEffect, FC } from "react"; +import styles from "./forms.module.css"; export const ParametersForm = ({ websocket }: any) => { - const [pidAxis, setPidAxis] = useState(''); - const [constantP, setConstantP] = useState(''); - const [constantI, setConstantI] = useState(''); - const [constantD, setConstantD] = useState(''); - const makePidRequest = () => { - if (!pidAxis || !constantP || !constantI || !constantD) { return; } - const pidConstants = { - axis: '' + pidAxis, - p: '' + constantP, - i: '' + constantI, - d: '' + constantD - }; - const request = { - command: "pid", - content: pidConstants - }; - websocket.send(JSON.stringify(request)); + const [pidAxis, setPidAxis] = useState(""); + const [constantP, setConstantP] = useState(""); + const [constantI, setConstantI] = useState(""); + const [constantD, setConstantD] = useState(""); + const makePidRequest = () => { + if (!pidAxis || !constantP || !constantI || !constantD) { + return; } + const pidConstants = { + axis: "" + pidAxis, + p: "" + constantP, + i: "" + constantI, + d: "" + constantD, + }; + const request = { + command: "pid", + content: pidConstants, + }; + websocket.send(JSON.stringify(request)); + }; - return ( -
-

Set PID Constants

-
-
- - setConstantP(e.target.value)} - /> - setConstantI(e.target.value)} - /> - setConstantD(e.target.value)} - /> -
- -
+ return ( +
+

Set PID Constants

+
+
+ + setConstantP(e.target.value)} + /> + setConstantI(e.target.value)} + /> + setConstantD(e.target.value)} + />
- ) -} + +
+
+ ); +}; export const MotorTestForm = ({ websocket }: any) => { - const [motor1, setMotor1] = useState("0"); - const [motor2, setMotor2] = useState("0"); - const [motor3, setMotor3] = useState("0"); - const [motor4, setMotor4] = useState("0"); + const [motor1, setMotor1] = useState("0"); + const [motor2, setMotor2] = useState("0"); + const [motor3, setMotor3] = useState("0"); + const [motor4, setMotor4] = useState("0"); - const makeMotorRequest = (arr: number[] | undefined = undefined) => { - console.log("Making motor request.") - let motorTest = [] - try { - if (arr) motorTest = arr; - else motorTest = [ - parseFloat(motor1), - parseFloat(motor2), - parseFloat(motor3), - parseFloat(motor4), - ]; - } catch { - return - } - const request = { - command: "motor", - content: motorTest - }; - websocket.send(JSON.stringify(request)); + const makeMotorRequest = (arr: number[] | undefined = undefined) => { + console.log("Making motor request."); + let motorTest = []; + try { + if (arr) motorTest = arr; + else + motorTest = [ + parseFloat(motor1), + parseFloat(motor2), + parseFloat(motor3), + parseFloat(motor4), + ]; + } catch { + return; } + const request = { + command: "motor", + content: motorTest, + }; + websocket.send(JSON.stringify(request)); + }; - const zeroOut = () => { - setMotor1("0"); - setMotor2("0"); - setMotor3("0"); - setMotor4("0"); - makeMotorRequest([0,0,0,0]); - } + const zeroOut = () => { + setMotor1("0"); + setMotor2("0"); + setMotor3("0"); + setMotor4("0"); + makeMotorRequest([0, 0, 0, 0]); + }; - const [keymap, setKeymap] = useState(false); + const [keymap, setKeymap] = useState(false); - return ( -
-

Motor Testing

-
-
- setMotor1(e.target.value)} - /> - setMotor2(e.target.value)} - /> - setMotor3(e.target.value)} - /> - setMotor4(e.target.value)} - /> -
-
- - - -
-
+ return ( +
+

Motor Testing

+
+
+ setMotor1(e.target.value)} + /> + setMotor2(e.target.value)} + /> + setMotor3(e.target.value)} + /> + setMotor4(e.target.value)} + /> +
+
+ + +
- ) -} +
+
+ ); +}; export const HeadingTestForm = ({ websocket }: any) => { - const [targetHeading, setTargetHeading] = useState(''); - const headingRequest = () => { - console.log("Making heading request.") - const request = { - command: "control", - content: targetHeading - }; - websocket.send(JSON.stringify(request)); - } + const [targetHeading, setTargetHeading] = useState(""); + const headingRequest = () => { + console.log("Making heading request."); + const request = { + command: "control", + content: targetHeading, + }; + websocket.send(JSON.stringify(request)); + }; - return ( -
-

Heading Test

-
- (setTargetHeading(e.target.value))} - /> - -
-
- ) -} + return ( +
+

Heading Test

+
+ setTargetHeading(e.target.value)} + /> + +
+
+ ); +}; export const StartMission = ({ websocket }: any) => { - const [targetPoint, setTargetPoint] = useState(''); - const missionRequest = () => { - websocket.send(JSON.stringify({ - command: "mission", - content: targetPoint - })) - } + const [targetPoint, setTargetPoint] = useState(""); + const missionRequest = () => { + websocket.send( + JSON.stringify({ + command: "mission", + content: targetPoint, + }), + ); + }; - return ( + return (
-

Start Mission

-
- (setTargetPoint(e.target.value))} - /> - -
+

Start Mission

+
+ setTargetPoint(e.target.value)} + /> + +
- ) -} + ); +}; diff --git a/web_app/frontend_gui/src/inputs/SocketHealth.module.css b/web_app/frontend_gui/src/inputs/SocketHealth.module.css index 6e81f3c..ddf0bdf 100644 --- a/web_app/frontend_gui/src/inputs/SocketHealth.module.css +++ b/web_app/frontend_gui/src/inputs/SocketHealth.module.css @@ -1,22 +1,23 @@ -.socketForm, .socketButtons { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; +.socketForm, +.socketButtons { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; } .socketButtons { - place-items: center; - justify-content: space-around; + place-items: center; + justify-content: space-around; } .socketButtons > button { - margin-left: 0; - padding: 0.5rem; - height: 2.5rem; - width: 2.5rem; + margin-left: 0; + padding: 0.5rem; + height: 2.5rem; + width: 2.5rem; } .socketForm > p { - margin: 0.25rem; + margin: 0.25rem; } diff --git a/web_app/frontend_gui/src/inputs/SocketHealth.tsx b/web_app/frontend_gui/src/inputs/SocketHealth.tsx index 96fe219..49af170 100644 --- a/web_app/frontend_gui/src/inputs/SocketHealth.tsx +++ b/web_app/frontend_gui/src/inputs/SocketHealth.tsx @@ -3,37 +3,39 @@ import styles from "./SocketHealth.module.css"; import { RestartSvg, PingSvg } from "../utils/icons"; export const SocketHealth = ({ websocket }: any) => { - const [status, setStatus] = useState<"open"|"closed"|"unknown">("unknown"); - const restart = () => { - const request = { - command: "websocket", - content: "restart", - }; - websocket.send(JSON.stringify(request)); - } - const ping = () => { - setStatus("unknown"); - const request = { - command: "websocket", - content: "ping", - }; - websocket.send(JSON.stringify(request)); - } + const [status, setStatus] = useState<"open" | "closed" | "unknown">( + "unknown", + ); + const restart = () => { + const request = { + command: "websocket", + content: "restart", + }; + websocket.send(JSON.stringify(request)); + }; + const ping = () => { + setStatus("unknown"); + const request = { + command: "websocket", + content: "ping", + }; + websocket.send(JSON.stringify(request)); + }; - return ( -
-

Websocket Health

-
-

Status: {status}

-
- - -
-
+ return ( +
+

Websocket Health

+
+

Status: {status}

+
+ +
- ) -} +
+
+ ); +}; diff --git a/web_app/frontend_gui/src/inputs/TaskManager.module.css b/web_app/frontend_gui/src/inputs/TaskManager.module.css index fece705..4627fa9 100644 --- a/web_app/frontend_gui/src/inputs/TaskManager.module.css +++ b/web_app/frontend_gui/src/inputs/TaskManager.module.css @@ -1,27 +1,27 @@ .taskOuter { - padding-inline: 0.5rem; - width: 100%; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem; + padding-inline: 0.5rem; + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; } .taskInner { - width: 100%; - padding-inline: 0.5rem; - display: flex; - flex-direction: row; - justify-content: space-between; + width: 100%; + padding-inline: 0.5rem; + display: flex; + flex-direction: row; + justify-content: space-between; } .taskMiddle { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } button.taskButton { - margin: 0.5rem auto; - display: flex; - place-items: center; - gap: 0.5rem; + margin: 0.5rem auto; + display: flex; + place-items: center; + gap: 0.5rem; } diff --git a/web_app/frontend_gui/src/inputs/TaskManager.tsx b/web_app/frontend_gui/src/inputs/TaskManager.tsx index 315628f..2eb5f25 100644 --- a/web_app/frontend_gui/src/inputs/TaskManager.tsx +++ b/web_app/frontend_gui/src/inputs/TaskManager.tsx @@ -1,112 +1,112 @@ -import { useState, useEffect, useCallback, FC } from "react" -import { Tasks, isData } from "../types" -import { RestartSvg } from "../utils/icons" -import styles from "./TaskManager.module.css" +import { useState, useEffect, useCallback, FC } from "react"; +import { Tasks, isData } from "../types"; +import { RestartSvg } from "../utils/icons"; +import styles from "./TaskManager.module.css"; const tryJson = (raw: any): any => { - try { - return JSON.parse(raw) - } - catch { - return undefined - } -} + try { + return JSON.parse(raw); + } catch { + return undefined; + } +}; const useForceUpdate = () => { - const [, setValue] = useState(0); - return useCallback(() => setValue(value => value + 1), []); - } + const [, setValue] = useState(0); + return useCallback(() => setValue((value) => value + 1), []); +}; // Map from task status to displayed text on button const displayMap = { - "unknown": "Unknown", - "active": "Disable", - "inactive": "Enable", -} as const + unknown: "Unknown", + active: "Disable", + inactive: "Enable", +} as const; // Map from current task value to possible action*/ const actionMap = { - "unknown": "info", - "active": "disable", - "inactive": "enable" -} as const + unknown: "info", + active: "disable", + inactive: "enable", +} as const; export const TaskManager: FC<{ websocket: WebSocket }> = ({ websocket }) => { - const forceUpdate = useForceUpdate(); - const [tasks, setTasks] = useState({ - "Localization": "unknown", - "Perception": "unknown", - "Control": "unknown", - "Navigation": "unknown", - }); + const forceUpdate = useForceUpdate(); + const [tasks, setTasks] = useState({ + Localization: "unknown", + Perception: "unknown", + Control: "unknown", + Navigation: "unknown", + }); - useEffect(() => { - if (websocket) - websocket.addEventListener("message", (raw: MessageEvent) => { - const msg = tryJson(raw.data); - if (msg && isData(msg) && msg.type == "tasks") { - const content = msg.content as Tasks; - const newTasks = Object.assign(tasks, content); - setTasks(newTasks); - forceUpdate(); - } - }); - }, [websocket]); - - const manageTask = (taskName: keyof Tasks | undefined) => { - let subcommand: "info" | "enable" | "disable"; - if (taskName) - subcommand = actionMap[tasks[taskName]]; - else - subcommand = "info" - const request = { - command: "tasks", - content: { - sub: subcommand, - task: taskName, - }, + useEffect(() => { + if (websocket) + websocket.addEventListener("message", (raw: MessageEvent) => { + const msg = tryJson(raw.data); + if (msg && isData(msg) && msg.type == "tasks") { + const content = msg.content as Tasks; + const newTasks = Object.assign(tasks, content); + setTasks(newTasks); + forceUpdate(); } - websocket.send(JSON.stringify(request)); - } + }); + }, [websocket]); + + const manageTask = (taskName: keyof Tasks | undefined) => { + let subcommand: "info" | "enable" | "disable"; + if (taskName) subcommand = actionMap[tasks[taskName]]; + else subcommand = "info"; + const request = { + command: "tasks", + content: { + sub: subcommand, + task: taskName, + }, + }; + websocket.send(JSON.stringify(request)); + }; - return ( -
-

Task Manager

-
-
-
-

Localization

- -
-
-

Perception

- -
-
-
-
-

Control

- -
-
-

Navigation

- -
-
-
-
- -
+ return ( +
+

Task Manager

+
+
+
+

Localization

+ +
+
+

Perception

+ +
+
+
+
+

Control

+ +
+
+

Navigation

+ +
- ) -} +
+
+ +
+
+ ); +}; diff --git a/web_app/frontend_gui/src/inputs/forms.module.css b/web_app/frontend_gui/src/inputs/forms.module.css index 54c5bc2..4a6d9b7 100644 --- a/web_app/frontend_gui/src/inputs/forms.module.css +++ b/web_app/frontend_gui/src/inputs/forms.module.css @@ -1,32 +1,34 @@ .formBody { - margin: 1rem 0 0; - display: flex; - flex-direction: column; - align-items: center; + margin: 1rem 0 0; + display: flex; + flex-direction: column; + align-items: center; } .formBody select, input { - margin-bottom: 0.75rem; - border-radius: 0.5rem; - border: 1px solid lightgray; - padding: 0.3rem; - background: #f2f2f2; - text-align: center; + margin-bottom: 0.75rem; + border-radius: 0.5rem; + border: 1px solid lightgray; + padding: 0.3rem; + background: #f2f2f2; + text-align: center; } -.motorForm, .parameterForm { - width: 100%; - display: flex; - flex-direction: row; - justify-content: space-around; - align-items: flex-start; +.motorForm, +.parameterForm { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-around; + align-items: flex-start; } -.motorForm input, .parameterForm input { - width: 5ch; +.motorForm input, +.parameterForm input { + width: 5ch; } .parameterForm { - justify-content: space-between; + justify-content: space-between; } diff --git a/web_app/frontend_gui/src/outputs/map.tsx b/web_app/frontend_gui/src/outputs/map.tsx index f03ed43..a8499fe 100644 --- a/web_app/frontend_gui/src/outputs/map.tsx +++ b/web_app/frontend_gui/src/outputs/map.tsx @@ -1,26 +1,26 @@ -import { useEffect, useRef } from "react" -import * as L from "leaflet" -import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet" -import "../../public/leaflet.css" +import { useEffect, useRef } from "react"; +import * as L from "leaflet"; +import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; +import "../../public/leaflet.css"; -export const Map = ({ coordinates } : { coordinates: number[][]}) => { - const center = L.latLng(32.865249, -117.243515); - const zoom = 13 +export const Map = ({ coordinates }: { coordinates: number[][] }) => { + const center = L.latLng(32.865249, -117.243515); + const zoom = 13; - return ( - <> -

Map

- - - - Origin - - - - ) -} + return ( + <> +

+ Map +

+ + + + Origin + + + + ); +}; diff --git a/web_app/frontend_gui/src/outputs/sim.tsx b/web_app/frontend_gui/src/outputs/sim.tsx index 6159062..83da2d8 100644 --- a/web_app/frontend_gui/src/outputs/sim.tsx +++ b/web_app/frontend_gui/src/outputs/sim.tsx @@ -1,45 +1,44 @@ import { useRef, useState, useEffect } from "react"; -import { - Canvas, - useFrame -} from "@react-three/fiber" -import * as THREE from "three" +import { Canvas, useFrame } from "@react-three/fiber"; +import * as THREE from "three"; THREE.Object3D.DEFAULT_UP.set(0, 0, 1); -const Mesh = ({euler}: {euler: number[]}) => { - const eulerAngles = new THREE.Euler() - let meshRef = useRef(null!); +const Mesh = ({ euler }: { euler: number[] }) => { + const eulerAngles = new THREE.Euler(); + let meshRef = useRef(null!); - useEffect(() => { - eulerAngles.set(euler[0], euler[1], euler[2], "ZYX"); - console.log("Euler Angles: " + JSON.stringify(euler)); - }, [euler]) + useEffect(() => { + eulerAngles.set(euler[0], euler[1], euler[2], "ZYX"); + console.log("Euler Angles: " + JSON.stringify(euler)); + }, [euler]); - useFrame(() => { - const [z, y, x] = eulerAngles.toArray(); - meshRef.current.rotation.set(x, y, z); - }) + useFrame(() => { + const [z, y, x] = eulerAngles.toArray(); + meshRef.current.rotation.set(x, y, z); + }); - return ( - - - - - ) -} + return ( + + + + + ); +}; -export const Simulation = ({euler}: {euler: number[]}) => { - - return ( - <> -

Visualization

- - - - - - - - ) -} +export const Simulation = ({ euler }: { euler: number[] }) => { + return ( + <> +

Visualization

+ + + + + + + + ); +}; diff --git a/web_app/frontend_gui/src/outputs/status.module.css b/web_app/frontend_gui/src/outputs/status.module.css index 9216379..61ee515 100644 --- a/web_app/frontend_gui/src/outputs/status.module.css +++ b/web_app/frontend_gui/src/outputs/status.module.css @@ -1,39 +1,38 @@ .StatusMessages { - height: 100%; - width: 100%; - border-radius: 1rem; + height: 100%; + width: 100%; + border-radius: 1rem; - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .StatusMessages > h2 { - flex-grow: 0; - margin: 0 0 1rem; - text-align: center; + flex-grow: 0; + margin: 0 0 1rem; + text-align: center; } .StatusMessages > ul { - flex-grow: 1; - border-radius: 1rem; - margin: 0; - padding: 1rem; - background-color: black; - color: white; - overflow-y: scroll; + flex-grow: 1; + border-radius: 1rem; + margin: 0; + padding: 1rem; + background-color: black; + color: white; + overflow-y: scroll; } .StatusMessages li { - border-top: 1px solid grey; + border-top: 1px solid grey; } .StatusMessage pre { - margin: 0.5rem; + margin: 0.5rem; } .StatusMessages > button { - flex-grow: 0; - margin: 1rem auto 0; - width: fit-content; + flex-grow: 0; + margin: 1rem auto 0; + width: fit-content; } - diff --git a/web_app/frontend_gui/src/outputs/status.tsx b/web_app/frontend_gui/src/outputs/status.tsx index 9bf8b62..fb2e128 100644 --- a/web_app/frontend_gui/src/outputs/status.tsx +++ b/web_app/frontend_gui/src/outputs/status.tsx @@ -1,75 +1,78 @@ -import React, { useRef, useEffect, useState} from "react" +import React, { useRef, useEffect, useState } from "react"; -import styles from "./status.module.css" +import styles from "./status.module.css"; interface dataPart { - id: number, - title: string, - value: string + id: number; + title: string; + value: string; } -export const StatusItem = ({ statusType, statusData }:any) => { - const statusListItems = statusData.map((dataPart: dataPart) => -
  • {dataPart.title}: {dataPart.value}
  • - ) +export const StatusItem = ({ statusType, statusData }: any) => { + const statusListItems = statusData.map((dataPart: dataPart) => ( +
  • + {dataPart.title}: {dataPart.value} +
  • + )); - return ( -
    -

    {statusType}

    -
      {statusListItems}
    -
    - ) -} + return ( +
    +

    {statusType}

    +
      {statusListItems}
    +
    + ); +}; export const StatusMessages = ({ - statusMessages, - setStatusMessages + statusMessages, + setStatusMessages, }: { - statusMessages: string[], - setStatusMessages: React.Dispatch> - }) => { - const listRef = useRef(undefined!); - const [doScroll, setDoScroll] = useState(); + statusMessages: string[]; + setStatusMessages: React.Dispatch>; +}) => { + const listRef = useRef(undefined!); + const [doScroll, setDoScroll] = useState(); - const clearMessages = () => { - setStatusMessages([]); - }; + const clearMessages = () => { + setStatusMessages([]); + }; - const checkScroll = () => { - const list = listRef.current; - setDoScroll(list.scrollHeight - list.clientHeight <= list.scrollTop); - }; + const checkScroll = () => { + const list = listRef.current; + setDoScroll(list.scrollHeight - list.clientHeight <= list.scrollTop); + }; - useEffect(() => { // Check for scroll decoupled from react render loop - listRef.current.addEventListener("scroll", checkScroll); - }); + useEffect(() => { + // Check for scroll decoupled from react render loop + listRef.current.addEventListener("scroll", checkScroll); + }); - useEffect(() => { // This bit is for autoscroll (50px as a buffer to bottom of scroll container) - const list = listRef.current; - if(doScroll) { - list.scrollTop = list.scrollHeight - list.clientHeight; - } - checkScroll(); - }, [statusMessages]); + useEffect(() => { + // This bit is for autoscroll (50px as a buffer to bottom of scroll container) + const list = listRef.current; + if (doScroll) { + list.scrollTop = list.scrollHeight - list.clientHeight; + } + checkScroll(); + }, [statusMessages]); - return ( -
    -

    Status Messages

    -
      - {statusMessages?.map((message: any, index: any) => { - try { - const newMessage: any = JSON.parse(message); - message = JSON.stringify(newMessage, null, 2); - } - catch {} - return ( -
    • -
      {message}
      -
    • - ) - })} -
    - -
    - ) -} + return ( +
    +

    Status Messages

    +
      + {statusMessages?.map((message: any, index: any) => { + try { + const newMessage: any = JSON.parse(message); + message = JSON.stringify(newMessage, null, 2); + } catch {} + return ( +
    • +
      {message}
      +
    • + ); + })} +
    + +
    + ); +}; diff --git a/web_app/frontend_gui/src/outputs/video.module.css b/web_app/frontend_gui/src/outputs/video.module.css index fc89a6b..b16e771 100644 --- a/web_app/frontend_gui/src/outputs/video.module.css +++ b/web_app/frontend_gui/src/outputs/video.module.css @@ -1,11 +1,11 @@ .videoContainer { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .video { - margin: auto; - border-radius: 1rem; - display: block; - height: 100%; + margin: auto; + border-radius: 1rem; + display: block; + height: 100%; } diff --git a/web_app/frontend_gui/src/outputs/video.tsx b/web_app/frontend_gui/src/outputs/video.tsx index 2ce8fff..57ad76e 100644 --- a/web_app/frontend_gui/src/outputs/video.tsx +++ b/web_app/frontend_gui/src/outputs/video.tsx @@ -1,14 +1,14 @@ -import styles from "./video.module.css" +import styles from "./video.module.css"; export const VideoStream = () => { - return ( -
    -

    Video Stream

    - Video stream not enabled or not available -
    - ) -} + return ( +
    +

    Video Stream

    + Video stream not enabled or not available +
    + ); +}; diff --git a/web_app/frontend_gui/src/types.ts b/web_app/frontend_gui/src/types.ts index 0693fb8..105cd61 100644 --- a/web_app/frontend_gui/src/types.ts +++ b/web_app/frontend_gui/src/types.ts @@ -1,61 +1,63 @@ export interface Command { - command: string - content: any - ack?: boolean + command: string; + content: any; + ack?: boolean; } export const isCommand = (obj: any): obj is Command => { - return ( - typeof obj === 'object' && - obj !== null && - typeof obj.command === 'string' && - typeof obj.content === 'number' - ) -} + return ( + typeof obj === "object" && + obj !== null && + typeof obj.command === "string" && + typeof obj.content === "number" + ); +}; export interface State { - position: number[] - velocity: number[] - attitude: number[] - angular_velocity: number[] + position: number[]; + velocity: number[]; + attitude: number[]; + angular_velocity: number[]; } const isNumberArray = (value: any): value is number[] => { - return Array.isArray(value) && value.every((item) => typeof item === "number"); -} + return ( + Array.isArray(value) && value.every((item) => typeof item === "number") + ); +}; export const isState = (obj: any): obj is State => { - return ( - typeof obj === 'object' && - obj !== null && - isNumberArray(obj.position) && - isNumberArray(obj.velocity) && - isNumberArray(obj.attitude) && - isNumberArray(obj.angular_velocity) //&& - ) -} + return ( + typeof obj === "object" && + obj !== null && + isNumberArray(obj.position) && + isNumberArray(obj.velocity) && + isNumberArray(obj.attitude) && + isNumberArray(obj.angular_velocity) //&& + ); +}; export type Status = "unknown" | "active" | "inactive"; export interface Tasks { - "Localization": Status, - "Perception": Status, - "Control": Status, - "Navigation": Status, + Localization: Status; + Perception: Status; + Control: Status; + Navigation: Status; } export interface Data { - source: string - type: string - content: Tasks | State | string -} + source: string; + type: string; + content: Tasks | State | string; +} export const isData = (obj: any): obj is Data => { - return ( - typeof obj === 'object' && - obj !== null && - typeof obj.source === 'string' && - typeof obj.type === 'string' && - (typeof obj.content === 'object' || typeof obj.content === 'string') - ) -} + return ( + typeof obj === "object" && + obj !== null && + typeof obj.source === "string" && + typeof obj.type === "string" && + (typeof obj.content === "object" || typeof obj.content === "string") + ); +}; diff --git a/web_app/frontend_gui/src/utils/icons.tsx b/web_app/frontend_gui/src/utils/icons.tsx index ad8bcc9..b1e47ab 100644 --- a/web_app/frontend_gui/src/utils/icons.tsx +++ b/web_app/frontend_gui/src/utils/icons.tsx @@ -1,41 +1,76 @@ -import { CSSProperties } from "react" +import { CSSProperties } from "react"; // This is a function so that the svg color can be reactive -export const GridSvg = ({ color="#1f1f1f" }: { color?: CSSProperties["color"]})=> { - - return ( - - { + return ( + + - - ) -} + 400h160v-160H600v160Zm-400 0h160v-160H200v160Zm400-400Zm0 240Zm-240 0Zm0-240Z" + /> + + ); +}; export const RestartSvg = () => { - return ( - - - - ) -} + return ( + + + + ); +}; export const CommandKeySvg = () => { - return ( - - - - ) -} + return ( + + + + ); +}; export const PingSvg = () => ( - - - -) + + + +); export const SaveSvg = () => ( - - - -) + + + +); diff --git a/web_app/frontend_gui/vite.config.js b/web_app/frontend_gui/vite.config.js index c1e38f2..275c9b7 100644 --- a/web_app/frontend_gui/vite.config.js +++ b/web_app/frontend_gui/vite.config.js @@ -1,10 +1,10 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vite.dev/config/ export default defineConfig({ - build: { - sourcemap: true, - }, - plugins: [react()], -}) + build: { + sourcemap: true, + }, + plugins: [react()], +}); diff --git a/web_app/radio_setup/README.md b/web_app/radio_setup/README.md index 62fa8d1..9b510c5 100644 --- a/web_app/radio_setup/README.md +++ b/web_app/radio_setup/README.md @@ -1,26 +1,26 @@ -# PPP Connection Instructions -This directory is for creating the ppp (point to point protocol) connection over the USB radio between the base station and the AUV. We will be using the [pppd (point-to-point protocol Daemon)](https://linux.die.net/man/8/pppd). +# PPP Connection Instructions +This directory is for creating the ppp (point to point protocol) connection over the USB radio between the base station and the AUV. We will be using the [pppd (point-to-point protocol Daemon)](https://linux.die.net/man/8/pppd). -Currently, we support setting up a ppp connection only for Windows (WSL) and MacOS. +Currently, we support setting up a ppp connection only for Windows (WSL) and MacOS. -## Windows (WSL) -- Connect your radio to a serial port. -- Use [usbipd](https://learn.microsoft.com/en-us/windows/wsl/connect-usb) to attach the radio to WSL so that you can access it in WSL. +## Windows (WSL) +- Connect your radio to a serial port. +- Use [usbipd](https://learn.microsoft.com/en-us/windows/wsl/connect-usb) to attach the radio to WSL so that you can access it in WSL. - You can check if the radio is detected in WSL by running `lsusb` - First, on the AUV-side (Raspberry Pi) start a pppd connection with `sudo pppd [path to radio] 57600 lock local noauth 192.168.100.11:192.168.100.10` - - `[path to radio]` is often `/dev/ttyUSB0` - - `57600` = baud rate - - `lock` = Ensures exclusive access to the device that uses this connection + - `[path to radio]` is often `/dev/ttyUSB0` + - `57600` = baud rate + - `lock` = Ensures exclusive access to the device that uses this connection - `local` = non-modem - - `noauth` = don't require authentication + - `noauth` = don't require authentication - `IP1 : IP2` = local IP : remote IP -- Then, on the base station (WSL), connect to the pi with `sudo pppd [path to radio] 57600 lock local noauth nodetach 192.168.100.10:192.168.100.11` +- Then, on the base station (WSL), connect to the pi with `sudo pppd [path to radio] 57600 lock local noauth nodetach 192.168.100.10:192.168.100.11` - `nodetach` = don't run the command in the background (i.e. it's fine if the terminal gets locked on this process) -- You will know that the connection is successful, if the devices and IPs appear in your WSL terminal +- You will know that the connection is successful, if the devices and IPs appear in your WSL terminal -Some useful commands: -- `lsusb` = see what serial devices are detected by WSL -- `ps aux | grep pppd` = checks if a pppd process is running +Some useful commands: +- `lsusb` = see what serial devices are detected by WSL +- `ps aux | grep pppd` = checks if a pppd process is running ## MacOS #### Conceptually: diff --git a/web_app/radio_setup/radio.m b/web_app/radio_setup/radio.m index d44061f..5589db1 100644 --- a/web_app/radio_setup/radio.m +++ b/web_app/radio_setup/radio.m @@ -25,7 +25,7 @@ &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(matchingUSBDict, CFSTR(kIOPropertyMatchKey), subDict); - + //NSLog(@"Matching Dict:"); //NSLog(@"%@", matchingUSBDict); @@ -34,7 +34,7 @@ matchedService = IOServiceGetMatchingService(kIOMainPortDefault, matchingUSBDict); //NSLog(@"Service: %u", matchedService); - + // Service path, not really that useful for our purposes io_string_t servicePath; IORegistryEntryGetPath(matchedService, kIOServicePlane, servicePath); @@ -54,7 +54,7 @@ size_t devPathLength; Boolean gotString = false; - /* This commented part is from the Apple docs but always comes up null + /* This commented part is from the Apple docs but always comes up null CFTypeRef deviceNameAsCFString; deviceNameAsCFString = (CFStringRef) IORegistryEntrySearchCFProperty ( matchedService, @@ -75,7 +75,7 @@ deviceFilePath + strlen(deviceFilePath), MAXPATHLEN - strlen(deviceFilePath), kCFStringEncodingASCII); - + if (gotString) { //NSLog(@"Device file path: %s", deviceFilePath); char* finalResult = malloc(strlen(deviceFilePath)); @@ -111,7 +111,7 @@ int main() { NSString *radioPath = [NSString stringWithUTF8String:result]; free(result); - + if (radioPath && ![radioPath isEqual: @"Radio device not found"]) { NSLog(@"Calling pppd for device at %@", radioPath); NSTask *task = [[NSTask alloc] init]; diff --git a/web_app/radio_setup/radio.py b/web_app/radio_setup/radio.py index 9fd3c45..4a5dd1f 100644 --- a/web_app/radio_setup/radio.py +++ b/web_app/radio_setup/radio.py @@ -26,4 +26,4 @@ print("Named path: " ) # Launch pppd - subprocess("pppd", "call", "usbRadio") \ No newline at end of file + subprocess("pppd", "call", "usbRadio") diff --git a/web_app/startup_win.bat b/web_app/startup_win.bat index 94396a3..69f3994 100644 --- a/web_app/startup_win.bat +++ b/web_app/startup_win.bat @@ -1,2 +1,2 @@ echo "Starting basestation web app..." -python app.py \ No newline at end of file +python app.py diff --git a/web_app/tests/__init__.py b/web_app/tests/__init__.py new file mode 100644 index 0000000..a8a65ec --- /dev/null +++ b/web_app/tests/__init__.py @@ -0,0 +1 @@ +# Backend tests