diff --git a/.envrc b/.envrc deleted file mode 100644 index 6bc8abe38..000000000 --- a/.envrc +++ /dev/null @@ -1,11 +0,0 @@ -# Hyperloop H10 Development Environment -# -# This file is used by direnv to automatically load the development environment -# when you enter this directory. -# -# To use: -# 1. Install direnv: https://direnv.net/ -# 2. Run: direnv allow -# -# For pure shell (default): -use nix diff --git a/.github/ex_workflows/build-backend.yaml b/.github/ex_workflows/build-backend.yaml deleted file mode 100644 index 663769996..000000000 --- a/.github/ex_workflows/build-backend.yaml +++ /dev/null @@ -1,134 +0,0 @@ -name: Build backend - -on: - workflow_dispatch: - pull_request: - paths: - - backend/** - -jobs: - build-backend-linux: - name: "Build backend for linux" - runs-on: ubuntu-latest - - # Runs on alpine because it is easier to statically link the library - container: - image: golang:alpine - - env: - BACKEND_DIR: ./backend - - steps: - - name: "Install packages" - run: apk update && apk add --no-cache libpcap-dev musl-dev gcc go - - - uses: actions/checkout@v4 - with: - sparse-checkout: backend - - - name: "Create output path" - working-directory: "${{env.BACKEND_DIR}}" - run: mkdir ./output - - - name: "Build (64 bit)" - working-directory: "${{env.BACKEND_DIR}}/cmd" - env: - CGO_ENABLED: 1 - GOARCH: amd64 - GOOS: linux - run: | - go build -ldflags '-linkmode external -extldflags "-static"' -o ../output/backend-linux-64 - - - name: "Upload build" - uses: actions/upload-artifact@v4 - with: - name: backend-linux - path: "${{env.BACKEND_DIR}}/output/*" - retention-days: 3 - compression-level: 9 - - build-backend-windows: - name: "Build backend for windows" - runs-on: windows-latest - - env: - BACKEND_DIR: ".\\backend" - - steps: - - uses: actions/checkout@v3 - - - name: "Setup Go" - uses: actions/setup-go@v4 - with: - go-version: "1.21.3" - cache-dependency-path: "${{env.BACKEND_DIR}}\\go.sum" - - - name: "Create output path" - working-directory: "${{env.BACKEND_DIR}}" - run: mkdir .\output - - - name: "Build (64 bit)" - working-directory: "${{env.BACKEND_DIR}}\\cmd" - env: - CGO_ENABLED: 1 - GOARCH: amd64 - GOOS: windows - run: | - go build -o ..\output\backend-windows-64.exe - - - name: "Upload build" - uses: actions/upload-artifact@v4 - with: - name: backend-windows - path: "${{env.BACKEND_DIR}}\\output\\*" - retention-days: 3 - compression-level: 9 - - build-backend-mac: - name: "Build backend for macOS" - runs-on: macos-latest - - env: - BACKEND_DIR: ./backend - - steps: - - name: "Install packages" - run: brew install libpcap - - - name: "Setup Go" - uses: actions/setup-go@v4 - with: - go-version: "1.21.3" - cache-dependency-path: "${{env.BACKEND_DIR}}/go.sum" - - - uses: actions/checkout@v3 - - - name: "Create output path" - working-directory: "${{env.BACKEND_DIR}}" - run: mkdir ./output - - - name: "Build (64 bit)" - working-directory: "${{env.BACKEND_DIR}}/cmd" - env: - CGO_ENABLED: 1 - GOARCH: amd64 - GOOS: darwin - run: | - go build -o ../output/backend-macos-64 - - - name: "Build (apple 64 bit)" - working-directory: "${{env.BACKEND_DIR}}/cmd" - env: - CGO_ENABLED: 1 - GOARCH: arm64 - GOOS: darwin - run: | - go build -o ../output/backend-macos-m1-64 - - - name: "Upload build" - uses: actions/upload-artifact@v4 - with: - name: backend-macos - path: "${{env.BACKEND_DIR}}/output/*" - retention-days: 3 - compression-level: 9 diff --git a/.github/ex_workflows/build-control-station.yaml b/.github/ex_workflows/build-control-station.yaml deleted file mode 100644 index 265707b12..000000000 --- a/.github/ex_workflows/build-control-station.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: Build control station - -on: - workflow_dispatch: - pull_request: - paths: - - control-station/** - - common-front/** - -jobs: - build-control-station: - name: 'Build control station' - runs-on: ubuntu-latest - - env: - FRONTEND_DIR: ./control-station - COMMON_DIR: ./common-front - - steps: - - uses: actions/checkout@v4 - with: - sparse-checkout: | - control-station - common-front - - - name: 'Install common front dependencies' - working-directory: '${{env.COMMON_DIR}}' - run: npm install - - - name: 'Build common front' - working-directory: '${{env.COMMON_DIR}}' - run: npm run build - - - name: 'Install control station dependencies' - working-directory: '${{env.FRONTEND_DIR}}' - run: npm install - - - name: 'Build control station' - working-directory: '${{env.FRONTEND_DIR}}' - run: npm run build - - - name: 'Upload build' - uses: actions/upload-artifact@v4 - with: - name: control-station - path: '${{env.FRONTEND_DIR}}/static/*' - retention-days: 3 - compression-level: 9 diff --git a/.github/ex_workflows/build-ethernet-view.yaml b/.github/ex_workflows/build-ethernet-view.yaml deleted file mode 100644 index face89659..000000000 --- a/.github/ex_workflows/build-ethernet-view.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: Build ethernet view - -on: - workflow_dispatch: - pull_request: - paths: - - ethernet-view/** - - common-front/** - -jobs: - build-ethernet-view: - name: "Build ethernet view" - runs-on: ubuntu-latest - - env: - FRONTEND_DIR: ./ethernet-view - COMMON_DIR: ./common-front - - steps: - - uses: actions/checkout@v4 - with: - sparse-checkout: | - ethernet-view - common-front - - - name: "Install common front dependencies" - working-directory: "${{env.COMMON_DIR}}" - run: npm install - - - - name: "Build common front" - working-directory: "${{env.COMMON_DIR}}" - run: npm run build - - - name: "Install ethernet view dependencies" - working-directory: "${{env.FRONTEND_DIR}}" - run: npm install - - - - name: "Build ethernet view" - working-directory: "${{env.FRONTEND_DIR}}" - run: npm run build - - - - name: "Upload build" - uses: actions/upload-artifact@v4 - with: - name: ethernet-view - path: "${{env.FRONTEND_DIR}}/static/*" - retention-days: 3 - compression-level: 9 diff --git a/.github/ex_workflows/build-updater.yaml b/.github/ex_workflows/build-updater.yaml deleted file mode 100644 index ce9af5e6b..000000000 --- a/.github/ex_workflows/build-updater.yaml +++ /dev/null @@ -1,131 +0,0 @@ -name: Build updater - -on: - workflow_dispatch: - pull_request: - paths: - - updater/** - -jobs: - build-updater-linux: - name: Build updater for Linux - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Mark workspace as safe - run: git config --global --add safe.directory $GITHUB_WORKSPACE - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.21.3" - cache-dependency-path: updater/go.sum - - - name: Ensure dependencies - working-directory: updater - run: go mod tidy - - - name: Make output dir - run: mkdir -p updater/output - - - name: Build (linux/amd64) - run: | - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -o updater/output/updater-linux-64 ./updater - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: updater-linux - path: updater/output/* - - build-updater-windows: - name: Build updater for Windows - runs-on: windows-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Mark workspace as safe - shell: pwsh - run: git config --global --add safe.directory $Env:GITHUB_WORKSPACE - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.21.3" - cache-dependency-path: updater\go.sum - - - name: Ensure dependencies - working-directory: updater - shell: pwsh - run: go mod tidy - - - name: Make output dir - shell: pwsh - run: New-Item -ItemType Directory -Path updater\output -Force - - - name: Build (windows/amd64) - shell: pwsh - run: | - $Env:CGO_ENABLED='0' - go build -o updater\output\updater-windows-64.exe ./updater - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: updater-windows - path: updater\output\* - - build-updater-mac: - name: Build updater for macOS - runs-on: macos-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Mark workspace as safe - run: git config --global --add safe.directory $GITHUB_WORKSPACE - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.21.3" - cache-dependency-path: updater/go.sum - - - name: Ensure dependencies - working-directory: updater - run: go mod tidy - - - name: Make output dir - run: mkdir -p updater/output - - - name: Build (macOS Intel) - run: | - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 \ - go build -o updater/output/updater-macos-64 ./updater - - - name: Build (macOS ARM64) - run: | - CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \ - go build -o updater/output/updater-macos-m1-64 ./updater - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: updater-macos - path: updater/output/* diff --git a/.github/ex_workflows/release.yaml b/.github/ex_workflows/release.yaml deleted file mode 100644 index ce8c2c6ea..000000000 --- a/.github/ex_workflows/release.yaml +++ /dev/null @@ -1,595 +0,0 @@ -name: Release - -on: - workflow_dispatch: - inputs: - version: - description: 'Release version' - required: true - default: '' - draft: - description: 'Create as draft release' - type: boolean - default: true - release: - types: [created] - -jobs: - build-backend: - name: Build Backend - runs-on: ${{ matrix.os }} - container: ${{ matrix.container }} - strategy: - matrix: - include: - - os: ubuntu-latest - name: linux - container: - image: golang:alpine - setup: | - apk update && apk add --no-cache libpcap-dev musl-dev gcc go bash - build_cmd: | - cd backend/cmd - CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -ldflags '-linkmode external -extldflags "-static"' -o ../output/backend-linux-amd64 - artifact_name: backend-linux - - - os: windows-latest - name: windows - setup: | - echo "Setting up Windows environment" - build_cmd: | - cd backend\cmd - $env:CGO_ENABLED="1" - $env:GOARCH="amd64" - $env:GOOS="windows" - go build -o ..\output\backend-windows-amd64.exe - artifact_name: backend-windows - - - os: macos-latest - name: macos - setup: | - brew install libpcap - build_cmd: | - cd backend/cmd - CGO_ENABLED=1 GOARCH=amd64 GOOS=darwin go build -o ../output/backend-macos-amd64 - CGO_ENABLED=1 GOARCH=arm64 GOOS=darwin go build -o ../output/backend-macos-arm64 - artifact_name: backend-macos - - steps: - - uses: actions/checkout@v4 - - - name: Setup environment - run: ${{ matrix.setup }} - - - name: Setup Go - if: matrix.os != 'ubuntu-latest' - uses: actions/setup-go@v4 - with: - go-version: "1.21.3" - cache-dependency-path: backend/go.sum - - - name: Create output directory (Linux) - if: matrix.os == 'ubuntu-latest' - run: mkdir -p backend/output - shell: sh - - - name: Create output directory (macOS) - if: matrix.os == 'macos-latest' - run: mkdir -p backend/output - shell: bash - - - name: Create output directory (Windows) - if: matrix.os == 'windows-latest' - run: mkdir -p backend/output - shell: bash - - - name: Build backend (Linux) - if: matrix.os == 'ubuntu-latest' - run: ${{ matrix.build_cmd }} - shell: sh - - - name: Build backend (macOS) - if: matrix.os == 'macos-latest' - run: ${{ matrix.build_cmd }} - shell: bash - - - name: Build backend (Windows) - if: matrix.os == 'windows-latest' - run: ${{ matrix.build_cmd }} - shell: pwsh - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.artifact_name }} - path: backend/output/* - retention-days: 7 - compression-level: 9 - - build-frontend: - name: Build Frontends - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - cache: 'npm' - - - name: Build common front dependencies - working-directory: ./common-front - run: | - npm install - npm run build - - - name: Build ethernet view - working-directory: ./ethernet-view - run: | - npm install - npm run build - - - name: Upload ethernet view artifact - uses: actions/upload-artifact@v4 - with: - name: ethernet-view - path: ethernet-view/static/* - retention-days: 7 - compression-level: 9 - - - name: Build control station - working-directory: ./control-station - run: | - npm install - npm run build - - - name: Upload control station artifact - uses: actions/upload-artifact@v4 - with: - name: control-station - path: control-station/static/* - retention-days: 7 - compression-level: 9 - - build-updater: - name: Build Updater - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: ubuntu-latest - name: linux - build_cmd: | - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o updater/output/updater-linux-amd64 ./updater - artifact_name: updater-linux - - - os: windows-latest - name: windows - build_cmd: | - $Env:CGO_ENABLED='0' - $Env:GOOS='windows' - $Env:GOARCH='amd64' - go build -o updater\output\updater-windows-amd64.exe ./updater - artifact_name: updater-windows - - - os: macos-latest - name: macos - build_cmd: | - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o updater/output/updater-macos-amd64 ./updater - CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o updater/output/updater-macos-arm64 ./updater - artifact_name: updater-macos - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Mark workspace as safe - run: git config --global --add safe.directory $GITHUB_WORKSPACE - shell: bash - - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: "1.21.3" - cache-dependency-path: updater/go.sum - - - name: Ensure dependencies (Linux) - if: matrix.os == 'ubuntu-latest' - working-directory: updater - run: go mod tidy - shell: bash - - - name: Ensure dependencies (macOS) - if: matrix.os == 'macos-latest' - working-directory: updater - run: go mod tidy - shell: bash - - - name: Ensure dependencies (Windows) - if: matrix.os == 'windows-latest' - working-directory: updater - run: go mod tidy - shell: pwsh - - - name: Create output directory (Linux) - if: matrix.os == 'ubuntu-latest' - run: mkdir -p updater/output - shell: bash - - - name: Create output directory (macOS) - if: matrix.os == 'macos-latest' - run: mkdir -p updater/output - shell: bash - - - name: Create output directory (Windows) - if: matrix.os == 'windows-latest' - run: mkdir -p updater/output - shell: bash - - - name: Build updater (Linux) - if: matrix.os == 'ubuntu-latest' - run: ${{ matrix.build_cmd }} - shell: bash - - - name: Build updater (macOS) - if: matrix.os == 'macos-latest' - run: ${{ matrix.build_cmd }} - shell: bash - - - name: Build updater (Windows) - if: matrix.os == 'windows-latest' - run: ${{ matrix.build_cmd }} - shell: pwsh - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.artifact_name }} - path: updater/output/* - retention-days: 7 - compression-level: 9 - - build-testadj: - name: Build testadj executable - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: ubuntu-latest - name: linux - setup: | - python3 -m pip install pyinstaller - build_cmd: | - cd backend/cmd - pyinstaller --onefile --name testadj-linux testadj.py - artifact_name: testadj-linux - artifact_path: backend/cmd/dist/testadj-linux - - - os: windows-latest - name: windows - setup: | - python -m pip install pyinstaller - build_cmd: | - cd backend\cmd - pyinstaller --onefile --name testadj-windows testadj.py - artifact_name: testadj-windows - artifact_path: backend\cmd\dist\testadj-windows.exe - - - os: macos-latest - name: macos - setup: | - python3 -m pip install pyinstaller - build_cmd: | - cd backend/cmd - pyinstaller --onefile --name testadj-macos testadj.py - artifact_name: testadj-macos - artifact_path: backend/cmd/dist/testadj-macos - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - - name: Setup environment - run: ${{ matrix.setup }} - shell: bash - - - name: Build testadj (Linux) - if: matrix.os == 'ubuntu-latest' - run: ${{ matrix.build_cmd }} - shell: bash - - - name: Build testadj (macOS) - if: matrix.os == 'macos-latest' - run: ${{ matrix.build_cmd }} - shell: bash - - - name: Build testadj (Windows) - if: matrix.os == 'windows-latest' - run: ${{ matrix.build_cmd }} - shell: pwsh - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.artifact_name }} - path: ${{ matrix.artifact_path }} - retention-days: 7 - compression-level: 9 - - prepare-common-files: - name: Prepare Common Files - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Create common files directory - run: mkdir -p common-files - - - name: Copy config.toml - run: cp backend/cmd/config.toml common-files/ - - - - name: Copy README.md - run: cp README.md common-files/ - - - name: Create VERSION.txt - run: | - VERSION="${{ github.event.inputs.version }}" - echo "$VERSION" > common-files/VERSION.txt - - - name: Upload common files artifact - uses: actions/upload-artifact@v4 - with: - name: common-files - path: common-files/* - retention-days: 7 - compression-level: 9 - - package-release: - name: Package Release - needs: [build-backend, build-frontend, build-updater, build-testadj, prepare-common-files] - runs-on: ubuntu-latest - steps: - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - - - name: Create release directories for each platform - run: | - VERSION="${{ github.event.inputs.version }}" - - # Create directory structure for each platform - mkdir -p release-linux - mkdir -p release-windows - mkdir -p release-macos - mkdir -p release-macos-arm64 - - - name: Organize Linux release files - run: | - VERSION="${{ github.event.inputs.version }}" - - # Copy Linux backend - cp artifacts/backend-linux/backend-linux-amd64 release-linux/backend - - # Copy Linux updater - cp artifacts/updater-linux/updater-linux-amd64 release-linux/updater - - # Copy Linux testadj - cp artifacts/testadj-linux/testadj-linux release-linux/testadj - - # Copy frontends - mkdir -p release-linux/ethernet-view - mkdir -p release-linux/control-station - cp -r artifacts/ethernet-view/* release-linux/ethernet-view/ - cp -r artifacts/control-station/* release-linux/control-station/ - - # Copy common files - cp -r artifacts/common-files/* release-linux/ - - # Set executable permissions - chmod +x release-linux/backend release-linux/updater release-linux/testadj - - # Create Linux release archive - cd release-linux - tar -czf ../linux-$VERSION.tar.gz . - - - name: Organize Windows release files - run: | - VERSION="${{ github.event.inputs.version }}" - - # Copy Windows backend - cp artifacts/backend-windows/backend-windows-amd64.exe release-windows/backend.exe - - # Copy Windows updater - cp artifacts/updater-windows/updater-windows-amd64.exe release-windows/updater.exe - - # Copy Windows testadj - cp artifacts/testadj-windows/testadj-windows.exe release-windows/testadj.exe - - # Copy frontends - mkdir -p release-windows/ethernet-view - mkdir -p release-windows/control-station - cp -r artifacts/ethernet-view/* release-windows/ethernet-view/ - cp -r artifacts/control-station/* release-windows/control-station/ - - # Copy common files - cp -r artifacts/common-files/* release-windows/ - - # Create Windows release archive - cd release-windows - zip -r ../windows-$VERSION.zip . - - - name: Organize macOS Intel release files - run: | - VERSION="${{ github.event.inputs.version }}" - - # Copy macOS Intel backend - cp artifacts/backend-macos/backend-macos-amd64 release-macos/backend - - # Copy macOS Intel updater - cp artifacts/updater-macos/updater-macos-amd64 release-macos/updater - - # Copy macOS testadj - cp artifacts/testadj-macos/testadj-macos release-macos/testadj - - # Copy frontends - mkdir -p release-macos/ethernet-view - mkdir -p release-macos/control-station - cp -r artifacts/ethernet-view/* release-macos/ethernet-view/ - cp -r artifacts/control-station/* release-macos/control-station/ - - # Copy common files - cp -r artifacts/common-files/* release-macos/ - - # Set executable permissions - chmod +x release-macos/backend release-macos/updater release-macos/testadj - - # Create macOS Intel release archive - cd release-macos - tar -czf ../macos-intel-$VERSION.tar.gz . - - - name: Organize macOS ARM64 release files - run: | - VERSION="${{ github.event.inputs.version }}" - - # Copy macOS ARM64 backend - cp artifacts/backend-macos/backend-macos-arm64 release-macos-arm64/backend - - # Copy macOS ARM64 updater - cp artifacts/updater-macos/updater-macos-arm64 release-macos-arm64/updater - - # Copy macOS testadj - cp artifacts/testadj-macos/testadj-macos release-macos-arm64/testadj - - # Copy frontends - mkdir -p release-macos-arm64/ethernet-view - mkdir -p release-macos-arm64/control-station - cp -r artifacts/ethernet-view/* release-macos-arm64/ethernet-view/ - cp -r artifacts/control-station/* release-macos-arm64/control-station/ - - # Copy common files - cp -r artifacts/common-files/* release-macos-arm64/ - - # Set executable permissions - chmod +x release-macos-arm64/backend release-macos-arm64/updater release-macos-arm64/testadj - - # Create macOS ARM64 release archive - cd release-macos-arm64 - tar -czf ../macos-arm64-$VERSION.tar.gz . - - - name: Upload release packages - uses: actions/upload-artifact@v4 - with: - name: releases - path: | - *.tar.gz - *.zip - retention-days: 7 - compression-level: 9 - - - name: Create Release - if: github.event_name == 'workflow_dispatch' - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v${{ github.event.inputs.version }} - release_name: Release ${{ github.event.inputs.version }} - draft: ${{ github.event.inputs.draft }} - prerelease: false - - - name: Upload Linux package to release - if: github.event_name == 'workflow_dispatch' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./linux-${{ github.event.inputs.version }}.tar.gz - asset_name: linux-${{ github.event.inputs.version }}.tar.gz - asset_content_type: application/gzip - - - name: Upload Windows package to release - if: github.event_name == 'workflow_dispatch' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./windows-${{ github.event.inputs.version }}.zip - asset_name: windows-${{ github.event.inputs.version }}.zip - asset_content_type: application/zip - - - name: Upload macOS Intel package to release - if: github.event_name == 'workflow_dispatch' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./macos-intel-${{ github.event.inputs.version }}.tar.gz - asset_name: macos-intel-${{ github.event.inputs.version }}.tar.gz - asset_content_type: application/gzip - - - name: Upload macOS ARM64 package to release - if: github.event_name == 'workflow_dispatch' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./macos-arm64-${{ github.event.inputs.version }}.tar.gz - asset_name: macos-arm64-${{ github.event.inputs.version }}.tar.gz - asset_content_type: application/gzip - - - name: Upload Linux package to existing release - if: github.event_name == 'release' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./linux-${{ github.event.release.tag_name }}.tar.gz - asset_name: linux-${{ github.event.release.tag_name }}.tar.gz - asset_content_type: application/gzip - - - name: Upload Windows package to existing release - if: github.event_name == 'release' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./windows-${{ github.event.release.tag_name }}.zip - asset_name: windows-${{ github.event.release.tag_name }}.zip - asset_content_type: application/zip - - - name: Upload macOS Intel package to existing release - if: github.event_name == 'release' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./macos-intel-${{ github.event.release.tag_name }}.tar.gz - asset_name: macos-intel-${{ github.event.release.tag_name }}.tar.gz - asset_content_type: application/gzip - - - name: Upload macOS ARM64 package to existing release - if: github.event_name == 'release' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./macos-arm64-${{ github.event.release.tag_name }}.tar.gz - asset_name: macos-arm64-${{ github.event.release.tag_name }}.tar.gz - asset_content_type: application/gzip \ No newline at end of file diff --git a/.github/ex_workflows/test-backend.yaml b/.github/ex_workflows/test-backend.yaml deleted file mode 100644 index a1f1e780f..000000000 --- a/.github/ex_workflows/test-backend.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Test backend - -on: - push: - paths: - - backend/** - pull_request: - paths: - - backend/** - workflow_dispatch: - -jobs: - test-backend: - name: "Test backend" - runs-on: ubuntu-latest - - env: - BACKEND_DIR: ./backend - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: 1.21 - cache: false - - - - name: Install Dependencies - run: | - sudo apt-get update && sudo apt-get install -y libpcap-dev - - - name: Test with Go - working-directory: "${{env.BACKEND_DIR}}" - run: go test -v -timeout 30s ./... diff --git a/.github/ex_workflows/test-dev-scripts.yaml b/.github/ex_workflows/test-dev-scripts.yaml deleted file mode 100644 index d74a43168..000000000 --- a/.github/ex_workflows/test-dev-scripts.yaml +++ /dev/null @@ -1,82 +0,0 @@ -name: Test Development Scripts - -on: - pull_request: - paths: - - scripts/** - workflow_dispatch: - -jobs: - test-dev-scripts: - name: Test Development Scripts - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: ubuntu-latest - name: linux - shell: bash - script: ./scripts/dev.sh - - - os: windows-latest - name: windows-powershell - shell: pwsh - script: .\scripts\dev.ps1 - - - os: windows-latest - name: windows-cmd - shell: cmd - script: scripts\dev.cmd - - - os: macos-latest - name: macos - shell: bash - script: ./scripts/dev.sh - - steps: - - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: "1.21.3" - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - cache: 'npm' - - - name: Install tmux (Linux/macOS) - if: matrix.os != 'windows-latest' - run: | - if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then - sudo apt-get update && sudo apt-get install -y tmux - elif [ "${{ matrix.os }}" = "macos-latest" ]; then - brew install tmux - fi - shell: bash - - - name: Make script executable (Unix) - if: matrix.os != 'windows-latest' - run: chmod +x scripts/dev.sh - shell: bash - - - name: Test script help/usage - run: ${{ matrix.script }} - shell: ${{ matrix.shell }} - continue-on-error: true - - - name: Test dependency check - run: ${{ matrix.script }} setup - shell: ${{ matrix.shell }} - - - name: Test build command - run: ${{ matrix.script }} build - shell: ${{ matrix.shell }} - continue-on-error: true - - - name: Test backend build (quick test) - run: ${{ matrix.script }} test - shell: ${{ matrix.shell }} - continue-on-error: true \ No newline at end of file diff --git a/.github/workflows/frontend-tests.yaml b/.github/workflows/frontend-tests.yaml new file mode 100644 index 000000000..149d955df --- /dev/null +++ b/.github/workflows/frontend-tests.yaml @@ -0,0 +1,45 @@ +name: Frontend Tests + +on: + pull_request: + branches: + - main + - develop + - production + - "frontend/**" + - "testing-view/**" + - "competition-view/**" + paths: + - "frontend/**" + - "pnpm-lock.yaml" + - ".github/workflows/frontend-tests.yaml" + + push: + branches: + - "frontend/**" + - "testing-view/**" + - "competition-view/**" + paths: + - "frontend/**" + - "pnpm-lock.yaml" + - ".github/workflows/frontend-tests.yaml" + +jobs: + test: + name: Run Frontend Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile --filter=testing-view --filter=ui --filter=core + + - name: Run tests + run: pnpm test --filter="./frontend/**" diff --git a/.gitignore b/.gitignore index d05a2ae9f..1d914a987 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,12 @@ -build -profiles -backend/cmd/cmd -.ropeproject - -# MacOS Files +# Monorepo / IDE +node_modules/ +.turbo/ +.vscode/ .DS_Store +*.code-workspace -# Code Editor -.idea/ -.vscode/* -!.vscode/settings.json - -# Claude -CLAUDE* -.claude - -*.exe - -# Rust build artifacts -packet-sender/target/ +# Environment +.env -# VS Code Workspace -*.code-workspace \ No newline at end of file +# Global binaries +*.exe \ No newline at end of file diff --git a/.starship.toml b/.starship.toml deleted file mode 100644 index 74b0b59b7..000000000 --- a/.starship.toml +++ /dev/null @@ -1,90 +0,0 @@ -# Hyperloop H10 Starship Prompt Configuration - -format = """ -[โ”Œโ”€](bold white) \ -$username\ -$hostname\ -$directory\ -$git_branch\ -$git_status\ -$golang\ -$nodejs\ -$python\ -$env_var\ -$custom\ -$cmd_duration\ -$line_break\ -[โ””โ”€](bold white) $character""" - -[directory] -truncation_length = 3 -truncate_to_repo = true -style = "bold cyan" -read_only = " ๐Ÿ”’" - -[character] -success_symbol = "[๐Ÿš„โฏ](bold green)" -error_symbol = "[๐Ÿš„โฏ](bold red)" -vicmd_symbol = "[๐Ÿš„โฎ](bold green)" - -[git_branch] -symbol = " " -style = "bold purple" -format = "on [$symbol$branch]($style) " - -[git_status] -style = "bold red" -format = '([\[$all_status$ahead_behind\]]($style) )' -conflicted = "โš”๏ธ " -ahead = "โ‡ก${count}" -behind = "โ‡ฃ${count}" -diverged = "โ‡•โ‡ก${ahead_count}โ‡ฃ${behind_count}" -untracked = "?${count}" -stashed = "๐Ÿ“ฆ${count}" -modified = "!${count}" -staged = "+${count}" -renamed = "ยป${count}" -deleted = "โœ˜${count}" - -[golang] -symbol = " " -style = "bold blue" -format = "via [$symbol($version )]($style)" - -[nodejs] -symbol = " " -style = "bold green" -format = "via [$symbol($version )]($style)" - -[python] -symbol = " " -style = "bold yellow" -format = "via [$symbol($version )]($style)" - -[cmd_duration] -min_time = 3_000 -format = "took [$duration]($style) " -style = "bold yellow" - -[env_var.NIX_SHELL] -symbol = "โ„๏ธ " -style = "bold blue" -format = "[$symbol]($style)" - -[custom.hyperloop] -command = "echo ๐Ÿš„" -when = """ test "$REPO_ROOT" != "" """ -format = "[$output]($style) " -style = "bold" - -[hostname] -ssh_only = false -format = "on [$hostname](bold red) " -disabled = false - -[username] -style_user = "white bold" -style_root = "red bold" -format = "[$user]($style) " -disabled = false -show_always = false \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index dff16ee72..9ef398bb3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, // JSON files "[json]": { @@ -39,5 +42,13 @@ "go.lintFlags": ["-checks=all"], // Which checks to run // Prettier settings - "prettier.useEditorConfig": false + "prettier.useEditorConfig": false, + + // Rust settings + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.codeActionsOnSave": { + "source.fixAll.rust-analyzer": "explicit" + } + } } diff --git a/DOCUMENTATION_REORGANIZATION.md b/DOCUMENTATION_REORGANIZATION.md deleted file mode 100644 index 0bbbab6b0..000000000 --- a/DOCUMENTATION_REORGANIZATION.md +++ /dev/null @@ -1,124 +0,0 @@ -# Documentation Reorganization Summary - -This document outlines the reorganization of project documentation from scattered files to a centralized `docs/` folder structure. - -## ๐Ÿ“ New Documentation Structure - -``` -docs/ -โ”œโ”€โ”€ README.md # Main documentation index -โ”œโ”€โ”€ architecture/ -โ”‚ โ””โ”€โ”€ README.md # System architecture overview -โ”œโ”€โ”€ development/ -โ”‚ โ”œโ”€โ”€ DEVELOPMENT.md # Development setup guide -โ”‚ โ”œโ”€โ”€ CROSS_PLATFORM_DEV_SUMMARY.md # Cross-platform scripts documentation -โ”‚ โ””โ”€โ”€ scripts.md # Scripts reference guide -โ”œโ”€โ”€ guides/ -โ”‚ โ””โ”€โ”€ getting-started.md # New user getting started guide -โ””โ”€โ”€ troubleshooting/ - โ””โ”€โ”€ BLCU_FIX_SUMMARY.md # BLCU repair documentation -``` - -## ๐Ÿ“‹ File Migrations - -### Moved Files -| Original Location | New Location | Status | -|-------------------|--------------|--------| -| `DEVELOPMENT.md` | `docs/development/DEVELOPMENT.md` | โœ… Moved | -| `CROSS_PLATFORM_DEV_SUMMARY.md` | `docs/development/CROSS_PLATFORM_DEV_SUMMARY.md` | โœ… Moved | -| `scripts/README.md` | `docs/development/scripts.md` | โœ… Moved | -| `backend/BLCU_FIX_SUMMARY.md` | `docs/troubleshooting/BLCU_FIX_SUMMARY.md` | โœ… Moved | - -### New Files Created -| File | Purpose | -|------|---------| -| `docs/README.md` | Main documentation index with navigation | -| `docs/architecture/README.md` | System architecture overview | -| `docs/guides/getting-started.md` | Comprehensive new user guide | -| `scripts/README.md` | Quick reference pointing to full docs | - -### Updated Files -| File | Changes | -|------|---------| -| `README.md` | Added documentation section with quick links | -| `docs/development/scripts.md` | Updated paths for new location | - -## ๐ŸŽฏ Benefits of New Structure - -### 1. **Improved Organization** -- Clear categorization by purpose (development, architecture, guides, troubleshooting) -- Logical hierarchy that scales as documentation grows -- Centralized location for all project documentation - -### 2. **Better Discoverability** -- Single entry point through `docs/README.md` -- Clear navigation between related documents -- Quick links in main README for common tasks - -### 3. **Enhanced User Experience** -- Dedicated getting started guide for new users -- Platform-specific guidance clearly organized -- Troubleshooting docs easily accessible - -### 4. **Maintainability** -- Related documentation grouped together -- Easier to update and maintain consistency -- Clear ownership and responsibility areas - -## ๐Ÿš€ How to Use the New Structure - -### For New Users -1. Start with [`docs/guides/getting-started.md`](docs/guides/getting-started.md) -2. Follow platform-specific setup in [`docs/development/DEVELOPMENT.md`](docs/development/DEVELOPMENT.md) -3. Refer to troubleshooting docs if needed - -### For Developers -1. Check [`docs/development/`](docs/development/) for all development-related docs -2. Use [`docs/architecture/`](docs/architecture/) to understand system design -3. Reference [`docs/development/scripts.md`](docs/development/scripts.md) for tooling - -### For Contributors -1. Review existing documentation structure before adding new docs -2. Place new documentation in appropriate category folders -3. Update main index (`docs/README.md`) when adding major new sections - -## ๐Ÿ“ Documentation Guidelines - -### Placement Rules -- **Development docs** โ†’ `docs/development/` -- **Architecture docs** โ†’ `docs/architecture/` -- **User guides** โ†’ `docs/guides/` -- **Troubleshooting** โ†’ `docs/troubleshooting/` -- **Component-specific** โ†’ Keep in respective component directories - -### Linking Guidelines -- Use relative paths for internal documentation links -- Update `docs/README.md` index when adding major new documents -- Cross-reference related documentation where helpful - -### File Naming -- Use lowercase with hyphens: `getting-started.md` -- Use descriptive names that indicate content purpose -- Keep README.md files for directory overviews - -## ๐Ÿ”— Key Entry Points - -### Primary Documentation -- **[docs/README.md](docs/README.md)** - Main documentation hub -- **[README.md](README.md)** - Project overview with quick start - -### Quick Access -- **New Users**: [Getting Started Guide](docs/guides/getting-started.md) -- **Developers**: [Development Setup](docs/development/DEVELOPMENT.md) -- **Troubleshooting**: [Common Issues](docs/troubleshooting/BLCU_FIX_SUMMARY.md) - -## ๐ŸŽ‰ Migration Complete - -The documentation reorganization provides: -- โœ… Better organization and navigation -- โœ… Improved new user experience -- โœ… Clearer separation of concerns -- โœ… Scalable structure for future growth -- โœ… Maintained backward compatibility through redirect notes - -All existing functionality remains accessible while providing a much better documentation experience for users, developers, and contributors. \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 928f9e100..000000000 --- a/Makefile +++ /dev/null @@ -1,158 +0,0 @@ -# Hyperloop H10 Control Station Makefile - -.PHONY: all install clean build-backend build-common-front build-control-station build-ethernet-view -.PHONY: backend common-front control-station ethernet-view -.PHONY: dev-backend dev-control-station dev-ethernet-view -.PHONY: test test-backend test-frontend -.PHONY: ethernet-view-tmux control-station-tmux - -# Colors for output -GREEN := \033[0;32m -YELLOW := \033[0;33m -BLUE := \033[0;34m -RED := \033[0;31m -NC := \033[0m # No Color - -# Build directories -BACKEND_DIR := backend -COMMON_FRONT_DIR := common-front -CONTROL_STATION_DIR := control-station -ETHERNET_VIEW_DIR := ethernet-view - -# Output binary -BACKEND_BIN := $(BACKEND_DIR)/cmd/backend - -# Default target -all: install build - -# Install all dependencies -install: install-backend install-frontend - @echo "$(GREEN)โœ“ All dependencies installed$(NC)" - -install-backend: - @echo "$(BLUE)Installing backend dependencies...$(NC)" - @cd $(BACKEND_DIR) && go mod download - @echo "$(GREEN)โœ“ Backend dependencies installed$(NC)" - -install-frontend: - @echo "$(BLUE)Installing frontend dependencies...$(NC)" - @cd $(COMMON_FRONT_DIR) && npm install - @cd $(CONTROL_STATION_DIR) && npm install - @cd $(ETHERNET_VIEW_DIR) && npm install - @echo "$(GREEN)โœ“ Frontend dependencies installed$(NC)" - -# Build all components -build: build-backend build-frontend - @echo "$(GREEN)โœ“ All components built successfully$(NC)" - -build-frontend: build-common-front build-control-station build-ethernet-view - -# Individual build targets -backend build-backend: - @echo "$(BLUE)Building backend...$(NC)" - @cd $(BACKEND_DIR)/cmd && go build -o backend - @echo "$(GREEN)โœ“ Backend built: $(BACKEND_BIN)$(NC)" - -common-front build-common-front: - @echo "$(BLUE)Building common-front...$(NC)" - @cd $(COMMON_FRONT_DIR) && npm run build - @echo "$(GREEN)โœ“ Common-front built$(NC)" - -control-station build-control-station: build-common-front - @echo "$(BLUE)Building control-station...$(NC)" - @cd $(CONTROL_STATION_DIR) && npm run build - @echo "$(GREEN)โœ“ Control-station built$(NC)" - -ethernet-view build-ethernet-view: build-common-front - @echo "$(BLUE)Building ethernet-view...$(NC)" - @cd $(ETHERNET_VIEW_DIR) && npm run build - @echo "$(GREEN)โœ“ Ethernet-view built$(NC)" - -# Development servers (individual) -dev-backend: - @echo "$(YELLOW)Starting backend development server...$(NC)" - @cd $(BACKEND_DIR)/cmd && ./backend - -dev-control-station: - @echo "$(YELLOW)Starting control-station development server...$(NC)" - @cd $(CONTROL_STATION_DIR) && npm run dev - -dev-ethernet-view: - @echo "$(YELLOW)Starting ethernet-view development server...$(NC)" - @cd $(ETHERNET_VIEW_DIR) && npm run dev - -# Testing -test: test-backend test-frontend - -test-backend: - @echo "$(BLUE)Running backend tests...$(NC)" - @cd $(BACKEND_DIR) && go test -v -timeout 30s ./... - -test-frontend: - @echo "$(BLUE)Running frontend tests...$(NC)" - @cd $(ETHERNET_VIEW_DIR) && npm test || true - @echo "$(YELLOW)Note: Only ethernet-view has tests configured$(NC)" - -# Clean build artifacts -clean: - @echo "$(YELLOW)Cleaning build artifacts...$(NC)" - @rm -f $(BACKEND_BIN) - @rm -rf $(COMMON_FRONT_DIR)/dist - @rm -rf $(CONTROL_STATION_DIR)/dist - @rm -rf $(ETHERNET_VIEW_DIR)/dist - @echo "$(GREEN)โœ“ Clean complete$(NC)" - -# Combined tmux sessions -ethernet-view-tmux: build-backend - @echo "$(BLUE)Starting backend + ethernet-view in tmux...$(NC)" - @tmux new-session -d -s ethernet-view-session -n main - @tmux send-keys -t ethernet-view-session:main "cd $(BACKEND_DIR)/cmd && ./backend" C-m - @tmux split-window -t ethernet-view-session:main -h - @tmux send-keys -t ethernet-view-session:main.1 "cd $(ETHERNET_VIEW_DIR) && npm run dev" C-m - @tmux select-pane -t ethernet-view-session:main.0 - @tmux attach-session -t ethernet-view-session - -control-station-tmux: build-backend - @echo "$(BLUE)Starting backend + control-station in tmux...$(NC)" - @tmux new-session -d -s control-station-session -n main - @tmux send-keys -t control-station-session:main "cd $(BACKEND_DIR)/cmd && ./backend" C-m - @tmux split-window -t control-station-session:main -h - @tmux send-keys -t control-station-session:main.1 "cd $(CONTROL_STATION_DIR) && npm run dev" C-m - @tmux select-pane -t control-station-session:main.0 - @tmux attach-session -t control-station-session - -# Help target -help: - @echo "Hyperloop H10 Control Station - Build System" - @echo "===========================================" - @echo "" - @echo "$(YELLOW)Installation:$(NC)" - @echo " make install - Install all dependencies" - @echo " make install-backend - Install backend dependencies only" - @echo " make install-frontend - Install frontend dependencies only" - @echo "" - @echo "$(YELLOW)Building:$(NC)" - @echo " make all - Install deps and build everything" - @echo " make build - Build all components" - @echo " make backend - Build backend only" - @echo " make common-front - Build common frontend library" - @echo " make control-station - Build control station" - @echo " make ethernet-view - Build ethernet view" - @echo "" - @echo "$(YELLOW)Development:$(NC)" - @echo " make dev-backend - Run backend dev server" - @echo " make dev-control-station - Run control station dev server" - @echo " make dev-ethernet-view - Run ethernet view dev server" - @echo "" - @echo "$(YELLOW)Combined Sessions:$(NC)" - @echo " make ethernet-view-tmux - Run backend + ethernet-view in tmux" - @echo " make control-station-tmux - Run backend + control-station in tmux" - @echo "" - @echo "$(YELLOW)Testing:$(NC)" - @echo " make test - Run all tests" - @echo " make test-backend - Run backend tests" - @echo " make test-frontend - Run frontend tests" - @echo "" - @echo "$(YELLOW)Maintenance:$(NC)" - @echo " make clean - Remove build artifacts" - @echo " make help - Show this help message" \ No newline at end of file diff --git a/README.md b/README.md index 2b01eaa1d..21109ad84 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,63 @@ -# Software - Control Station +# Hyperloop Control Station H11 -[![CI](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-backend.yaml/badge.svg)](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-backend.yaml) -[![CI](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-ethernet-view.yaml/badge.svg)](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-ethernet-view.yaml) -[![CI](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-control-station.yaml/badge.svg)](https://github.com/HyperloopUPV-H8/software/actions/workflows/build-control-station.yaml) +## Monorepo usage -Hyperloop UPV's Control Station is a unified software solution for real-time monitoring and commanding of the pod. It combines a back-end (Go) that ingests and interprets sensor dataโ€“defined via the JSON-based "ADJ" specificationsโ€“and a front-end (Typescript/React) that displays metrics, logs, and diagnostics to operators. With features like packet parsing, logging, and live dashboards, it acts as the central hub to safely interface the pod, making it easier for team members to oversee performance, detect faults, and send precise orders to the vehicle. +This project implements `pnpm` workspaces and `turbo` pack to manage the development lifecycle across multiple languages and frameworks. -control_station_mock +### Prerequisites -## Quick Start +Before starting, ensure you have the following installed: -### For Users +- **PNPM** (v10.26.0+) +- **Node.js** (v20+) +- **Go** (for the backend) +- **Rust/Cargo** (for the packet-sender) -Download the latest release, unzip it and run the executable compatible with your OS. +--- -### For Developers +### Workspaces Overview -See our comprehensive [Documentation](./docs/README.md) or jump to [Getting Started](./docs/guides/getting-started.md). Quick start: +Our `pnpm-workspace.yaml` defines the following workspaces: -```bash -# Clone and setup -git clone https://github.com/HyperloopUPV-H8/software.git -cd software -./scripts/dev.sh setup +| Workspace | Language | Description | +| :----------------------------- | :------- | :--------------------------------------------- | +| `testing-view` | TS/React | Web interface for telemetry testing | +| `competition-view` | TS/React | UI for the competition | +| `backend` | Go | Data ingestion and pod communication server | +| `packet-sender` | Rust | Utility for simulating vehicle packets | +| `electron-app` | JS | The main Control Station desktop application | +| `@workspace/ui` | TS/React | Shared UI component library (frontend-kit) | +| `@workspace/core` | TS | Shared business logic and types (frontend-kit) | +| `@workspace/eslint-config` | ESLint | Common ESLint configuration (frontend-kit) | +| `@workspace/typescript-config` | TS | Common TypeScript configuration (frontend-kit) | -# Run services -./scripts/dev.sh backend # Backend server -./scripts/dev.sh ethernet # Ethernet view -./scripts/dev.sh control # Control station -``` +--- -## Configuration +### Terminal Commands -When using the Control Station make sure that you have configured your IP as the one specified in the ADJโ€”usually `192.168.0.9`. Then make sure to configure the boards you'll be making use of in the `config.toml` (at the top of the file you'll be able to see the `vehicle/boards` option, just add or remove the boards as needed following the format specified in the ADJ. +These commands should be executed from the root directory (`/software`). -To change the ADJ branch from `main`, change the option `adj/branch` at the end of the `config.toml` with the name of the branch you want to use or leave it blank if you'll be making use of a custom ADJ. +> **Note:** If you prefer to run scripts from a specific `package.json`, you can `cd` into the folder and execute them with `pnpm` without filtering. -## Documentation +#### Global Development Scripts -๐Ÿ“š **[Complete Documentation](./docs/README.md)** - All guides and references +- `pnpm dev` โ€“ Runs both frontends, the backend (with `dev-config.toml`), and the packet-sender in a single terminal window. +- `pnpm dev:main` โ€“ Runs frontends and the backend using the standard `config.toml`. -### Quick Links -- ๐Ÿš€ **[Getting Started](./docs/guides/getting-started.md)** - New user guide -- ๐Ÿ› ๏ธ **[Development Setup](./docs/development/DEVELOPMENT.md)** - Developer environment setup -- ๐Ÿ—๏ธ **[Architecture](./docs/architecture/README.md)** - System design overview -- ๐Ÿ”ง **[Troubleshooting](./docs/troubleshooting/BLCU_FIX_SUMMARY.md)** - Common issues and fixes +#### Turbo Filtering -## Contributing +All Turbo scripts support filtering to target specific workspaces: -See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways to contribute to the Control Station. +- `pnpm dev --filter testing-view` โ€“ Runs the `dev` script specifically for the Testing View. -### About +> **! Important:** You must refer to a workspace by the `name` field defined in its local `package.json`. -HyperloopUPV is a student team based at Universitat Politรจcnica de Valรจncia (Spain), which works every year to develop the transport of the future, the hyperloop. Check out [our website](https://hyperloopupv.com/#/) +#### Lifecycle Scripts + +- `pnpm build` โ€“ Compiles every package in the monorepo (Go binaries, Rust crates, and Vite apps). +- `pnpm test` โ€“ Runs all test suites across the repo (Vitest, Go tests, and Cargo tests). +- `pnpm lint` โ€“ Runs ESLint across all TypeScript packages. +- `pnpm preview` โ€“ Previews the production Vite builds for the frontend applications. +- `pnpm ui:add ` - To add shadcn/ui components + + > Note: don't forget to also include it in frontend-kit/ui/src/components/shadcn/index.ts to be able to access it from @workspace/ui diff --git a/backend/.gitignore b/backend/.gitignore index 141acc284..384729b1f 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,31 +1,22 @@ log trace.json -# Windows build -*.exe -# Linux build -Backend-H8 -backend -!build -# MacOS build -cmd/cmd -cmd/logger/ - -# EXCEL -*.xlsx - -static - -.vscode - -downloads +/bin -audience_static - -cmd/adj/ -cmd/config.toml +# Test coverage +*testfile +# Build artifacts +/bin/ +backend +trace.json # Test data +*.out c.out -cover.out + +# Logs +cmd/logger/ +cmd/cmd +cmd/adj +/logger/ \ No newline at end of file diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev deleted file mode 100644 index 7c86595f5..000000000 --- a/backend/Dockerfile.dev +++ /dev/null @@ -1,14 +0,0 @@ -FROM golang:1.21-alpine - -RUN apk add --no-cache gcc musl-dev libpcap-dev pkgconfig - -WORKDIR /app - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -EXPOSE 8080 - -CMD ["sh", "-c", "cd cmd && go run ."] \ No newline at end of file diff --git a/backend/build/Dockerfile b/backend/build/Dockerfile deleted file mode 100644 index 564b8bcc4..000000000 --- a/backend/build/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM golang:1.21-alpine - -RUN apk update -RUN apk add --no-cache libpcap-dev musl-dev gcc - -WORKDIR /backend - -COPY . /backend - -RUN go mod tidy - -ENV CGO_ENABLED=1 -ENV GOARCH=amd64 -ENV GOOS=linux - -CMD go build -C "cmd" -ldflags '-linkmode external -extldflags "-static"' -o "../build/backend" \ No newline at end of file diff --git a/backend/build/config/config.toml b/backend/build/config/config.toml deleted file mode 100644 index efa6e85af..000000000 --- a/backend/build/config/config.toml +++ /dev/null @@ -1,96 +0,0 @@ - -[server.local] -address = "127.0.0.1:4000" -static = "./static" - -[server.local.endpoints] -pod_data = "/podDataStructure" -order_data = "/orderStructures" -programable_boards = "/uploadableBoards" -connections = "/backend" -file_server = "/" - -[vehicle] -boards = ["VCU"] - -[vehicle.network] -tcp_client_tag = "TCP_CLIENT" -tcp_server_tag = "TCP_SERVER" -udp_tag = "UDP" -mtu = 1500 -interface = "lo" -keepalive = "1s" -timeout = "1s" - -[vehicle.messages] -info_id_key = "info" -fault_id_key = "fault" -warning_id_key = "warning" -error_id_key = "error" -blcu_ack_id_key = "blcu_ack" -add_state_orders_id_key = "add_state_orders" -remove_state_orders_id_key = "remove_state_orders" - -[excel.download] -id = "1BEwASubu0el9oQA6PSwVKaNU-Q6gbJ40JR6kgqguKYE" -name = "ade.xlsx" -path = "." - -[excel.parse] -global_sheet_prefix = "GLOBAL " -board_sheet_prefix = "BOARD " -table_prefix = "[TABLE] " -[excel.parse.global] -address_table = "addresses" -backend_key = "Backend" -blcu_address_key = "BLCU" -units_table = "units" -ports_table = "ports" -board_ids_table = "board_ids" -message_ids_table = "message_ids" - -[logger_handler] -topics = { enable = "logger/enable" } -base_path = "log" -flush_interval = "5s" - -[packet_logger] -file_name = "packets" -flush_interval = "5s" - -[value_logger] -folder_name = "values" -flush_interval = "5s" - -[order_logger] -file_name = "orders" -flush_interval = "5s" - -[protection_logger] -file_name = "protections" -flush_interval = "5s" - -[orders] -send_topic = "order/send" - -[messages] -update_topic = "message/update" - -[data_transfer] -fps = 20 -topics = { update = "podData/update" } - -[connections] -update_topic = "connection/update" - -[blcu] -download_path = "downloads" - -[blcu.packets] -upload = { id = 700, field = "board" } -download = { id = 701, field = "board" } -ack = { name = "tftp_ack" } - -[blcu.topics] -upload = "blcu/upload" -download = "blcu/download" diff --git a/backend/build/config/ethernet-view.toml b/backend/build/config/ethernet-view.toml deleted file mode 100644 index dc33487bb..000000000 --- a/backend/build/config/ethernet-view.toml +++ /dev/null @@ -1,101 +0,0 @@ - -[server.local] -address = "127.0.0.1:4000" -static = "./static" - -[server.local.endpoints] -pod_data = "/podDataStructure" -order_data = "/orderStructures" -programable_boards = "/uploadableBoards" -connections = "/backend" -file_server = "/" - -[vehicle] -boards = ["VCU"] - -[vehicle.network] -tcp_client_tag = "TCP_CLIENT" -tcp_server_tag = "TCP_SERVER" -udp_tag = "UDP" -# sniffer = { mtu = 1500, interface = "lo" } -mtu = 1500 -interface = "lo" -# blcu_ack_id = "blcu_ack" -keep_alive_interval = "1s" -keep_alive_probes = 3 -timeout = "1s" - -[vehicle.messages] -info_id_key = "info" -fault_id_key = "fault" -warning_id_key = "warning" -error_id_key = "error" -blcu_ack_id_key = "blcu_ack" -add_state_orders_id_key = "add_state_orders" -remove_state_orders_id_key = "remove_state_orders" - -[excel.download] -#id = "1XE9V2PI0hwSdAC8P6MePnSLyzADqsdWCOlx_kct7dps" -id="1b_nOrWqjMLOSEFIV9dMUObnJ15J7ypmF-KVJ4qztAtw" -#id = "1BEwASubu0el9oQA6PSwVKaNU-Q6gbJ40JR6kgqguKYE" -name = "ade.xlsx" -path = "." - -[excel.parse] -global_sheet_prefix = "GLOBAL " -board_sheet_prefix = "BOARD " -table_prefix = "[TABLE] " -[excel.parse.global] -address_table = "addresses" -backend_key = "Backend" -blcu_address_key = "BLCU" -units_table = "units" -ports_table = "ports" -board_ids_table = "board_ids" -message_ids_table = "message_ids" - -[logger_handler] -topics = { enable = "logger/enable" } -base_path = "log" -flush_interval = "5s" - -[packet_logger] -file_name = "packets" -flush_interval = "5s" - -[value_logger] -folder_name = "values" -flush_interval = "5s" - -[order_logger] -file_name = "orders" -flush_interval = "5s" - -[protection_logger] -file_name = "protections" -flush_interval = "5s" - -[orders] -send_topic = "order/send" - -[messages] -update_topic = "message/update" - -[data_transfer] -fps = 20 -topics = { update = "podData/update" } - -[connections] -update_topic = "connection/update" - -[blcu] -download_path = "downloads" - -[blcu.packets] -upload = { id = 700, field = "write_board" } -download = { id = 701, field = "read_board" } -ack = { name = "tftp_ack" } - -[blcu.topics] -upload = "blcu/upload" -download = "blcu/download" diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 107c92a7a..637bfd8e7 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -112,7 +112,7 @@ func main() { ) // <--- http server ---> - configureHttpServer( + configureHTTPServer( adj, podData, vehicleOrders, diff --git a/backend/cmd/setup_vehicle.go b/backend/cmd/setup_vehicle.go index 30a5b049d..21423de33 100644 --- a/backend/cmd/setup_vehicle.go +++ b/backend/cmd/setup_vehicle.go @@ -42,7 +42,7 @@ func configureBroker(subloggers abstraction.SubloggersMap, loggerHandler *logger cleanup := func() { dataTopic.Stop() } connectionTopic := connection_topic.NewUpdateTopic() orderTopic := order_topic.NewSendTopic() - loggerTopic := logger_topic.NewEnableTopic() + loggerTopic := logger_topic.NewEnableTopic(trace.Logger) loggerTopic.SetDataLogger(subloggers[data_logger.Name].(*data_logger.Logger)) loggerHandler.SetOnStart(func() { if err := loggerTopic.NotifyStarted(); err != nil { @@ -64,6 +64,16 @@ func configureBroker(subloggers abstraction.SubloggersMap, loggerHandler *logger broker.AddTopic(message_topic.UpdateName, messageTopic) pool := websocket.NewPool(connections, trace.Logger) + pool.SetOnDisconnect(func(count int) { + if count == 0 { + trace.Info().Msg("no clients connected, stopping logger") + loggerHandler.Stop() + if err := loggerTopic.NotifyStopped(); err != nil { + trace.Error().Err(err).Msg("failed to notify logger stopped") + } + } + }) + broker.SetPool(pool) blcu_topics.RegisterTopics(broker, pool) @@ -161,7 +171,7 @@ func configureSNTP(adj adj_module.ADJ) bool { return false } -func configureHttpServer( +func configureHTTPServer( adj adj_module.ADJ, podData pod_data.PodData, vehicleOrders vehicle_models.VehicleOrders, diff --git a/backend/package-lock.json b/backend/package-lock.json deleted file mode 100644 index 8f9553dbe..000000000 --- a/backend/package-lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "hyperloop-backend", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "hyperloop-backend", - "version": "1.0.0", - "license": "ISC" - } - } -} diff --git a/backend/package.json b/backend/package.json index 1d9f29dba..e433c7772 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,12 +1,13 @@ { - "name": "hyperloop-backend", + "name": "backend", "version": "1.0.0", - "main": "index.js", + "private": true, + "author": "Hyperloop UPV Team", + "license": "MIT", "scripts": { - "start": "node index.js" - }, - "author": "", - "license": "ISC", - "keywords": [], - "description": "" + "dev": "go run ./cmd --config ./cmd/dev-config.toml", + "dev:main": "go run ./cmd --config ./cmd/config.toml", + "build": "go build -o bin/backend ./cmd", + "test": "go test ./..." + } } diff --git a/backend/pkg/broker/topics/logger/enable.go b/backend/pkg/broker/topics/logger/enable.go index adc74cdb4..57b30f690 100644 --- a/backend/pkg/broker/topics/logger/enable.go +++ b/backend/pkg/broker/topics/logger/enable.go @@ -2,7 +2,6 @@ package logger import ( "encoding/json" - "fmt" "sync" "sync/atomic" @@ -11,6 +10,7 @@ import ( "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" "github.com/google/uuid" ws "github.com/gorilla/websocket" + "github.com/rs/zerolog" ) const EnableName abstraction.BrokerTopic = "logger/enable" @@ -24,13 +24,15 @@ type Enable struct { subscribers map[websocket.ClientId]struct{} api abstraction.BrokerAPI data_logger *data_logger.Logger + baseLogger zerolog.Logger } -func NewEnableTopic() *Enable { +func NewEnableTopic(baseLogger zerolog.Logger) *Enable { enable := &Enable{ isRunning: &atomic.Bool{}, connectionMx: new(sync.Mutex), subscribers: make(map[websocket.ClientId]struct{}), + baseLogger: baseLogger, } enable.isRunning.Store(false) return enable @@ -53,19 +55,37 @@ func (enable *Enable) ClientMessage(id websocket.ClientId, message *websocket.Me case EnableName: err := enable.handleToggle(id, message) if err != nil { - fmt.Printf("error handling logger: %v\n", err) + enable.baseLogger.Error().Err(err).Msg("error handling logger/enable") } case ResponseName: enable.connectionMx.Lock() defer enable.connectionMx.Unlock() - fmt.Printf("logger/response subscribed %s\n", uuid.UUID(id).String()) + enable.baseLogger.Debug().Msgf("logger/response subscribed %s", uuid.UUID(id).String()) enable.subscribers[id] = struct{}{} + // Get current logger state + payload, err := json.Marshal(enable.isRunning.Load()) + if err != nil { + enable.baseLogger.Error().Err(err).Msg("error marshaling logger state") + } + + // Prepare message + message := websocket.Message{ + Topic: ResponseName, + Payload: payload, + } + + // Send current logger state to client that just subscribed + err = enable.pool.Write(id, message) + if err != nil { + enable.baseLogger.Error().Err(err).Msg("error sending logger state to client") + } + case VariablesName: err := enable.handleVariables(id, message) if err != nil { - fmt.Printf("error handling logger/variables: %v\n", err) + enable.baseLogger.Error().Err(err).Msg("error handling logger/variables") } default: enable.connectionMx.Lock() @@ -73,7 +93,7 @@ func (enable *Enable) ClientMessage(id websocket.ClientId, message *websocket.Me enable.pool.Disconnect(id, ws.CloseUnsupportedData, "unsupported topic") delete(enable.subscribers, id) - fmt.Printf("logger/response unsubscribed %s\n", uuid.UUID(id).String()) + enable.baseLogger.Debug().Msgf("logger/response unsubscribed %s", uuid.UUID(id).String()) } } @@ -84,11 +104,25 @@ func (enable *Enable) handleToggle(_ websocket.ClientId, message *websocket.Mess return err } + // If we are already in the state the user wants, + // just confirm it immediately and don't bother the rest of the system. + if enable.isRunning.Load() == request { + return enable.broadcastState() + } + status := newStatus(request) go enable.api.UserPush(status) go func() { - enable.isRunning.Store(<-status.response) + response := <-status.response + + enable.isRunning.Store(response) + + if request && response { + // Successfully started logging: do nothing because NotifyStarted already broadcasts the state + return + } + enable.broadcastState() }() return nil @@ -109,6 +143,11 @@ func (enable *Enable) NotifyStarted() error { return enable.broadcastState() } +func (enable *Enable) NotifyStopped() error { + enable.isRunning.Store(false) + return enable.broadcastState() +} + func (enable *Enable) broadcastState() error { payload, err := json.Marshal(enable.isRunning.Load()) if err != nil { @@ -133,7 +172,8 @@ func (enable *Enable) broadcastState() error { for _, id := range flaged { enable.pool.Disconnect(id, ws.CloseInternalServerErr, "client disconnected") delete(enable.subscribers, id) - fmt.Printf("logger/response unsubscribed %s\n", uuid.UUID(id).String()) + + enable.baseLogger.Debug().Msgf("logger/response unsubscribed %s", uuid.UUID(id).String()) } return nil diff --git a/backend/pkg/broker/topics/logger/logger_test.go b/backend/pkg/broker/topics/logger/logger_test.go index abc74da2a..22829bf76 100644 --- a/backend/pkg/broker/topics/logger/logger_test.go +++ b/backend/pkg/broker/topics/logger/logger_test.go @@ -2,12 +2,15 @@ package logger_test import ( "encoding/json" - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - data "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/logger" - "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" "log" "testing" "time" + + "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" + data "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/logger" + "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" + + trace "github.com/rs/zerolog/log" ) var errorFlag bool @@ -38,7 +41,7 @@ func TestLoggerTopic_ClientMessage(t *testing.T) { errorFlag = true api := MockAPI{} - loggerTopic := data.NewEnableTopic() + loggerTopic := data.NewEnableTopic(trace.Logger) loggerTopic.SetAPI(api) payload, _ := json.Marshal(true) diff --git a/backend/pkg/broker/topics/message/update.go b/backend/pkg/broker/topics/message/update.go index 1adecd145..40ae5479a 100644 --- a/backend/pkg/broker/topics/message/update.go +++ b/backend/pkg/broker/topics/message/update.go @@ -134,7 +134,7 @@ func (push *pushStruct) Data(boardName string) wrapper { Kind: "info", Payload: "Order Sent", Board: boardName, - Name: string(data.Id()), + Name: fmt.Sprintf("%d", data.Id()), Timestamp: protection.NowTimestamp(), } } diff --git a/backend/pkg/websocket/pool.go b/backend/pkg/websocket/pool.go index 6d677fe4e..41736015e 100644 --- a/backend/pkg/websocket/pool.go +++ b/backend/pkg/websocket/pool.go @@ -13,12 +13,12 @@ type ClientId uuid.UUID type messageCallback = func(ClientId, *Message) type Pool struct { - clientMx *sync.Mutex - clients map[ClientId]*Client - connections <-chan *Client - onMessage messageCallback - - logger zerolog.Logger + clientMx *sync.Mutex + clients map[ClientId]*Client + connections <-chan *Client + onMessage messageCallback + onDisconnect func(count int) + logger zerolog.Logger } func NewPool(connections <-chan *Client, baseLogger zerolog.Logger) *Pool { @@ -31,12 +31,12 @@ func NewPool(connections <-chan *Client, baseLogger zerolog.Logger) *Pool { }) handler := &Pool{ - clientMx: &sync.Mutex{}, - clients: make(map[ClientId]*Client), - connections: connections, - onMessage: func(ClientId, *Message) {}, - - logger: logger, + clientMx: &sync.Mutex{}, + clients: make(map[ClientId]*Client), + connections: connections, + onMessage: func(ClientId, *Message) {}, + onDisconnect: func(count int) {}, + logger: logger, } go handler.listen() @@ -111,6 +111,11 @@ func (pool *Pool) Broadcast(message Message) { } } +func (pool *Pool) SetOnDisconnect(onDisconnect func(count int)) { + pool.logger.Trace().Msg("set on disconnect") + pool.onDisconnect = onDisconnect +} + func (pool *Pool) Disconnect(id ClientId, code int, reason string) error { clientLogger := pool.logger.With().Str("id", uuid.UUID(id).String()).Logger() @@ -129,10 +134,16 @@ func (pool *Pool) Disconnect(id ClientId, code int, reason string) error { func (pool *Pool) onClose(id ClientId) func() { return func() { pool.clientMx.Lock() - defer pool.clientMx.Unlock() pool.logger.Debug().Str("id", uuid.UUID(id).String()).Msg("close") delete(pool.clients, id) + + count := len(pool.clients) + pool.clientMx.Unlock() + + if pool.onDisconnect != nil { + pool.onDisconnect(count) + } } } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index cd795342e..000000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: '3.8' - -services: - # Backend service - backend: - build: - context: ./backend - dockerfile: Dockerfile.dev - volumes: - - ./backend:/app - - ./adj:/app/adj - ports: - - "8080:8080" - environment: - - CGO_ENABLED=1 - command: sh -c "cd cmd && go run ." - - # Ethernet View - ethernet-view: - image: node:18-alpine - working_dir: /app - volumes: - - ./ethernet-view:/app - - ./common-front:/common-front - ports: - - "5174:5174" - command: sh -c "npm install && npm run dev -- --host" - - # Control Station - control-station: - image: node:18-alpine - working_dir: /app - volumes: - - ./control-station:/app - - ./common-front:/common-front - ports: - - "5173:5173" - command: sh -c "npm install && npm run dev -- --host" - - # Common front (for building) - common-front: - image: node:18-alpine - working_dir: /app - volumes: - - ./common-front:/app - command: sh -c "npm install && npm run build && npm run dev" \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 537a8aad0..000000000 --- a/docs/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# Hyperloop UPV Control Station Documentation - -Welcome to the comprehensive documentation for the Hyperloop UPV Control Station - a real-time monitoring and control system for hyperloop pod operations. - -## ๐Ÿš€ Quick Navigation - -### For New Users -1. **[Getting Started Guide](guides/getting-started.md)** - First steps with the Control Station -2. **[System Overview](architecture/README.md)** - Understand the architecture -3. **[Complete Architecture Guide](../CONTROL_STATION_COMPLETE_ARCHITECTURE.md)** - Deep dive into the system - -### For Developers -1. **[Development Setup](development/DEVELOPMENT.md)** - Set up your environment -2. **[Packet Flow Reference](architecture/packet-flow-reference.md)** - Understand data flow -3. **[Backend Architecture](architecture/backend.md)** - Backend implementation details -4. **[Issues and Improvements](architecture/issues-and-improvements.md)** - Known issues and roadmap - -### For Operators -1. **[Configuration Guide](guides/configuration.md)** - System configuration -2. **[Deployment Guide](guides/deployment.md)** - Production deployment -3. **[Common Issues](troubleshooting/common-issues.md)** - Troubleshooting guide - -## ๐Ÿ“š Complete Documentation Index - -### ๐Ÿ—๏ธ Architecture & Design -- **[System Overview](architecture/README.md)** - High-level architecture overview -- **[Complete Architecture Guide](../CONTROL_STATION_COMPLETE_ARCHITECTURE.md)** - Comprehensive system documentation with full packet flow -- **[Backend Architecture](architecture/backend.md)** - Go backend design and implementation -- **[Frontend Architecture](architecture/frontend.md)** - React frontend structure -- **[Packet Flow Reference](architecture/packet-flow-reference.md)** - Quick reference for data flow -- **[Communication Protocols](architecture/protocols.md)** - Network protocol overview -- **[Issues and Improvements](architecture/issues-and-improvements.md)** - Technical debt and roadmap - -### ๐Ÿ› ๏ธ Development -- **[Development Setup](development/DEVELOPMENT.md)** - Complete development environment guide -- **[Cross-Platform Scripts](development/CROSS_PLATFORM_DEV_SUMMARY.md)** - Development scripts documentation -- **[Scripts Reference](development/scripts.md)** - All available development scripts - -### ๐Ÿ“– User Guides -- **[Getting Started](guides/getting-started.md)** - Quick start for new users -- **[Configuration](guides/configuration.md)** - config.toml and ADJ specifications -- **[Deployment](guides/deployment.md)** - Production deployment instructions -- **[Testing](guides/testing.md)** - Testing strategies and tools - -### ๐Ÿ”ง Troubleshooting -- **[Common Issues](troubleshooting/common-issues.md)** - Frequently encountered problems -- **[BLCU Fix Summary](troubleshooting/BLCU_FIX_SUMMARY.md)** - Bootloader troubleshooting -- **[Platform-Specific Issues](troubleshooting/platform-issues.md)** - OS-specific problems - -## ๐Ÿš€ Quick Start - -New to the project? Start here: - -1. **[Getting Started Guide](guides/getting-started.md)** - Overview and first steps -2. **[Development Setup](development/DEVELOPMENT.md)** - Set up your development environment -3. **[Architecture Overview](architecture/README.md)** - Understand the system design - -## ๐Ÿ“‹ Additional Resources - -### Root Level Documentation -- **[README.md](../README.md)** - Project overview and quick setup -- **[CONTRIBUTING.md](../CONTRIBUTING.md)** - How to contribute to the project -- **[CLAUDE.md](../CLAUDE.md)** - AI assistant instructions for code development - -### Component-Specific Documentation -- **[Common Frontend](../common-front/README.md)** - Shared React component library -- **[Backend Captures](../backend/captures/README.md)** - Network packet capture samples - -### GitHub Templates -- **[Bug Reports](../.github/ISSUE_TEMPLATE/bug.md)** - Bug report template -- **[Feature Requests](../.github/ISSUE_TEMPLATE/feature.md)** - Feature request template -- **[Task Templates](../.github/ISSUE_TEMPLATE/task.md)** - Task template - -## ๐Ÿ”„ Documentation Updates - -This documentation is actively maintained. If you find errors or have suggestions: - -1. Check existing [issues](https://github.com/HyperloopUPV-H8/h9-backend/issues) -2. Create a new issue using the appropriate template -3. Submit a pull request with improvements - -## ๐Ÿ“ Contributing to Documentation - -When adding new documentation: - -1. **Development docs** โ†’ `docs/development/` -2. **Architecture docs** โ†’ `docs/architecture/` -3. **User guides** โ†’ `docs/guides/` -4. **Troubleshooting** โ†’ `docs/troubleshooting/` -5. **Component-specific** โ†’ Keep in respective component directories - -Follow the existing markdown style and include: -- Clear headings and structure -- Code examples where applicable -- Cross-references to related documentation -- Platform-specific notes when relevant - ---- - -*Last updated: 2025-06-03* diff --git a/docs/architecture/README.md b/docs/architecture/README.md deleted file mode 100644 index 3bc3ebb6c..000000000 --- a/docs/architecture/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# System Architecture - -The Hyperloop UPV Control Station is a real-time monitoring and control system for pod operations. - -## Quick Links -- ๐Ÿ“– **[Complete Architecture Guide](../../CONTROL_STATION_COMPLETE_ARCHITECTURE.md)** - Comprehensive system documentation -- ๐Ÿ”„ **[Packet Flow Reference](packet-flow-reference.md)** - Quick reference for data flow -- ๐Ÿ“ก **[Communication Protocols](protocols.md)** - Network protocol specifications -- ๐Ÿ› **[Known Issues](issues-and-improvements.md)** - Current limitations and roadmap - -## Overview - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Pod Boards โ”‚โ—„โ”€โ”€โ–บโ”‚ Backend (Go) โ”‚โ—„โ”€โ”€โ–บโ”‚ Frontend (React)โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ€ข Sensors โ”‚ โ”‚ โ€ข TCP/UDP โ”‚ โ”‚ โ€ข Control UI โ”‚ -โ”‚ โ€ข Actuators โ”‚ โ”‚ โ€ข Processing โ”‚ โ”‚ โ€ข Monitoring โ”‚ -โ”‚ โ€ข Controllers โ”‚ โ”‚ โ€ข WebSocket โ”‚ โ”‚ โ€ข Logging โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ–ฒ โ”‚ โ–ฒ - โ”‚ โ–ผ โ”‚ - โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ ADJ Config โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ (JSON specs) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## Core Components - -### Backend (Go) -High-performance server managing real-time communication and data processing. -- **Location**: [`backend/`](../../backend) -- **Key Features**: Concurrent packet processing, automatic reconnection, sub-10ms response time -- **Documentation**: [Backend Architecture](backend.md) - -### Frontend (React/TypeScript) -Modern web interfaces for system monitoring and control. -- **Locations**: [`control-station/`](../../control-station), [`ethernet-view/`](../../ethernet-view) -- **Key Features**: Real-time updates, interactive controls, data visualization -- **Documentation**: [Frontend Architecture](frontend.md) - -### ADJ System -JSON-based configuration defining all communication specifications. -- **Location**: [`adj/`](https://github.com/HyperloopUPV-H8/adj) (external repository) -- **Purpose**: Board definitions, packet structures, unit conversions -- **Documentation**: [ADJ Specification](../../backend/internal/adj/README.md) - -## Key Capabilities - -- **Real-time Performance**: 100+ Mbps data processing, <10ms fault detection -- **Modular Design**: Board-agnostic architecture using ADJ specifications -- **Fault Tolerance**: Automatic reconnection, graceful degradation -- **Scalability**: Supports 10+ concurrent board connections - -## Technology Stack - -| Component | Technology | Purpose | -|-----------|------------|---------| -| Backend | Go 1.21+ | High-performance networking and concurrency | -| Frontend | React 18 + TypeScript | Type-safe UI development | -| Communication | WebSocket | Real-time bidirectional updates | -| Configuration | JSON (ADJ) | Flexible system specification | -| Networking | TCP/UDP/TFTP | Reliable and fast data transfer | - -## Documentation Index - -### Architecture Details -- [Backend Architecture](backend.md) - Go server design and implementation -- [Frontend Architecture](frontend.md) - React application structure -- [Binary Protocol](binary-protocol.md) - Wire protocol specification -- [WebSocket API](websocket-api.md) - Frontend-backend communication - -### Development Resources -- [Getting Started](../guides/getting-started.md) - New developer guide -- [Development Setup](../development/DEVELOPMENT.md) - Environment configuration -- [Troubleshooting](../troubleshooting/common-issues.md) - Common problems - -## Next Steps - -- **New to the project?** Start with the [Getting Started Guide](../guides/getting-started.md) -- **Understanding data flow?** Read the [Complete Architecture Guide](../../CONTROL_STATION_COMPLETE_ARCHITECTURE.md) -- **Debugging issues?** Check [Troubleshooting](../troubleshooting/common-issues.md) -- **Contributing?** Review [Known Issues](issues-and-improvements.md) for areas needing help - ---- - -*For the complete technical deep-dive, see the [Control Station Complete Architecture](../../CONTROL_STATION_COMPLETE_ARCHITECTURE.md) document.* \ No newline at end of file diff --git a/docs/architecture/backend.md b/docs/architecture/backend.md deleted file mode 100644 index f3cd74400..000000000 --- a/docs/architecture/backend.md +++ /dev/null @@ -1,346 +0,0 @@ -# Backend Architecture - -The backend is a high-performance Go server managing real-time communication between pod boards and the control station frontend. - -> **Note**: For complete system architecture and packet flow, see the [Control Station Complete Architecture](../../CONTROL_STATION_COMPLETE_ARCHITECTURE.md) document. - -## Design Principles - -- **Real-time Performance**: Sub-10ms fault detection with concurrent processing -- **Type Safety**: Strongly typed packet definitions from ADJ specifications -- **Fault Tolerance**: Automatic reconnection and graceful degradation -- **Modular Architecture**: Clear separation of concerns across packages - -## Package Structure - -``` -backend/ -โ”œโ”€โ”€ cmd/ -โ”‚ โ””โ”€โ”€ main.go # Entry point, initialization -โ”œโ”€โ”€ internal/ -โ”‚ โ”œโ”€โ”€ adj/ # ADJ parser and validator -โ”‚ โ”œโ”€โ”€ pod_data/ # Packet structure definitions -โ”‚ โ”œโ”€โ”€ update_factory/ # Update message creation -โ”‚ โ””โ”€โ”€ vehicle/ # Vehicle state management -โ””โ”€โ”€ pkg/ - โ”œโ”€โ”€ abstraction/ # Core interfaces - โ”œโ”€โ”€ boards/ # Board-specific logic (BLCU) - โ”œโ”€โ”€ broker/ # Message distribution - โ”œโ”€โ”€ transport/ # Network communication - โ”œโ”€โ”€ vehicle/ # Board coordination - โ””โ”€โ”€ websocket/ # Frontend connections -``` - -## Core Components - -### Transport Layer (`pkg/transport/`) - -Manages all network I/O with pluggable handlers: - -```go -// Transport coordinates all network communication -type Transport struct { - decoder *presentation.Decoder - encoder *presentation.Encoder - connections map[TransportTarget]net.Conn - mu sync.RWMutex -} -``` - -**Key Features**: -- Thread-safe connection pooling -- Automatic reconnection with exponential backoff -- Concurrent packet processing -- Special handling for fault propagation (packet ID 0) - -### Presentation Layer - -Handles binary protocol encoding/decoding: - -```go -// Packet structure (little-endian) -type Packet struct { - ID uint16 // 2 bytes - Payload []byte // Variable length -} -``` - -**Decoding Process**: -1. Read packet ID (2 bytes) -2. Look up decoder by ID -3. Parse payload based on ADJ descriptor -4. Apply unit conversions -5. Return typed packet object - -### Message Broker - -Central hub for internal message routing: - -```go -// Topic-based publish/subscribe -type Broker struct { - topics map[string]Topic - pool *WebSocketPool -} -``` - -**Topic Hierarchy**: -- `data/update` - Real-time measurements -- `connection/update` - Board status -- `order/send` - Command dispatch -- `protection/alert` - Safety notifications -- `logger/*` - Logging control -- `blcu/*` - Bootloader operations - -### Vehicle Manager - -Coordinates board interactions and state: - -```go -type Vehicle struct { - broker *Broker - logger *Logger - transport *Transport - boards map[BoardId]Board - updateFactory *UpdateFactory -} -``` - -**Responsibilities**: -- Order validation and execution -- State management -- Board registry -- Error propagation - -## Concurrency Model - -### Goroutine Architecture - -```go -// Per-connection handler -go func(conn net.Conn) { - defer conn.Close() - for { - packet := readPacket(conn) - packetChan <- packet - } -}() - -// Packet processor -go func() { - for packet := range packetChan { - processPacket(packet) - } -}() -``` - -### Channel Usage - -- **Packet Distribution**: Buffered channels for flow control -- **Error Handling**: Dedicated error channel -- **Shutdown Coordination**: Context cancellation - -## Performance Optimizations - -### Network Tuning - -```go -// TCP optimizations -conn.SetNoDelay(true) // Disable Nagle -conn.SetKeepAlive(true) // Enable keep-alive -conn.SetKeepAlivePeriod(1*time.Second) -``` - -### Memory Management - -- **Object Pooling**: Reused packet buffers -- **Bounded Queues**: Prevent memory exhaustion -- **Zero-Copy Operations**: Minimize allocations - -### Profiling Support - -```bash -# CPU profiling -./backend -cpuprofile=cpu.prof - -# Memory profiling -curl http://localhost:4040/debug/pprof/heap > heap.prof - -# Block profiling -./backend -blockprofile=10 -``` - -## Configuration - -### Structure (`config.toml`) - -```toml -[adj] -branch = "main" -test = false - -[vehicle] -boards = ["LCU", "HVSCU", "BMSL"] - -[tcp] -connection_timeout = 5000 -keep_alive = 1000 -backoff_multiplier = 1.5 - -[server.control-station] -addr = "0.0.0.0:8081" -static_path = "./control-station" -``` - -### Environment Overrides - -```bash -BACKEND_LOG_LEVEL=debug ./backend -BACKEND_ADJ_BRANCH=dev ./backend -``` - -## Error Handling - -### Error Types - -```go -// Structured errors with context -type ConnectionError struct { - Board string - Addr string - Err error - Timestamp time.Time -} - -func (e ConnectionError) Error() string { - return fmt.Sprintf("[%s] board %s at %s: %v", - e.Timestamp.Format(time.RFC3339), - e.Board, e.Addr, e.Err) -} -``` - -### Recovery Strategies - -1. **Connection Loss**: Exponential backoff reconnection -2. **Decode Errors**: Log and continue processing -3. **Panic Recovery**: Goroutine-level recovery -4. **Resource Exhaustion**: Circuit breakers - -## Critical Issues - -### 1. BLCU Hardcoding - -```go -// FIXME: Move to ADJ configuration -const ( - BlcuDownloadOrderId = 701 - BlcuUploadOrderId = 700 -) -``` - -**Impact**: Cannot adapt to different BLCU versions -**Solution**: Define BLCU packets in ADJ like other boards - -### 2. Large Monolithic Files - -- `main.go`: 800+ lines -- Complex initialization logic -- Hard to test - -**Solution**: Refactor into logical modules - -### 3. Test Coverage - -Current coverage: ~30% -**Critical gaps**: -- Connection failure scenarios -- Concurrent access patterns -- Edge cases in packet decoding - -## Development Guidelines - -### Adding New Board Types - -1. Define board in ADJ: -```json -{ - "board_id": 5, - "board_ip": "192.168.1.5", - "measurements": ["measurements.json"], - "packets": ["packets.json", "orders.json"] -} -``` - -2. Register in config.toml: -```toml -[vehicle] -boards = ["LCU", "HVSCU", "BMSL", "NEWBOARD"] -``` - -3. Implement board-specific logic if needed: -```go -type NewBoard struct { - api abstraction.BoardAPI -} - -func (b *NewBoard) Notify(notification abstraction.BoardNotification) { - // Handle board-specific notifications -} -``` - -### Testing - -```bash -# Run all tests -go test ./... - -# Run with coverage -go test -coverprofile=coverage.out ./... -go tool cover -html=coverage.out - -# Run specific package tests -go test -v ./pkg/transport/... - -# Benchmark tests -go test -bench=. ./pkg/transport/presentation/ -``` - -### Debugging - -```bash -# Enable trace logging -./backend -trace=trace - -# Enable pprof endpoints -./backend # pprof available at localhost:4040 - -# Use delve debugger -dlv debug ./cmd/main.go - -# Trace specific boards -tail -f trace.json | jq 'select(.board == "LCU")' -``` - -## Future Roadmap - -### Short Term (1-2 months) -- [ ] Move BLCU configuration to ADJ -- [ ] Increase test coverage to 50% -- [ ] Refactor main.go into modules -- [ ] Add connection pooling limits - -### Medium Term (3-6 months) -- [ ] Plugin system for board types -- [ ] Configuration hot reload -- [ ] Distributed deployment support -- [ ] Advanced message filtering - -### Long Term (6+ months) -- [ ] GraphQL API alongside WebSocket -- [ ] Time-series database integration -- [ ] Horizontal scaling support -- [ ] Full telemetry system - ---- - -*For complete system documentation including packet flow and troubleshooting, see the [Control Station Complete Architecture](../../CONTROL_STATION_COMPLETE_ARCHITECTURE.md).* \ No newline at end of file diff --git a/docs/architecture/binary-protocol.md b/docs/architecture/binary-protocol.md deleted file mode 100644 index 798a998d6..000000000 --- a/docs/architecture/binary-protocol.md +++ /dev/null @@ -1,276 +0,0 @@ -# Binary Protocol Specification - -This document defines the binary wire protocol used for communication between vehicle boards and the backend. - -## Protocol Overview - -All communication uses a simple, efficient binary protocol with fixed headers and variable-length payloads. - -### Endianness -**All multi-byte values use little-endian byte order.** - -### Packet Structure -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Header (2B) โ”‚ Payload (variable) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Packet ID โ”‚ Data fields per ADJ โ”‚ -โ”‚ (uint16) โ”‚ specification โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## Data Type Encoding - -### Numeric Types -| Type | Size | Range | Encoding | -|------|------|-------|----------| -| uint8 | 1 byte | 0 to 255 | Raw byte | -| uint16 | 2 bytes | 0 to 65,535 | Little-endian | -| uint32 | 4 bytes | 0 to 4,294,967,295 | Little-endian | -| uint64 | 8 bytes | 0 to 18,446,744,073,709,551,615 | Little-endian | -| int8 | 1 byte | -128 to 127 | Two's complement | -| int16 | 2 bytes | -32,768 to 32,767 | Two's complement, little-endian | -| int32 | 4 bytes | -2,147,483,648 to 2,147,483,647 | Two's complement, little-endian | -| int64 | 8 bytes | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | Two's complement, little-endian | -| float32 | 4 bytes | IEEE 754 single precision | Little-endian | -| float64 | 8 bytes | IEEE 754 double precision | Little-endian | - -### Boolean Type -- Encoded as uint8: 0 = false, non-zero = true - -### Enum Type -- Encoded as uint8 representing the index in the enum definition - -## Packet Categories - -### 1. Data Packets (Board โ†’ Backend) -Used for sensor measurements and telemetry. - -#### Example: Temperature Sensor -``` -ADJ Definition: -{ - "id": 100, - "variables": ["sensor_id", "temperature", "timestamp"] -} - -Measurements: -- sensor_id: uint8 -- temperature: float32 (ยฐC) -- timestamp: uint32 (milliseconds) - -Binary encoding: -64 00 // Packet ID: 100 -01 // sensor_id: 1 -CD CC 8C 41 // temperature: 17.6ยฐC (float32) -10 27 00 00 // timestamp: 10000ms -``` - -### 2. Protection Packets (Board โ†’ Backend) -Safety-critical notifications with severity levels. - -#### Packet ID Ranges -- 1000-1999: Fault (critical failures) -- 2000-2999: Warning (non-critical issues) -- 3000-3999: OK (cleared conditions) - -#### Structure -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Packet ID โ”‚ Board ID โ”‚ Error Code โ”‚ -โ”‚ (uint16) โ”‚ (uint16) โ”‚ (uint8) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -#### Example: Overheat Fault -``` -E8 03 // ID: 1000 (fault) -04 00 // Board ID: 4 (LCU) -15 // Error code: 21 (overheat) -``` - -### 3. Order Packets (Backend โ†’ Board) -Commands sent from backend to boards. - -#### Example: Set Current -``` -ADJ Definition: -{ - "id": 9995, - "variables": ["ldu_id", "lcu_desired_current"] -} - -Binary encoding: -0B 27 // ID: 9995 -02 // ldu_id: 2 -00 00 20 40 // desired_current: 2.5A (float32) -``` - -### 4. State Order Packets -Special packets for managing periodic order execution. - -#### Add State Order (Enable periodic execution) -``` -Structure: -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Packet ID โ”‚ Board Name โ”‚ Order IDs โ”‚ -โ”‚ (uint16) โ”‚ Length + Str โ”‚ Count + List โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -Example: -05 00 // ID: 5 (add_state_order) -03 // String length: 3 -4C 43 55 // "LCU" -02 00 // Order count: 2 -0B 27 // Order ID: 9995 -0C 27 // Order ID: 9996 -``` - -#### Remove State Order (Disable periodic execution) -Same structure as Add State Order but with ID 6. - -### 5. BLCU Packets -Special packets for bootloader operations. - -#### Download Order (ID: 50) -``` -32 00 // ID: 50 -[filename] // Null-terminated string -[file_size] // uint32 -[checksum] // uint32 -``` - -#### Upload Order (ID: 51) -``` -33 00 // ID: 51 -[filename] // Null-terminated string -``` - -## Special Packet IDs - -### ID 0: Fault Propagation -When any board sends a packet with ID 0, the backend immediately replicates it to all connected boards. This enables system-wide emergency stop. - -``` -00 00 // ID: 0 (emergency stop) -[payload] // Board-specific fault data -``` - -## Unit Conversions - -The protocol supports automatic unit conversions between pod units and display units. - -### Conversion Operations -- `*X`: Multiply by X -- `/X`: Divide by X -- `+X`: Add X -- `-X`: Subtract X - -### Example: Temperature Conversion -``` -Pod units: Celsius -Display units: Kelvin -Conversion: "+273.15" - -Wire value: 25.0ยฐC (float32: 0x41C80000) -Display value: 298.15K -``` - -## Transport Protocols - -### TCP (Reliable Commands) -- Used for: Orders, critical data, connection management -- Port: 50500 (board โ†’ backend), 50401 (backend โ†’ board) -- Features: Guaranteed delivery, order preservation - -### UDP (High-Speed Data) -- Used for: Sensor data, non-critical telemetry -- Port: 50400 -- Features: Low latency, no retransmission - -## Error Handling - -### Malformed Packets -- Invalid packet ID: Log and drop -- Incorrect length: Close connection -- Decode failure: Send protection fault - -### Connection Errors -- TCP disconnect: Automatic reconnection -- UDP packet loss: Update statistics -- Timeout: Configurable retry logic - -## Performance Considerations - -### Packet Size Limits -- TCP: No inherent limit (fragmented as needed) -- UDP: 1500 bytes (Ethernet MTU) -- Recommended: Keep packets under 1000 bytes - -### Throughput -- Design target: 1000+ packets/second per board -- Actual limit: Network and processing dependent - -### Latency -- TCP: ~1-5ms typical -- UDP: <1ms typical -- Critical path (protection): <5ms requirement - -## Security Notes - -### Current Implementation -- No encryption (trusted network assumed) -- No authentication -- Basic validation only - -### Recommendations -- Add packet signing for critical commands -- Implement sequence numbers -- Consider TLS for external connections - -## Debugging - -### Packet Capture -Use Wireshark with these filters: -``` -# All pod traffic -ip.addr == 192.168.1.0/24 - -# Specific board -ip.addr == 192.168.1.4 - -# UDP sensor data only -udp.port == 50400 -``` - -### Common Issues -1. **Byte order mismatch**: Check endianness -2. **Packet truncation**: Verify buffer sizes -3. **ID not recognized**: Check ADJ configuration -4. **Float precision**: Use float32 unless needed - -## Implementation Notes - -### C/C++ (Firmware) -```c -// Pack structure (GCC) -struct __attribute__((packed)) DataPacket { - uint16_t id; - uint8_t sensor_id; - float temperature; - uint32_t timestamp; -}; -``` - -### Go (Backend) -```go -// Use encoding/binary -binary.Write(buffer, binary.LittleEndian, packet) -``` - -### Python (Testing) -```python -# Use struct module -import struct -data = struct.pack('; -} - -interface OrderField { - value: string | number | boolean; - isEnabled: boolean; - type: string; // Data type for encoding -} -``` - -#### Data Flow -1. **Subscriptions**: Components subscribe to WebSocket topics -2. **Updates**: Real-time data pushed from backend -3. **State Management**: Updates stored in Redux/Zustand -4. **Rendering**: React components re-render on state changes - -## State Management - -### Redux Store (Legacy) -Used for complex application state: -- Board configurations -- Historical data -- User preferences - -### Zustand Stores -Modern stores for specific features: -- **Orders Store**: Order definitions and state -- **Connection Store**: Board connection status -- **Logger Store**: Logging configuration - -### Local Component State -Used for: -- UI interactions (modals, dropdowns) -- Form inputs -- Temporary values - -## WebSocket Communication - -### Topic Subscriptions -```typescript -// Subscribe to data updates -handler.subscribe("podData/update", { - id: uniqueId, - cb: (data) => updateTelemetry(data) -}); - -// Send order -handler.post("order/send", orderPayload); - -// Exchange pattern for BLCU -handler.exchange("blcu/upload", request, id, (response) => { - // Handle progressive updates -}); -``` - -### Message Flow -1. **Connection**: WebSocket connects to backend -2. **Handshake**: Re-establish subscriptions on reconnect -3. **Message Reception**: JSON messages parsed and routed -4. **State Update**: Stores updated with new data -5. **UI Update**: Components re-render - -## Performance Optimizations - -### Rendering Optimizations -- **React.memo**: Prevent unnecessary re-renders -- **useMemo/useCallback**: Optimize expensive computations -- **Virtualization**: For large data lists -- **Throttling**: Limit update frequency for high-rate data - -### Data Management -- **Selective Subscriptions**: Only subscribe to needed data -- **Data Aggregation**: Backend aggregates before sending -- **Caching**: Cache static data locally -- **Cleanup**: Unsubscribe when components unmount - -### Bundle Optimization -- **Code Splitting**: Lazy load heavy components -- **Tree Shaking**: Remove unused code -- **Compression**: Gzip/Brotli for production -- **CDN**: Serve static assets from CDN - -## Development Workflow - -### Project Structure -``` -control-station/ -โ”œโ”€โ”€ src/ -โ”‚ โ”œโ”€โ”€ components/ # UI components -โ”‚ โ”œโ”€โ”€ pages/ # Page-level components -โ”‚ โ”œโ”€โ”€ services/ # API and WebSocket -โ”‚ โ”œโ”€โ”€ hooks/ # Custom hooks -โ”‚ โ”œโ”€โ”€ styles/ # Global styles -โ”‚ โ””โ”€โ”€ types/ # TypeScript definitions -โ”œโ”€โ”€ public/ # Static assets -โ””โ”€โ”€ vite.config.ts # Build configuration -``` - -### Development Tools -- **Vite Dev Server**: Hot module replacement -- **TypeScript**: Type checking and IntelliSense -- **ESLint**: Code quality enforcement -- **Prettier**: Code formatting -- **React DevTools**: Component debugging - -### Building and Deployment -```bash -# Development -npm run dev # Start dev server - -# Production -npm run build # Create production build -npm run preview # Preview production build -``` - -## Common Patterns - -### Custom Hooks -```typescript -// WebSocket subscription hook -function useSubscription(topic: string, callback: (data: T) => void) { - const handler = useWsHandler(); - - useEffect(() => { - const id = nanoid(); - handler.subscribe(topic, { id, cb: callback }); - return () => handler.unsubscribe(topic, id); - }, [topic]); -} -``` - -### Error Boundaries -```typescript -class ErrorBoundary extends Component { - // Catch and display errors gracefully - // Log errors to monitoring service - // Provide fallback UI -} -``` - -### Loading States -```typescript -function DataPanel() { - const { data, loading, error } = useData(); - - if (loading) return ; - if (error) return ; - return ; -} -``` - -## Testing Strategy - -### Unit Tests -- Component logic testing -- Hook behavior verification -- Utility function testing - -### Integration Tests -- WebSocket communication -- State management flows -- User interaction scenarios - -### E2E Tests -- Critical user journeys -- Cross-browser compatibility -- Performance benchmarks - -## Security Considerations - -### Current Implementation -- No authentication (trusted network) -- Input validation on forms -- XSS protection via React -- Content Security Policy headers - -### Recommended Improvements -- Token-based authentication -- Request signing -- Rate limiting -- Audit logging - -## Future Enhancements - -### Planned Features -1. **Real-time Collaboration**: Multi-user support -2. **Advanced Visualizations**: 3D pod representation -3. **Machine Learning**: Anomaly detection -4. **Mobile Support**: Responsive design -5. **Offline Mode**: Local data caching - -### Technical Improvements -1. **Type Generation**: Auto-generate from ADJ -2. **Component Library**: Storybook documentation -3. **Performance Monitoring**: Real user metrics -4. **Accessibility**: WCAG compliance -5. **Internationalization**: Multi-language support \ No newline at end of file diff --git a/docs/architecture/issues-and-improvements.md b/docs/architecture/issues-and-improvements.md deleted file mode 100644 index b9e5e5568..000000000 --- a/docs/architecture/issues-and-improvements.md +++ /dev/null @@ -1,290 +0,0 @@ -# Control Station - Issues and Improvement Recommendations - -## Critical Issues - -### 1. BLCU Hardcoded Configuration -**Issue**: BLCU packet IDs and configuration are hardcoded in the backend -```go -const ( - BlcuDownloadOrderId = 50 - BlcuUploadOrderId = 51 -) -``` -**Impact**: Cannot adapt to different BLCU versions or configurations -**Solution**: Move BLCU configuration to ADJ specification like other boards - -### 2. Missing Error Recovery Documentation -**Issue**: No clear documentation on error recovery procedures -**Impact**: Operators don't know how to recover from common failures -**Solution**: Create operational runbooks for common scenarios - -### 3. Inconsistent Error Handling -**Issue**: Error handling varies across packages -- Some use wrapped errors -- Some panic -- Some silently log -**Impact**: Difficult to debug issues in production -**Solution**: Implement consistent error handling strategy with proper error types - -## High Priority Improvements - -### 1. WebSocket Message Type Safety -**Current State**: Frontend uses `any` types for WebSocket payloads -```typescript -payload: any; // No type safety -``` -**Improvement**: Generate TypeScript types from ADJ specifications -**Benefits**: -- Compile-time error detection -- Better IDE support -- Reduced runtime errors - -### 2. Connection Pool Management -**Current State**: Simple map with mutex protection -```go -connections map[abstraction.TransportTarget]net.Conn -``` -**Improvement**: Implement proper connection pool with: -- Health checks -- Connection limits -- Metrics tracking -- Load balancing (for multiple boards of same type) - -### 3. Packet Validation -**Current State**: Limited validation after decoding -**Improvement**: Add validation layer: -- Range checking based on ADJ limits -- Rate limiting per packet type -- Anomaly detection -- Data integrity checks - -### 4. Configuration Hot Reload -**Current State**: Requires restart for configuration changes -**Improvement**: Implement configuration watcher: -- Hot reload for non-critical settings -- Graceful handling of ADJ updates -- Configuration validation before apply - -## Medium Priority Improvements - -### 1. Testing Infrastructure -**Current Coverage**: ~30% (estimated) -**Target**: 80%+ coverage -**Areas Needing Tests**: -- Packet encoding/decoding edge cases -- Connection failure scenarios -- WebSocket message handling -- Concurrent access patterns - -### 2. Performance Monitoring -**Current State**: Basic logging only -**Improvement**: Add metrics collection: -- Prometheus metrics endpoint -- Packet processing latency -- Queue depths -- Memory usage patterns -- Connection statistics - -### 3. Documentation Gaps -**Missing Documentation**: -- ADJ specification format details -- Board development guide -- Performance tuning guide -- Security hardening guide -- Deployment best practices - -### 4. Frontend State Management -**Current State**: Mixed patterns (Redux, Zustand, local state) -**Improvement**: Consolidate to single state management solution -**Benefits**: -- Consistent patterns -- Better debugging -- Time-travel debugging -- State persistence - -## Code Quality Issues - -### 1. Circular Dependencies -**Found In**: Internal packages have some circular references -**Solution**: Refactor to clear dependency hierarchy - -### 2. Large Files -**Examples**: -- `main.go`: 800+ lines -- Some frontend components: 500+ lines -**Solution**: Split into logical modules - -### 3. Magic Numbers -**Examples**: -```go -time.Second / 10 // What is this interval for? -64KB // Buffer size - why this value? -``` -**Solution**: Named constants with documentation - -### 4. Inconsistent Naming -**Examples**: -- `podData` vs `pod_data` -- `WsHandler` vs `WebSocketHandler` -**Solution**: Adopt consistent naming conventions - -## Architecture Improvements - -### 1. Plugin System -**Current**: All packet types compiled in -**Proposed**: Dynamic loading of packet handlers -**Benefits**: -- Easier board additions -- Reduced binary size -- Hot-swappable handlers - -### 2. Message Queue Integration -**Current**: In-memory broker only -**Proposed**: Optional persistent message queue (Redis/RabbitMQ) -**Benefits**: -- Message persistence -- Horizontal scaling -- Better fault tolerance - -### 3. Microservice Option -**Current**: Monolithic backend -**Proposed**: Optional microservice deployment -**Benefits**: -- Independent scaling -- Technology diversity -- Fault isolation - -### 4. Advanced Routing -**Current**: Simple topic-based routing -**Proposed**: Content-based routing with filters -**Benefits**: -- Reduced network traffic -- Client-specific data streams -- Better performance - -## Security Enhancements - -### 1. Authentication -**Current**: None (trusted network assumed) -**Needed**: -- Token-based auth for WebSocket -- Certificate-based auth for boards -- API key management - -### 2. Encryption -**Current**: Plaintext communication -**Needed**: -- TLS for external connections -- Optional encryption for board communication -- Secure key exchange - -### 3. Audit Logging -**Current**: Basic operational logs -**Needed**: -- Who did what when -- Configuration changes -- Critical commands -- Access patterns - -### 4. Rate Limiting -**Current**: None -**Needed**: -- Per-client limits -- Per-packet-type limits -- Burst handling -- DDoS protection - -## Operational Improvements - -### 1. Health Checks -**Current**: Basic ping/pong -**Needed**: -- Comprehensive health endpoint -- Dependency checks -- Performance metrics -- Ready/live probes - -### 2. Graceful Degradation -**Current**: All-or-nothing operation -**Needed**: -- Operate with partial boards -- Fallback modes -- Circuit breakers -- Bulkheading - -### 3. Deployment Automation -**Current**: Manual deployment -**Needed**: -- CI/CD pipeline -- Automated testing -- Blue-green deployment -- Rollback procedures - -### 4. Monitoring Integration -**Current**: Logs only -**Needed**: -- Grafana dashboards -- Alert rules -- SLO tracking -- Incident response - -## Developer Experience - -### 1. Development Environment -**Issues**: -- Complex setup process -- Platform-specific scripts -- Dependency management -**Solutions**: -- Docker-based development -- Unified script interface -- Better documentation - -### 2. Debugging Tools -**Current**: Basic logging -**Needed**: -- Packet inspector UI -- Message flow visualizer -- Performance profiler -- Debug mode with verbose output - -### 3. Code Generation -**Current**: Manual synchronization with ADJ -**Needed**: -- Auto-generate types from ADJ -- Validation code generation -- Documentation generation -- Test case generation - -## Priority Matrix - -### Immediate (This Week) -1. Document critical operational procedures -2. Fix BLCU hardcoding -3. Improve error messages - -### Short Term (This Month) -1. Increase test coverage to 50% -2. Implement basic metrics -3. Consolidate error handling - -### Medium Term (This Quarter) -1. Type safety improvements -2. Performance monitoring -3. Security audit - -### Long Term (This Year) -1. Plugin architecture -2. Microservice option -3. Advanced routing features - -## Migration Path - -For each improvement: -1. Design detailed specification -2. Implement behind feature flag -3. Test in parallel with existing code -4. Gradual rollout -5. Remove old code after validation - -This ensures system stability while improving the codebase incrementally. \ No newline at end of file diff --git a/docs/architecture/message-structures.md b/docs/architecture/message-structures.md deleted file mode 100644 index fa50b4d40..000000000 --- a/docs/architecture/message-structures.md +++ /dev/null @@ -1,525 +0,0 @@ -# Message Structures and Communication Protocols - -This document provides a comprehensive specification of all message structures and communication protocols used between the vehicle, backend, and frontend components of the Hyperloop Control Station. - -## Overview - -The communication system uses a three-layer architecture: - -1. **Vehicle โ†” Backend**: Binary packet protocols over TCP/UDP -2. **Backend โ†” Frontend**: JSON messages over WebSocket -3. **Internal Backend**: Go struct-based message routing - -## Table of Contents - -- [1. Vehicle to Backend Communication](#1-vehicle-to-backend-communication) -- [2. Backend to Frontend Communication](#2-backend-to-frontend-communication) -- [3. Backend Internal Message Flow](#3-backend-internal-message-flow) -- [4. Message Timing and Ordering](#4-message-timing-and-ordering) -- [5. Error Handling and Fault Tolerance](#5-error-handling-and-fault-tolerance) - ---- - -## 1. Vehicle to Backend Communication - -### 1.1 Transport Layer - -**Protocol**: TCP (primary) and UDP (secondary) -**Ports**: Defined in `adj/general_info.json` -- TCP_CLIENT: 50401 (backend connects to vehicle) -- TCP_SERVER: 50500 (vehicle connects to backend) -- UDP: 8000 (bidirectional communication) -- TFTP: 69 (file transfers) - -**Endianness**: Little-endian for all multi-byte fields - -### 1.2 Binary Packet Structure - -All packets follow this general structure: - -``` -Byte Offset: 0 2 4 6 8+ - |========|========|========|========|========| - | Packet ID | Payload Data... | - | (uint16) | (variable length) | - |========|========|========|========|========| -``` - -**Packet ID**: 16-bit unsigned integer identifying the packet type (defined in ADJ specifications) - -### 1.3 Incoming Message Types - -#### 1.3.1 Data Packets - -**Purpose**: Continuous telemetry data from vehicle sensors -**Go Type**: `pkg/transport/packet/data.Packet` - -**Binary Structure**: -``` -Byte Offset: 0 2 4 6 8+ - |========|========|========|========|========| - | Packet ID | Data Values... | - | (uint16) | (per ADJ spec) | - |========|========|========|========|========| -``` - -**Go Structure**: -```go -type Packet struct { - id abstraction.PacketId // uint16 packet identifier - values map[ValueName]Value // Measurement name -> value - enabled map[ValueName]bool // Measurement name -> enabled state - timestamp time.Time // Packet reception time -} -``` - -**Value Types**: -- `NumericValue[T]`: For numeric data (uint8-uint64, int8-int64, float32, float64) -- `BooleanValue`: For boolean measurements -- `EnumValue`: For discrete state values - -**Example ADJ Data Packet Definition**: -```json -{ - "id": 211, - "name": "vcu_regulator_packet", - "type": "data", - "variables": ["valve_state", "reference_pressure", "actual_pressure"] -} -``` - -#### 1.3.2 Protection Packets - -**Purpose**: Safety alerts and fault notifications -**Go Type**: `pkg/transport/packet/protection.Packet` - -**Binary Structure** (per package documentation): -``` -Byte Offset: 0 8 16 24 32+ - |========|========|========|========|========| - | Packet ID | type | kind | ... | - | (uint16) | (uint8)|(uint8) | | - |========|========|========|========|========| - | Name (null-terminated string) | - |========|========|========|========|========| - | Protection Data (variable length) | - |========|========|========|========|========| - |counter |counter | second | minute | hour | - |(uint16)|(uint16)|(uint8) |(uint8) |(uint8) | - |========|========|========|========|========| - | day | month | year | - |(uint8) |(uint8) | (uint16) | - |========|========|========|========| -``` - -**Go Structure**: -```go -type Packet struct { - id abstraction.PacketId // Packet identifier - Type Type // Data type being protected - Kind Kind // Protection condition type - Name string // Human-readable protection name - Data Data // Protection-specific data - Timestamp *Timestamp // RTC timestamp from vehicle - severity Severity // Protection severity level -} -``` - -**Protection Types**: -- `FaultSeverity`: Critical faults requiring immediate system shutdown -- `WarningSeverity`: Warnings requiring operator attention -- `OkSeverity`: Recovery from previous fault/warning state - -**Protection Kinds**: -- `BelowKind`: Value below threshold -- `AboveKind`: Value above threshold -- `OutOfBoundsKind`: Value outside acceptable range -- `EqualsKind`: Value equals specific condition -- `NotEqualsKind`: Value doesn't equal expected condition -- `ErrorHandlerKind`: System error condition -- `TimeAccumulationKind`: Time-based accumulation fault -- `WarningKind`: General warning condition - -#### 1.3.3 State Space Packets - -**Purpose**: Control system state information -**Go Type**: `pkg/transport/packet/state.Space` - -**Go Structure**: -```go -type Space struct { - id abstraction.PacketId // Packet identifier - state [8][15]float32 // 8x15 state matrix -} -``` - -**Usage**: Contains control parameters and state variables used by the vehicle's control algorithms. - -#### 1.3.4 BLCU Packets - -**Purpose**: Bootloader Communication Unit responses -**Go Type**: `pkg/transport/packet/blcu.Packet` - -**Usage**: Handles firmware update acknowledgments and bootloader communication during vehicle programming operations. - -### 1.4 Outgoing Message Types - -#### 1.4.1 Order Packets - -**Purpose**: Commands sent from backend to vehicle -**Go Types**: `pkg/transport/packet/order.Add`, `pkg/transport/packet/order.Remove` - -**Add Order Structure**: -```go -type Add struct { - id abstraction.PacketId // Packet identifier - orders []abstraction.PacketId // List of order IDs to enable -} -``` - -**Remove Order Structure**: -```go -type Remove struct { - id abstraction.PacketId // Packet identifier - orders []abstraction.PacketId // List of order IDs to disable -} -``` - -**Example ADJ Order Definition**: -```json -{ - "type": "order", - "name": "Brake Command", - "variables": ["brake_command", "target_pressure"] -} -``` - -### 1.5 File Transfer Protocol - -**Protocol**: TFTP (Trivial File Transfer Protocol) -**Port**: 69 -**Go Types**: `FileWriteMessage`, `FileReadMessage` - -**File Write (Backend โ†’ Vehicle)**: -```go -type FileWriteMessage struct { - filename string // Target filename on vehicle - io.Reader // File content source -} -``` - -**File Read (Vehicle โ†’ Backend)**: -```go -type FileReadMessage struct { - filename string // Source filename on vehicle - io.Writer // File content destination -} -``` - -**Usage**: Primarily used for firmware updates and configuration file transfers. - ---- - -## 2. Backend to Frontend Communication - -### 2.1 Transport Layer - -**Protocol**: WebSocket over HTTP -**Content Type**: JSON -**Go Type**: `pkg/websocket.Message` - -### 2.2 WebSocket Message Structure - -**Base Message Format**: -```go -type Message struct { - Topic abstraction.BrokerTopic `json:"topic"` // Message routing topic - Payload json.RawMessage `json:"payload"` // Topic-specific data -} -``` - -**JSON Wire Format**: -```json -{ - "topic": "data/update", - "payload": { /* topic-specific payload */ } -} -``` - -### 2.3 Message Topics - -#### 2.3.1 Data Update Messages - -**Topic**: `"data/update"` -**Direction**: Backend โ†’ Frontend -**Purpose**: Real-time sensor data updates - -**Payload Structure**: -```json -{ - "board_id": 0, - "packet_id": 211, - "packet_name": "vcu_regulator_packet", - "timestamp": "2024-01-15T10:30:45.123Z", - "measurements": { - "valve_state": { - "value": "open", - "type": "enum", - "enabled": true - }, - "reference_pressure": { - "value": 8.5, - "type": "float32", - "enabled": true, - "units": "bar" - }, - "actual_pressure": { - "value": 8.3, - "type": "float32", - "enabled": true, - "units": "bar" - } - } -} -``` - -#### 2.3.2 Connection Status Messages - -**Topic**: `"connection/update"` -**Direction**: Backend โ†’ Frontend -**Purpose**: Vehicle connection status updates - -**Payload Structure**: -```json -{ - "board_id": 0, - "board_name": "VCU", - "ip_address": "127.0.0.6", - "connected": true, - "last_seen": "2024-01-15T10:30:45.123Z", - "packets_received": 1250, - "connection_quality": "excellent" -} -``` - -#### 2.3.3 Protection Messages - -**Topic**: `"protection/alert"` -**Direction**: Backend โ†’ Frontend -**Purpose**: Safety alerts and fault notifications - -**Payload Structure**: -```json -{ - "board_id": 0, - "packet_id": 2, - "severity": "fault", - "protection_type": "above", - "measurement_name": "brake_pressure", - "current_value": 105.0, - "threshold_value": 100.0, - "message": "Brake pressure above safe limit", - "timestamp": "2024-01-15T10:30:45.123Z", - "acknowledged": false -} -``` - -#### 2.3.4 Order Status Messages - -**Topic**: `"order/state"` -**Direction**: Backend โ†’ Frontend -**Purpose**: Command execution status updates - -**Payload Structure**: -```json -{ - "board_id": 0, - "order_name": "brake_engage", - "status": "executed", - "timestamp": "2024-01-15T10:30:45.123Z", - "parameters": { - "target_pressure": 85.0 - } -} -``` - -#### 2.3.5 Logger Messages - -**Topic**: `"logger/enable"`, `"logger/disable"` -**Direction**: Frontend โ†’ Backend -**Purpose**: Control data logging - -**Enable Payload**: -```json -{ - "measurements": ["brake_pressure", "valve_state"], - "log_rate": 100, - "format": "csv" -} -``` - -**Disable Payload**: -```json -{ - "stop_logging": true -} -``` - -#### 2.3.6 BLCU Messages - -**Topic**: `"blcu/upload"`, `"blcu/download"` -**Direction**: Bidirectional -**Purpose**: Bootloader operations - -**Upload Request Payload**: -```json -{ - "board_id": 0, - "filename": "firmware_v2.1.bin", - "file_size": 524288, - "checksum": "a1b2c3d4" -} -``` - -**Upload Status Payload**: -```json -{ - "board_id": 0, - "operation": "upload", - "status": "in_progress", - "progress": 0.65, - "bytes_transferred": 340000, - "estimated_time_remaining": 45 -} -``` - ---- - -## 3. Backend Internal Message Flow - -### 3.1 Transport Layer Messages - -**Go Type**: `transport.PacketMessage`, `transport.FileWriteMessage`, `transport.FileReadMessage` - -**Internal Message Events**: -- `PacketEvent`: Packet send/receive operations -- `ErrorEvent`: Transport layer errors -- `FileWriteEvent`: TFTP write requests -- `FileReadEvent`: TFTP read requests - -### 3.2 Broker System - -**Architecture**: Topic-based message routing -**Go Type**: `pkg/broker.Broker` - -**Message Flow**: -1. Transport layer receives packet from vehicle -2. Packet decoded via presentation layer (`pkg/transport/presentation.Decoder`) -3. Packet routed to appropriate board handler via broker -4. Board handler processes packet and generates WebSocket messages -5. WebSocket messages broadcast to connected frontend clients - -### 3.3 Vehicle Management - -**Go Type**: `pkg/vehicle.Vehicle` - -**Responsibilities**: -- Board lifecycle management -- Order execution coordination -- State space management -- Protection system coordination - ---- - -## 4. Message Timing and Ordering - -### 4.1 Data Packet Timing - -**Frequency**: Variable per packet type, typically 10-100 Hz -**Buffering**: Session-level buffering for network packet reassembly -**Ordering**: Packets processed in receive order, timestamped on arrival - -### 4.2 Protection Message Priority - -**Priority Order**: -1. Fault messages (immediate processing) -2. Warning messages (high priority queue) -3. OK messages (normal priority queue) - -### 4.3 Order Execution Timing - -**Request Flow**: -1. Frontend sends order via WebSocket -2. Backend validates order parameters -3. Backend sends binary order packet to vehicle -4. Vehicle acknowledgment expected within 1 second -5. Status update sent to frontend - ---- - -## 5. Error Handling and Fault Tolerance - -### 5.1 Network Error Handling - -**TCP Connection Failures**: -- Automatic reconnection with exponential backoff -- Connection status broadcast to frontend -- Packet queuing during disconnection - -**UDP Packet Loss**: -- No retransmission (fire-and-forget) -- Packet sequence tracking for loss detection -- Statistics reporting to frontend - -### 5.2 Protocol Error Handling - -**Invalid Packet Format**: -- Packet discarded with error logging -- Decoder error recovery to next packet boundary -- Error statistics tracked per board - -**Unknown Packet IDs**: -- Warning logged with packet ID -- Packet contents logged for debugging -- Connection maintained - -### 5.3 ADJ Configuration Errors - -**Missing Measurements**: -- Default value substitution where possible -- Warning alerts to operators -- Graceful degradation of functionality - -**Invalid Board Configuration**: -- Board disabled with error notification -- System continues with remaining boards -- Configuration reload capability - ---- - -## Implementation Notes - -### Network Layer - -The network implementation (`pkg/transport/network/`) provides: -- **TCP Client/Server**: Reliable packet delivery -- **UDP**: Fast, unreliable communication for high-frequency data -- **TFTP**: File transfer capabilities -- **Packet Sniffer**: Network traffic monitoring and debugging - -### Presentation Layer - -The presentation layer (`pkg/transport/presentation/`) handles: -- **Packet ID routing**: Maps packet IDs to appropriate decoders -- **Binary decoding**: Converts binary data to Go structs -- **Encoder support**: Converts Go structs to binary for transmission - -### Data Types - -All data types support: -- **Type safety**: Strong typing prevents runtime errors -- **JSON serialization**: Automatic conversion for WebSocket transmission -- **Value validation**: Range checking and enum validation -- **Unit conversion**: Automatic conversion between pod and display units - -This documentation should be kept synchronized with ADJ specification changes and backend implementation updates. \ No newline at end of file diff --git a/docs/architecture/packet-flow-reference.md b/docs/architecture/packet-flow-reference.md deleted file mode 100644 index 8bee23e10..000000000 --- a/docs/architecture/packet-flow-reference.md +++ /dev/null @@ -1,183 +0,0 @@ -# Packet Flow Quick Reference - -## Board โ†’ Backend โ†’ Frontend (Data Flow) - -### 1. Binary Packet Structure -``` -[Packet ID: 2 bytes (uint16, little-endian)] [Data: variable length based on ADJ] -``` - -### 2. Network Reception -- **TCP**: Reliable delivery for critical data -- **UDP**: High-frequency sensor data (no retransmission) -- Board IP must match ADJ configuration - -### 3. Decoding Process -```go -// Simplified flow -id := readUint16(connection) // Read 2-byte packet ID -decoder := getDecoderForId(id) // Lookup from ADJ -packet := decoder.Decode(connection) // Parse remaining bytes -applyUnitConversions(packet) // Convert units if needed -``` - -### 4. Message Routing -``` -Packet Type โ†’ Broker Topic โ†’ WebSocket Message -Data โ†’ data/update โ†’ {"topic": "data/update", "payload": {...}} -Protection โ†’ protection/* โ†’ {"topic": "protection/fault", "payload": {...}} -``` - -### 5. Frontend Reception -```typescript -handler.subscribe("data/update", { - id: "unique-id", - cb: (data) => updateUI(data) -}); -``` - -## Frontend โ†’ Backend โ†’ Board (Order Flow) - -### 1. Order Creation -```typescript -const order: Order = { - id: 9995, // From ADJ orders.json - fields: { - "ldu_id": { value: 1, isEnabled: true, type: "uint8" }, - "lcu_desired_current": { value: 2.5, isEnabled: true, type: "float32" } - } -}; -``` - -### 2. WebSocket Transmission -```typescript -handler.post("order/send", order); -// Sends: {"topic": "order/send", "payload": order} -``` - -### 3. Backend Processing -1. Receive JSON order from WebSocket -2. Validate order ID exists in ADJ -3. Create packet structure from order fields -4. Encode to binary format (little-endian) -5. Lookup target board from packet ID - -### 4. Binary Encoding -``` -Order ID: 9995 (0x270B) -Fields: ldu_id=1, lcu_desired_current=2.5 - -Binary output: -[0B 27] // ID: 9995 in little-endian -[01] // ldu_id: 1 (uint8) -[00 00 20 40] // lcu_desired_current: 2.5 (float32, little-endian) -``` - -### 5. Transmission to Board -- TCP connection to board IP (from ADJ) -- Synchronous write with error handling -- Connection status tracked - -## Special Cases - -### Fault Propagation (ID: 0) -``` -When packet ID = 0: -1. Received from any board -2. Replicated to ALL connected boards -3. Triggers emergency stop procedures -``` - -### BLCU File Transfer -``` -1. Frontend: handler.exchange("blcu/upload", {filename, data}) -2. Backend: TFTP PUT to BLCU IP -3. Progress updates via WebSocket -4. Completion notification -``` - -### State Orders -``` -Special orders that modify board state: -- add_state_order: Enable periodic execution -- remove_state_order: Disable periodic execution -- Managed by backend state machine -``` - -## Common Packet Types - -### Data Packet Example (LCU Coil Current) -``` -ADJ Definition (packets.json): -{ - "id": 320, - "type": "data", - "name": "lcu_coil_current", - "variables": ["general_state", "vertical_state", ...] -} - -Binary Format: -[40 01] // ID: 320 -[01] // general_state (uint8) -[02] // vertical_state (uint8) -[00 00 80 3F] // coil_current_1 (float32: 1.0) -... -``` - -### Protection Packet Example -``` -ID Range: 1000-3999 (based on severity) -- 1xxx: Fault (critical) -- 2xxx: Warning -- 3xxx: OK (cleared) - -Binary Format: -[E8 03] // ID: 1000 (fault) -[05 00] // Board ID: 5 -[01] // Error code -``` - -## Network Configuration - -### Default Ports -- TCP Client: 50401 (Backend โ†’ Board) -- TCP Server: 50500 (Board โ†’ Backend) -- UDP: 50400 (Bidirectional) -- TFTP: 69 (BLCU only) -- WebSocket: 8080 (Frontend โ†’ Backend) - -### IP Scheme -- Backend: 192.168.0.9 -- Boards: 192.168.1.x (defined in ADJ) -- BLCU: 192.168.1.254 (typical) - -## Debugging Tips - -### 1. Check Packet Flow -```bash -# Monitor WebSocket messages in browser -# DevTools โ†’ Network โ†’ WS โ†’ Messages - -# Backend logs -tail -f trace.json | jq 'select(.msg | contains("packet"))' -``` - -### 2. Verify ADJ Configuration -```bash -# Check board definition -cat adj/boards/LCU/LCU.json - -# Verify packet exists -jq '.[] | select(.id == 320)' adj/boards/LCU/packets.json -``` - -### 3. Connection Status -- Frontend: Check connection indicator -- Backend logs: "new connection" / "close" -- Network: `netstat -an | grep 504` - -### 4. Common Issues -- **No data**: Check board in config.toml vehicle.boards -- **Order fails**: Verify order ID in ADJ -- **Decode error**: Packet structure mismatch -- **Connection drops**: Network/firewall issues \ No newline at end of file diff --git a/docs/architecture/protocols.md b/docs/architecture/protocols.md deleted file mode 100644 index 24342d8ca..000000000 --- a/docs/architecture/protocols.md +++ /dev/null @@ -1,243 +0,0 @@ -# Communication Protocols Overview - -This document provides a high-level overview of all communication protocols used in the Hyperloop Control Station system. - -## Architecture Overview - -The system uses a three-layer communication architecture: - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” Binary Protocol โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” WebSocket API โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Vehicle โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ Backend โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ Frontend โ”‚ -โ”‚ Systems โ”‚ TCP/UDP + TFTP โ”‚ Server โ”‚ JSON/WS โ”‚ Applications โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## Layer 1: Vehicle โ†” Backend Communication - -### Transport Protocols -- **TCP**: Reliable command/control messages -- **UDP**: High-frequency sensor data -- **TFTP**: File transfers (firmware, configs) - -### Message Types -1. **Data Packets**: Sensor measurements and telemetry -2. **Protection Packets**: Safety alerts and fault notifications -3. **Order Packets**: Commands from backend to vehicle -4. **State Space Packets**: Control system matrices -5. **BLCU Packets**: Bootloader communication - -### Key Characteristics -- **Binary Encoding**: Efficient, compact representation -- **Little-Endian**: Consistent byte ordering -- **Real-Time**: Sub-10ms latency for critical messages -- **ADJ-Defined**: Message structure defined by JSON specifications - -**Detailed Specification**: [Binary Protocol](./binary-protocol.md) - -## Layer 2: Backend โ†” Frontend Communication - -### Transport Protocol -- **WebSocket**: Full-duplex communication over HTTP -- **JSON Encoding**: Human-readable, self-describing messages - -### Topic-Based Routing -Messages are routed by topic strings: -- `data/*`: Measurement data and updates -- `connection/*`: Network status and board connectivity -- `order/*`: Command execution and status -- `protection/*`: Safety alerts and acknowledgments -- `logger/*`: Data logging control -- `blcu/*`: Bootloader operations -- `message/*`: System notifications - -### Key Characteristics -- **Real-Time**: WebSocket enables instant updates -- **Bidirectional**: Frontend can send commands to backend -- **Type-Safe**: Structured JSON schemas -- **Scalable**: Multiple frontend clients supported - -**Detailed Specification**: [WebSocket API](./websocket-api.md) - -## Layer 3: Backend Internal Architecture - -### Message Broker System -The backend uses a topic-based message broker to route data between components: - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Transport โ”‚ -> โ”‚ Broker โ”‚ -> โ”‚ WebSocket โ”‚ -โ”‚ Layer โ”‚ โ”‚ System โ”‚ โ”‚ Clients โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ”‚ โ–ผ โ”‚ - โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - โ”‚ โ”‚ Vehicle โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€> โ”‚ Management โ”‚ <โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### Data Flow - -1. **Incoming Path** (Vehicle โ†’ Frontend): - ``` - Vehicle Binary Packet โ†’ Transport Decoder โ†’ Message Broker โ†’ WebSocket JSON - ``` - -2. **Outgoing Path** (Frontend โ†’ Vehicle): - ``` - WebSocket JSON โ†’ Message Broker โ†’ Vehicle Manager โ†’ Transport Encoder โ†’ Binary Packet - ``` - -## Message Categories and Timing - -### Real-Time Data (High Frequency) -- **Data Packets**: 10-100 Hz per measurement -- **Protection Alerts**: Immediate (fault conditions) -- **Connection Status**: On change + 1 Hz heartbeat -- **Transport**: UDP preferred for speed - -### Command and Control (Low Frequency) -- **Order Commands**: As needed (user-initiated) -- **Logger Control**: Manual operation -- **Configuration Changes**: Manual operation -- **Transport**: TCP required for reliability - -### File Operations (Occasional) -- **Firmware Updates**: Manual operation -- **Configuration Files**: Manual operation -- **Log Downloads**: Manual operation -- **Transport**: TFTP for large data transfers - -## Network Configuration - -### Port Assignments -``` -Service Port Protocol Direction -โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -TCP Client 50401 TCP Backend โ†’ Vehicle -TCP Server 50500 TCP Vehicle โ†’ Backend -UDP Data 8000 UDP Bidirectional -TFTP Files 69 UDP Bidirectional -SNTP Time 123 UDP Vehicle โ†’ NTP Server -WebSocket API 8080 TCP/HTTP Frontend โ†’ Backend -``` - -### IP Address Scheme -- **Backend**: 127.0.0.9 (default, configurable) -- **Vehicle Boards**: Per-board IPs defined in ADJ config - - VCU: 127.0.0.6 - - BCU: 127.0.0.7 - - LCU: 127.0.0.8 - - etc. - -## Protocol Evolution and Versioning - -### ADJ Specification Versioning -- **Current Version**: ADJ v2 -- **Backward Compatibility**: v1 configs automatically migrated -- **Schema Evolution**: Additive changes only for compatibility - -### Protocol Versioning -- **Binary Protocol**: Version embedded in packet structure -- **WebSocket API**: Version negotiation during connection -- **Error Handling**: Graceful degradation for unknown message types - -## Security Considerations - -### Current Implementation -- **Trusted Network**: Assumes secure LAN environment -- **No Authentication**: Direct connection without credentials -- **Input Validation**: All messages validated against schema -- **Rate Limiting**: Prevents message flooding - -### Production Recommendations -- **TLS Encryption**: Secure WebSocket connections (WSS) -- **Authentication**: Token-based client authentication -- **Authorization**: Role-based access control -- **Network Isolation**: Dedicated VLAN for vehicle communication - -## Error Handling and Fault Tolerance - -### Network-Level Errors -- **TCP Disconnection**: Automatic reconnection with exponential backoff -- **UDP Packet Loss**: Statistics tracking, no retransmission -- **WebSocket Disconnect**: Client reconnection with state resync - -### Protocol-Level Errors -- **Malformed Packets**: Graceful error recovery and logging -- **Unknown Message Types**: Warning logged, connection maintained -- **Schema Validation**: Invalid messages rejected with error response - -### Application-Level Errors -- **Board Offline**: Connection status propagated to frontend -- **Protection Faults**: Immediate alert propagation with acknowledgment tracking -- **Command Failures**: Error status returned to frontend client - -## Performance Characteristics - -### Latency -- **Vehicle โ†’ Frontend**: < 20ms typical -- **Frontend โ†’ Vehicle**: < 50ms typical -- **Protection Alerts**: < 5ms critical path - -### Throughput -- **Data Rate**: 1000+ measurements/second supported -- **Concurrent Clients**: 10+ frontend connections -- **Packet Rate**: 500+ packets/second per board - -### Memory Usage -- **Per Connection**: ~1MB WebSocket client -- **Packet Buffers**: 64KB per transport connection -- **Message Queue**: Bounded queues prevent memory leaks - -## Monitoring and Debugging - -### Built-in Diagnostics -- **Connection Statistics**: Packet counts, error rates, latency -- **Message Logs**: Structured logging with correlation IDs -- **Performance Metrics**: Throughput and latency monitoring - -### Debug Tools -- **Packet Sender**: Test packet generation for development -- **Ethernet View**: Real-time protocol monitoring -- **Network Captures**: PCAP files for detailed analysis - -### Logging Integration -- **Structured Logging**: JSON format with contextual information -- **Log Levels**: Debug, info, warning, error, critical -- **Log Rotation**: Automatic archival of historical data - -## Development Guidelines - -### Adding New Message Types - -1. **Update ADJ Specification**: Define packet structure in JSON -2. **Implement Backend Decoder**: Add packet parsing logic -3. **Define WebSocket Topic**: Specify JSON message format -4. **Update Frontend Types**: TypeScript interfaces for type safety -5. **Add Documentation**: Update protocol specifications - -### Testing Protocol Changes - -1. **Unit Tests**: Individual packet codec validation -2. **Integration Tests**: End-to-end message flow verification -3. **Load Testing**: Performance validation under stress -4. **Compatibility Testing**: Ensure backward compatibility - -### Best Practices - -- **Schema Evolution**: Only additive changes to maintain compatibility -- **Error Handling**: Graceful degradation for unknown message types -- **Documentation**: Keep specifications synchronized with implementation -- **Versioning**: Clear version tracking for all protocol changes - -## Related Documentation - -- [Message Structures](./message-structures.md) - Detailed structure specifications -- [Binary Protocol](./binary-protocol.md) - Vehicle communication details -- [WebSocket API](./websocket-api.md) - Frontend communication details -- [ADJ Specification](../../packet-sender/adj/README.md) - Configuration format -- [Development Guide](../development/DEVELOPMENT.md) - Setup and workflow - -This protocol overview provides the foundation for understanding all communication aspects of the Hyperloop Control Station system. \ No newline at end of file diff --git a/docs/architecture/websocket-api.md b/docs/architecture/websocket-api.md deleted file mode 100644 index 0889812ea..000000000 --- a/docs/architecture/websocket-api.md +++ /dev/null @@ -1,421 +0,0 @@ -# WebSocket API Specification - -This document defines the complete WebSocket API used for communication between the backend and frontend applications (control-station, ethernet-view). - -## Connection - -**Endpoint**: `ws://localhost:8080/ws` (configurable in backend) -**Protocol**: WebSocket over HTTP -**Content-Type**: `application/json` - -## Message Format - -All WebSocket messages follow a consistent structure: - -```json -{ - "topic": "string", - "payload": {} -} -``` - -- `topic`: String identifying the message type and routing -- `payload`: Object containing topic-specific data - -## Topics Reference - -### Data Topics - -#### `data/update` -**Direction**: Backend โ†’ Frontend -**Purpose**: Real-time measurement data updates - -```json -{ - "topic": "data/update", - "payload": { - "board_id": 0, - "packet_id": 211, - "packet_name": "vcu_regulator_packet", - "timestamp": "2024-01-15T10:30:45.123Z", - "measurements": { - "measurement_name": { - "value": "number|string|boolean", - "type": "uint8|uint16|uint32|uint64|int8|int16|int32|int64|float32|float64|bool|enum", - "enabled": true, - "units": "string" - } - } - } -} -``` - -#### `data/push` -**Direction**: Backend โ†’ Frontend -**Purpose**: Batch data updates for chart displays - -```json -{ - "topic": "data/push", - "payload": { - "board_id": 0, - "measurements": [ - { - "name": "brake_pressure", - "value": 85.2, - "timestamp": "2024-01-15T10:30:45.123Z" - } - ] - } -} -``` - -### Connection Topics - -#### `connection/update` -**Direction**: Backend โ†’ Frontend -**Purpose**: Board connection status updates - -```json -{ - "topic": "connection/update", - "payload": { - "board_id": 0, - "board_name": "VCU", - "ip_address": "127.0.0.6", - "connected": true, - "last_seen": "2024-01-15T10:30:45.123Z", - "packets_received": 1250, - "bytes_received": 125000, - "connection_quality": "excellent|good|poor|unknown" - } -} -``` - -#### `connection/push` -**Direction**: Backend โ†’ Frontend -**Purpose**: Initial connection state when client connects - -```json -{ - "topic": "connection/push", - "payload": { - "connections": [ - { - "board_id": 0, - "board_name": "VCU", - "connected": true, - "last_seen": "2024-01-15T10:30:45.123Z" - } - ] - } -} -``` - -### Order Topics - -#### `order/send` -**Direction**: Frontend โ†’ Backend -**Purpose**: Send command to vehicle board - -```json -{ - "topic": "order/send", - "payload": { - "board_id": 0, - "order_name": "brake_engage", - "parameters": { - "target_pressure": 85.0, - "engage_time": 2000 - } - } -} -``` - -#### `order/state` -**Direction**: Backend โ†’ Frontend -**Purpose**: Order execution status updates - -```json -{ - "topic": "order/state", - "payload": { - "board_id": 0, - "order_name": "brake_engage", - "status": "pending|executing|completed|failed", - "timestamp": "2024-01-15T10:30:45.123Z", - "parameters": { - "target_pressure": 85.0 - }, - "error_message": "string" - } -} -``` - -### Logger Topics - -#### `logger/enable` -**Direction**: Frontend โ†’ Backend -**Purpose**: Start data logging - -```json -{ - "topic": "logger/enable", - "payload": { - "log_type": "data|protection|state|order", - "measurements": ["brake_pressure", "valve_state"], - "log_rate": 100, - "format": "csv|json", - "filename": "run_001_data.csv" - } -} -``` - -#### `logger/disable` -**Direction**: Frontend โ†’ Backend -**Purpose**: Stop data logging - -```json -{ - "topic": "logger/disable", - "payload": { - "log_type": "data|protection|state|order" - } -} -``` - -### Message Topics - -#### `message/update` -**Direction**: Backend โ†’ Frontend -**Purpose**: System messages and notifications - -```json -{ - "topic": "message/update", - "payload": { - "id": "unique_message_id", - "level": "info|warning|error", - "source": "backend|vehicle|system", - "title": "Connection Established", - "message": "Successfully connected to VCU board", - "timestamp": "2024-01-15T10:30:45.123Z", - "board_id": 0, - "auto_dismiss": true, - "dismiss_timeout": 5000 - } -} -``` - -### Protection Topics - -#### `protection/alert` -**Direction**: Backend โ†’ Frontend -**Purpose**: Safety alerts and fault notifications - -```json -{ - "topic": "protection/alert", - "payload": { - "board_id": 0, - "packet_id": 2, - "severity": "fault|warning|ok", - "protection_type": "above|below|outofbounds|equals|notequals|errorhandler|timeaccumulation", - "measurement_name": "brake_pressure", - "current_value": 105.0, - "threshold_value": 100.0, - "message": "Brake pressure above safe limit", - "timestamp": "2024-01-15T10:30:45.123Z", - "acknowledged": false, - "protection_id": "brake_pressure_high" - } -} -``` - -### BLCU Topics (Bootloader) - -#### `blcu/upload` -**Direction**: Frontend โ†’ Backend -**Purpose**: Upload firmware to vehicle board - -```json -{ - "topic": "blcu/upload", - "payload": { - "board_id": 0, - "filename": "firmware_v2.1.bin", - "file_data": "base64_encoded_data", - "checksum": "sha256_hash" - } -} -``` - -#### `blcu/download` -**Direction**: Frontend โ†’ Backend -**Purpose**: Download file from vehicle board - -```json -{ - "topic": "blcu/download", - "payload": { - "board_id": 0, - "filename": "config.json", - "destination": "local_config.json" - } -} -``` - -#### `blcu/register` -**Direction**: Backend โ†’ Frontend -**Purpose**: BLCU operation status updates - -```json -{ - "topic": "blcu/register", - "payload": { - "board_id": 0, - "operation": "upload|download", - "status": "pending|in_progress|completed|failed", - "progress": 0.65, - "bytes_transferred": 340000, - "total_bytes": 524288, - "estimated_time_remaining": 45, - "error_message": "string" - } -} -``` - -## Implementation Guidelines - -### Client Connection Handling - -1. **Connection Establishment**: Client connects to WebSocket endpoint -2. **Topic Subscription**: Implicit subscription to all relevant topics -3. **Initial State**: Backend sends current state via `push` topics -4. **Real-time Updates**: Backend streams updates via `update` topics - -### Error Handling - -**Invalid Message Format**: -```json -{ - "topic": "error", - "payload": { - "error": "Invalid message format", - "details": "Missing required field: topic", - "original_message": {} - } -} -``` - -**Unknown Topic**: -```json -{ - "topic": "error", - "payload": { - "error": "Unknown topic", - "topic": "invalid/topic", - "available_topics": ["data/update", "connection/update", ...] - } -} -``` - -### Message Ordering - -- **Data Updates**: No ordering guarantee, latest value wins -- **Order Commands**: Processed in receive order -- **Protection Alerts**: Immediate priority processing -- **Connection Updates**: Ordered by timestamp - -### Rate Limiting - -- **Data Updates**: Max 100 Hz per measurement -- **Order Commands**: Max 10 per second per client -- **Logger Operations**: Max 1 per second -- **BLCU Operations**: Max 1 concurrent operation per board - -### Client Reconnection - -1. **Automatic Reconnection**: Clients should implement exponential backoff -2. **State Resynchronization**: Backend sends current state on reconnection -3. **Message Buffer**: Backend may buffer critical messages during disconnect - -## Frontend Integration - -### TypeScript Types - -```typescript -interface WebSocketMessage { - topic: string; - payload: any; -} - -interface DataUpdatePayload { - board_id: number; - packet_id: number; - packet_name: string; - timestamp: string; - measurements: Record; -} - -interface MeasurementValue { - value: number | string | boolean; - type: DataType; - enabled: boolean; - units?: string; -} - -type DataType = 'uint8' | 'uint16' | 'uint32' | 'uint64' | - 'int8' | 'int16' | 'int32' | 'int64' | - 'float32' | 'float64' | 'bool' | 'enum'; -``` - -### Usage Example - -```typescript -const ws = new WebSocket('ws://localhost:8080/ws'); - -ws.onmessage = (event) => { - const message: WebSocketMessage = JSON.parse(event.data); - - switch (message.topic) { - case 'data/update': - handleDataUpdate(message.payload); - break; - case 'connection/update': - handleConnectionUpdate(message.payload); - break; - case 'protection/alert': - handleProtectionAlert(message.payload); - break; - } -}; - -// Send order command -const orderMessage = { - topic: 'order/send', - payload: { - board_id: 0, - order_name: 'brake_engage', - parameters: { target_pressure: 85.0 } - } -}; -ws.send(JSON.stringify(orderMessage)); -``` - -## Security Considerations - -- **No Authentication**: Current implementation assumes trusted network -- **Input Validation**: All incoming messages validated against schema -- **Rate Limiting**: Prevents message flooding attacks -- **Connection Limits**: Maximum concurrent connections enforced -- **Origin Checking**: WebSocket origin validation recommended for production - -## Performance Characteristics - -- **Latency**: Typical message latency < 10ms on local network -- **Throughput**: Supports 1000+ messages/second per connection -- **Memory Usage**: ~1MB per active connection -- **CPU Usage**: Minimal overhead for message routing - -This API specification should be implemented consistently across all frontend applications to ensure proper integration with the backend WebSocket server. \ No newline at end of file diff --git a/docs/development/CROSS_PLATFORM_DEV_SUMMARY.md b/docs/development/CROSS_PLATFORM_DEV_SUMMARY.md deleted file mode 100644 index 8e650c2cf..000000000 --- a/docs/development/CROSS_PLATFORM_DEV_SUMMARY.md +++ /dev/null @@ -1,159 +0,0 @@ -# Cross-Platform Development Scripts Summary - -This document summarizes the cross-platform development improvements made to support Windows, macOS, and Linux development environments. - -## Files Added/Modified - -### New Development Scripts - -1. **`scripts/dev.cmd`** - Windows Batch script - - Native Windows batch file for Command Prompt - - Handles Windows-specific path separators and commands - - Opens services in separate command windows for the `all` command - -2. **`scripts/dev.ps1`** - Windows PowerShell script (Recommended) - - Modern PowerShell script with better error handling - - Colored output and enhanced functionality - - Opens services in separate PowerShell windows for the `all` command - -3. **`scripts/dev-unified.sh`** - Universal cross-platform script - - Enhanced Bash script with comprehensive OS detection - - Supports Unix, Linux, macOS, Windows (Git Bash), WSL, MSYS2, Cygwin - - Intelligent platform-specific behavior adaptation - - Fallback mechanisms for different environments - -4. **`scripts/README.md`** - Comprehensive documentation - - Usage instructions for all platforms - - Platform-specific notes and troubleshooting - - Installation requirements and prerequisites - -### Modified Files - -5. **`scripts/dev.sh`** - Enhanced original script - - Added OS detection (`linux`, `macos`, `windows`) - - Improved Windows support in tmux alternatives - - Better error messages and user guidance - -6. **`DEVELOPMENT.md`** - Updated development guide - - Added platform-specific script instructions - - Included Windows PowerShell and Command Prompt examples - - Added platform-specific notes and troubleshooting - -### New GitHub Workflow - -7. **`.github/workflows/test-dev-scripts.yaml`** - CI/CD testing - - Tests all development scripts across platforms - - Validates functionality on Ubuntu, Windows, and macOS - - Tests both PowerShell and Command Prompt on Windows - -## Platform Support Matrix - -| Platform | Script | Shell | Status | Notes | -|----------|--------|-------|---------|--------| -| **Linux** | `dev.sh` | bash | โœ… Full | Uses tmux for `all` command | -| **macOS** | `dev.sh` | bash | โœ… Full | Uses tmux for `all` command | -| **Windows** | `dev.ps1` | PowerShell | โœ… Full | **Recommended** - Opens separate windows | -| **Windows** | `dev.cmd` | cmd | โœ… Basic | Opens separate windows | -| **Git Bash** | `dev-unified.sh` | bash | โœ… Full | Enhanced Windows compatibility | -| **WSL** | `dev-unified.sh` | bash | โœ… Full | Auto-detects WSL environment | -| **MSYS2** | `dev-unified.sh` | bash | โœ… Full | Windows-native paths | -| **Cygwin** | `dev-unified.sh` | bash | โœ… Basic | Limited testing | - -## Key Features - -### Cross-Platform Compatibility -- **Path Handling**: Automatic Windows/Unix path conversion -- **Command Adaptation**: Platform-specific command variations -- **Shell Detection**: Bash, Zsh, PowerShell, Command Prompt support -- **Environment Detection**: WSL, MSYS2, Cygwin recognition - -### Service Management -- **Unix/Linux/macOS**: tmux sessions with fallback to parallel processes -- **Windows**: Separate windows for each service (PowerShell/cmd) -- **Git Bash/WSL**: Intelligent adaptation based on environment - -### Developer Experience -- **Consistent Commands**: Same command syntax across all platforms -- **Colored Output**: Platform-appropriate colored terminal output -- **Error Handling**: Clear error messages and troubleshooting guidance -- **Dependency Checking**: Validates Go, Node.js, npm availability - -## Usage Examples - -### Windows PowerShell (Recommended) -```powershell -# Setup project -.\scripts\dev.ps1 setup - -# Run all services -.\scripts\dev.ps1 all - -# Run individual service -.\scripts\dev.ps1 backend -``` - -### Windows Command Prompt -```cmd -scripts\dev.cmd setup -scripts\dev.cmd all -scripts\dev.cmd backend -``` - -### Unix/Linux/macOS -```bash -./scripts/dev.sh setup -./scripts/dev.sh all -./scripts/dev.sh backend -``` - -### Git Bash/WSL/MSYS2 -```bash -./scripts/dev-unified.sh setup -./scripts/dev-unified.sh all -./scripts/dev-unified.sh backend -``` - -## GitHub Actions Integration - -The existing release workflow already had excellent cross-platform support: -- โœ… **Linux builds**: Alpine container with static linking -- โœ… **Windows builds**: Native Windows runners with proper PowerShell -- โœ… **macOS builds**: Both Intel (amd64) and Apple Silicon (arm64) - -### New Workflow Added -- **`test-dev-scripts.yaml`**: Tests development scripts across all platforms -- Validates script functionality before merging changes -- Ensures consistent behavior across Windows, macOS, and Linux - -## Troubleshooting - -### Windows Issues -1. **PowerShell Execution Policy**: Run `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser` -2. **Path Issues**: Use PowerShell script instead of batch file -3. **Missing Dependencies**: Ensure Go, Node.js, npm are in PATH - -### Unix Issues -1. **tmux Not Found**: Install tmux or services will run in parallel processes -2. **Permission Denied**: Run `chmod +x scripts/dev.sh` -3. **Path Issues**: Ensure script is run from project root - -### Cross-Platform Issues -1. **Git Bash**: Use `dev-unified.sh` for better Windows compatibility -2. **WSL**: Automatically detected and handled by unified script -3. **MSYS2**: Use unified script for proper path handling - -## Benefits - -1. **Developer Onboarding**: New developers can start regardless of platform -2. **Team Consistency**: Same workflow across different development environments -3. **CI/CD Confidence**: Scripts tested in automation prevent deployment issues -4. **Maintenance Efficiency**: Single command set for all platforms -5. **Future-Proof**: Easy to extend for new platforms or requirements - -## Future Enhancements - -- **Docker Integration**: Add containerized development option -- **IDE Integration**: VS Code tasks for script integration -- **Progress Indicators**: Enhanced visual feedback during setup -- **Configuration Validation**: Pre-flight checks for environment setup -- **Hot Reload**: File watching for automatic service restarts \ No newline at end of file diff --git a/docs/development/DEVELOPMENT.md b/docs/development/DEVELOPMENT.md deleted file mode 100644 index 6d06511bb..000000000 --- a/docs/development/DEVELOPMENT.md +++ /dev/null @@ -1,195 +0,0 @@ -# Development Setup - -This guide provides multiple ways to set up your development environment for the Hyperloop H10 project. - -## Quick Start (Recommended) - -### Option 1: Cross-Platform Development Scripts - -We provide multiple scripts to work across different platforms: - -#### Unix/Linux/macOS (Bash) -```bash -# Make script executable -chmod +x scripts/dev.sh - -# Install dependencies and setup -./scripts/dev.sh setup - -# Run individual services -./scripts/dev.sh backend # Run backend server -./scripts/dev.sh ethernet # Run ethernet-view -./scripts/dev.sh control # Run control-station -./scripts/dev.sh packet # Run packet-sender - -# Run all services in tmux -./scripts/dev.sh all -``` - -#### Windows (PowerShell - Recommended) -```powershell -# You may need to allow script execution first: -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - -# Install dependencies and setup -.\scripts\dev.ps1 setup - -# Run individual services -.\scripts\dev.ps1 backend # Run backend server -.\scripts\dev.ps1 ethernet # Run ethernet-view -.\scripts\dev.ps1 control # Run control-station -.\scripts\dev.ps1 packet # Run packet-sender - -# Run all services in separate windows -.\scripts\dev.ps1 all -``` - -#### Windows (Command Prompt) -```cmd -REM Install dependencies and setup -scripts\dev.cmd setup - -REM Run individual services -scripts\dev.cmd backend REM Run backend server -scripts\dev.cmd ethernet REM Run ethernet-view -scripts\dev.cmd control REM Run control-station -scripts\dev.cmd packet REM Run packet-sender - -REM Run all services in separate windows -scripts\dev.cmd all -``` - -#### Universal Script (Git Bash/WSL/MSYS2) -For Windows users with Git Bash, WSL, or MSYS2: -```bash -# Enhanced cross-platform script with better Windows support -chmod +x scripts/dev-unified.sh -./scripts/dev-unified.sh setup -./scripts/dev-unified.sh all -``` - -### Option 2: Using Docker Compose - -```bash -# Run all services -docker-compose -f docker-compose.dev.yml up - -# Run specific service -docker-compose -f docker-compose.dev.yml up backend -``` - -### Option 3: Manual Setup - -```bash -# 1. Build common-front first (required) -cd common-front -npm install -npm run build - -# 2. Install frontend dependencies -cd ../ethernet-view -npm install - -cd ../control-station -npm install - -# 3. Run services -cd backend/cmd && go run . # Terminal 1 -cd ethernet-view && npm run dev # Terminal 2 -cd control-station && npm run dev # Terminal 3 -cd packet-sender && go run . # Terminal 4 -``` - -### Option 4: Using Nix (Advanced) - -For developers who prefer Nix for reproducible environments: - -```bash -# Pure shell with Fish -nix-shell - -# Or with your existing shell config -nix-shell -A impure -``` - -This provides a fully reproducible development environment with all dependencies. - -## Platform-Specific Notes - -### Windows -- **PowerShell Script** (`dev.ps1`) is recommended over Command Prompt (`dev.cmd`) for better functionality -- If you encounter execution policy issues: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser` -- The `all` command opens each service in separate windows instead of using tmux -- For Unix-like experience on Windows, use **Git Bash**, **WSL**, or **MSYS2** with the unified script - -### Unix/Linux/macOS -- The `all` command uses **tmux** for session management (install with your package manager) -- If tmux is unavailable, services run in parallel background processes -- Use `Ctrl+C` to stop all services when running in parallel mode - -### Git Bash/WSL/MSYS2 -- Use the `dev-unified.sh` script for enhanced Windows compatibility -- Automatically detects WSL and adjusts behavior accordingly -- Provides better path handling for Windows environments - -## Prerequisites - -- Go 1.21+ -- Node.js 18+ -- npm -- libpcap (for packet capture) - - macOS: `brew install libpcap` - - Ubuntu: `sudo apt-get install libpcap-dev` - - Windows: Install WinPcap or Npcap - -## Service Ports - -- Backend: 8080 -- Control Station: 5173 -- Ethernet View: 5174 - -## Common Tasks - -### Run Tests -```bash -./scripts/dev.sh test -# or manually: -cd backend && go test -v ./... -``` - -### Build All Components -```bash -make all -``` - -### Update Dependencies -```bash -# Go dependencies -cd backend && go mod tidy - -# Node dependencies -cd common-front && npm update -cd control-station && npm update -cd ethernet-view && npm update -``` - -## Troubleshooting - -1. **Permission denied on macOS/Linux for packet capture**: - ```bash - sudo setcap cap_net_raw+ep $(which go) - ``` - -2. **Common-front not found errors**: - Make sure to build common-front first: - ```bash - cd common-front && npm install && npm run build - ``` - -3. **Port already in use**: - Check and kill processes using the ports: - ```bash - lsof -i :8080 # backend - lsof -i :5173 # control-station - lsof -i :5174 # ethernet-view - ``` \ No newline at end of file diff --git a/docs/development/scripts.md b/docs/development/scripts.md deleted file mode 100644 index f8ee607c2..000000000 --- a/docs/development/scripts.md +++ /dev/null @@ -1,149 +0,0 @@ -# Development Scripts - -This directory contains cross-platform development scripts for the Hyperloop H10 project. - -## Available Scripts - -### Unix/Linux/macOS -- `dev.sh` - Main development script with OS detection - -### Windows -- `dev.cmd` - Windows Batch script -- `dev.ps1` - PowerShell script (recommended for Windows) - -## Prerequisites - -Before using any script, ensure you have the following installed: - -- **Go** (1.19+) -- **Node.js** (18+) -- **npm** (comes with Node.js) - -### Additional for Unix systems: -- **tmux** (for running all services simultaneously) - -## Usage - -### Unix/Linux/macOS - -```bash -# Make script executable -chmod +x ../../scripts/dev.sh - -# Run commands (from project root) -./scripts/dev.sh setup # Install dependencies -./scripts/dev.sh backend # Run backend server -./scripts/dev.sh ethernet # Run ethernet-view -./scripts/dev.sh control # Run control-station -./scripts/dev.sh packet # Run packet-sender -./scripts/dev.sh all # Run all services (requires tmux) -./scripts/dev.sh test # Run tests -./scripts/dev.sh build # Build all components -``` - -### Windows Command Prompt - -```cmd -REM Run commands -scripts\dev.cmd setup REM Install dependencies -scripts\dev.cmd backend REM Run backend server -scripts\dev.cmd ethernet REM Run ethernet-view -scripts\dev.cmd control REM Run control-station -scripts\dev.cmd packet REM Run packet-sender -scripts\dev.cmd all REM Run all services in separate windows -scripts\dev.cmd test REM Run tests -scripts\dev.cmd build REM Build all components -``` - -### Windows PowerShell - -```powershell -# You may need to allow script execution first: -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - -# Run commands -.\scripts\dev.ps1 setup # Install dependencies -.\scripts\dev.ps1 backend # Run backend server -.\scripts\dev.ps1 ethernet # Run ethernet-view -.\scripts\dev.ps1 control # Run control-station -.\scripts\dev.ps1 packet # Run packet-sender -.\scripts\dev.ps1 all # Run all services in separate windows -.\scripts\dev.ps1 test # Run tests -.\scripts\dev.ps1 build # Build all components -``` - -## Commands Explained - -### `setup` -Installs all project dependencies: -- Installs npm packages for `common-front`, `ethernet-view`, and `control-station` -- Downloads Go module dependencies for `backend` and `packet-sender` -- Builds the `common-front` library - -### `backend` -Starts the Go backend server in development mode. - -### `ethernet` -Starts the ethernet-view frontend development server (typically on port 5174). - -### `control` -Starts the control-station frontend development server (typically on port 5173). - -### `packet` -Starts the packet-sender utility. - -### `all` -Runs all services simultaneously: -- **Unix/Linux/macOS**: Uses tmux to create a session with multiple windows -- **Windows**: Opens each service in a separate command/PowerShell window - -### `test` -Runs all project tests (currently backend Go tests). - -### `build` -Builds all project components for production. - -## Platform-Specific Notes - -### Windows -- The `all` command opens separate windows for each service instead of using tmux -- PowerShell script (`dev.ps1`) is recommended over batch script (`dev.cmd`) for better functionality -- If you encounter execution policy issues with PowerShell, run: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser` - -### Unix/Linux/macOS -- The `all` command requires tmux to be installed -- If tmux is not available, run services individually in separate terminals -- The script automatically detects the operating system - -## Troubleshooting - -### "Command not found" errors -Ensure Go, Node.js, and npm are installed and available in your PATH. - -### tmux not found (Unix systems) -Install tmux or run services individually: -```bash -# Install tmux on Ubuntu/Debian -sudo apt install tmux - -# Install tmux on macOS with Homebrew -brew install tmux - -# Or run services individually in separate terminals -./scripts/dev.sh backend # Terminal 1 -./scripts/dev.sh ethernet # Terminal 2 -./scripts/dev.sh control # Terminal 3 -./scripts/dev.sh packet # Terminal 4 -``` - -### PowerShell execution policy (Windows) -If you get an execution policy error, run PowerShell as Administrator and execute: -```powershell -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -``` - -### Port conflicts -If you encounter port conflicts, check that no other services are running on the default ports: -- Backend: 8080 (configurable) -- Ethernet-view: 5174 -- Control-station: 5173 \ No newline at end of file diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md deleted file mode 100644 index 0312ec79f..000000000 --- a/docs/guides/getting-started.md +++ /dev/null @@ -1,219 +0,0 @@ -# Getting Started with Hyperloop H10 Control Station - -This guide will help you get up and running with the Hyperloop H10 Control Station, whether you're a developer, operator, or contributor. - -## What is the H10 Control Station? - -The Hyperloop H10 Control Station is a real-time monitoring and control system for Hyperloop UPV's competition pod. It provides: - -- **Real-time monitoring** of pod sensors and systems -- **Control interface** for pod operations -- **Data logging** and analysis capabilities -- **Network debugging** tools for development - -## Quick Setup - -### Prerequisites - -Before you begin, ensure you have: - -- **Go 1.21+** - [Download here](https://golang.org/doc/install) -- **Node.js 18+** - [Download here](https://nodejs.org/) -- **Git** - For version control - -### Platform-Specific Requirements - -#### Windows -- **PowerShell 5.1+** (recommended) or Command Prompt -- **Visual C++ Build Tools** (for native modules) - -#### macOS -- **Homebrew** (recommended): `brew install libpcap` -- **Xcode Command Line Tools**: `xcode-select --install` - -#### Linux -- **libpcap development headers**: - - Ubuntu/Debian: `sudo apt install libpcap-dev` - - CentOS/RHEL: `sudo yum install libpcap-devel` - -### Installation - -Choose your platform and follow the appropriate steps: - -#### Windows (PowerShell - Recommended) -```powershell -# Clone the repository -git clone https://github.com/HyperloopUPV-H8/software.git -cd software - -# Allow script execution -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - -# Setup dependencies -.\scripts\dev.ps1 setup - -# Run all services -.\scripts\dev.ps1 all -``` - -#### Windows (Command Prompt) -```cmd -REM Clone the repository -git clone https://github.com/HyperloopUPV-H8/software.git -cd software - -REM Setup dependencies -scripts\dev.cmd setup - -REM Run all services -scripts\dev.cmd all -``` - -#### macOS/Linux -```bash -# Clone the repository -git clone https://github.com/HyperloopUPV-H8/software.git -cd software - -# Make scripts executable -chmod +x scripts/dev.sh - -# Setup dependencies -./scripts/dev.sh setup - -# Run all services -./scripts/dev.sh all -``` - -## What Happens During Setup - -The setup process will: - -1. **Install frontend dependencies** for all React applications -2. **Build the common frontend library** used by all applications -3. **Download Go module dependencies** for backend services -4. **Verify all tools** are properly installed - -## Running the Applications - -After setup, you can access: - -- **Control Station**: http://localhost:5173 - Main pod control interface -- **Ethernet View**: http://localhost:5174 - Network monitoring and debugging -- **Backend API**: http://localhost:8080 - Backend services and WebSocket - -### Individual Services - -You can also run services individually: - -```bash -# Backend only -./scripts/dev.sh backend - -# Control station only -./scripts/dev.sh control - -# Ethernet view only -./scripts/dev.sh ethernet - -# Packet sender (testing tool) -./scripts/dev.sh packet -``` - -## Understanding the Interface - -### Control Station -- **Main Dashboard**: Pod status and sensor readings -- **Control Panel**: Send commands to pod systems -- **Data Visualization**: Real-time charts and graphs -- **Camera Views**: Live video feeds from pod cameras - -### Ethernet View -- **Packet Monitor**: Real-time network traffic analysis -- **Board Communication**: Monitor communication with individual boards -- **Data Tables**: Structured view of incoming sensor data -- **Debugging Tools**: Network troubleshooting utilities - -## Configuration - -### Backend Configuration -The main configuration file is located at `backend/cmd/config.toml`: - -```toml -[network] -# Your machine's IP address (must match ADJ specifications) -backend_ip = "192.168.0.9" - -[vehicle.boards] -# Enable/disable specific boards -VCU = true -PCU = true -TCU = true -# ... other boards -``` - -### ADJ Specifications -ADJ (JSON-based specifications) define: -- Board configurations and IDs -- Measurement definitions with units -- Packet structures and communication protocols -- Command (order) definitions - -Located in: `backend/cmd/adj/boards/[BOARD_NAME]/` - -## Next Steps - -### For Developers -1. Read the [Development Setup](../development/DEVELOPMENT.md) guide -2. Explore the [Architecture Overview](../architecture/README.md) -3. Check out [Contributing Guidelines](../../CONTRIBUTING.md) - -### For Operators -1. Review [Configuration Guide](configuration.md) -2. Learn about [Testing Procedures](testing.md) -3. Understand [Deployment Process](deployment.md) - -### For Contributors -1. Read [Contributing Guidelines](../../CONTRIBUTING.md) -2. Explore existing [GitHub Issues](https://github.com/HyperloopUPV-H8/software/issues) -3. Join the development discussion - -## Troubleshooting - -### Common Issues - -**Services won't start** -- Check that all prerequisites are installed -- Verify no other services are using the ports (5173, 5174, 8080) -- Run `./scripts/dev.sh setup` again to ensure proper installation - -**Build failures** -- Ensure Go and Node.js versions meet requirements -- Check internet connectivity for downloading dependencies -- Review error messages for specific missing packages - -**Permission errors (Unix)** -- Make sure scripts are executable: `chmod +x scripts/dev.sh` -- Check file permissions in the project directory - -**PowerShell execution errors (Windows)** -- Run: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser` -- Use "Run as Administrator" if needed - -### Getting Help - -- **Documentation**: Check the [troubleshooting guide](../troubleshooting/common-issues.md) -- **Issues**: Search and create [GitHub Issues](https://github.com/HyperloopUPV-H8/software/issues) -- **Community**: Join the development discussion - -## Success Indicators - -You'll know everything is working when: - -โœ… All services start without errors -โœ… Control Station loads at http://localhost:5173 -โœ… Ethernet View loads at http://localhost:5174 -โœ… Backend API responds at http://localhost:8080 -โœ… No console errors in browser developer tools - -Welcome to the Hyperloop H10 development community! ๐Ÿš„ \ No newline at end of file diff --git a/docs/troubleshooting/BLCU_FIX_SUMMARY.md b/docs/troubleshooting/BLCU_FIX_SUMMARY.md deleted file mode 100644 index 8f4fe5b3e..000000000 --- a/docs/troubleshooting/BLCU_FIX_SUMMARY.md +++ /dev/null @@ -1,61 +0,0 @@ -# BLCU (Boot Loader Control Unit) Fix Summary - -## Issues Fixed - -1. **BLCU Board Not Registered**: The BLCU board was never registered with the vehicle, causing all BLCU operations to fail silently. - -2. **Frontend/Backend Data Format Mismatch**: - - Frontend sends `board` and `file` (base64 encoded) fields - - Backend expected `Board` and `Data` (raw bytes) fields - -3. **Topic Name Mismatch**: The vehicle was listening for the wrong topic names. - -4. **Response Format**: Backend wasn't sending proper response format expected by frontend. - -## Changes Made - -### 1. Updated Request Structures (`pkg/broker/topics/blcu/upload.go`) -- Changed `UploadRequest` to accept `file` field with base64 encoded data -- Added `UploadRequestInternal` for internal processing with decoded bytes -- Added base64 decoding in the handler - -### 2. Fixed Response Handling (`pkg/broker/topics/blcu/download.go` & `upload.go`) -- Updated `Push` methods to send proper JSON responses with: - - `percentage`: Progress indicator (100 for success, 0 for failure) - - `failure`: Boolean flag - - `file`: Downloaded data (for download responses) - -### 3. Registered BLCU Board (`cmd/main.go`) -- Added BLCU board registration after vehicle setup -- Uses BLCU IP from ADJ configuration - -### 4. Fixed Event Handling (`pkg/boards/blcu.go`) -- Updated event constants to use proper types -- Fixed notification type assertions (added pointer types) - -### 5. Updated Vehicle UserPush (`pkg/vehicle/vehicle.go`) -- Fixed topic names to match request topics -- Added proper board existence checks -- Handle both UploadRequest and UploadRequestInternal types - -## Testing - -Created comprehensive tests in: -- `pkg/boards/blcu_integration_test.go` - Integration tests -- `pkg/boards/blcu_simple_test.go` - Simple unit tests - -## How It Works Now - -1. Frontend sends WebSocket message to `blcu/download` or `blcu/upload` topic -2. Broker topic handler processes the message and calls `UserPush` -3. Vehicle receives the push and notifies the BLCU board -4. BLCU board executes TFTP operations -5. BLCU board sends success/failure response back through broker -6. Frontend receives properly formatted response - -## Remaining Considerations - -- TFTP server must be running and accessible at the BLCU IP address -- The board name in the request must match a valid board in the ADJ configuration -- File data from frontend must be base64 encoded -- Progress updates during transfer are not yet implemented (TODO comments in code) \ No newline at end of file diff --git a/docs/troubleshooting/common-issues.md b/docs/troubleshooting/common-issues.md deleted file mode 100644 index 4363181ec..000000000 --- a/docs/troubleshooting/common-issues.md +++ /dev/null @@ -1,392 +0,0 @@ -# Control Station Troubleshooting Guide - -## Quick Diagnostics - -### System Health Check - -Run this checklist when experiencing issues: - -1. **Backend Status** - ```bash - # Check if backend is running - ps aux | grep backend - - # Check backend logs - tail -f trace.json | jq - - # Verify network interfaces - ip addr show | grep 192.168.0.9 - ``` - -2. **Network Connectivity** - ```bash - # Test board connectivity - ping -c 3 192.168.1.4 # Replace with board IP - - # Check active connections - netstat -an | grep -E "504[0-9]" - - # Monitor network traffic - sudo tcpdump -i any -n host 192.168.1.4 - ``` - -3. **Frontend Connection** - - Open browser DevTools (F12) - - Go to Network tab โ†’ WS - - Check WebSocket connection status - - Look for error messages in Console - -## Common Issues and Solutions - -### 1. No Data from Boards - -#### Symptoms -- Empty dashboard -- No measurements updating -- Connection indicator red - -#### Diagnosis -```bash -# Check if board is configured -grep -A 5 "vehicle" config.toml - -# Verify ADJ board definition exists -ls adj/boards/[BOARD_NAME]/ - -# Check network connectivity -ping 192.168.1.[BOARD_IP] -``` - -#### Solutions - -**Solution A: Board not in config.toml** -```toml -# Edit config.toml -[vehicle] -boards = ["LCU", "HVSCU", "BMSL"] # Add missing board -``` - -**Solution B: Network configuration issue** -```bash -# Set correct IP on backend machine -sudo ip addr add 192.168.0.9/24 dev eth0 - -# Verify routing -ip route | grep 192.168 -``` - -**Solution C: Board firmware issue** -- Check board has correct backend IP (192.168.0.9) -- Verify board is sending to correct ports (50400/50500) -- Use Wireshark to capture packets - -### 2. Orders Not Executing - -#### Symptoms -- Orders sent but no response -- Board doesn't react to commands -- No error messages - -#### Diagnosis -```bash -# Check order exists in ADJ -cat adj/boards/[BOARD]/orders.json | jq '.[] | select(.id == [ORDER_ID])' - -# Monitor order flow -tail -f trace.json | jq 'select(.topic == "order/send")' -``` - -#### Solutions - -**Solution A: Invalid order ID** -- Verify order ID exists in board's orders.json -- Check for typos in order name -- Ensure all required fields are provided - -**Solution B: WebSocket message format** -```javascript -// Correct format -{ - "topic": "order/send", - "payload": { - "id": 9995, - "fields": { - "ldu_id": { "value": 1, "type": "uint8" } - } - } -} -``` - -**Solution C: Board not processing orders** -- Check board TCP connection is established -- Verify board firmware handles the order ID -- Look for ACK messages in logs - -### 3. WebSocket Disconnections - -#### Symptoms -- "Disconnected" message in UI -- Data stops updating -- Need to refresh page frequently - -#### Diagnosis -```javascript -// In browser console -// Check WebSocket state -console.log(ws.readyState); // Should be 1 (OPEN) -``` - -#### Solutions - -**Solution A: Backend crashed** -```bash -# Check backend process -ps aux | grep backend - -# Restart if needed -./scripts/dev.sh backend -``` - -**Solution B: Network interruption** -- Check for proxy/firewall blocking WebSocket -- Verify no rate limiting on network -- Try different browser/disable extensions - -**Solution C: Frontend error** -- Check browser console for JavaScript errors -- Clear browser cache and reload -- Update to latest frontend version - -### 4. BLCU Upload/Download Fails - -#### Symptoms -- Firmware upload stuck at 0% -- "Transfer failed" error -- Timeout during TFTP operation - -#### Diagnosis -```bash -# Test TFTP connectivity -tftp 192.168.1.254 -> status -> quit - -# Check BLCU orders in logs -tail -f trace.json | jq 'select(.board == "BLCU")' -``` - -#### Solutions - -**Solution A: BLCU not responding** -- Verify BLCU IP address is correct -- Check BLCU is in bootloader mode -- Power cycle the BLCU board - -**Solution B: TFTP timeout** -```go -// Increase timeout in config -[blcu] -timeout_ms = 10000 # Increase from 5000 -``` - -**Solution C: File too large** -- Check file size is within limits -- Split large files if necessary -- Verify adequate network bandwidth - -### 5. High CPU/Memory Usage - -#### Symptoms -- System sluggish -- Fan running constantly -- Backend using >50% CPU - -#### Diagnosis -```bash -# Monitor resource usage -htop - -# Check goroutine count -curl http://localhost:4040/debug/pprof/goroutine?debug=1 | grep goroutine | wc -l - -# Profile CPU usage -go tool pprof http://localhost:4040/debug/pprof/profile?seconds=30 -``` - -#### Solutions - -**Solution A: Too many reconnection attempts** -- Check for boards that are offline -- Increase reconnection backoff -- Remove offline boards from config - -**Solution B: Memory leak** -- Restart backend periodically -- Update to latest version -- Report issue with heap profile - -**Solution C: Excessive logging** -```bash -# Reduce log verbosity -./backend -trace=warn # Instead of debug/trace -``` - -## Advanced Debugging - -### Packet Analysis - -**Capture all pod traffic**: -```bash -sudo tcpdump -i any -w capture.pcap 'net 192.168.0.0/16' -``` - -**Analyze with Wireshark**: -1. Open capture.pcap in Wireshark -2. Apply filter: `ip.addr == 192.168.1.4` -3. Look for: - - TCP RST packets (connection issues) - - Retransmissions (network problems) - - Malformed packets (firmware bugs) - -### Backend Debug Mode - -**Enable verbose logging**: -```bash -./backend -trace=trace -log=detailed.json -``` - -**Enable CPU profiling**: -```bash -./backend -cpuprofile=cpu.prof -go tool pprof -http=:8081 cpu.prof -``` - -**Memory profiling**: -```bash -curl http://localhost:4040/debug/pprof/heap > heap.prof -go tool pprof -http=:8082 heap.prof -``` - -### Frontend Debugging - -**Enable React DevTools**: -1. Install React Developer Tools extension -2. Open Components tab in DevTools -3. Search for problematic component -4. Check props and state - -**Network debugging**: -```javascript -// In browser console -// Log all WebSocket messages -const originalSend = WebSocket.prototype.send; -WebSocket.prototype.send = function(data) { - console.log('WS Send:', data); - return originalSend.call(this, data); -}; -``` - -## Error Messages Reference - -### Backend Errors - -| Error | Meaning | Solution | -|-------|---------|----------| -| `failed to obtain sniffer source` | Cannot access network interface | Run with sudo or fix permissions | -| `backend address not found in any device` | Wrong network configuration | Set correct IP on network interface | -| `failed to compile bpf filter` | Invalid packet filter | Check filter syntax | -| `Backend is already running` | PID file exists | Remove `/tmp/backendPid` or kill existing process | - -### Frontend Errors - -| Error | Meaning | Solution | -|-------|---------|----------| -| `WebSocket connection failed` | Cannot reach backend | Check backend is running and accessible | -| `Invalid message format` | Malformed WebSocket message | Check message structure matches API | -| `Unknown topic` | Topic not recognized | Verify topic name is correct | - -### Board Communication Errors - -| Error | Meaning | Solution | -|-------|---------|----------| -| `Connection refused` | Board not listening | Check board IP and port | -| `Connection timeout` | Network unreachable | Verify network path to board | -| `Packet decode error` | Malformed packet | Check ADJ matches firmware | -| `Invalid packet ID` | Unknown packet type | Update ADJ configuration | - -## Performance Tuning - -### Network Optimization - -```toml -# config.toml -[tcp] -connection_timeout = 5000 # Increase for slow networks -keep_alive = 1000 # Decrease for faster detection -backoff_multiplier = 1.5 # Adjust reconnection rate - -[transport] -propagate_fault = true # Enable fast fault propagation -``` - -### Resource Limits - -```bash -# Increase file descriptor limit -ulimit -n 4096 - -# Increase network buffers -sudo sysctl -w net.core.rmem_max=134217728 -sudo sysctl -w net.core.wmem_max=134217728 -``` - -## Getting Help - -### Before Asking for Help - -1. **Collect information**: - ```bash - # System info - uname -a - go version - node --version - - # Recent logs - tail -n 1000 trace.json > debug_logs.json - - # Configuration - cp config.toml debug_config.toml - ``` - -2. **Try basic fixes**: - - Restart backend and frontend - - Clear browser cache - - Update to latest version - - Check network connectivity - -3. **Document the issue**: - - What were you trying to do? - - What did you expect to happen? - - What actually happened? - - Can you reproduce it? - -### Where to Get Help - -1. **GitHub Issues**: For bugs and feature requests -2. **Team Discord**: For quick questions -3. **Documentation**: Check if already documented -4. **Code Comments**: Often contain helpful context - -### Creating Bug Reports - -Include: -- System information -- Steps to reproduce -- Expected vs actual behavior -- Relevant logs -- Screenshots if UI issue -- Configuration files (sanitized) - ---- - -*For architecture details and how the system works, see the [Complete Architecture Guide](../../CONTROL_STATION_COMPLETE_ARCHITECTURE.md).* \ No newline at end of file diff --git a/electron-app/package.json b/electron-app/package.json index 997531854..b34bb56ad 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -1,5 +1,5 @@ { - "name": "hyperloop-control-station", + "name": "electron-app", "version": "1.0.0", "description": "Hyperloop UPV Control Station", "main": "main.js", diff --git a/ethernet-view/.gitignore b/ethernet-view/.gitignore deleted file mode 100644 index 533354d12..000000000 --- a/ethernet-view/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -.env - -static -*.zip -*tgz diff --git a/ethernet-view/index.html b/ethernet-view/index.html deleted file mode 100644 index 95b45b7e5..000000000 --- a/ethernet-view/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - Ethernet View - - -
- - - diff --git a/ethernet-view/package-lock.json b/ethernet-view/package-lock.json deleted file mode 100644 index e25d71ca1..000000000 --- a/ethernet-view/package-lock.json +++ /dev/null @@ -1,5600 +0,0 @@ -{ - "name": "frontend-h8", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "frontend-h8", - "version": "0.0.0", - "dependencies": { - "@canvasjs/react-charts": "^1.0.0", - "@react-spring/web": "^9.7.2", - "@reduxjs/toolkit": "^1.9.0", - "axios": "^1.1.3", - "common": "file:../common-front", - "dotenv": "^16.0.3", - "events": "^3.3.0", - "lightweight-charts": "^4.1.2", - "lodash": "^4.17.21", - "nanoid": "^4.0.2", - "papaparse": "^5.4.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-icons": "^4.6.0", - "react-redux": "^8.0.5", - "react-router-dom": "^6.22.3", - "react-use-measure": "^2.1.1", - "react-virtualized-auto-sizer": "^1.0.7", - "react-window": "^1.8.8", - "vite-plugin-svgr": "^2.4.0", - "vite-tsconfig-paths": "^4.0.5", - "zustand": "^4.4.6", - "zustymiddleware": "^1.2.0", - "zustymiddlewarets": "^1.4.2" - }, - "devDependencies": { - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@types/events": "^3.0.0", - "@types/lodash": "^4.14.191", - "@types/node": "^18.11.9", - "@types/papaparse": "^5.3.14", - "@types/react": "^18.0.24", - "@types/react-dom": "^18.0.8", - "@types/react-virtualized-auto-sizer": "^1.0.1", - "@types/react-window": "^1.8.5", - "@vitejs/plugin-react": "^2.2.0", - "jsdom": "^20.0.2", - "nock": "^13.2.9", - "sass": "^1.56.1", - "typescript": "^4.6.4", - "vite": "^3.2.10", - "vitest": "^0.25.2" - } - }, - "../common-front": { - "name": "@hyperloop-upv/common-front", - "version": "2.0.24", - "dependencies": { - "@react-spring/web": "^9.7.2", - "@reduxjs/toolkit": "^1.9.5", - "@rollup/plugin-typescript": "^11.1.0", - "@types/lodash": "^4.14.194", - "@types/node": "^18.16.1", - "@types/react": "^18.2.0", - "@vitejs/plugin-react": "^4.0.0", - "@zerollup/ts-transform-paths": "^1.7.18", - "hls.js": "^1.6.2", - "lodash": "^4.17.21", - "math": "^0.0.3", - "react": "^18.2.0", - "react-icons": "^4.9.0", - "react-redux": "^8.0.5", - "react-use-websocket": "^3.0.0", - "rollup-plugin-import-map": "^3.0.0", - "rollup-plugin-includepaths": "^0.2.4", - "rollup-plugin-postcss": "^4.0.2", - "rollup-plugin-typescript-paths": "^1.4.0", - "rollup-plugin-typescript2": "^0.34.1", - "sass": "^1.62.1", - "tslib": "^2.5.0", - "ttypescript": "^1.5.15", - "typescript": "^5.0.2", - "vite": "^4.5.14", - "vite-plugin-svgr": "^3.2.0", - "vite-tsconfig-paths": "^4.2.0", - "zustand": "^4.4.6" - } - }, - "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", - "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@canvasjs/charts": { - "version": "3.14.9", - "resolved": "https://registry.npmjs.org/@canvasjs/charts/-/charts-3.14.9.tgz", - "integrity": "sha512-pyjjfyPcmgsrvHJ+f6nh8FbiQx2tqHblVHggyH5Ldx6jZjm57j2WzWcwlDLjzlkrfGZ+8TSWS2ItBUhcAJh6Yw==", - "license": "SEE LICENSE IN ", - "peer": true - }, - "node_modules/@canvasjs/react-charts": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@canvasjs/react-charts/-/react-charts-1.0.2.tgz", - "integrity": "sha512-PZgJlDbGdMF4AN/KvrvGY9X50EByJMZ7MHfQB/U0aky9Onn9mt0CpsvwudBsBe+DofaV3SHR4SHn/Wfo/pubDw==", - "license": "MIT", - "peerDependencies": { - "@canvasjs/charts": "^3.7.5", - "react": ">=16.0.0" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/types/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/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@react-spring/animated": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz", - "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", - "license": "MIT", - "dependencies": { - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/core": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", - "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", - "license": "MIT", - "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/react-spring/donate" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/rafz": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", - "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==", - "license": "MIT" - }, - "node_modules/@react-spring/shared": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", - "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", - "license": "MIT", - "dependencies": { - "@react-spring/rafz": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/types": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", - "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==", - "license": "MIT" - }, - "node_modules/@react-spring/web": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz", - "integrity": "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==", - "license": "MIT", - "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/core": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@reduxjs/toolkit": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", - "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", - "license": "MIT", - "dependencies": { - "immer": "^9.0.21", - "redux": "^4.2.1", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.8" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, - "node_modules/@remix-run/router": { - "version": "1.23.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz", - "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz", - "integrity": "sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz", - "integrity": "sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz", - "integrity": "sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz", - "integrity": "sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz", - "integrity": "sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz", - "integrity": "sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.5.1.tgz", - "integrity": "sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==", - "license": "MIT", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "^6.5.1", - "@svgr/babel-plugin-remove-jsx-attribute": "*", - "@svgr/babel-plugin-remove-jsx-empty-expression": "*", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^6.5.1", - "@svgr/babel-plugin-svg-dynamic-title": "^6.5.1", - "@svgr/babel-plugin-svg-em-dimensions": "^6.5.1", - "@svgr/babel-plugin-transform-react-native-svg": "^6.5.1", - "@svgr/babel-plugin-transform-svg-component": "^6.5.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz", - "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.19.6", - "@svgr/babel-preset": "^6.5.1", - "@svgr/plugin-jsx": "^6.5.1", - "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz", - "integrity": "sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.0", - "entities": "^4.4.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz", - "integrity": "sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.19.6", - "@svgr/babel-preset": "^6.5.1", - "@svgr/hast-util-to-babel-ast": "^6.5.1", - "svg-parser": "^2.0.4" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "^6.0.0" - } - }, - "node_modules/@testing-library/dom": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", - "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/@testing-library/dom/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/@testing-library/jest-dom": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", - "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=8", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/react": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", - "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^8.5.0", - "@types/react-dom": "^18.0.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "4.3.20", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", - "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/chai-subset": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.6.tgz", - "integrity": "sha512-m8lERkkQj+uek18hXOZuec3W/fCRTrU4hrnXjH3qhHy96ytuPaPiWGgu7sJb7tZxZonO75vYAjCvpe/e4VUwRw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/chai": "<5.2.0" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/events": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", - "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", - "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", - "license": "MIT", - "dependencies": { - "hoist-non-react-statics": "^3.3.0" - }, - "peerDependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@types/jest/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/papaparse": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.0.tgz", - "integrity": "sha512-GVs5iMQmUr54BAZYYkByv8zPofFxmyxUpISPb2oh8sayR3+1zbxasrOvoKiHJ/nnoq/uULuPsu1Lze1EkagVFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/react-virtualized-auto-sizer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz", - "integrity": "sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-window": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz", - "integrity": "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", - "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/jest": "*" - } - }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz", - "integrity": "sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.19.6", - "@babel/plugin-transform-react-jsx": "^7.19.0", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-jsx-self": "^7.18.6", - "@babel/plugin-transform-react-jsx-source": "^7.19.6", - "magic-string": "^0.26.7", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^3.0.0" - } - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "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/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "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/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.32", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", - "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "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==", - "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==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", - "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/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/common": { - "resolved": "../common-front", - "link": true - }, - "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/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "license": "Apache-2.0", - "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "license": "MIT", - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "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==", - "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.262", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", - "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", - "license": "ISC" - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "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==", - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "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==", - "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/esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.15.18", - "@esbuild/linux-loong64": "0.15.18", - "esbuild-android-64": "0.15.18", - "esbuild-android-arm64": "0.15.18", - "esbuild-darwin-64": "0.15.18", - "esbuild-darwin-arm64": "0.15.18", - "esbuild-freebsd-64": "0.15.18", - "esbuild-freebsd-arm64": "0.15.18", - "esbuild-linux-32": "0.15.18", - "esbuild-linux-64": "0.15.18", - "esbuild-linux-arm": "0.15.18", - "esbuild-linux-arm64": "0.15.18", - "esbuild-linux-mips64le": "0.15.18", - "esbuild-linux-ppc64le": "0.15.18", - "esbuild-linux-riscv64": "0.15.18", - "esbuild-linux-s390x": "0.15.18", - "esbuild-netbsd-64": "0.15.18", - "esbuild-openbsd-64": "0.15.18", - "esbuild-sunos-64": "0.15.18", - "esbuild-windows-32": "0.15.18", - "esbuild-windows-64": "0.15.18", - "esbuild-windows-arm64": "0.15.18" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", - "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "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": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "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/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/fancy-canvas": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-2.1.0.tgz", - "integrity": "sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==", - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "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/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/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==", - "license": "MIT", - "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/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-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "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==", - "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==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "license": "MIT" - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "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-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "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==", - "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==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/immutable": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", - "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "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/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "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-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-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "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==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "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==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "license": "MIT", - "optional": true, - "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-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "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-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "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-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": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-diff/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/jest-diff/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils/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/jest-matcher-utils/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util/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/jest-message-util/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-util": "30.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util/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/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lightweight-charts": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-4.2.3.tgz", - "integrity": "sha512-5kS/2hY3wNYNzhnS8Gb+GAS07DX8GPF2YVDnd2NMC85gJVQ6RLU6YrXNgNJ6eg0AnWPwCnvaGtYmGky3HiLQEw==", - "license": "Apache-2.0", - "dependencies": { - "fancy-canvas": "2.1.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/magic-string": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", - "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - }, - "engines": { - "node": ">=12" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "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": "4.0.2", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", - "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/nock": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", - "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 10.13" - } - }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT", - "optional": true - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "license": "MIT" - }, - "node_modules/nwsapi": { - "version": "2.2.22", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", - "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", - "dev": true, - "license": "MIT" - }, - "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-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "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/papaparse": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", - "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", - "license": "MIT" - }, - "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==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "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==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "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/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, - "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/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-icons": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", - "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-redux": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", - "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4 || ^5.0.0-beta.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "node_modules/react-redux/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "6.30.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz", - "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.30.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz", - "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.1", - "react-router": "6.30.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-use-measure": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", - "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.13", - "react-dom": ">=16.13" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-virtualized-auto-sizer": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz", - "integrity": "sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==", - "license": "MIT", - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-window": { - "version": "1.8.11", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", - "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" - }, - "engines": { - "node": ">8.0.0" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "license": "MIT", - "peerDependencies": { - "redux": "^4" - } - }, - "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/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/reselect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", - "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", - "license": "MIT", - "peer": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "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/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sass": { - "version": "1.94.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz", - "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" - } - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "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/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/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/sponsors/ljharb" - } - }, - "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": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "license": "MIT" - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", - "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.1.1.tgz", - "integrity": "sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/vite": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz", - "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.15.9", - "postcss": "^8.4.18", - "resolve": "^1.22.1", - "rollup": "^2.79.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-plugin-svgr": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-2.4.0.tgz", - "integrity": "sha512-q+mJJol6ThvqkkJvvVFEndI4EaKIjSI0I3jNFgSoC9fXAz1M7kYTVUin8fhUsFojFDKZ9VHKtX6NXNaOLpbsHA==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.2", - "@svgr/core": "^6.5.1" - }, - "peerDependencies": { - "vite": "^2.6.0 || 3 || 4" - } - }, - "node_modules/vite-tsconfig-paths": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", - "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^3.0.3" - }, - "peerDependencies": { - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/vite-tsconfig-paths/node_modules/tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vite-tsconfig-paths/node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/vitest": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.8.tgz", - "integrity": "sha512-X75TApG2wZTJn299E/TIYevr4E9/nBo1sUtZzn0Ci5oK8qnpZAZyhwg0qCeMSakGIWtc6oRwcQFyFfW14aOFWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^4.3.4", - "@types/chai-subset": "^1.3.3", - "@types/node": "*", - "acorn": "^8.8.1", - "acorn-walk": "^8.2.0", - "chai": "^4.3.7", - "debug": "^4.3.4", - "local-pkg": "^0.4.2", - "source-map": "^0.6.1", - "strip-literal": "^1.0.0", - "tinybench": "^2.3.1", - "tinypool": "^0.3.0", - "tinyspy": "^1.0.2", - "vite": "^3.0.0 || ^4.0.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": ">=v14.16.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@vitest/browser": "*", - "@vitest/ui": "*", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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-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.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "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/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/zustymiddleware": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zustymiddleware/-/zustymiddleware-1.2.0.tgz", - "integrity": "sha512-N2/OrW3r5TwX5slwSwk81xynDnM24pWkcIjvoHDeGJJY/fKrfKjZ4EmvvADKANeMPkkCp4Mkf49Ok2BhDR0scg==", - "license": "MIT" - }, - "node_modules/zustymiddlewarets": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/zustymiddlewarets/-/zustymiddlewarets-1.4.2.tgz", - "integrity": "sha512-fwXF02TgFtrtxSwgyQg/mlFGU1lDC8bgDDQiKTARt2TqC508jTiBD/6ztb0yZ4Qp+fVBjfbOJN1JLx2PXNW+HQ==", - "license": "MIT", - "dependencies": { - "zustand": "^4.5.1" - } - } - } -} diff --git a/ethernet-view/package.json b/ethernet-view/package.json deleted file mode 100644 index 6652f8ea0..000000000 --- a/ethernet-view/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "frontend-h8", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "test": "vitest", - "preview": "npm run build && vite preview" - }, - "dependencies": { - "@canvasjs/react-charts": "^1.0.0", - "@react-spring/web": "^9.7.2", - "@reduxjs/toolkit": "^1.9.0", - "axios": "^1.1.3", - "common": "file:../common-front", - "dotenv": "^16.0.3", - "events": "^3.3.0", - "lightweight-charts": "^4.1.2", - "lodash": "^4.17.21", - "nanoid": "^4.0.2", - "papaparse": "^5.4.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-icons": "^4.6.0", - "react-redux": "^8.0.5", - "react-router-dom": "^6.22.3", - "react-use-measure": "^2.1.1", - "react-virtualized-auto-sizer": "^1.0.7", - "react-window": "^1.8.8", - "vite-plugin-svgr": "^2.4.0", - "vite-tsconfig-paths": "^4.0.5", - "zustand": "^4.4.6", - "zustymiddleware": "^1.2.0", - "zustymiddlewarets": "^1.4.2" - }, - "devDependencies": { - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@types/events": "^3.0.0", - "@types/lodash": "^4.14.191", - "@types/node": "^18.11.9", - "@types/papaparse": "^5.3.14", - "@types/react": "^18.0.24", - "@types/react-dom": "^18.0.8", - "@types/react-virtualized-auto-sizer": "^1.0.1", - "@types/react-window": "^1.8.5", - "@vitejs/plugin-react": "^2.2.0", - "jsdom": "^20.0.2", - "nock": "^13.2.9", - "sass": "^1.56.1", - "typescript": "^4.6.4", - "vite": "^3.2.10", - "vitest": "^0.25.2" - } -} diff --git a/ethernet-view/pod_navigation.md b/ethernet-view/pod_navigation.md deleted file mode 100644 index c6771d163..000000000 --- a/ethernet-view/pod_navigation.md +++ /dev/null @@ -1,11 +0,0 @@ -# Pod Navigation System - -This document defines the components and backend modules that will make dynamic point-to-point navigation posible. - -## Protocol - -When the VCU is `IDLE`, one of the available orders is `start_traction`. If we hit that, the next state order we receive is `custom_route`. This order acceptes an unlimited number of position + velocity pairs, which indicate the route the vehicle is going to follow. To perform this, the frontend will send an order which follows a different structure from what the other orders are. - -## Frontend - -The frontend will actually send separate orders. diff --git a/ethernet-view/public/vite.svg b/ethernet-view/public/vite.svg deleted file mode 100644 index e7b8dfb1b..000000000 --- a/ethernet-view/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ethernet-view/src/App.css b/ethernet-view/src/App.css deleted file mode 100644 index f1a98c5ef..000000000 --- a/ethernet-view/src/App.css +++ /dev/null @@ -1,6 +0,0 @@ -.App { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; -} diff --git a/ethernet-view/src/App.tsx b/ethernet-view/src/App.tsx deleted file mode 100644 index d29e32ce3..000000000 --- a/ethernet-view/src/App.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import "./App.css"; -import { TestingPage } from "pages/TestingPage/TestingPage"; -import { SplashScreen } from "components/SplashScreen/SplashScreen"; -import { WsHandlerProvider } from "common"; -import { useLoadBackend } from "common"; -import { AppLayout } from "layouts/AppLayout/AppLayout"; -import { useState, useEffect } from "react"; -import { LoggerPage } from "pages/LoggerPage/LoggerPage"; -import { CamerasPage } from "pages/CamerasPage/CamerasPage"; - -function App() { - - const isProduction = import.meta.env.PROD; - const loadBackend = useLoadBackend(isProduction); - const [pageShown, setPageShown] = useState("testing"); - - useEffect(() => { - const savedTheme = localStorage.getItem("theme") || "light"; - document.documentElement.setAttribute("data-theme", savedTheme); - }, []); - - return ( - -
- {loadBackend.state === "fulfilled" && - - - } - {loadBackend.state === "pending" && } - {loadBackend.state === "rejected" &&
{`${loadBackend.error}`}
} -
-
- -
-
- -
-
- ); -} - -export default App; \ No newline at end of file diff --git a/ethernet-view/src/BackendTypes.ts b/ethernet-view/src/BackendTypes.ts deleted file mode 100644 index 5b811b3e9..000000000 --- a/ethernet-view/src/BackendTypes.ts +++ /dev/null @@ -1,44 +0,0 @@ -export type BackendType = NumericType | Bool | Enum; - -export type NumericType = - | SignedIntegerType - | UnsignedIntegerType - | FloatingType; - -export type SignedIntegerType = "int8" | "int16" | "int32" | "int64"; - -export type UnsignedIntegerType = "uint8" | "uint16" | "uint32" | "uint64"; - -export type FloatingType = "float32" | "float64"; - -export function isNumericType(type: string): type is NumericType { - return ( - isUnsignedIntegerType(type) || - isSignedIntegerType(type) || - isFloatingType(type) - ); -} - -export function isUnsignedIntegerType( - type: string -): type is UnsignedIntegerType { - return ( - type == "uint8" || - type == "uint16" || - type == "uint32" || - type == "uint64" - ); -} - -export function isSignedIntegerType(type: string): type is SignedIntegerType { - return ( - type == "int8" || type == "int16" || type == "int32" || type == "int64" - ); -} - -export function isFloatingType(type: string): type is FloatingType { - return type == "float32" || type == "float64"; -} - -type Enum = "enum"; -type Bool = "bool"; diff --git a/ethernet-view/src/assets/fonts/Consolas/Consolas-Bold-Italic.ttf b/ethernet-view/src/assets/fonts/Consolas/Consolas-Bold-Italic.ttf deleted file mode 100644 index d9df2110f..000000000 Binary files a/ethernet-view/src/assets/fonts/Consolas/Consolas-Bold-Italic.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Consolas/Consolas-Bold.ttf b/ethernet-view/src/assets/fonts/Consolas/Consolas-Bold.ttf deleted file mode 100644 index 77f5d6052..000000000 Binary files a/ethernet-view/src/assets/fonts/Consolas/Consolas-Bold.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Consolas/Consolas-Italic.ttf b/ethernet-view/src/assets/fonts/Consolas/Consolas-Italic.ttf deleted file mode 100644 index 2de4de8a9..000000000 Binary files a/ethernet-view/src/assets/fonts/Consolas/Consolas-Italic.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Consolas/Consolas.scss b/ethernet-view/src/assets/fonts/Consolas/Consolas.scss deleted file mode 100644 index fb1bd23d7..000000000 --- a/ethernet-view/src/assets/fonts/Consolas/Consolas.scss +++ /dev/null @@ -1,27 +0,0 @@ -@font-face { - font-family: Consolas; - src: url("/src/assets/fonts/Consolas/Consolas.ttf"); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: Consolas; - src: url("/src/assets/fonts/Consolas/Consolas-Bold.ttf"); - font-weight: bold; - font-style: normal; -} - -@font-face { - font-family: Consolas; - src: url("/src/assets/fonts/Consolas/Consolas-Italic.ttf"); - font-weight: normal; - font-style: italic; -} - -@font-face { - font-family: Consolas; - src: url("/src/assets/fonts/Consolas/Consolas-Bold-Italic.ttf"); - font-weight: bold; - font-style: italic; -} diff --git a/ethernet-view/src/assets/fonts/Consolas/Consolas.ttf b/ethernet-view/src/assets/fonts/Consolas/Consolas.ttf deleted file mode 100644 index e881ca4b5..000000000 Binary files a/ethernet-view/src/assets/fonts/Consolas/Consolas.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-Black.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-Black.ttf deleted file mode 100644 index e284fa005..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-Black.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-Bold.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-Bold.ttf deleted file mode 100644 index f13d511d8..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-Bold.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-ExtraBold.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-ExtraBold.ttf deleted file mode 100644 index 2b55fc13f..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-ExtraBold.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-ExtraLight.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-ExtraLight.ttf deleted file mode 100644 index af2bfbb32..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-ExtraLight.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-Light.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-Light.ttf deleted file mode 100644 index 34546cfd7..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-Light.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-Medium.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-Medium.ttf deleted file mode 100644 index 9a3396fc4..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-Medium.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-Regular.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-Regular.ttf deleted file mode 100644 index 2c164bb2d..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-Regular.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-SemiBold.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-SemiBold.ttf deleted file mode 100644 index b97437120..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-SemiBold.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter-Thin.ttf b/ethernet-view/src/assets/fonts/Inter/Inter-Thin.ttf deleted file mode 100644 index 7f5b005a2..000000000 Binary files a/ethernet-view/src/assets/fonts/Inter/Inter-Thin.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/fonts/Inter/Inter.scss b/ethernet-view/src/assets/fonts/Inter/Inter.scss deleted file mode 100644 index febd6611b..000000000 --- a/ethernet-view/src/assets/fonts/Inter/Inter.scss +++ /dev/null @@ -1,62 +0,0 @@ -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-Thin.ttf"); - font-weight: 100; - font-style: normal; -} - -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-ExtraLight.ttf"); - font-weight: 200; - font-style: normal; -} - -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-Light.ttf"); - font-weight: 300; - font-style: normal; -} - -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-Regular.ttf"); - font-weight: 400; - font-style: normal; -} - -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-Medium.ttf"); - font-weight: 500; - font-style: normal; -} - -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-SemiBold.ttf"); - font-weight: 600; - font-style: normal; -} - -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-Bold.ttf"); - font-weight: 700; - font-style: normal; -} - -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-ExtraBold.ttf"); - font-weight: 800; - font-style: normal; -} - -@font-face { - font-family: Inter; - src: url("/src/assets/fonts/Inter/Inter-ExtraBold.ttf"); - font-weight: 900; - font-style: normal; -} diff --git a/ethernet-view/src/assets/fonts/NotoColorEmoji/NotoColorEmoji.scss b/ethernet-view/src/assets/fonts/NotoColorEmoji/NotoColorEmoji.scss deleted file mode 100644 index 7f0545e47..000000000 --- a/ethernet-view/src/assets/fonts/NotoColorEmoji/NotoColorEmoji.scss +++ /dev/null @@ -1,4 +0,0 @@ -@font-face { - font-family: NotoColorEmoji; - src: url("/src/assets/fonts/NotoColorEmoji/NotoColorEmoji.ttf"); -} diff --git a/ethernet-view/src/assets/fonts/NotoColorEmoji/NotoColorEmoji.ttf b/ethernet-view/src/assets/fonts/NotoColorEmoji/NotoColorEmoji.ttf deleted file mode 100644 index d21205d9a..000000000 Binary files a/ethernet-view/src/assets/fonts/NotoColorEmoji/NotoColorEmoji.ttf and /dev/null differ diff --git a/ethernet-view/src/assets/svg/binary-file.svg b/ethernet-view/src/assets/svg/binary-file.svg deleted file mode 100644 index 82741071a..000000000 --- a/ethernet-view/src/assets/svg/binary-file.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - diff --git a/ethernet-view/src/assets/svg/camera.svg b/ethernet-view/src/assets/svg/camera.svg deleted file mode 100644 index 35be6eba3..000000000 --- a/ethernet-view/src/assets/svg/camera.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/ethernet-view/src/assets/svg/chart.svg b/ethernet-view/src/assets/svg/chart.svg deleted file mode 100644 index 78f13281f..000000000 --- a/ethernet-view/src/assets/svg/chart.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ethernet-view/src/assets/svg/close-folder.svg b/ethernet-view/src/assets/svg/close-folder.svg deleted file mode 100644 index 69b4be200..000000000 --- a/ethernet-view/src/assets/svg/close-folder.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ethernet-view/src/assets/svg/connection.svg b/ethernet-view/src/assets/svg/connection.svg deleted file mode 100644 index 804e56fd7..000000000 --- a/ethernet-view/src/assets/svg/connection.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ethernet-view/src/assets/svg/cross-active.svg b/ethernet-view/src/assets/svg/cross-active.svg deleted file mode 100644 index 9a036ccc6..000000000 --- a/ethernet-view/src/assets/svg/cross-active.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ethernet-view/src/assets/svg/cross.svg b/ethernet-view/src/assets/svg/cross.svg deleted file mode 100644 index c0b33dcd0..000000000 --- a/ethernet-view/src/assets/svg/cross.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ethernet-view/src/assets/svg/fault.svg b/ethernet-view/src/assets/svg/fault.svg deleted file mode 100644 index 8e1c800bc..000000000 --- a/ethernet-view/src/assets/svg/fault.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/ethernet-view/src/assets/svg/folder-closed.svg b/ethernet-view/src/assets/svg/folder-closed.svg deleted file mode 100644 index bccc3b366..000000000 --- a/ethernet-view/src/assets/svg/folder-closed.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ethernet-view/src/assets/svg/folder-open.svg b/ethernet-view/src/assets/svg/folder-open.svg deleted file mode 100644 index 56e6cd6b0..000000000 --- a/ethernet-view/src/assets/svg/folder-open.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ethernet-view/src/assets/svg/incoming-message.svg b/ethernet-view/src/assets/svg/incoming-message.svg deleted file mode 100644 index f07b00d68..000000000 --- a/ethernet-view/src/assets/svg/incoming-message.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ethernet-view/src/assets/svg/info.svg b/ethernet-view/src/assets/svg/info.svg deleted file mode 100644 index ec4360ee1..000000000 --- a/ethernet-view/src/assets/svg/info.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ethernet-view/src/assets/svg/letter.svg b/ethernet-view/src/assets/svg/letter.svg deleted file mode 100644 index b63895d54..000000000 --- a/ethernet-view/src/assets/svg/letter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ethernet-view/src/assets/svg/logger.svg b/ethernet-view/src/assets/svg/logger.svg deleted file mode 100644 index a4b5a560b..000000000 --- a/ethernet-view/src/assets/svg/logger.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ethernet-view/src/assets/svg/logs.svg b/ethernet-view/src/assets/svg/logs.svg deleted file mode 100644 index 3df71cd6c..000000000 --- a/ethernet-view/src/assets/svg/logs.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ethernet-view/src/assets/svg/mokey-face.svg b/ethernet-view/src/assets/svg/mokey-face.svg deleted file mode 100644 index 11df02b8b..000000000 --- a/ethernet-view/src/assets/svg/mokey-face.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ethernet-view/src/assets/svg/monkey.svg b/ethernet-view/src/assets/svg/monkey.svg deleted file mode 100644 index c0e505d88..000000000 --- a/ethernet-view/src/assets/svg/monkey.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/ethernet-view/src/assets/svg/oscilloscope.svg b/ethernet-view/src/assets/svg/oscilloscope.svg deleted file mode 100644 index 31b666f08..000000000 --- a/ethernet-view/src/assets/svg/oscilloscope.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ethernet-view/src/assets/svg/outgoing-message.svg b/ethernet-view/src/assets/svg/outgoing-message.svg deleted file mode 100644 index 51458f753..000000000 --- a/ethernet-view/src/assets/svg/outgoing-message.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ethernet-view/src/assets/svg/paper-airplane.svg b/ethernet-view/src/assets/svg/paper-airplane.svg deleted file mode 100644 index 8ffdea69e..000000000 --- a/ethernet-view/src/assets/svg/paper-airplane.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ethernet-view/src/assets/svg/right-arrow.svg b/ethernet-view/src/assets/svg/right-arrow.svg deleted file mode 100644 index 2647c340c..000000000 --- a/ethernet-view/src/assets/svg/right-arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ethernet-view/src/assets/svg/target.svg b/ethernet-view/src/assets/svg/target.svg deleted file mode 100644 index bc2af3c13..000000000 --- a/ethernet-view/src/assets/svg/target.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ethernet-view/src/assets/svg/team_logo.svg b/ethernet-view/src/assets/svg/team_logo.svg deleted file mode 100644 index 1bba6ddb0..000000000 --- a/ethernet-view/src/assets/svg/team_logo.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - diff --git a/ethernet-view/src/assets/svg/testing.svg b/ethernet-view/src/assets/svg/testing.svg deleted file mode 100644 index efb3860d7..000000000 --- a/ethernet-view/src/assets/svg/testing.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ethernet-view/src/assets/svg/warning.svg b/ethernet-view/src/assets/svg/warning.svg deleted file mode 100644 index 821e6e711..000000000 --- a/ethernet-view/src/assets/svg/warning.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/Bootloader.module.scss b/ethernet-view/src/components/BootloaderContainer/Bootloader/Bootloader.module.scss deleted file mode 100644 index 2959362cf..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/Bootloader.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use "src/styles/styles"; - -.bootloader { - height: 10rem; - display: flex; - align-items: stretch; - justify-content: stretch; - @include styles.alternate-code-text; - gap: 1rem; - font-size: 1.1rem; - padding: 1px; - - overflow-x: auto; - overflow-y: hidden; -} diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/Bootloader.tsx b/ethernet-view/src/components/BootloaderContainer/Bootloader/Bootloader.tsx deleted file mode 100644 index 5f6c5db25..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/Bootloader.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import styles from "./Bootloader.module.scss"; -import { useState } from "react"; -import { DropElement } from "./DropElement/DropElement"; -import { SendElement } from "./SendElement/SendElement"; -import { useBootloaderState } from "./useBootloaderState"; -import { LoadingElement } from "./LoadingElement/LoadingElement"; -import { ResponseElement } from "./ResponseElement/ResponseElement"; -import { Controls } from "./Controls/Controls"; -import { Island } from "components/Island/Island"; - -type Props = { - boards: string[]; -}; - -export const Bootloader = ({ boards }: Props) => { - const [state, upload, download, setFile, removeFile] = useBootloaderState(); - const [board, setBoard] = useState(boards[0] ?? "Default"); - - return ( - -
- {state.kind == "empty" && } - {state.kind == "send" && ( - removeFile()} - /> - )} - {state.kind == "awaiting" && ( - - )} - {state.kind == "success" && } - {state.kind == "failure" && } - {(state.kind == "empty" || state.kind == "send") && ( - setBoard(board)} - onDownloadClick={() => download(board)} - onUploadClick={() => { - if (state.kind == "send") upload(board, state.file); - }} - /> - )} -
-
- ); -}; diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/BootloaderViewState.ts b/ethernet-view/src/components/BootloaderContainer/Bootloader/BootloaderViewState.ts deleted file mode 100644 index 2999fcfa0..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/BootloaderViewState.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type BootloaderViewState = - | EmptyState - | SendState - | AwaitingState - | SuccessState - | FailureState; - -type EmptyState = { - kind: "empty"; -}; - -type SendState = { - kind: "send"; - file: File; -}; - -type AwaitingState = { - kind: "awaiting"; - progress: number; // Between 0 and 100 -}; - -type SuccessState = { - kind: "success"; -}; - -type FailureState = { - kind: "failure"; -}; diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/Controls/Controls.module.scss b/ethernet-view/src/components/BootloaderContainer/Bootloader/Controls/Controls.module.scss deleted file mode 100644 index acd09fd08..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/Controls/Controls.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.boardControlsWrapper { - width: 12rem; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 0.5rem; -} diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/Controls/Controls.tsx b/ethernet-view/src/components/BootloaderContainer/Bootloader/Controls/Controls.tsx deleted file mode 100644 index dba7a1edf..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/Controls/Controls.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Dropdown } from "components/FormComponents/Dropdown/Dropdown"; -import styles from "./Controls.module.scss"; -import { Button } from "components/FormComponents/Button/Button"; - -type Props = { - options: Array; - enableUpload: boolean; - onBoardChange: (board: string) => void; - onDownloadClick: () => void; - onUploadClick: () => void; -}; - -export const Controls = ({ - options, - enableUpload, - onBoardChange, - onDownloadClick, - onUploadClick, -}: Props) => { - return ( -
- - - - -
- ); -}; diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/DropElement/DropElement.module.scss b/ethernet-view/src/components/BootloaderContainer/Bootloader/DropElement/DropElement.module.scss deleted file mode 100644 index 0989f4dfd..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/DropElement/DropElement.module.scss +++ /dev/null @@ -1,30 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.dropElementWrapper { - flex: 1 1 0; - display: flex; - justify-content: center; - align-items: center; - padding: 1rem; - outline: 1px dashed colors.getThemeColor("border"); - border-radius: styles.$large-border-radius; - - text-align: center; - overflow: hidden; - min-width: 6rem; -} - -.dragOver { - outline: 2px solid colors.getThemeColor("primary"); - background-color: colors.getThemeColor("primary-surface"); -} - -.link { - font-weight: bold; - color: colors.getThemeColor("primary"); - - &:hover { - color: colors.getThemeColor("primary-hover"); - } -} diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/DropElement/DropElement.tsx b/ethernet-view/src/components/BootloaderContainer/Bootloader/DropElement/DropElement.tsx deleted file mode 100644 index 9c788d6af..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/DropElement/DropElement.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import styles from "./DropElement.module.scss"; -import { useState } from "react"; -import { FileInput } from "components/FormComponents/FileInput/FileInput"; -import { getFile } from "../getFile"; - -type Props = { - onFile: (file: File) => void; -}; - -export const DropElement = ({ onFile }: Props) => { - const [isDragOver, setDragOver] = useState(false); - - return ( -
{ - setDragOver(true); - }} - onDragLeave={(ev) => { - if (ev.currentTarget.contains(ev.relatedTarget as Node)) { - return; - } - setDragOver(false); - }} - // onDragLeaveCapture={() => setDragOver(false)} - onDrop={(ev) => { - const file = getFile(ev, "bin"); - if (file) { - onFile(file); - } - }} - onDragOver={(ev) => ev.preventDefault()} - > -
- Drop or  - -
-
- ); -}; diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/AnimatedEllipsis/AnimatedEllipsis.tsx b/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/AnimatedEllipsis/AnimatedEllipsis.tsx deleted file mode 100644 index 7c86c5d5d..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/AnimatedEllipsis/AnimatedEllipsis.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useInterval } from "hooks/useInterval"; -import { useState } from "react"; - -export const AnimatedEllipsis = () => { - const [points, setPoints] = useState(["."]); - - useInterval(() => { - setPoints((prevPoints) => { - return new Array((prevPoints.length + 1) % 4).fill("."); - }); - }, 800); - - return {points}; -}; diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/LoadingElement.module.scss b/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/LoadingElement.module.scss deleted file mode 100644 index d4400dfb5..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/LoadingElement.module.scss +++ /dev/null @@ -1,10 +0,0 @@ -.awaitElementWrapper { - flex: 1 1 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: stretch; - font-size: 1.5rem; - text-align: center; - gap: 1rem; -} diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/LoadingElement.tsx b/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/LoadingElement.tsx deleted file mode 100644 index d814eb2ea..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/LoadingElement/LoadingElement.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import styles from "./LoadingElement.module.scss"; -import { ProgressBar } from "components/ProgressBar/ProgressBar"; - -type Props = { - progress: number; -}; - -export const LoadingElement = ({ progress }: Props) => { - return ( -
- Loading - -
- ); -}; diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/ResponseElement/ResponseElement.module.scss b/ethernet-view/src/components/BootloaderContainer/Bootloader/ResponseElement/ResponseElement.module.scss deleted file mode 100644 index 313614f38..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/ResponseElement/ResponseElement.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "src/styles/colors" as colors; - -.responseElementWrapper { - flex: 1 1 0; - display: flex; - justify-content: center; - align-items: center; - font-size: 1.5rem; - gap: 0.5rem; -} - -.icon { - font-size: 2rem; -} - -.successIcon { - color: colors.getThemeColor("success"); -} - -.failureIcon { - color: colors.getThemeColor("error"); -} diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/ResponseElement/ResponseElement.tsx b/ethernet-view/src/components/BootloaderContainer/Bootloader/ResponseElement/ResponseElement.tsx deleted file mode 100644 index f9bfcfc2e..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/ResponseElement/ResponseElement.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import styles from "./ResponseElement.module.scss"; -import { HiCheckCircle } from "react-icons/hi"; -import { MdCancel } from "react-icons/md"; - -type Props = { - success: boolean; -}; - -export const ResponseElement = ({ success }: Props) => { - return ( -
- {success && ( - <> - - Success! - - )} - {!success && ( - <> - - Error! - - )} -
- ); -}; diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/SendElement/SendElement.module.scss b/ethernet-view/src/components/BootloaderContainer/Bootloader/SendElement/SendElement.module.scss deleted file mode 100644 index e3365be57..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/SendElement/SendElement.module.scss +++ /dev/null @@ -1,46 +0,0 @@ -@use "src/styles/colors" as colors; - -.sendElementWrapper { - flex: 1 1 0; - align-self: center; - height: fit-content; - display: flex; - flex-direction: row; - align-items: center; - gap: 1rem; - padding: 1rem 0.5rem; - border-radius: 1rem; - background-color: colors.getThemeColor("surface-variant"); -} - -.fileIcon { - font-size: 0.9rem; - color: colors.getThemeColor("secondary"); -} - -.fileData { - flex: 1 1 0; - display: flex; - flex-direction: column; - gap: 0.5rem; - text-overflow: ellipsis; - font-size: 1rem; - color: colors.getThemeColor("text-primary"); -} - -.name { - flex: 1 1 0; - font-weight: bold; - text-overflow: ellipsis; -} - -.removeBtn { - cursor: pointer; - width: 25px; - height: 25px; - color: colors.getThemeColor("text-secondary"); - - &:hover { - color: colors.getThemeColor("error"); - } -} diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/SendElement/SendElement.tsx b/ethernet-view/src/components/BootloaderContainer/Bootloader/SendElement/SendElement.tsx deleted file mode 100644 index 400e87a0f..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/SendElement/SendElement.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import styles from "./SendElement.module.scss"; -import { ReactComponent as FileIcon } from "assets/svg/binary-file.svg"; -import { ReactComponent as Cross } from "assets/svg/cross.svg"; - -type Props = { - file: { name: string; size: number }; - onRemove: () => void; -}; - -export const SendElement = ({ file, onRemove }: Props) => { - return ( -
- -
-
{file.name}
-
{file.size} B
-
- onRemove()} - /> -
- ); -}; diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/getFile.ts b/ethernet-view/src/components/BootloaderContainer/Bootloader/getFile.ts deleted file mode 100644 index 158884fbd..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/getFile.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { DragEvent } from "react"; - -function isCorrectFormat(fileName: string, fileFormat: string): boolean { - return fileName.endsWith(`.${fileFormat}`); -} - -export function getFile( - ev: DragEvent, - format: string -): File | null { - ev.preventDefault(); - - if (ev.dataTransfer.items) { - return handleFileWithDataTransferItems(ev, format); - } else { - return handleFileWidthDataTransferFiles(ev); - } -} - -function handleFileWithDataTransferItems( - ev: DragEvent, - format: string -): File | null { - if (ev.dataTransfer.items.length > 1 || ev.dataTransfer.items.length < 1) { - console.error("Expected one file"); - return null; - } else if (ev.dataTransfer.items[0].kind !== "file") { - console.error("Dropped item is not file"); - return null; - } else if ( - !isCorrectFormat( - [...ev.dataTransfer.items][0].getAsFile()!.name, - format - ) - ) { - const fileName = [...ev.dataTransfer.items][0].getAsFile()!.name; - const extension = fileName.substring(fileName.lastIndexOf(".")); - - console.error( - `Incorrect file format: expected "${format}" got "${extension}"` - ); - return null; - } else { - return [...ev.dataTransfer.items][0].getAsFile()!; - } -} - -function handleFileWidthDataTransferFiles( - ev: DragEvent -): File | null { - if (ev.dataTransfer.files.length > 1 || ev.dataTransfer.files.length < 1) { - console.error("Expected one file"); - return null; - } else { - return [...ev.dataTransfer.files][0]; - } -} diff --git a/ethernet-view/src/components/BootloaderContainer/Bootloader/useBootloaderState.ts b/ethernet-view/src/components/BootloaderContainer/Bootloader/useBootloaderState.ts deleted file mode 100644 index baaabd794..000000000 --- a/ethernet-view/src/components/BootloaderContainer/Bootloader/useBootloaderState.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { BootloaderViewState } from "./BootloaderViewState"; -import { useState } from "react"; -import { useBootloader } from "services/useBootloader"; - -export function useBootloaderState() { - const [state, setState] = useState({ kind: "empty" }); - - const { uploader, downloader } = useBootloader( - onSuccess, - onFailure, - onSuccess, - onFailure, - onProgress - ); - - function upload(board: string, file: File) { - file.arrayBuffer().then((data) => { - uploader( - board, - window.btoa(Array.from(new Uint8Array(data), (x) => String.fromCodePoint(x)).join("")) - ); - }); - } - - function download(board: string) { - downloader(board); - setState({ kind: "awaiting", progress: 0 }); - } - - function onSuccess() { - setState({ kind: "success" }); - setTimeout(() => { - setState(() => { - return { kind: "empty" }; - }); - }, 1000); - } - - function onFailure() { - setState({ kind: "failure" }); - setTimeout(() => { - setState(() => { - return { kind: "empty" }; - }); - }, 1000); - } - - function onProgress(progress: number) { - setState({ kind: "awaiting", progress: progress }); - } - - function removeFile() { - setState({ kind: "empty" }); - } - - function setFile(file: File) { - setState({ kind: "send", file: file }); - } - - return [state, upload, download, setFile, removeFile] as const; -} diff --git a/ethernet-view/src/components/BootloaderContainer/BootloaderContainer.tsx b/ethernet-view/src/components/BootloaderContainer/BootloaderContainer.tsx deleted file mode 100644 index 7b9bbe49f..000000000 --- a/ethernet-view/src/components/BootloaderContainer/BootloaderContainer.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Bootloader } from "./Bootloader/Bootloader"; -import { useEffect, useState } from "react"; -import { config, useFetchBack } from "common"; - -export const BootloaderContainer = () => { - const [boards, setBoards] = useState(); - const uploadableBoardsPromise = useFetchBack( - import.meta.env.PROD, - config.paths.uploadableBoards - ); - useEffect(() => { - uploadableBoardsPromise.then((value: string[]) => { - setBoards(value); - }); - }, []); - - if (boards) { - return ; - } else { - return <>Fetching boards...; - } -}; diff --git a/ethernet-view/src/components/Caret/Caret.module.scss b/ethernet-view/src/components/Caret/Caret.module.scss deleted file mode 100644 index e856b3c18..000000000 --- a/ethernet-view/src/components/Caret/Caret.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -.caretWrapper { - width: min-content; - height: min-content; - display: flex; - justify-content: center; - align-items: center; - color: inherit; - cursor: pointer; -} diff --git a/ethernet-view/src/components/Caret/Caret.tsx b/ethernet-view/src/components/Caret/Caret.tsx deleted file mode 100644 index 47d9275b8..000000000 --- a/ethernet-view/src/components/Caret/Caret.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import styles from "components/Caret/Caret.module.scss"; -import { BsFillCaretRightFill } from "react-icons/bs"; -type Props = { - isOpen: boolean; - onClick?: () => void; - className?: string; -}; - -export const Caret = ({ isOpen, onClick, className = "" }: Props) => { - return ( -
- {} -
- ); -}; diff --git a/ethernet-view/src/components/ChartMenu/ChartElement/ChartCanvas/ChartCanvas.tsx b/ethernet-view/src/components/ChartMenu/ChartElement/ChartCanvas/ChartCanvas.tsx deleted file mode 100644 index 323cb324e..000000000 --- a/ethernet-view/src/components/ChartMenu/ChartElement/ChartCanvas/ChartCanvas.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { - MeasurementId, - NumericMeasurementInfo, - UpdateFunctionNumeric, - useGlobalTicker, -} from "common"; -import { - ColorType, - IChartApi, - ISeriesApi, - UTCTimestamp, - createChart, -} from "lightweight-charts"; -import { useEffect, useRef } from "react"; - -type DataSerieAndUpdater = Map< - MeasurementId, - [ISeriesApi<"Line">, UpdateFunctionNumeric] ->; - -interface Props { - measurementsInChart: NumericMeasurementInfo[]; -} - -const CHART_HEIGHT = 300; - -export const ChartCanvas = ({ measurementsInChart }: Props) => { - const chart = useRef(null); - const chartContainerRef = useRef(null); - const chartDataSeries = useRef(new Map()); - - // Helper function to get theme-aware chart options - const getThemeOptions = () => { - const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; - return { - layout: { - background: { - type: ColorType.Solid, - color: isDark ? 'black' : 'white' - }, - textColor: isDark ? 'white' : 'black', - }, - grid: { - vertLines: { - color: isDark ? '#1f1f1f' : '#f0f0f0', - }, - horzLines: { - color: isDark ? '#1f1f1f' : '#f0f0f0', - }, - }, - }; - }; - - useEffect(() => { - const handleResize = () => { - if (chartContainerRef.current && chart.current) { - chart.current.applyOptions({ - width: chartContainerRef.current.clientWidth, - }); - } - }; - - const resizeObserver = new ResizeObserver(handleResize); - let themeObserver: MutationObserver | null = null; - - if (chartContainerRef.current) { - resizeObserver.observe(chartContainerRef.current); - - chart.current = createChart(chartContainerRef.current, { - ...getThemeOptions(), - width: chartContainerRef.current.clientWidth, - height: CHART_HEIGHT, - timeScale: { - timeVisible: true, - secondsVisible: true, - rightOffset: 12, - barSpacing: 0.1, - tickMarkFormatter: (time: UTCTimestamp) => { - const date = new Date(time * 1000); - return date.toLocaleTimeString() + "." + date.getMilliseconds(); - }, - }, - localization: { - timeFormatter: (time: UTCTimestamp) => { - const date = new Date(time * 1000); - return date.toLocaleTimeString() + "." + date.getMilliseconds(); - }, - }, - }); - - // Set up MutationObserver to watch for theme changes - themeObserver = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') { - chart.current?.applyOptions(getThemeOptions()); - } - }); - }); - - themeObserver.observe(document.documentElement, { - attributes: true, - attributeFilter: ['data-theme'] - }); - } - - return () => { - resizeObserver.disconnect(); - themeObserver?.disconnect(); - chart.current?.remove(); - }; - }, []); - - useEffect(() => { - chartDataSeries.current.clear(); - measurementsInChart.forEach((measurement) => { - if (chart.current) - chartDataSeries.current.set(measurement.id, [ - chart.current.addLineSeries({ - color: measurement.color, - priceFormat: { - type: "price", - precision: 3, - minMove: 0.001, - }, - }), - measurement.getUpdate, - ]); - }); - }); - - useGlobalTicker(() => { - const now = (Date.now() / 1000) as UTCTimestamp; - chartDataSeries.current?.forEach((serieAndUpdater) => { - const [DataSerie, Updater] = serieAndUpdater; - DataSerie.update({ time: now, value: Updater() }); - }); - }); - - return
; -}; diff --git a/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.module.scss b/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.module.scss deleted file mode 100644 index 4b556f5af..000000000 --- a/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.chartWrapper { - flex-shrink: 0; - - border: 1px solid colors.getThemeColor("border"); - border-radius: styles.$normal-border-radius; - overflow: hidden; - background-color: colors.getThemeColor("surface"); -} - -.chart { - padding: 0.8rem; -} diff --git a/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.tsx b/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.tsx deleted file mode 100644 index 39c2e3db7..000000000 --- a/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import styles from "./ChartElement.module.scss"; -import { AiOutlineCloseCircle } from 'react-icons/ai' -import { MeasurementId, NumericMeasurementInfo, useMeasurementsStore } from 'common'; -import { ChartCanvas } from './ChartCanvas/ChartCanvas'; -import { ChartLegend } from './ChartLegend/ChartLegend'; -import { memo, useCallback, useState } from "react"; -import { ChartId } from "../ChartMenu"; - -type Props = { - chartId: ChartId; - initialMeasurementId: MeasurementId; - removeChart: (chartId: ChartId) => void; -}; - -// React component that keeps the chart render and measurements represented on it. -export const ChartElement = memo(({ chartId, initialMeasurementId, removeChart }: Props) => { - - const getNumericMeasurementInfo = useMeasurementsStore(state => state.getNumericMeasurementInfo); - const initialMeasurement = getNumericMeasurementInfo(initialMeasurementId); - - const [measurementsInChart, setMeasurementsInChart] = useState([initialMeasurement]); - - const addMeasurementToChart = (measurement: NumericMeasurementInfo) => { - if(!measurementsInChart.some(measurementInChart => measurementInChart.id === measurement.id)) { - setMeasurementsInChart([...measurementsInChart, measurement]); - } - } - - const removeMeasurementFromChart = useCallback((measurementId: MeasurementId) => { - setMeasurementsInChart(prevMeasurements => prevMeasurements.filter(measurement => measurement.id !== measurementId)); - }, []); - - const handleDrop = (ev: React.DragEvent) => { - ev.stopPropagation(); - const id = ev.dataTransfer.getData("id"); - const measurementInfo = getNumericMeasurementInfo(id); - addMeasurementToChart(measurementInfo); - }; - - return ( -
ev.preventDefault()} - onDragOver={(ev) => ev.preventDefault()} - > -
- removeChart(chartId)} - /> - - -
-
- ); -}); diff --git a/ethernet-view/src/components/ChartMenu/ChartElement/ChartLegend/ChartLegend.module.scss b/ethernet-view/src/components/ChartMenu/ChartElement/ChartLegend/ChartLegend.module.scss deleted file mode 100644 index a1bcc5880..000000000 --- a/ethernet-view/src/components/ChartMenu/ChartElement/ChartLegend/ChartLegend.module.scss +++ /dev/null @@ -1,49 +0,0 @@ -@use "src/styles/colors" as colors; - -.chartLegend { - display: flex; - gap: 1rem; - flex-wrap: wrap; - align-items: flex-start; - justify-content: flex-start; - padding: 0.8rem; -} - -:global([data-theme="dark"]) .chartLegend { - background-color: colors.getThemeColor("surface-variant"); - border-radius: 0.5rem; -} - -.chartLegendItem { - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - transition: all 0.2s ease; - - &:hover { - opacity: 0.8; - } -} - -:global([data-theme="dark"]) .chartLegendItem { - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; - background-color: colors.getThemeColor("surface"); - color: colors.getThemeColor("text-primary"); - - &:hover { - background-color: colors.getThemeColor("surface-hover"); - transform: translateY(-1px); - box-shadow: colors.getThemeColor("shadow-sm"); - } -} - -.chartLegendItemColor { - width: 1rem; - height: 1rem; -} - -:global([data-theme="dark"]) .chartLegendItemColor { - border-radius: 0.125rem; -} \ No newline at end of file diff --git a/ethernet-view/src/components/ChartMenu/ChartElement/ChartLegend/ChartLegend.tsx b/ethernet-view/src/components/ChartMenu/ChartElement/ChartLegend/ChartLegend.tsx deleted file mode 100644 index 3b2f5f7af..000000000 --- a/ethernet-view/src/components/ChartMenu/ChartElement/ChartLegend/ChartLegend.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import styles from "./ChartLegend.module.scss"; -import { useEffect, useRef } from "react"; -import { MeasurementId, NumericMeasurementInfo } from "common"; -import { ChartId } from "components/ChartMenu/ChartMenu"; - - -interface Props { - chartId: ChartId; - measurementsInChart: NumericMeasurementInfo[]; - removeMeasurementFromChart: (measurementId: MeasurementId) => void; - removeChart: (chartId: ChartId) => void; -} - -export const ChartLegend = ({ chartId, measurementsInChart, removeMeasurementFromChart, removeChart }: Props) => { - - const legendRef = useRef(null); - - const onRemoveMeasurement = (measurementId: MeasurementId) => { - removeMeasurementFromChart(measurementId); - }; - - useEffect(() => { - if(measurementsInChart.length == 0) removeChart(chartId); - }, [measurementsInChart.length]) - - useEffect(() => { - if (legendRef.current) { - while (legendRef.current.firstChild) { - legendRef.current.removeChild(legendRef.current.firstChild); - } - measurementsInChart.forEach((measurement) => { - const newChartLegendItem = createChartLegendItem(measurement); - newChartLegendItem.onclick = (_) => onRemoveMeasurement(measurement.id); - legendRef.current?.appendChild(newChartLegendItem); - }); - } - }); - - return
; -}; - -function createChartLegendItem(measurement: NumericMeasurementInfo) { - const legendItem = document.createElement("div"); - legendItem.setAttribute("data-id", measurement.id); - legendItem.className = styles.chartLegendItem; - const seriesColor = document.createElement("div"); - seriesColor.className = styles.chartLegendItemColor; - seriesColor.style.backgroundColor = measurement.color; - const seriesName = document.createElement("p"); - seriesName.innerText = measurement.name; - legendItem.appendChild(seriesColor); - legendItem.appendChild(seriesName); - return legendItem; -} diff --git a/ethernet-view/src/components/ChartMenu/ChartMenu.module.scss b/ethernet-view/src/components/ChartMenu/ChartMenu.module.scss deleted file mode 100644 index edfb04f94..000000000 --- a/ethernet-view/src/components/ChartMenu/ChartMenu.module.scss +++ /dev/null @@ -1,72 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.chartMenuWrapper { - width: 100%; - height: 100%; - display: grid; - grid-template-columns: minmax(12rem, auto) 1fr; - transition: grid-template-columns 0.3s ease; - - border-radius: styles.$normal-border-radius; - overflow: hidden; - - border: 1px solid colors.getThemeColor("primary"); - border-left: 0; -} - -:global([data-theme="dark"]) .chartMenuWrapper { - border-color: colors.getColor("neutral", 20); -} - -.chartMenuWrapper.sidebarHidden { - grid-template-columns: 1fr; -} - -.chartContentWrapper { - position: relative; - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - min-height: 0; - overflow: hidden; -} - -.toggleButton { - position: absolute; - top: 0.5rem; - left: 0.5rem; - z-index: 1000; - background-color: colors.getThemeColor("surface"); - border: 1px solid colors.getThemeColor("border"); - color: colors.getThemeColor("text-primary"); - border-radius: 4px; - padding: 0.25rem 0.5rem; - cursor: pointer; - font-size: 0.8rem; - transition: background-color 0.2s ease; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - - &:hover { - background-color: colors.getThemeColor("primary-surface"); - } -} - -.noValues { - text-align: center; - color: colors.getThemeColor("text-tertiary"); -} - -.chartListWrapper { - display: flex; - flex-direction: column; - gap: 1rem; - - width: 100%; - height: 100%; - background-color: colors.getThemeColor("surface"); - padding: 1rem; - overflow-y: auto; - overflow-x: hidden; -} diff --git a/ethernet-view/src/components/ChartMenu/ChartMenu.tsx b/ethernet-view/src/components/ChartMenu/ChartMenu.tsx deleted file mode 100644 index 692a1ddaa..000000000 --- a/ethernet-view/src/components/ChartMenu/ChartMenu.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import styles from "components/ChartMenu/ChartMenu.module.scss"; -import { DragEvent, memo, useCallback, useState, useEffect } from "react"; -import Sidebar from "components/ChartMenu/Sidebar/Sidebar"; -import { Section } from "./Sidebar/Section/Section"; -import { MeasurementId, useMeasurementsStore } from "common"; -import { nanoid } from "nanoid"; -import { ChartElement } from "./ChartElement/ChartElement"; - -export type ChartId = string; - -export type ChartInfo = { - chartId: ChartId; - initialMeasurementId: MeasurementId; -}; - -type Props = { - sidebarSections: Section[]; -}; - -export const ChartMenu = memo(({ sidebarSections }: Props) => { - - const getNumericMeasurementInfo = useMeasurementsStore((state) => state.getNumericMeasurementInfo); - - const [charts, setCharts] = useState([]); - const [sidebarVisible, setSidebarVisible] = useState(true); - - const addChart = ((chartId: ChartId, initialMeasurementId: MeasurementId) => { - setCharts([...charts, { chartId, initialMeasurementId }]); - }); - - const removeChart = useCallback((chartId: ChartId) => { - setCharts(prevCharts => prevCharts.filter(chart => chart.chartId !== chartId)); - }, []); - - const handleDrop = (ev: DragEvent) => { - ev.preventDefault(); - const id = ev.dataTransfer.getData("id"); - const initialMeasurementId = getNumericMeasurementInfo(id).id; - addChart(nanoid(), initialMeasurementId); - }; - - const toggleSidebar = () => { - setSidebarVisible(!sidebarVisible); - }; - - // Trigger resize event when sidebar visibility changes to help charts adjust - useEffect(() => { - const timeoutId = setTimeout(() => { - window.dispatchEvent(new Event('resize')); - }, 350); // Wait for CSS transition to complete - - return () => clearTimeout(timeoutId); - }, [sidebarVisible]); - - if (sidebarSections.length == 0) { - return ( -
- No available values to chart. This might happen if none of the - measurements are numeric (only numeric measurements are - chartable). -
- ); - } else { - return ( -
- {sidebarVisible && } -
- -
ev.preventDefault()} - onDragOver={(ev) => ev.preventDefault()} - > - {charts.map((chart) => ( - - ))} -
-
-
- ); - } -}); - diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Section.module.scss b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Section.module.scss deleted file mode 100644 index d953a5c81..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Section.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.section { - display: flex; - flex-direction: column; - - cursor: pointer; -} - -.header { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.4rem; - padding: 0.5rem; - background-color: colors.getColor("primary", 80); - color: colors.getColor("primary", 50); - font-weight: bold; - border-bottom: 1px solid colors.getColor("primary", 70); - transition: all 0.2s ease; - - &:hover { - background-color: colors.getColor("primary", 75); - } -} - -:global([data-theme="dark"]) .header { - background-color: colors.getThemeColor("surface-variant"); - color: colors.getThemeColor("text-primary"); - border-bottom: 1px solid colors.getThemeColor("border"); - - &:hover { - background-color: colors.getThemeColor("surface-hover"); - } -} diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Section.tsx b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Section.tsx deleted file mode 100644 index 1f700c9dc..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Section.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import styles from "./Section.module.scss"; -import { Caret } from "components/Caret/Caret"; -import { useState } from "react"; -import { SubsectionsView } from "./Subsection/Subsections"; -import { Subsection } from "./Subsection/Subsection/Subsection"; - -export type Section = { - name: string; - subsections: Subsection[]; -}; - -type Props = { - section: Section; -}; - -export const Section = ({ section }: Props) => { - const [isOpen, setIsOpen] = useState(false); - return ( -
-
{ - setIsOpen((prev) => !prev); - }} - > - - {section.name} -
- {isOpen && ( - - )} -
- ); -}; diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Item/ItemView.module.scss b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Item/ItemView.module.scss deleted file mode 100644 index 24df18c4e..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Item/ItemView.module.scss +++ /dev/null @@ -1,10 +0,0 @@ -@use "src/styles/styles"; - -.item { - display: grid; - align-items: center; - grid-template-columns: auto 1fr; - column-gap: 0.3rem; - font-weight: 500; - cursor: pointer; -} diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Item/ItemView.tsx b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Item/ItemView.tsx deleted file mode 100644 index 3953bb790..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Item/ItemView.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import styles from "./ItemView.module.scss"; -import { DragEvent } from "react"; -import { FiBox } from "react-icons/fi"; - -export type Item = { - id: string; - name: string; -}; - -type Props = { - item: Item; -}; - -export const ItemView = ({ item }: Props) => { - function handleDragStart(ev: DragEvent) { - ev.dataTransfer.setData("id", item.id); - } - - return ( -
- {item.name} -
- ); -}; diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Items.module.scss b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Items.module.scss deleted file mode 100644 index 0d8711e99..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Items.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.items { - display: flex; - flex-direction: column; - gap: 0.5rem; - padding-left: 1rem; -} diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Items.tsx b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Items.tsx deleted file mode 100644 index 0a6ba7a84..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Items.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ItemView, Item } from "./Item/ItemView"; -import styles from "./Items.module.scss"; - -type Props = { - items: Item[]; -}; - -export const Items = ({ items }: Props) => { - return ( -
- {items.map((item) => { - return ( - - ); - })} -
- ); -}; diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Subsection.module.scss b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Subsection.module.scss deleted file mode 100644 index 60c8be652..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Subsection.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.subsection { - display: flex; - flex-direction: column; -} - -.name { - margin-bottom: 0.5rem; - font-style: italic; - color: colors.getThemeColor("text-tertiary"); -} diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Subsection.tsx b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Subsection.tsx deleted file mode 100644 index dba1b75da..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsection/Subsection.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import styles from "./Subsection.module.scss"; -import { Items } from "./Items/Items"; -import { Item } from "./Items/Item/ItemView"; - -export type Subsection = { - name: string; - items: Item[]; -}; - -type Props = { - subsection: Subsection; -}; - -export const SubsectionView = ({ subsection }: Props) => { - return ( -
-
{subsection.name}
- -
- ); -}; diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsections.module.scss b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsections.module.scss deleted file mode 100644 index a17d0de74..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsections.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.subsections { - display: flex; - flex-direction: column; - padding-top: 0.5rem; - padding-left: 1rem; - padding-right: 1.5rem; - overflow: hidden; - - > * { - margin-bottom: 0.8rem; - } -} diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsections.tsx b/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsections.tsx deleted file mode 100644 index 6bbcd7b74..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Section/Subsection/Subsections.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import styles from "./Subsections.module.scss"; -import { Subsection, SubsectionView } from "./Subsection/Subsection"; - -type Props = { - subsections: Subsection[]; - isVisible: boolean; -}; - -export const SubsectionsView = ({ subsections, isVisible }: Props) => { - return ( -
- {subsections.map((subsection) => { - return ( - - ); - })} -
- ); -}; diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Sidebar.module.scss b/ethernet-view/src/components/ChartMenu/Sidebar/Sidebar.module.scss deleted file mode 100644 index c12d35c6c..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Sidebar.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.sidebar { - width: auto; - height: 100%; - - display: flex; - flex-direction: column; - overflow: auto; - - background-color: colors.getThemeColor("primary-surface"); -} - -:global([data-theme="dark"]) .sidebar { - background-color: colors.getColor("neutral", 20); -} diff --git a/ethernet-view/src/components/ChartMenu/Sidebar/Sidebar.tsx b/ethernet-view/src/components/ChartMenu/Sidebar/Sidebar.tsx deleted file mode 100644 index dccc98d3d..000000000 --- a/ethernet-view/src/components/ChartMenu/Sidebar/Sidebar.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import styles from "./Sidebar.module.scss"; -import { Section } from "./Section/Section"; -import { memo } from "react"; -import { Item, ItemView } from "./Section/Subsection/Subsection/Items/Item/ItemView"; - -type Props = { - sections?: Section[]; - items?: Item[]; -}; - -const Sidebar = ({ sections, items }: Props) => { - - return sections && sections.length > 0 ? - ( -
- {sections.map((section) => { - return ( -
- ); - })} -
- ) : (items && items.length > 0 ? ( -
- {items.map((item) => { - return ( - //
- //
{item.name}
- //
- - ); - })} -
- ) : null); - -}; - -export default memo(Sidebar); diff --git a/ethernet-view/src/components/ChartMenu/sidebar.ts b/ethernet-view/src/components/ChartMenu/sidebar.ts deleted file mode 100644 index 6a07538e2..000000000 --- a/ethernet-view/src/components/ChartMenu/sidebar.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Board, PodData, isNumericMeasurement } from "common"; -import { Packet } from "common"; -import { Section } from "./Sidebar/Section/Section"; -import { Subsection } from "./Sidebar/Section/Subsection/Subsection/Subsection"; -import { Item } from "./Sidebar/Section/Subsection/Subsection/Items/Item/ItemView"; - -export function createSidebarSections(podData: PodData): Section[] { - const sections: Section[] = []; - - podData.boards.forEach((board) => { - const subsections = getNumericPacketSubsections(board); - if (subsections.length > 0) { - sections.push({ name: board.name, subsections }); - } - }); - - return sections; -} - -function getNumericPacketSubsections(board: Board): Subsection[] { - const packets: Subsection[] = []; - - board.packets.forEach((packet) => { - const items = getNumericMeasurementItems(packet); - if (items.length > 0) { - packets.push({ name: packet.name, items }); - } - }); - - return packets; -} - -function getNumericMeasurementItems(packet: Packet): Item[] { - const items: Item[] = []; - packet.measurements.forEach((measurement) => { - if (isNumericMeasurement(measurement)) { - items.push({ - id: measurement.id, - name: measurement.name, - }); - } - }); - return items; -} diff --git a/ethernet-view/src/components/ConfigPopup/ConfigPopup.module.scss b/ethernet-view/src/components/ConfigPopup/ConfigPopup.module.scss deleted file mode 100644 index 4fd18ec37..000000000 --- a/ethernet-view/src/components/ConfigPopup/ConfigPopup.module.scss +++ /dev/null @@ -1,243 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.loadingOverlay { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(255, 255, 255, 0.8); - border-radius: styles.$normal-border-radius; - display: flex; - align-items: center; - justify-content: center; - z-index: 20; - backdrop-filter: blur(2px); - - &::after { - content: ""; - width: 40px; - height: 40px; - border: 4px solid colors.getThemeColor("border"); - border-top-color: colors.getThemeColor("primary"); - border-radius: 50%; - animation: spin 0.8s linear infinite; - } -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -.backdrop { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} - -.popup { - position: relative; - background-color: colors.getThemeColor("surface"); - border-radius: styles.$normal-border-radius; - width: 90%; - max-width: 1000px; - max-height: 90vh; - display: flex; - flex-direction: column; - @include styles.shadow; - box-shadow: colors.getThemeColor("shadow-lg"); -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - padding: styles.$normal-padding; - border-bottom: styles.$normal-border-width solid - colors.getThemeColor("border"); -} - -.headerActions { - display: flex; - align-items: center; - gap: 1rem; -} - -.importButton { - flex: 0 0 auto; - min-width: 100px; -} - -.title { - @include styles.title-text; - font-size: 1.5rem; - margin: 0; -} - -.closeButton { - background: none; - border: none; - font-size: 2rem; - line-height: 1; - color: colors.getThemeColor("text-secondary"); - cursor: pointer; - padding: 0; - width: 2rem; - height: 2rem; - display: flex; - align-items: center; - justify-content: center; - border-radius: styles.$normal-border-radius; - transition: styles.$background-color-transition; - - &:hover { - background-color: colors.getThemeColor("surface-hover"); - color: colors.getThemeColor("text-primary"); - } -} - -.content { - padding: styles.$normal-padding; - overflow-y: auto; - flex: 1; -} - -.field { - margin-bottom: 1.5rem; - - &:last-child { - margin-bottom: 0; - } -} - -.label { - @include styles.normal-text; - display: block; - margin-bottom: 0.5rem; - color: colors.getThemeColor("text-primary"); -} - -.input { - width: 100%; - padding: 0.75rem; - border: styles.$normal-border-width solid colors.getThemeColor("border"); - border-radius: styles.$normal-border-radius; - background-color: colors.getThemeColor("input-bg"); - color: colors.getThemeColor("text-primary"); - font-family: styles.$sans-font; - font-size: styles.$normal-font-size; - transition: styles.$background-color-transition, - border-color styles.$normal-transition-time linear; - - &:focus { - outline: none; - border-color: colors.getThemeColor("primary"); - background-color: colors.getThemeColor("surface"); - } - - &:hover { - border-color: colors.getThemeColor("border-hover"); - } -} - -.footer { - display: flex; - gap: 1rem; - padding: styles.$normal-padding; - border-top: styles.$normal-border-width solid colors.getThemeColor("border"); - justify-content: flex-end; -} - -.cancelButton { - flex: 0 0 auto; - min-width: 100px; -} - -.saveButton { - flex: 0 0 auto; - min-width: 100px; -} - -.section { - margin-bottom: 2rem; - padding-bottom: 1.5rem; - border-bottom: styles.$normal-border-width solid - colors.getThemeColor("border"); - - &:last-of-type { - border-bottom: none; - margin-bottom: 0; - } -} - -.sectionTitle { - @include styles.normal-text; - font-size: 1.1rem; - font-weight: styles.$bold-font-weight; - color: colors.getThemeColor("text-primary"); - margin: 0 0 1rem 0; -} - -.checkboxGroup { - display: flex; - flex-wrap: wrap; - gap: 1rem; -} - -.checkboxLabel { - @include styles.normal-text; - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - color: colors.getThemeColor("text-primary"); -} - -.folderPickerContainer { - display: flex; - gap: 0.5rem; - align-items: stretch; -} - -.folderInput { - flex: 1; -} - -.browseButton { - flex: 0 0 auto; - min-width: 80px; -} - -.select { - width: 100%; - padding: 0.75rem; - border: styles.$normal-border-width solid colors.getThemeColor("border"); - border-radius: styles.$normal-border-radius; - background-color: colors.getThemeColor("input-bg"); - color: colors.getThemeColor("text-primary"); - font-family: styles.$sans-font; - font-size: styles.$normal-font-size; - cursor: pointer; - transition: styles.$background-color-transition, - border-color styles.$normal-transition-time linear; - - &:focus { - outline: none; - border-color: colors.getThemeColor("primary"); - background-color: colors.getThemeColor("surface"); - } - - &:hover { - border-color: colors.getThemeColor("border-hover"); - } -} diff --git a/ethernet-view/src/components/ConfigPopup/ConfigPopup.tsx b/ethernet-view/src/components/ConfigPopup/ConfigPopup.tsx deleted file mode 100644 index 7c57b3e1c..000000000 --- a/ethernet-view/src/components/ConfigPopup/ConfigPopup.tsx +++ /dev/null @@ -1,651 +0,0 @@ -import { useEffect, useState } from "react"; -import styles from "./ConfigPopup.module.scss"; -import { Button } from "../FormComponents/Button/Button"; -import { CheckBox } from "../FormComponents/CheckBox/CheckBox"; -import { TextInput } from "../FormComponents/TextInput/TextInput"; -import { NumericInput } from "../FormComponents/NumericInput/NumericInput"; -import type { ConfigData } from "../../types/ConfigData"; - -type Props = { - isOpen: boolean; - onClose: () => void; -}; - -const AVAILABLE_BOARDS = [ - "BCU", - "BMSL", - "HVSCU", - "HVSCU-Cabinet", - "LCU", - "PCU", - "VCU", - "BLCU", -]; - -const TIME_UNITS = ["ns", "us", "ms", "s"]; - -const DEFAULT_CONFIG: ConfigData = { - vehicle: { - boards: [ - "BCU", - "BMSL", - "HVSCU", - "HVSCU-Cabinet", - "LCU", - "PCU", - "VCU", - "BLCU", - ], - }, - adj: { - branch: "main", - }, - network: { - manual: false, - }, - transport: { - propagate_fault: false, - }, - tcp: { - backoff_min_ms: 100, - backoff_max_ms: 5000, - backoff_multiplier: 1.5, - max_retries: 0, - connection_timeout_ms: 1000, - keep_alive_ms: 1000, - }, - blcu: { - ip: "127.0.0.1", - download_order_id: 0, - upload_order_id: 0, - }, - tftp: { - block_size: 131072, - retries: 3, - timeout_ms: 5000, - backoff_factor: 2, - enable_progress: true, - }, - logging: { - time_unit: "us", - logging_path: "", - }, -}; - -export const ConfigPopup = ({ isOpen, onClose }: Props) => { - const [config, setConfig] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [isLoadingConfig, setIsLoadingConfig] = useState(false); - const [isImporting, setIsImporting] = useState(false); - - // Combined disabled state for all interactions - const isDisabled = isLoading || isLoadingConfig || isImporting; - - const handleImport = async () => { - if (!window.electronAPI) { - console.warn("Electron API not available"); - return; - } - - setIsImporting(true); - try { - await window.electronAPI.importConfig(); - // Reload config after import - await loadConfig(); - } catch (error) { - console.error("Error importing config:", error); - // You might want to show an error message to the user here - } finally { - setIsImporting(false); - } - }; - - const loadConfig = async () => { - if (window.electronAPI) { - setIsLoadingConfig(true); - try { - const loadedConfig = await window.electronAPI.getConfig(); - setConfig(loadedConfig); - } catch (error) { - console.error("Error loading config:", error); - } finally { - setIsLoadingConfig(false); - } - } else { - console.log( - "Electron API is not available. Using default config constant." - ); - // No Electron API - use default config immediately - setConfig(DEFAULT_CONFIG); - } - }; - - const handleSaveConfig = async (config: ConfigData) => { - console.log("Saving config:", config); - - if (window.electronAPI) { - // Call Electron API when available - await window.electronAPI.saveConfig(config); - } else { - // Simulate config save for now - console.log("Electron API is not available. This is a browser test."); - } - }; - - // Helper function to safely update config - const updateConfig = (updater: (prev: ConfigData) => ConfigData) => { - setConfig((prev) => { - if (!prev) return null; - return updater(prev); - }); - }; - - // Load config when popup opens - useEffect(() => { - if (isOpen) { - loadConfig(); - } else { - // Reset config when popup closes - setConfig(null); - } - }, [isOpen]); - - if (!isOpen) return null; - - const handleSave = async () => { - if (!config) return; - - setIsLoading(true); - - try { - await handleSaveConfig(config); - onClose(); - } catch (error) { - console.error("Error saving config:", error); - } finally { - setIsLoading(false); - } - }; - - const handleBackdropClick = (e: React.MouseEvent) => { - if (e.target === e.currentTarget && !isDisabled) { - onClose(); - } - }; - - const toggleBoard = (board: string) => { - if (!config) return; - - updateConfig((prev) => ({ - ...prev, - vehicle: { - boards: prev.vehicle.boards.includes(board) - ? prev.vehicle.boards.filter((b: string) => b !== board) - : [...prev.vehicle.boards, board], - }, - })); - }; - - return ( -
-
- {isLoading &&
} - -
-

Configuration

-
- -
-
- - {isLoadingConfig ? ( -
-
-

Loading configuration...

-
- ) : config || config != null ? ( -
- {/* Vehicle Configuration */} -
-

Vehicle Configuration

-
- -
- {AVAILABLE_BOARDS.map((board) => ( - - ))} -
-
-
- - {/* ADJ Configuration */} -
-

ADJ Configuration

-
- - - updateConfig((prev) => ({ - ...prev, - adj: { ...prev.adj, branch: e.target.value }, - })) - } - placeholder="main" - disabled={isDisabled} - /> -
-
- - {/* Network Configuration */} -
-

Network Configuration

-
- -
-
- - {/* Transport Configuration */} -
-

Transport Configuration

-
- -
-
- - {/* TCP Configuration */} -
-

TCP Configuration

-
- - - updateConfig((prev) => ({ - ...prev, - tcp: { ...prev.tcp, backoff_min_ms: Number(value) || 0 }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - tcp: { ...prev.tcp, backoff_max_ms: Number(value) || 0 }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - tcp: { - ...prev.tcp, - backoff_multiplier: Number(value) || 0, - }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - tcp: { ...prev.tcp, max_retries: Number(value) || 0 }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - tcp: { - ...prev.tcp, - connection_timeout_ms: Number(value) || 0, - }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - tcp: { ...prev.tcp, keep_alive_ms: Number(value) || 0 }, - })) - } - /> -
-
- - {/* BLCU Configuration */} -
-

BLCU Configuration

-
- - - updateConfig((prev) => ({ - ...prev, - blcu: { ...prev.blcu, ip: e.target.value }, - })) - } - placeholder="127.0.0.1" - disabled={isDisabled} - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - blcu: { - ...prev.blcu, - download_order_id: Number(value) || 0, - }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - blcu: { - ...prev.blcu, - upload_order_id: Number(value) || 0, - }, - })) - } - /> -
-
- - {/* TFTP Configuration */} -
-

TFTP Configuration

-
- - - updateConfig((prev) => ({ - ...prev, - tftp: { ...prev.tftp, block_size: Number(value) || 0 }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - tftp: { ...prev.tftp, retries: Number(value) || 0 }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - tftp: { ...prev.tftp, timeout_ms: Number(value) || 0 }, - })) - } - /> -
-
- - - updateConfig((prev) => ({ - ...prev, - tftp: { - ...prev.tftp, - backoff_factor: Number(value) || 0, - }, - })) - } - /> -
-
- -
-
- - {/* Logging Configuration */} -
-

Logger Configuration

-
- - -
-
- -
- - updateConfig((prev) => ({ - ...prev, - logging: { - ...prev.logging, - logging_path: e.target.value, - }, - })) - } - placeholder="Select logging folder..." - disabled={isDisabled} - className={styles.folderInput} - /> -
-
-
-
- ) : ( -

Config not found

- )} - -
-
-
-
- ); -}; diff --git a/ethernet-view/src/components/FormComponents/Button/Button.module.scss b/ethernet-view/src/components/FormComponents/Button/Button.module.scss deleted file mode 100644 index 00d054e9a..000000000 --- a/ethernet-view/src/components/FormComponents/Button/Button.module.scss +++ /dev/null @@ -1,41 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.buttonWrapper { - flex: 1 1 0; - display: flex; - justify-content: center; - align-items: center; - - padding: 0.5rem; - text-align: center; - border-radius: styles.$normal-border-radius; - background-color: colors.getThemeColor("secondary"); - color: colors.getThemeColor("surface"); - user-select: none; - - @include styles.shadow; - - &.enabled { - cursor: pointer; - - &:hover { - background-color: colors.getThemeColor("secondary-hover"); - } - } - - &.disabled { - cursor: auto; - background-color: colors.getThemeColor("border"); - color: colors.getThemeColor("text-disabled"); - } -} - -.label { - overflow: hidden; - text-overflow: ellipsis; -} - -.icon { - width: 75%; -} \ No newline at end of file diff --git a/ethernet-view/src/components/FormComponents/Button/Button.tsx b/ethernet-view/src/components/FormComponents/Button/Button.tsx deleted file mode 100644 index eb5d763f5..000000000 --- a/ethernet-view/src/components/FormComponents/Button/Button.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import styles from "components/FormComponents/Button/Button.module.scss"; -import { animated, useSpring } from "@react-spring/web"; -import { lightenHSL } from "utils/color"; - -type Props = { - label?: string; - icon?: string; - onClick?: (ev: React.MouseEvent) => void; - disabled?: boolean; - color?: string; - className?: string; -}; - -export const Button = ({ - label = undefined, - icon = undefined, - color = "hsl(29, 88%, 57%)", - onClick = () => {}, - disabled = false, - className = "", -}: Props) => { - const [springs, api] = useSpring(() => ({ - from: { backgroundColor: color }, - config: { - mass: 5, - tension: 3000, - friction: 1, - clamp: true, - }, - })); - - return ( - { - if (!disabled) { - onClick(ev); - } - ev.stopPropagation(); - }} - style={!disabled ? { ...springs } : {}} - onMouseDown={() => - api.start({ - to: { backgroundColor: lightenHSL(color, 15) }, - }) - } - onMouseLeave={() => - api.start({ - to: { backgroundColor: color }, - }) - } - onMouseUp={() => - api.start({ - to: { backgroundColor: color }, - }) - } - > - {icon && icon} - {label} - - ); -}; diff --git a/ethernet-view/src/components/FormComponents/CheckBox/CheckBox.module.scss b/ethernet-view/src/components/FormComponents/CheckBox/CheckBox.module.scss deleted file mode 100644 index 54dcfb39e..000000000 --- a/ethernet-view/src/components/FormComponents/CheckBox/CheckBox.module.scss +++ /dev/null @@ -1,19 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -#checkBox { - width: 1rem; - height: 1rem; - border: 1px solid colors.getThemeColor("border"); - background-color: colors.getThemeColor("input-bg"); - - &:checked { - background-color: colors.getThemeColor("primary"); - border-color: colors.getThemeColor("primary"); - } - - &:focus { - outline: none; - box-shadow: 0 0 0 2px colors.getThemeColor("primary-surface"); - } -} diff --git a/ethernet-view/src/components/FormComponents/CheckBox/CheckBox.tsx b/ethernet-view/src/components/FormComponents/CheckBox/CheckBox.tsx deleted file mode 100644 index 6a4904bcf..000000000 --- a/ethernet-view/src/components/FormComponents/CheckBox/CheckBox.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import styles from "components/FormComponents/CheckBox/CheckBox.module.scss"; -import { ChangeEvent, useState } from "react"; - -type Props = { - isRequired: boolean; - onChange: (value: boolean) => void; - disabled?: boolean; - initialValue?: boolean; - color?: string; -}; - -export const CheckBox = ({ - onChange, - disabled = false, - isRequired, - initialValue = false, - color, -}: Props) => { - const [checked, setChecked] = useState(initialValue); - - return ( - ) => { - onChange(Boolean(ev.target.checked)); - setChecked(Boolean(ev.target.checked)); - }} - id={styles.checkBox} - checked={checked} - /> - ); -}; diff --git a/ethernet-view/src/components/FormComponents/Dropdown/Dropdown.module.scss b/ethernet-view/src/components/FormComponents/Dropdown/Dropdown.module.scss deleted file mode 100644 index 6aa7f36e6..000000000 --- a/ethernet-view/src/components/FormComponents/Dropdown/Dropdown.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.select { - width: 100%; - padding: 0.5rem; - background-color: rgba(235, 137, 33, 0.1); - color: colors.getThemeColor("text-primary"); - border: 1px solid colors.getThemeColor("border"); - border-radius: styles.$normal-border-radius; - - &:focus { - outline: none; - border-color: colors.getThemeColor("primary"); - } -} - -:global([data-theme="dark"]) .select { - background-color: rgba(235, 137, 33, 0.05); -} - -option { - background-color: rgba(235, 137, 33, 0.1); - color: colors.getThemeColor("text-primary"); -} - -:global([data-theme="dark"]) option { - background-color: rgba(235, 137, 33, 0.05); -} diff --git a/ethernet-view/src/components/FormComponents/Dropdown/Dropdown.tsx b/ethernet-view/src/components/FormComponents/Dropdown/Dropdown.tsx deleted file mode 100644 index 9fe3af2c7..000000000 --- a/ethernet-view/src/components/FormComponents/Dropdown/Dropdown.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import styles from "components/FormComponents/Dropdown/Dropdown.module.scss"; - -type Props = { - value?: string; - options: string[]; - onChange: (newValue: string) => void; -}; - -export const Dropdown = ({ value, options, onChange }: Props) => { - return ( - - ); -}; diff --git a/ethernet-view/src/components/FormComponents/FileInput/FileInput.tsx b/ethernet-view/src/components/FormComponents/FileInput/FileInput.tsx deleted file mode 100644 index 4a458596b..000000000 --- a/ethernet-view/src/components/FormComponents/FileInput/FileInput.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useId } from "react"; - -type Props = { - label: string; - accept: string; - onFile: (file: File) => void; - className: string; - children?: React.ReactNode; -}; - -export const FileInput = ({ accept, className, label, onFile }: Props) => { - const id = useId(); - - return ( - <> - - { - if (ev.target.files) onFile(ev.target.files[0]); - }} - /> - - ); -}; diff --git a/ethernet-view/src/components/FormComponents/NumericInput/NumericInput.tsx b/ethernet-view/src/components/FormComponents/NumericInput/NumericInput.tsx deleted file mode 100644 index dda792a44..000000000 --- a/ethernet-view/src/components/FormComponents/NumericInput/NumericInput.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { TextInput } from "../TextInput/TextInput"; - -type Props = { - required: boolean; - defaultValue: number | string; - placeholder: string; - disabled: boolean; - isValid: boolean; - onChange: (value: string) => void; -}; - -function getUnion(arr: string[]): string { - return arr.map((value) => `(?:${value})`).join("|"); -} - -const numericInputKeyRegex = (() => { - const arrows = ["ArrowUp", "ArrowLeft", "ArrowRight", "ArrowDown"]; - const controlKeys = ["Delete", "Backspace", "Home", "End"]; // Home es inicio - const chars = "[0-9.+-]"; - - const regexp = chars + "|" + getUnion(arrows) + "|" + getUnion(controlKeys); - - return new RegExp(regexp); -})(); - -export const NumericInput = ({ - onChange, - required, - disabled, - isValid, - placeholder, - defaultValue, -}: Props) => { - return ( - { - if (!numericInputKeyRegex.test(ev.key)) { - ev.preventDefault(); - return false; - } - }} - onChange={(ev) => { - onChange(ev.target.value); - }} - /> - ); -}; diff --git a/ethernet-view/src/components/FormComponents/TextInput/TextInput.module.scss b/ethernet-view/src/components/FormComponents/TextInput/TextInput.module.scss deleted file mode 100644 index 72dda4771..000000000 --- a/ethernet-view/src/components/FormComponents/TextInput/TextInput.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.textInput { - flex: 1 1 0; - min-width: 5rem; - padding: 0.5rem 0.8rem; - border-radius: styles.$normal-border-radius; - font: inherit; - transition: border-color 0.1s linear; - background-color: colors.getThemeColor("input-bg"); - color: colors.getThemeColor("text-primary"); - border: 1px solid colors.getThemeColor("border"); - - &:focus { - outline: none; - border-color: colors.getThemeColor("primary"); - } -} - -.valid { - border-color: colors.getThemeColor("success"); -} - -.invalid { - border-color: colors.getThemeColor("error"); -} - -.disabled { - color: colors.getThemeColor("text-disabled"); - border-color: colors.getThemeColor("border"); - background-color: colors.getThemeColor("surface-variant"); -} diff --git a/ethernet-view/src/components/FormComponents/TextInput/TextInput.tsx b/ethernet-view/src/components/FormComponents/TextInput/TextInput.tsx deleted file mode 100644 index 1f54daece..000000000 --- a/ethernet-view/src/components/FormComponents/TextInput/TextInput.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import styles from "components/FormComponents/TextInput/TextInput.module.scss"; - -type Props = { isValid: boolean } & React.InputHTMLAttributes; - -export const TextInput = ({ isValid, ...props }: Props) => { - return ( - - ); -}; diff --git a/ethernet-view/src/components/Island/Island.module.scss b/ethernet-view/src/components/Island/Island.module.scss deleted file mode 100644 index 30b2eb030..000000000 --- a/ethernet-view/src/components/Island/Island.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.island { - display: flex; - flex-direction: column; - align-items: stretch; - height: 100%; - padding: 1rem; - background-color: colors.getThemeColor("island-bg"); - border-radius: 0.8rem; - box-shadow: colors.getThemeColor("shadow-sm"); -} diff --git a/ethernet-view/src/components/Island/Island.tsx b/ethernet-view/src/components/Island/Island.tsx deleted file mode 100644 index 8eca94177..000000000 --- a/ethernet-view/src/components/Island/Island.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import styles from "./Island.module.scss"; - -type Props = { - style?: React.CSSProperties; - children: React.ReactNode; -}; - -export const Island = ({ children, style }: Props) => { - return ( -
- {children} -
- ); -}; diff --git a/ethernet-view/src/components/Logger/Logger.module.scss b/ethernet-view/src/components/Logger/Logger.module.scss deleted file mode 100644 index a3d104015..000000000 --- a/ethernet-view/src/components/Logger/Logger.module.scss +++ /dev/null @@ -1,36 +0,0 @@ -@use "src/styles/styles"; - -.logger { - height: min-content; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; -} - -.state { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: bold; -} - -.buttons { - flex: 2; - width: 10rem; - display: flex; - flex-direction: row; - align-items: center; - gap: 1rem; - - > * { - min-width: 0; - min-height: 0; - height: 100%; - overflow: hidden; - display: flex; - align-items: center; - cursor: pointer; - } -} diff --git a/ethernet-view/src/components/Logger/Logger.tsx b/ethernet-view/src/components/Logger/Logger.tsx deleted file mode 100644 index a55b4011b..000000000 --- a/ethernet-view/src/components/Logger/Logger.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import styles from "components/Logger/Logger.module.scss"; -import { useLogger } from "./useLogger"; -import { Island } from "components/Island/Island"; -import { Button } from "components/FormComponents/Button/Button"; -import oscilloscope from "assets/svg/oscilloscope.svg"; -import { useConfig } from "common"; - -export const Logger = () => { - - const [state, startLogging, stopLogging] = useLogger(); - const config = useConfig(); - - return ( - -
- Logging: {`${state}`} -
- - - - -
-
-
- ); -}; diff --git a/ethernet-view/src/components/Logger/useLogger.ts b/ethernet-view/src/components/Logger/useLogger.ts deleted file mode 100644 index c4fc57df9..000000000 --- a/ethernet-view/src/components/Logger/useLogger.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useSubscribe, useWsHandler } from "common"; -import { useState } from "react"; -import { useMeasurementsStore } from "common"; - -export function useLogger() { - const [state, setState] = useState(false); - - const handler = useWsHandler(); - - function getLoggedVariableIds() { - return useMeasurementsStore.getState().getLogVariables(); - } - - function startLogging() { - const variables = getLoggedVariableIds(); - handler.post("logger/variables", variables); - handler.post("logger/enable", true); - } - - function stopLogging() { - handler.post("logger/enable", false); - } - - useSubscribe("logger/response", (result) => { - setState(result); - }); - - return [state, startLogging, stopLogging] as const; -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/Counter/Counter.module.scss b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/Counter/Counter.module.scss deleted file mode 100644 index 7ac6fc00d..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/Counter/Counter.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.counter { - width: fit-content; - height: fit-content; - border-radius: 4rem; - padding: 0.25rem 0.35rem; - - font-family: Consolas; - font-size: small; - line-height: 90%; - - color: var(--main-color); // Use main color for better contrast - background-color: var(--light-color); -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/Counter/Counter.tsx b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/Counter/Counter.tsx deleted file mode 100644 index 2994d7436..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/Counter/Counter.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import styles from "./Counter.module.scss"; - -type Props = { - count: number; - className: string; -}; - -export const Counter = ({ count, className }: Props) => { - return
{count}
; -}; diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/InfoMessageView/InfoMessageView.module.scss b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/InfoMessageView/InfoMessageView.module.scss deleted file mode 100644 index 88b98cd43..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/InfoMessageView/InfoMessageView.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.infoMessageView { - display: flex; - flex-direction: column; - gap: 0.5rem; - min-width: 0; -} - -.board { - color: var(--main-color); - font-weight: bold; - overflow: hidden; - text-overflow: ellipsis; -} - -.payload { - font-size: 0.9rem; - color: black; // Inherit from parent which has proper theme color -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/InfoMessageView/InfoMessageView.tsx b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/InfoMessageView/InfoMessageView.tsx deleted file mode 100644 index ba747e78c..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/InfoMessageView/InfoMessageView.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import styles from "./InfoMessageView.module.scss"; - -import { InfoMessage } from "common"; - -type Props = { - message: InfoMessage; - className: string; -}; - -export const InfoMessageView = ({ message, className }: Props) => { - return ( -
-
{message.board}
-
{message.payload}
-
- ); -}; diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/MessageView.module.scss b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/MessageView.module.scss deleted file mode 100644 index 3352dfe76..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/MessageView.module.scss +++ /dev/null @@ -1,60 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -//TODO: increase contrast info -.message.info { - --main-color: #{colors.getThemeColor("success")}; - --light-color: #{colors.getColor("success", 75)}; - --background-color: #{colors.getColor("success", 95)}; -} - -.message.fault { - --main-color: #{colors.getThemeColor("error")}; - --light-color: #{colors.getColor("error", 75)}; - --background-color: #{colors.getColor("error", 95)}; -} - -.message.warning { - --main-color: #{colors.getThemeColor("warning")}; - --light-color: #{colors.getColor("warning", 75)}; - --background-color: #{colors.getColor("warning", 95)}; -} - -.message { - display: grid; - grid-template: - "icon content counter" auto - "icon content counter" auto - "icon timestamp timestamp" auto / auto 1fr auto; - - gap: 0.4rem 0.8rem; - - padding: 0.6rem; - background-color: var(--background-color); - border-radius: 1rem; - @include styles.shadow; - color: colors.getThemeColor("text-primary"); -} - -.icon { - grid-area: icon; - color: var(--main-color); -} - -.counter { - grid-area: counter; -} - -.content { - grid-area: content; -} - -.idAndCounter { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.timestamp { - grid-area: timestamp; -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/MessageView.tsx b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/MessageView.tsx deleted file mode 100644 index 2912c69af..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/MessageView.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import styles from "./MessageView.module.scss"; -import React from "react"; -import { Counter } from "./Counter/Counter"; -import { Message } from "common"; - -import { ReactComponent as Info } from "assets/svg/info.svg"; -import { ReactComponent as Warning } from "assets/svg/warning.svg"; -import { ReactComponent as Fault } from "assets/svg/fault.svg"; - -import { TimestampView } from "./TimestampView/TimestampView"; -import { ProtectionMessageView } from "./ProtectionMessageView/ProtectionMessageView"; -import { InfoMessageView } from "./InfoMessageView/InfoMessageView"; - -type Props = { - message: Message; -}; - -const icons = { - info: Info, - fault: Fault, - warning: Warning, - ok: Info, -}; - -const appearances = { - info: styles.info, - warning: styles.warning, - fault: styles.fault, - ok: styles.info, -}; - -export const MessageView = React.memo(({ message }: Props) => { - const Icon = icons[message.kind]; - const appearance = appearances[message.kind]; - - const Message = - message.kind == "warning" || message.kind == "fault" || message.kind == "ok" ? ( - - ) : ( - - ); - - return ( -
- - {Message} - - -
- ); -}); diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/Origin/Origin.module.scss b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/Origin/Origin.module.scss deleted file mode 100644 index 54be8c6b0..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/Origin/Origin.module.scss +++ /dev/null @@ -1,14 +0,0 @@ -.origin { - display: flex; - align-items: center; - gap: 0.5rem; - font-weight: bold; -} - -.text { - color: var(--main-color); -} - -.arrow { - color: var(--light-color); -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/Origin/Origin.tsx b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/Origin/Origin.tsx deleted file mode 100644 index 11204fdb2..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/Origin/Origin.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import styles from "./Origin.module.scss"; -import { ReactComponent as RightArrow } from "assets/svg/right-arrow.svg"; - -type Props = { - board: string; - name: string; - className: string; -}; - -export const Origin = ({ board, name, className }: Props) => { - return ( -
- {board} - - - {name} - -
- ); -}; diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionMessageView.module.scss b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionMessageView.module.scss deleted file mode 100644 index 636a1ffc2..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionMessageView.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -.protectionMessage { - display: grid; - grid-template-rows: auto 1fr; - gap: 0.5rem; - color: black; -} - -.kindAndOrigin { - display: flex; - align-items: center; - flex-wrap: wrap; - overflow: hidden; - - column-gap: 0.8rem; - row-gap: 0.5rem; -} - -.protectionKind { - width: fit-content; - height: fit-content; - text-overflow: ellipsis; - overflow: hidden; - padding: 0.15rem 0.4rem; - border-radius: 0.5rem; - background-color: var(--main-color); - color: white; - font-weight: bold; -} - -.origin { - overflow: hidden; -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionMessageView.tsx b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionMessageView.tsx deleted file mode 100644 index 79368b0cc..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionMessageView.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ProtectionMessage } from "common"; -import styles from "./ProtectionMessageView.module.scss"; -import { Origin } from "./Origin/Origin"; -import { ProtectionView } from "./ProtectionView/ProtectionView"; - -type Props = { - message: ProtectionMessage; - className: string; -}; - -export const ProtectionMessageView = ({ message, className }: Props) => { - return ( -
-
-
- {message.payload.kind} -
- -
- -
- ); -}; diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionView/ProtectionView.module.scss b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionView/ProtectionView.module.scss deleted file mode 100644 index 0153c8e22..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionView/ProtectionView.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.protectionView { - display: inline-flex; - flex-wrap: wrap; - column-gap: 1rem; - row-gap: 0.5rem; -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionView/ProtectionView.tsx b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionView/ProtectionView.tsx deleted file mode 100644 index 7a98e98d5..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/ProtectionMessageView/ProtectionView/ProtectionView.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import styles from './ProtectionView.module.scss'; -import { ProtectionMessage } from 'common'; - -type Props = { - protection: ProtectionMessage; -}; - -const DECIMALS = 2; - -function safeFixed(val: any, decimals = DECIMALS) { - return typeof val === "number" ? val.toFixed(decimals) : String(val); -} - -export const ProtectionView = ({ protection }: Props) => { - const ProtectionText = getProtectionText(protection); - - return
{ProtectionText}
; -}; - -function getProtectionText(protection: ProtectionMessage) { - switch (protection.payload.kind) { - case "OUT_OF_BOUNDS": - return ( - <> - - {" "} - Want: [ - {safeFixed(protection.payload.data.bounds?.[0])}, {safeFixed(protection.payload.data.bounds?.[1])}] - {" "} - Got: {safeFixed(protection.payload.data.value)} - - ); - case "UPPER_BOUND": - return ( - <> - - Want: [{protection.name}] {"<"} {safeFixed(protection.payload.data.bound)} - {" "} - Got: {safeFixed(protection.payload.data.value)} - - ); - case "LOWER_BOUND": - return ( - <> - - Want: [{protection.name}] {">"} {safeFixed(protection.payload.data.bound)} - {" "} - Got: {safeFixed(protection.payload.data.value)} - - ); - case "EQUALS": - return ( - <> - - Mustn't be {safeFixed(protection.payload.data.value)} - - - ); - case "NOT_EQUALS": - return ( - <> - - Must be {safeFixed(protection.payload.data.want)} but is{" "} - {safeFixed(protection.payload.data.value)} - - - ); - case "TIME_ACCUMULATION": - return ( - - Value was {safeFixed(protection.payload.data.value)} for{" "} - {safeFixed(protection.payload.data.timelimit)} seconds - - ); - case "ERROR_HANDLER": - return {protection.payload.data}; - } -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/TimestampView/TimestampView.module.scss b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/TimestampView/TimestampView.module.scss deleted file mode 100644 index fda725afd..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/TimestampView/TimestampView.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.timestamp { - overflow: hidden; - text-overflow: ellipsis; - color: var(--main-color); -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/TimestampView/TimestampView.tsx b/ethernet-view/src/components/MessagesContainer/Messages/MessageView/TimestampView/TimestampView.tsx deleted file mode 100644 index 59ecd1240..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/MessageView/TimestampView/TimestampView.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import styles from "./TimestampView.module.scss"; -import { Timestamp } from "common"; - -type Props = { - timestamp: Timestamp; - className: string; -}; - -export const TimestampView = ({ timestamp, className }: Props) => { - return ( -
- {getTimestampString(timestamp)} -
- ); -}; - -function getTimestampString(timestamp: Timestamp): string { - return `${timestamp.hour}:${timestamp.minute}:${timestamp.second} ${timestamp.day}/${timestamp.month}/${timestamp.year}`; -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/Messages.module.scss b/ethernet-view/src/components/MessagesContainer/Messages/Messages.module.scss deleted file mode 100644 index 5ac818e20..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/Messages.module.scss +++ /dev/null @@ -1,31 +0,0 @@ -.messagesWrapper { - flex: 1 1 0; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 1rem; -} - -.messages { - flex: 1 1 0; - display: flex; - flex-direction: column; - align-items: start; - gap: 0.6rem; - min-height: 0; - overflow-y: auto; - height: 100%; - -} - -.buttons { - display: flex; - flex-direction: row; - justify-content: center; - gap: 1rem; -} - -.clearBtn { - flex: 0 0 auto; - height: min-content; -} diff --git a/ethernet-view/src/components/MessagesContainer/Messages/Messages.tsx b/ethernet-view/src/components/MessagesContainer/Messages/Messages.tsx deleted file mode 100644 index 6eb01af64..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/Messages.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Message } from "common"; -import styles from "./Messages.module.scss"; -import { MessageView } from "./MessageView/MessageView"; -import { useAutoScroll } from "./useAutoScroll"; -import { Button } from "components/FormComponents/Button/Button"; -import { useMessagesStore } from "common"; - -type Props = { - messages: Message[]; -}; - -export const Messages = ({ messages }: Props) => { - const { ref, handleScroll } = useAutoScroll(messages); - const clearMessages = useMessagesStore((state) => state.clearMessages); - - return ( -
-
- - { messages.map((message) => ( - - )) } -
-
-
-
- ); -}; diff --git a/ethernet-view/src/components/MessagesContainer/Messages/useAutoScroll.ts b/ethernet-view/src/components/MessagesContainer/Messages/useAutoScroll.ts deleted file mode 100644 index 93efe6a10..000000000 --- a/ethernet-view/src/components/MessagesContainer/Messages/useAutoScroll.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useLayoutEffect, useRef } from "react"; - -const AUTO_THRESHOLD = 30; - -export function useAutoScroll(items: Array) { - const ref = useRef(null); - const autoScroll = useRef(true); - const prevScrollTop = useRef(); - - useLayoutEffect(() => { - if (ref.current && autoScroll.current) { - ref.current.scroll({ - top: ref.current.scrollHeight, - behavior: "auto", - }); - } - }, [items]); - - function handleScroll(ev: React.UIEvent) { - // If user scrolls up, auto = false - if ( - prevScrollTop.current && - ev.currentTarget.scrollTop < prevScrollTop.current - ) { - autoScroll.current = false; - } else if ( - // If user scroll to the bottom, auto = true - Math.abs( - ev.currentTarget.scrollTop + - ev.currentTarget.offsetHeight - - ev.currentTarget.scrollHeight - ) < AUTO_THRESHOLD - ) { - autoScroll.current = true; - } - - prevScrollTop.current = ev.currentTarget.scrollTop; - } - - return { ref, handleScroll }; -} diff --git a/ethernet-view/src/components/MessagesContainer/MessagesContainer.module.scss b/ethernet-view/src/components/MessagesContainer/MessagesContainer.module.scss deleted file mode 100644 index e8825e328..000000000 --- a/ethernet-view/src/components/MessagesContainer/MessagesContainer.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -@use "src/styles/colors" as colors; - -.emptyAlert { - margin-top: 2rem; - text-align: center; - color: colors.getThemeColor("text-tertiary"); -} diff --git a/ethernet-view/src/components/MessagesContainer/MessagesContainer.tsx b/ethernet-view/src/components/MessagesContainer/MessagesContainer.tsx deleted file mode 100644 index 10d578c63..000000000 --- a/ethernet-view/src/components/MessagesContainer/MessagesContainer.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { Messages } from "./Messages/Messages"; -import { useMessages } from "components/MessagesContainer/useMessages"; - -export const MessagesContainer = () => { - const messages = useMessages(); - return ; -}; diff --git a/ethernet-view/src/components/MessagesContainer/useMessages.ts b/ethernet-view/src/components/MessagesContainer/useMessages.ts deleted file mode 100644 index 0f350a15b..000000000 --- a/ethernet-view/src/components/MessagesContainer/useMessages.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { MessageAdapter, useSubscribe } from "common"; -import { useMessagesStore } from "common"; - -export function useMessages() { - const { messages, addMessage } = useMessagesStore(state => ({messages: state.messages, addMessage: state.addMessage})); - - useSubscribe("message/update", (msg: MessageAdapter) => addMessage(msg)); - - return messages; -} diff --git a/ethernet-view/src/components/Navbar/Navbar.module.scss b/ethernet-view/src/components/Navbar/Navbar.module.scss deleted file mode 100644 index 801242650..000000000 --- a/ethernet-view/src/components/Navbar/Navbar.module.scss +++ /dev/null @@ -1,40 +0,0 @@ -@use "../../styles/styles.scss"; -@use "../../styles/colors.scss" as colors; - -.navbarWrapper { - width: fit-content; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - gap: 1.4rem; - padding: 2rem 0.8rem; - background-color: colors.getThemeColor("navbar-bg"); - border-right: 1px solid colors.getThemeColor("border"); - border-radius: styles.$large-border-radius; - min-height: 100%; -} - -.logo { - font-size: 3rem; - color: colors.getThemeColor("primary"); -} - -.separator { - width: 46%; - height: 0.25rem; - border-radius: 10rem; - background-color: colors.getThemeColor("border"); -} - -.items { - display: flex; - flex-direction: column; - align-items: center; - font-size: 1.5rem; - gap: 1.5rem; -} - -.spacer { - flex: 1; -} diff --git a/ethernet-view/src/components/Navbar/Navbar.tsx b/ethernet-view/src/components/Navbar/Navbar.tsx deleted file mode 100644 index 43e16dfbe..000000000 --- a/ethernet-view/src/components/Navbar/Navbar.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import styles from "./Navbar.module.scss"; -import { NavbarItem, NavbarItemData } from "./NavbarItem/NavbarItem"; -import { ThemeToggle } from "../ThemeToggle/ThemeToggle"; -import { ConfigPopup } from "components/ConfigPopup/ConfigPopup"; -import { useState } from "react"; - -type Props = { - items: NavbarItemData[]; - pageShown: string; - setPageShown: (page: string) => void; -}; - -export const Navbar = ({ items, pageShown, setPageShown }: Props) => { - const [isConfigOpen, setIsConfigOpen] = useState(false); - - return ( - - ); -}; diff --git a/ethernet-view/src/components/Navbar/NavbarItem/NavbarItem.module.scss b/ethernet-view/src/components/Navbar/NavbarItem/NavbarItem.module.scss deleted file mode 100644 index 8ed3833a0..000000000 --- a/ethernet-view/src/components/Navbar/NavbarItem/NavbarItem.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.iconWrapper { - cursor: pointer; - > * { - width: 2rem; - transition: filter 0.2s ease; - } - - &:hover > * { - filter: brightness(1.2); - } -} - -.active { - filter: brightness(0) saturate(100%) invert(69%) sepia(35%) saturate(2228%) hue-rotate(336deg) brightness(97%) contrast(96%); -} diff --git a/ethernet-view/src/components/Navbar/NavbarItem/NavbarItem.tsx b/ethernet-view/src/components/Navbar/NavbarItem/NavbarItem.tsx deleted file mode 100644 index 77315fdab..000000000 --- a/ethernet-view/src/components/Navbar/NavbarItem/NavbarItem.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import styles from "components/Navbar/NavbarItem/NavbarItem.module.scss"; - -export type NavbarItemData = { - icon: string; - page: string; - -}; - -type Props = { - item: NavbarItemData; - pageShown: string; - setPageShown: (page: string) => void; -}; - -export const NavbarItem = ({ item, pageShown, setPageShown }: Props) => { - - const handleClick = () => { - setPageShown(item.page); - } - - return ( -
-
- icon -
-
- ); -}; diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/BoardOrders.module.scss b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/BoardOrders.module.scss deleted file mode 100644 index e4ba258a0..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/BoardOrders.module.scss +++ /dev/null @@ -1,41 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.boardOrders { - display: flex; - flex-direction: column; - gap: 0.8rem; -} - -.name { - display: flex; - gap: 0.6rem; - font-family: Inter; - font-weight: 600; - color: hsl(29, 88%, 70%); - cursor: pointer; - padding: 0.5rem; - border-radius: 0.25rem; - - &:hover { - background-color: rgba(235, 137, 33, 0.15); - } -} - -:global([data-theme="dark"]) .name { - &:hover { - background-color: rgba(235, 137, 33, 0.08); - } -} - -.orders, -.stateOrders { - display: flex; - flex-direction: column; - gap: 0.6rem; - - .title { - color: rgb(145, 145, 145); - font-weight: 600; - } -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/BoardOrders.tsx b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/BoardOrders.tsx deleted file mode 100644 index 5a6a095d6..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/BoardOrders.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { BoardOrders } from "common"; -import styles from "./BoardOrders.module.scss"; -import { OrderForm } from "./OrderForm/OrderForm"; -import { useState } from "react"; -import { Caret } from "components/Caret/Caret"; - -type Props = { - boardOrders: BoardOrders; - alwaysShowStateOrders: boolean; -}; - -export const BoardOrdersView = ({ - boardOrders, - alwaysShowStateOrders, -}: Props) => { - - const [isOpen, setIsOpen] = useState(false); - - return ( -
-
setIsOpen((prev) => !prev)} > - - {boardOrders.name} -
- -
- Permanent orders - {boardOrders.orders.map((desc) => { - return ( - - ); - })} -
- - {(boardOrders.stateOrders.length > 0 && - (alwaysShowStateOrders || boardOrders.stateOrders.some((item) => item.enabled))) && ( -
- State orders - {boardOrders.stateOrders.map((desc) => { - if (alwaysShowStateOrders || desc.enabled) { - return ( - - ); - } else { - return false; - } - })} -
- )} -
- ); -}; diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/Field.module.scss b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/Field.module.scss deleted file mode 100644 index 5fe5bad44..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/Field.module.scss +++ /dev/null @@ -1,19 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.fieldWrapper { - width: 100%; - display: grid; - grid-template-columns: minmax(10rem, 1fr) 1fr auto; //TODO: show tooltip in names (to account for overflow) - gap: 1rem; - align-items: center; -} - -.name { - text-overflow: ellipsis; - overflow: hidden; -} - -.disabled { - color: colors.getThemeColor("text-disabled"); -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/Field.tsx b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/Field.tsx deleted file mode 100644 index 6f06d08cb..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/Field.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import styles from "./Field.module.scss"; -import { CheckBox } from "components/FormComponents/CheckBox/CheckBox"; -import { Dropdown } from "components/FormComponents/Dropdown/Dropdown"; -import { NumericType } from "common"; -import { isNumberValid } from "./validation"; -import { NumericInput } from "components/FormComponents/NumericInput/NumericInput"; -import { FormField } from "../../form"; - -type Props = { - name: string; - field: FormField; - onChange: (newValue: boolean | string | number, isValid: boolean) => void; - changeEnabled: (isEnabled: boolean) => void; -}; - -export const Field = ({ name, field, onChange, changeEnabled }: Props) => { - function handleTextInputChange( - value: string, - type: NumericType, - range: [number | null, number | null] - ) { - const isValid = isNumberValid(value, type, range); - onChange(Number.parseFloat(value), isValid); - } - - return ( -
-
{name}
- {field.kind == "numeric" ? ( - - handleTextInputChange( - value, - field.type, - field.safeRange - ) - } - /> - ) : field.kind == "boolean" ? ( - { - onChange(value, true); - }} - /> - ) : ( - { - onChange(newValue, true); - }} - /> - )} - - -
- ); -}; diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/validation.ts b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/validation.ts deleted file mode 100644 index 975d7e568..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Field/validation.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - NumericType, - isSignedIntegerType, - isUnsignedIntegerType, -} from "BackendTypes"; - -export function isNumberValid( - valueStr: string, - numberType: NumericType, - range: [number | null, number | null] -): boolean { - if (stringIsNumber(valueStr, numberType)) { - if (isUnsignedIntegerType(numberType)) { - let isValid = true; - if (range[0]) { - isValid &&= Number.parseInt(valueStr) >= range[0]; - } - - if (range[1]) { - isValid &&= Number.parseInt(valueStr) <= range[1]; - } - - return ( - isValid && - checkUnsignedIntegerOverflow( - Number.parseInt(valueStr), - getBits(numberType) - ) - ); - } else if (isSignedIntegerType(numberType)) { - let isValid = true; - if (range[0]) { - isValid &&= Number.parseInt(valueStr) >= range[0]; - } - - if (range[1]) { - isValid &&= Number.parseInt(valueStr) <= range[1]; - } - - return ( - isValid && - checkSignedIntegerOverflow( - Number.parseInt(valueStr), - getBits(numberType) - ) - ); - } else { - let isValid = true; - if (range[0]) { - isValid &&= Number.parseFloat(valueStr) >= range[0]; - } - - if (range[1]) { - isValid &&= Number.parseFloat(valueStr) <= range[1]; - } - - return isValid && checkFloatOverflow(Number.parseFloat(valueStr)); - } - } else { - return false; - } -} - -function stringIsNumber(valueStr: string, numberType: NumericType): boolean { - if (isUnsignedIntegerType(numberType)) { - return /^\d+$/.test(valueStr); - } else if (isSignedIntegerType(numberType)) { - return /^-?\d+$/.test(valueStr); - } else { - return /^-?\d+(?:\.\d+)?$/.test(valueStr); - } -} - -function checkUnsignedIntegerOverflow(value: number, bits: number): boolean { - return value >= 0 && value <= Math.pow(2, bits) - 1; -} - -function checkSignedIntegerOverflow(value: number, bits: number): boolean { - const min = -Math.pow(2, bits - 1); - const max = Math.pow(2, bits - 1) - 1; - return value >= min && value <= max; -} - -function checkFloatOverflow(value: number): boolean { - return !Number.isNaN(value); -} - -function getBits(type: NumericType): number { - switch (type) { - case "uint8": - return 8; - case "uint16": - return 16; - case "uint32": - return 32; - case "uint64": - return 64; - case "int8": - return 8; - case "int16": - return 16; - case "int32": - return 32; - case "int64": - return 64; - case "float32": - return 32; - case "float64": - return 64; - } -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Fields.module.scss b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Fields.module.scss deleted file mode 100644 index f2b0dc058..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Fields.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -@use "src/styles/styles"; - -.fieldsWrapper { - width: 100%; - padding: 1rem; - border-top: 1px solid styles.$orange; - - display: flex; - flex-direction: column; - gap: 1rem; - - overflow-x: auto; -} - -:global([data-theme="dark"]) .fieldsWrapper { - border-top-color: rgba(235, 137, 33, 0.8); -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Fields.tsx b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Fields.tsx deleted file mode 100644 index e2a53d5fe..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Fields/Fields.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import styles from "./Fields.module.scss"; -import { Field } from "./Field/Field"; -import { FormField } from "../form"; - -type Props = { - fields: FormField[]; - updateField: ( - id: string, - value: boolean | string | number, - isValid: boolean - ) => void; - changeEnable: (id: string, isEnabled: boolean) => void; -}; - -export const Fields = ({ fields, updateField, changeEnable }: Props) => { - return ( -
- {fields.map((field) => { - return ( - { - updateField(field.id, newValue, isValid); - }} - changeEnabled={(value) => changeEnable(field.id, value)} - /> - ); - })} -
- ); -}; diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Header/Header.module.scss b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Header/Header.module.scss deleted file mode 100644 index d9a6a83b0..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Header/Header.module.scss +++ /dev/null @@ -1,81 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.headerWrapper { - flex: 0 0 0; - padding: 0.5rem; - display: grid; - grid-template: - "caret name target button" auto - / auto 1fr auto 5rem; - align-items: center; - gap: 0.5rem; - background-color: #ffe4cc; - cursor: pointer; -} - -:global([data-theme="dark"]) .headerWrapper { - background-color: rgba(235, 137, 33, 0.2); -} - -.caret { - grid-area: caret; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.caret > * { - color: styles.$orange; -} - -.visible { - visibility: visible; -} - -.hidden { - visibility: hidden; -} - -.name { - grid-area: name; - color: styles.$orange; - font-weight: 500; - overflow: hidden; - text-overflow: ellipsis; -} - -:global([data-theme="dark"]) .name, -:global([data-theme="dark"]) .caret > * { - color: #f5b575; -} - -.target { - grid-area: target; - color: colors.getThemeColor("error"); - font-size: 0.8rem; - margin: 0 0.5rem; - opacity: 0; - cursor: pointer; - transition: opacity 0.07s linear; -} - -.targetVisible { - opacity: 1; -} - -.headerWrapper:hover { - .target:not(.targetVisible) { - opacity: 0.5; - } -} - -.sendBtn { - grid-area: button; - height: 100%; - button { - height: 100%; - } -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Header/Header.tsx b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Header/Header.tsx deleted file mode 100644 index c61d7c97f..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/Header/Header.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import styles from './Header.module.scss'; -import { useState, useEffect } from 'react'; -import { Button } from 'components/FormComponents/Button/Button'; -import { Caret } from 'components/Caret/Caret'; -import { SpringValue, animated } from '@react-spring/web'; -import { ReactComponent as Target } from 'assets/svg/target.svg'; - -export type HeaderInfo = ToggableHeader | FixedHeader; - -type ToggableHeader = { - type: 'toggable'; - isOpen: boolean; - toggleDropdown: () => void; -}; - -type FixedHeader = { - type: 'fixed'; -}; - -type Props = { - name: string; - disabled: boolean; - info: HeaderInfo; - springs: Record; - onTargetClick: (state: boolean) => void; - onButtonClick: () => void; -}; - -export const Header = ({ - name, - disabled, - info, - springs, - onTargetClick, - onButtonClick, -}: Props) => { - const [targetOn, setTargetOn] = useState(false); - - useEffect(() => { - onTargetClick(targetOn); - }, [targetOn]); - - return ( - {}} - style={{ - ...springs, - cursor: info.type == 'toggable' ? 'pointer' : 'auto', - }} - > - -
{name}
- { - ev.stopPropagation(); - setTargetOn((prev) => { - return !prev; - }); - }} - /> -
-
-
- ); -}; diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/OrderForm.module.scss b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/OrderForm.module.scss deleted file mode 100644 index 73190fcf7..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/OrderForm.module.scss +++ /dev/null @@ -1,19 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.orderFormWrapper { - flex-shrink: 0; - display: flex; - flex-direction: column; - align-items: stretch; - @include styles.code-text; - border: 1px solid styles.$orange; - border-radius: 0.5rem; - overflow: hidden; - - @include styles.shadow; -} - -:global([data-theme="dark"]) .orderFormWrapper { - border-color: rgba(235, 137, 33, 0.8); -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/OrderForm.tsx b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/OrderForm.tsx deleted file mode 100644 index 227c75a5d..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/OrderForm.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import styles from './OrderForm.module.scss'; -import { OrderDescription } from 'common'; -import { Header, HeaderInfo } from './Header/Header'; -import { Fields } from './Fields/Fields'; -import { useContext, useState } from 'react'; -import { Order } from 'common'; -import { useForm } from './useForm'; -import { useSpring } from '@react-spring/web'; -import { useListenKey } from './useListenKey'; -import { OrderContext } from '../../OrderContext'; -import { FormField } from './form'; - -type Props = { - description: OrderDescription; -}; - -function createOrder(id: number, fields: FormField[]): Order { - return { - id: id, - fields: Object.fromEntries( - fields.map((field) => { - return [ - field.id, - { - value: field.value, - isEnabled: field.isEnabled, - type: field.type, - }, - ]; - }) - ), - }; -} - -export const OrderForm = ({ description }: Props) => { - const sendOrder = useContext(OrderContext); - const { form, updateField, changeEnable } = useForm(description.fields); - const [isOpen, setIsOpen] = useState(false); - const [springs, api] = useSpring(() => ({ - from: { filter: 'brightness(1)' }, - config: { - tension: 600, - }, - })); - - const trySendOrder = () => { - if (form.isValid) { - api.start({ - from: { filter: 'brightness(1.2)' }, - to: { filter: 'brightness(1)' }, - }); - - sendOrder(createOrder(description.id, form.fields)); - } - }; - - const listen = useListenKey(' ', trySendOrder); - - const headerInfo: HeaderInfo = - form.fields.length > 0 - ? { - type: 'toggable', - isOpen: isOpen, - toggleDropdown: () => setIsOpen((prevValue) => !prevValue), - } - : { type: 'fixed' }; - - return ( -
-
- {isOpen && ( - - )} -
- ); -}; diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/form.ts b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/form.ts deleted file mode 100644 index 3c2181f29..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/form.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { - BooleanDescription, - EnumDescription, - NumericDescription, - OrderFieldDescription, -} from "common"; - -export type FormField = NumericField | BooleanField | EnumField; - -type AbstractFormField = { - id: string; - isValid: boolean; - isEnabled: boolean; -}; - -export type NumericField = AbstractFormField & - NumericDescription & { - value: number; - }; - -export type BooleanField = AbstractFormField & - BooleanDescription & { - value: boolean; - }; - -export type EnumField = AbstractFormField & - EnumDescription & { - value: string; - }; - -export type Form = { - fields: FormField[]; - isValid: boolean; -}; - -export function areFieldsValid(fields: Array): boolean { - return fields.reduce((prevValid, currentField) => { - return ( - prevValid && - ((currentField.isEnabled && currentField.isValid) || - !currentField.isEnabled) - ); - }, true); -} - -export function createForm( - descriptions: Record -): Form { - const fields = Object.entries(descriptions).map(([_, fieldDescription]) => { - const field = getFormField(fieldDescription); - return field; - }); - - return { fields, isValid: areFieldsValid(fields) }; -} - -function getFormField(desc: OrderFieldDescription): FormField { - if (desc.kind == "numeric") { - return getNumericFormField(desc); - } else if (desc.kind == "boolean") { - return getBooleanFormField(desc); - } else { - return getEnumFormField(desc); - } -} - -function getNumericFormField(desc: NumericDescription): NumericField { - return { - id: desc.id, - name: desc.name, - kind: desc.kind, - type: desc.type, - safeRange: desc.safeRange, - warningRange: desc.warningRange, - value: 0, - isValid: false, - isEnabled: true, - }; -} -function getBooleanFormField(desc: BooleanDescription): BooleanField { - return { - id: desc.id, - name: desc.name, - kind: desc.kind, - type: desc.type, - value: false, - isValid: true, - isEnabled: true, - }; -} -function getEnumFormField(desc: EnumDescription): EnumField { - return { - id: desc.id, - name: desc.name, - kind: desc.kind, - type: desc.type, - options: desc.options, - value: desc.options[0], - isValid: true, - isEnabled: true, - }; -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/useForm.ts b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/useForm.ts deleted file mode 100644 index df9edeb4f..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/useForm.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { OrderFieldDescription } from "common"; -import { useReducer } from "react"; -import { Form, areFieldsValid, createForm, FormField } from "./form"; - -type Action = UpdateField | ChangeEnable; - -type UpdateField = { - type: "update_field"; - payload: { - id: string; - isValid: boolean; - value: number | string | boolean; - }; -}; - -type ChangeEnable = { - type: "change_enable"; - payload: { - id: string; - enable: boolean; - }; -}; - -function reducer(state: Form, action: Action): Form { - switch (action.type) { - case "update_field": { - const fields: FormField[] = state.fields.map((field) => { - if (field.id == action.payload.id) { - return { - ...field, - value: action.payload.value, - isValid: action.payload.isValid, - }; - } - return field; - }) as FormField[]; - - return { - fields, - isValid: areFieldsValid(fields), - }; - } - - case "change_enable": { - const fields = state.fields.map((field) => - field.id == action.payload.id - ? { ...field, isEnabled: action.payload.enable } - : field - ); - return { - fields, - isValid: areFieldsValid(fields), - }; - } - } -} - -export function useForm(descriptions: Record) { - const [form, dispatch] = useReducer(reducer, descriptions, createForm); - - const updateField = ( - id: string, - value: string | boolean | number, - isValid: boolean - ) => - dispatch({ - type: "update_field", - payload: { id, value, isValid }, - }); - - const changeEnable = (id: string, enable: boolean) => - dispatch({ - type: "change_enable", - payload: { id, enable }, - }); - - return { form, updateField, changeEnable } as const; -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/useListenKey.ts b/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/useListenKey.ts deleted file mode 100644 index d4150974f..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/BoardOrders/OrderForm/useListenKey.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useEffect, useState } from "react"; - -export function useListenKey(key: string, callback: () => unknown) { - const [listen, setListen] = useState(false); - - const listener = (ev: KeyboardEvent) => { - if (ev.key == key) { - ev.preventDefault(); - callback(); - } - }; - - useEffect(() => { - if (listen) { - document.addEventListener("keydown", listener); - } - - return () => { - document.removeEventListener("keydown", listener); - }; - }, [listen, key, listener, callback]); - - return (value: boolean) => { - setListen(value); - }; -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/OrderContext.ts b/ethernet-view/src/components/OrdersContainer/Orders/OrderContext.ts deleted file mode 100644 index 4e67398ed..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/OrderContext.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Order } from "common"; -import { createContext } from "react"; - -export const OrderContext = createContext<(order: Order) => void>(() => {}); diff --git a/ethernet-view/src/components/OrdersContainer/Orders/Orders.module.scss b/ethernet-view/src/components/OrdersContainer/Orders/Orders.module.scss deleted file mode 100644 index a4a7baafb..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/Orders.module.scss +++ /dev/null @@ -1,45 +0,0 @@ -@use "src/styles/colors" as colors; - -.ordersWrapper { - width: 100%; - height: 100%; - min-height: 0; - display: flex; - flex-direction: column; - overflow-y: auto; - gap: 0.8rem; -} - -.stateOrdersToggle { - top: 0px; - position: sticky; - display: flex; - flex-direction: row; - gap: 0.2rem; - z-index: 1; - background-color: colors.getThemeColor("surface"); - align-items: center; -} - -.stateOrdersToggleButton { - font-size: 0.6rem; - padding: 0.15rem 0.3rem; - height: auto; - min-height: auto; -} - -.boardOrderList { - display: flex; - flex-direction: column; - gap: 0.8rem; -} - -.boardOrderList > :not(:last-child)::after { - content: ""; - border-bottom: 1px solid rgba(235, 137, 33, 0.368); // Orange with transparency - margin-bottom: 0.8rem; -} - -:global([data-theme="dark"]) .boardOrderList > :not(:last-child)::after { - border-bottom-color: rgba(235, 137, 33, 0.25); -} diff --git a/ethernet-view/src/components/OrdersContainer/Orders/Orders.tsx b/ethernet-view/src/components/OrdersContainer/Orders/Orders.tsx deleted file mode 100644 index 0073c4268..000000000 --- a/ethernet-view/src/components/OrdersContainer/Orders/Orders.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import styles from './Orders.module.scss'; -import { BoardOrders, Button } from 'common'; -import { OrderContext } from './OrderContext'; -import { useSendOrder } from '../useSendOrder'; -import { BoardOrdersView } from './BoardOrders/BoardOrders'; -import { useState } from 'react'; - -type Props = { - boards: BoardOrders[]; -}; - -export const Orders = ({ boards }: Props) => { - const sendOrder = useSendOrder(); - const [alwaysShowStateOrders, setAlwaysShowStateOrders] = useState(false); - - return ( - -
-
- Always show state orders:{' '} -
-
- {boards.map((board) => { - return ( - (board.orders.length > 0 || - board.stateOrders.length > 0) && ( - - ) - ); - })} -
-
-
- ); -}; diff --git a/ethernet-view/src/components/OrdersContainer/OrdersContainer.module.scss b/ethernet-view/src/components/OrdersContainer/OrdersContainer.module.scss deleted file mode 100644 index 946612df1..000000000 --- a/ethernet-view/src/components/OrdersContainer/OrdersContainer.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.orderTableWrapper { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; -} - -.emptyAlert { - margin-top: 2rem; - text-align: center; - color: rgb(156, 156, 156); -} diff --git a/ethernet-view/src/components/OrdersContainer/OrdersContainer.tsx b/ethernet-view/src/components/OrdersContainer/OrdersContainer.tsx deleted file mode 100644 index 78ab56754..000000000 --- a/ethernet-view/src/components/OrdersContainer/OrdersContainer.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import styles from "./OrdersContainer.module.scss"; -import { Orders } from "./Orders/Orders"; -import { useConfig, useFetchBack } from "common"; -import { useEffect } from "react"; -import { useOrders } from "common"; -import { useOrdersStore } from "common"; - -export const OrdersContainer = () => { - const config = useConfig(); - const setOrders = useOrdersStore((state) => state.setOrders); - - const orderDescriptionPromise = useFetchBack( - import.meta.env.PROD, - config.paths.orderDescription - ); - useEffect(() => { - orderDescriptionPromise.then((desc) => setOrders(desc)); - }, []); - - const orders = useOrders(); - - return ( -
- {orders.length == 0 ? ( - - Orders added to ADE will appear here - - ) : ( - - )} -
- ); -}; diff --git a/ethernet-view/src/components/OrdersContainer/useSendOrder.ts b/ethernet-view/src/components/OrdersContainer/useSendOrder.ts deleted file mode 100644 index 5b510bad0..000000000 --- a/ethernet-view/src/components/OrdersContainer/useSendOrder.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Order, useWsHandler } from "common"; - -export function useSendOrder() { - const handler = useWsHandler(); - - return (order: Order) => { - handler.post("order/send", order); - }; -} diff --git a/ethernet-view/src/components/ProgressBar/ProgressBar.module.scss b/ethernet-view/src/components/ProgressBar/ProgressBar.module.scss deleted file mode 100644 index ec3333de9..000000000 --- a/ethernet-view/src/components/ProgressBar/ProgressBar.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -@use "src/styles/colors" as colors; - -.progressBar { - height: 1rem; - display: flex; - align-items: stretch; - border-radius: 0.25rem; - overflow: hidden; - background-color: colors.getThemeColor("surface-variant"); -} - -.fill { - background-color: colors.getThemeColor("success"); - transition: width 0.3s ease; -} diff --git a/ethernet-view/src/components/ProgressBar/ProgressBar.tsx b/ethernet-view/src/components/ProgressBar/ProgressBar.tsx deleted file mode 100644 index 4458d0e4b..000000000 --- a/ethernet-view/src/components/ProgressBar/ProgressBar.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import styles from "./ProgressBar.module.scss"; - -type Props = { - progress: number; // Between 0 and 100 -}; - -export const ProgressBar = ({ progress }: Props) => { - return ( -
-
-
- ); -}; diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/BoardView.module.scss b/ethernet-view/src/components/ReceiveTable/BoardView/BoardView.module.scss deleted file mode 100644 index 2050fd1e7..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/BoardView.module.scss +++ /dev/null @@ -1,19 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.boardView { - display: flex; - flex-direction: column; - border-radius: 0.5rem; - overflow: hidden; - overflow-x: auto; - flex: 0 0 auto; - border: 1px solid var(--main-color); - color: var(--light-main-color); - background-color: colors.getThemeColor("surface-variant"); - @include styles.shadow; -} - -:global([data-theme="dark"]) .boardView { - background-color: colors.getThemeColor("surface"); -} diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/BoardView.tsx b/ethernet-view/src/components/ReceiveTable/BoardView/BoardView.tsx deleted file mode 100644 index 3da5fa922..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/BoardView.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Board } from "common"; -import styles from "./BoardView.module.scss"; -import { PacketView } from "./PacketView/PacketView"; -import { useState } from "react"; -import { Header } from "./Header/Header"; - -type Props = { - board: Board; -}; - -export const BoardView = ({ board }: Props) => { - const [open, setOpen] = useState(false); - - return ( -
-
setOpen((prev) => !prev)} - >
- {open && - board.packets.map((packet) => { - return ( - - ); - })} -
- ); -}; diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/Header/Header.module.scss b/ethernet-view/src/components/ReceiveTable/BoardView/Header/Header.module.scss deleted file mode 100644 index a3bf739a3..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/Header/Header.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -@use "src/styles/colors" as colors; - -.header { - display: flex; - align-items: center; - padding: 0.5rem; - gap: 0.5rem; - font-weight: bold; - background-color: var(--light-bg-color); - cursor: pointer; -} - diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/Header/Header.tsx b/ethernet-view/src/components/ReceiveTable/BoardView/Header/Header.tsx deleted file mode 100644 index 6b2cc93bc..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/Header/Header.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Caret } from "components/Caret/Caret"; -import styles from "./Header.module.scss"; - -type Props = { - name: string; - open: boolean; - onClick: () => void; -}; - -export const Header = ({ name, open, onClick }: Props) => { - return ( -
- - {name} -
- ); -}; diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/HexValue/HexValue.module.scss b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/HexValue/HexValue.module.scss deleted file mode 100644 index 186aa21da..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/HexValue/HexValue.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -.hexValue { - display: inline-flex; - flex-wrap: wrap; - column-gap: 1rem; - row-gap: 0.5rem; - font-family: Consolas; - font-weight: bold; -} diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/HexValue/HexValue.tsx b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/HexValue/HexValue.tsx deleted file mode 100644 index 4ce2d8152..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/HexValue/HexValue.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import styles from "./HexValue.module.scss"; -import { useMemo } from "react"; - -function getByteArr(hex: string): string[] { - const byteArr = [] as string[]; - - for (let i = 0; i < hex.length - 1; i += 2) { - byteArr.push(hex[i] + hex[i + 1]); - } - - if (hex.length % 2 != 0) { - byteArr.push(hex[hex.length - 1]); - } - - return byteArr; -} - -type Props = { - hex: string; -}; - -export const HexValue = ({ hex }: Props) => { - const byteArr = useMemo(() => getByteArr(hex.toUpperCase()), [hex]); - - return ( -
- {byteArr.map((byte, index) => { - return ( - - {byte} - - ); - })} -
- ); -}; diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.module.scss b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.module.scss deleted file mode 100644 index da95e0b1f..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.module.scss +++ /dev/null @@ -1,49 +0,0 @@ -@use "src/styles/colors" as colors; - -// .measurementView { -// display: flex; -// align-items: center; -// gap: 2rem; -// padding: 0.4rem 0; -// overflow: hidden; -// } - -// .measurementView:not(:last-child) { -// border-bottom: 1px solid var(--border-color); -// } - -.name, -.value { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.name { - grid-column: 1; - color: colors.getThemeColor("text-secondary"); -} - -.show_last { - grid-column: 2; -} - -.value { - grid-column: 3; - text-align: right; - color: colors.getThemeColor("secondary"); - font-family: Consolas; - font-size: 1.2rem; -} - -.units { - grid-column: 4; - text-align: center; - color: colors.getThemeColor("text-tertiary"); -} - -.type { - grid-column: 5; - text-align: center; - color: colors.getThemeColor("text-tertiary"); -} diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.tsx b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.tsx deleted file mode 100644 index 26c7b2566..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import styles from './MeasurementView.module.scss'; -import { - Measurement, - isNumericMeasurement, - useMeasurementsStore, -} from 'common'; -import { useUpdater } from './useUpdater'; -import { FormEvent } from 'react'; - -type Props = { - measurement: Measurement; -}; - -export const MeasurementView = ({ measurement }: Props) => { - const setShowMeasurementLatest = useMeasurementsStore( - (state) => (showLatest: boolean) => - state.showMeasurementLatest(measurement.id, showLatest) - ); - const isNumeric = isNumericMeasurement(measurement); - - const { valueRef } = useUpdater( - measurement.id, - isNumeric - ? measurement.value.showLatest - ? measurement.value.last.toFixed(3) - : measurement.value.average.toFixed(3) - : measurement.value.toString() - ); - - const onLatestValueChange = (event: FormEvent) => { - setShowMeasurementLatest(event.currentTarget.checked); - }; - - const setLog = (log: boolean) => { - useMeasurementsStore.setState(state => { - const measurements = { ...state.measurements }; - measurements[measurement.id] = { ...measurements[measurement.id], log }; - return { ...state, measurements }; - }); - }; - - const logChecked = useMeasurementsStore(state => state.measurements[measurement.id]?.log !== false); - - const showLatest = useMeasurementsStore(state => { - const meas = state.measurements[measurement.id]; - return isNumeric && meas && typeof meas.value === 'object' && 'showLatest' in meas.value - ? meas.value.showLatest - : false; - }); - - return ( - <> - - setLog(e.currentTarget.checked)} - /> - - {measurement.name} - - - {isNumeric && ( - <> - - - - - {measurement.units} - {measurement.type} - - )} - {!isNumeric && ( - <> - - {measurement.type} - - )} - - ); -}; diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/useUpdater.ts b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/useUpdater.ts deleted file mode 100644 index fbf23730f..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/useUpdater.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useLayoutEffect, useRef, useContext } from "react"; -import { TableContext } from "components/ReceiveTable/TableUpdater"; - -//TODO: receive just one id prop, or, even better, a getValue function (make it decoupled from store basically) -export function useUpdater(id: string, initialValue: string) { - const updater = useContext(TableContext); - const valueRef = useRef(null); - - useLayoutEffect(() => { - const valueNode = document.createTextNode(initialValue); - valueRef.current?.appendChild(valueNode); - - updater.addMeasurement({ - id: id, - value: valueNode, - }); - - return () => { - updater.removeMeasurement(id); - valueRef.current!.removeChild(valueNode); - }; - }, []); - - return { valueRef }; -} diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/PacketView.module.scss b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/PacketView.module.scss deleted file mode 100644 index c4ca130bd..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/PacketView.module.scss +++ /dev/null @@ -1,46 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.packetView { - display: flex; - flex-direction: column; - color: colors.getThemeColor("text-primary"); - --border-color: #{colors.getThemeColor("border")}; - --border-width: 1px; -} - -.data { - display: flex; - align-items: stretch; - border-top: var(--border-width) solid var(--border-color); - border-bottom: var(--border-width) solid var(--border-color); - background-color: colors.getThemeColor("surface"); - @include styles.shadow; -} - -.data > * { - flex: 1 1 0; - text-align: center; - padding: 0.5rem; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} - -.data > *:not(:first-child) { - border-left: var(--border-width) solid var(--border-color); -} - -.measurements { - padding: 0.5rem 1rem; - display: grid; - grid-template-columns: minmax(0, 15rem) min-content min-content min-content; - align-items: center; - gap: 1rem; -} - -.count, -.cycleTime { - font-size: 1.15rem; - font-family: Consolas; -} diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/PacketView.tsx b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/PacketView.tsx deleted file mode 100644 index e5475357b..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/PacketView.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Packet } from "common"; -import styles from "./PacketView.module.scss"; -import { MeasurementView } from "./MeasurementView/MeasurementView"; -import { memo } from "react"; -import { useUpdater } from "./useUpdater"; -import { useColumnsStore } from "store/columnsStore"; - -type Props = { - packet: Packet; -}; - -export const PacketView = memo(({ packet }: Props) => { - const columnSizes = useColumnsStore((state) => state.columnSizes); - - const { countRef, cycleTimeRef } = useUpdater(packet); - - return ( -
-
-
{packet.id}
-
{packet.name}
-
-
-
- {Object.keys(packet.measurements).length > 0 && ( -
- {packet.measurements.map((measurement) => { - return ( - - ); - })} -
- )} -
- ); -}); diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/useUpdater.ts b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/useUpdater.ts deleted file mode 100644 index 0d75bd08d..000000000 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/useUpdater.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Packet } from "common"; -import { useLayoutEffect, useRef, useContext } from "react"; -import { TableContext } from "../../TableUpdater"; - -export function useUpdater(packet: Packet) { - const updater = useContext(TableContext); - - const countRef = useRef(null); - const cycleTimeRef = useRef(null); - - useLayoutEffect(() => { - const countNode = document.createTextNode(packet.count.toFixed(2)); - const cycleTimeNode = document.createTextNode( - packet.cycleTime.toFixed(0) - ); - - countRef.current!.appendChild(countNode); - cycleTimeRef.current!.appendChild(cycleTimeNode); - - updater.addPacket(packet.id, { - count: countNode, - cycleTime: cycleTimeNode, - }); - - return () => { - updater.removePacket(packet.id); - - countRef.current!.removeChild(countNode); - cycleTimeRef.current!.removeChild(cycleTimeNode); - }; - }, [packet]); - - return { countRef, cycleTimeRef }; -} diff --git a/ethernet-view/src/components/ReceiveTable/Header/Header.module.scss b/ethernet-view/src/components/ReceiveTable/Header/Header.module.scss deleted file mode 100644 index ef4f2e3a5..000000000 --- a/ethernet-view/src/components/ReceiveTable/Header/Header.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use "src/styles/styles"; - -.header { - position: sticky; - top: 0; - display: flex; - align-items: stretch; - padding: 0 0.5rem; - border-radius: 0.5rem; - border: 1px solid var(--main-color); - background-color: var(--bg-color); - color: var(--main-color); - @include styles.shadow; -} - -.cell { - padding: 0.5rem 0; - text-align: center; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: bold; -} diff --git a/ethernet-view/src/components/ReceiveTable/Header/Header.tsx b/ethernet-view/src/components/ReceiveTable/Header/Header.tsx deleted file mode 100644 index efb2045bd..000000000 --- a/ethernet-view/src/components/ReceiveTable/Header/Header.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import styles from "./Header.module.scss"; -import { useSplit } from "hooks/useSplit/useSplit"; -import { Orientation } from "hooks/useSplit/Orientation"; -import { Separator } from "./Separator/Separator"; -import { useEffect } from "react"; -import { useColumnsStore } from "store/columnsStore"; - -type Props = { - items: string[]; -}; - -const MINIMUM_ITEM_SIZE = 0.05; - -export const Header = ({ items }: Props) => { - const columnSizes = useColumnsStore((state) => state.columnSizes); - const setColumnSizes = useColumnsStore((state) => state.setColumnSizes); - - const [splitElements, handleMouseDown] = useSplit( - new Array(items.length).fill(MINIMUM_ITEM_SIZE), - Orientation.HORIZONTAL - ); - - useEffect(() => { - setColumnSizes(splitElements.map((element) => `${element.length * 100}%`)); - }, [splitElements]); - - return ( -
- {items.map((item, index) => { - if (index < items.length - 1) { - return ( - handleMouseDown(index, ev)} - /> - ); - } - - return ( - - ); - })} -
- ); -}; - -const CellWithSeparator = ({ - label, - flexBasis, - onMouseDown, -}: { - label: string; - flexBasis: string; - onMouseDown: (ev: React.MouseEvent) => void; -}) => { - return ( - <> -
- {label} -
- onMouseDown(ev)}> - - ); -}; - -const CellWithoutSeparator = ({ - label, - flexBasis, -}: { - label: string; - flexBasis: string; -}) => { - return ( -
- {label} -
- ); -}; diff --git a/ethernet-view/src/components/ReceiveTable/Header/Separator/Separator.module.scss b/ethernet-view/src/components/ReceiveTable/Header/Separator/Separator.module.scss deleted file mode 100644 index 5a1df27d4..000000000 --- a/ethernet-view/src/components/ReceiveTable/Header/Separator/Separator.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -.separator { - padding: 0 0.6rem; - display: flex; - justify-content: center; - cursor: e-resize; -} - -.line { - width: 1px; - background-color: var(--main-color); -} diff --git a/ethernet-view/src/components/ReceiveTable/Header/Separator/Separator.tsx b/ethernet-view/src/components/ReceiveTable/Header/Separator/Separator.tsx deleted file mode 100644 index f39cfd555..000000000 --- a/ethernet-view/src/components/ReceiveTable/Header/Separator/Separator.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import styles from "./Separator.module.scss"; -import { MouseEvent } from "react"; - -type Props = { - onMouseDown: (ev: MouseEvent) => void; -}; - -export const Separator = ({ onMouseDown }: Props) => { - return ( -
-
-
- ); -}; diff --git a/ethernet-view/src/components/ReceiveTable/ReceiveTable.module.scss b/ethernet-view/src/components/ReceiveTable/ReceiveTable.module.scss deleted file mode 100644 index cb97d7809..000000000 --- a/ethernet-view/src/components/ReceiveTable/ReceiveTable.module.scss +++ /dev/null @@ -1,30 +0,0 @@ -@use "src/styles/colors" as colors; - -.newReceiveTable { - --main-color: #{colors.getThemeColor("primary")}; - --bg-color: #{colors.getColor("primary", 85)}; - --light-main-color: #{colors.getColor("primary", 65)}; - --light-bg-color: #{colors.getColor("primary", 95)}; - - display: flex; - flex-direction: column; - min-height: 0; - border-radius: 0.5rem; // Para que la tabla no se veo en las esquinas superiores debido al hueco que deja el border-radius del header - flex: 1 1 0; -} - -:global([data-theme="dark"]) .newReceiveTable { - --main-color: #{colors.getThemeColor("text-primary")}; - --bg-color: #{colors.getColor("neutral", 20)}; - --light-main-color: #{colors.getThemeColor("text-secondary")}; - --light-bg-color: #{colors.getThemeColor("surface-variant")}; -} - -.boards { - padding-top: 1rem; - display: flex; - flex-direction: column; - flex-grow: 1; - overflow-y: auto; - gap: 1rem; -} diff --git a/ethernet-view/src/components/ReceiveTable/ReceiveTable.tsx b/ethernet-view/src/components/ReceiveTable/ReceiveTable.tsx deleted file mode 100644 index f5cb00aeb..000000000 --- a/ethernet-view/src/components/ReceiveTable/ReceiveTable.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import styles from "./ReceiveTable.module.scss"; -import { BoardView } from "./BoardView/BoardView"; -import { Header } from "./Header/Header"; -import { TableUpdater } from "./TableUpdater"; -import { Board, useMeasurementsStore} from "common"; - -type Props = { - boards: Board[]; -}; - -export const ReceiveTable = ({ boards }: Props) => { - const handleLogAll = (log: boolean) => { - useMeasurementsStore.getState().setLogAll(log); - }; - - const handleShowAllLatest = (showLatest: boolean) => { - useMeasurementsStore.getState().setShowAllLatest(showLatest); - }; - return ( - -
-
-
- - -
-
- - -
-
-
-
- {boards - .filter((item) => item.packets.length > 0) - .map((board) => { - return ( - - ); - })} -
-
-
- ); -}; diff --git a/ethernet-view/src/components/ReceiveTable/TableUpdater.tsx b/ethernet-view/src/components/ReceiveTable/TableUpdater.tsx deleted file mode 100644 index e360745eb..000000000 --- a/ethernet-view/src/components/ReceiveTable/TableUpdater.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { - getPacket, - isNumericMeasurement, - useGlobalTicker, - useMeasurementsStore, - usePodDataStore, -} from 'common'; -import { createContext, useRef } from 'react'; - -export type PacketElement = { - count: Text; - cycleTime: Text; -}; - -type MeasurementElement = { id: string; value: Text }; - -type Updater = { - addPacket: (id: number, element: PacketElement) => void; - removePacket: (id: number) => void; - addMeasurement: (element: MeasurementElement) => void; - removeMeasurement: (id: string) => void; -}; - -export const TableContext = createContext({ - addPacket() {}, - removePacket() {}, - addMeasurement() {}, - removeMeasurement() {}, -}); - -type Props = { - children?: React.ReactNode; -}; - -export const TableUpdater = ({ children }: Props) => { - const packetElements = useRef>({}); - const measurementElements = useRef([]); - - const podData = usePodDataStore((state) => state.podData); - const getMeasurement = useMeasurementsStore( - (state) => state.getMeasurement - ); - - useGlobalTicker(() => { - for (const id in packetElements.current) { - const packet = getPacket(podData, Number.parseInt(id)); - const element = packetElements.current[id]; - if (packet) { - element.count.nodeValue = packet.count.toFixed(0); - element.cycleTime.nodeValue = packet.cycleTime.toFixed(0); - } else { - console.warn(`packet ${id} not found`); - } - } - - for (const item of measurementElements.current) { - const measurement = getMeasurement(item.id); - if (!measurement) { - console.warn(`measurement ${item.id} not found`); - return; - } - const element = measurementElements.current.find( - (elem) => elem.id == item.id - ); - if (!element) { - console.warn(`element of measurement ${item.id} not found`); - return; - } - element.value.nodeValue = isNumericMeasurement(measurement) - ? measurement.value.showLatest - ? measurement.value.last.toFixed(3) - : measurement.value.average.toFixed(3) - : measurement.value.toString(); - } - }); - - const updater: Updater = { - addPacket: (id: number, element: PacketElement) => { - packetElements.current[id] = element; - }, - removePacket(id) { - delete packetElements.current[id]; - }, - addMeasurement: (element: MeasurementElement) => { - if ( - !measurementElements.current.find( - (item) => item.id == element.id - ) - ) { - measurementElements.current.push(element); - } - }, - removeMeasurement: (id: string) => { - measurementElements.current = measurementElements.current.filter( - (item) => item.id != id - ); - }, - }; - - return ( - - {children} - - ); -}; diff --git a/ethernet-view/src/components/SplashScreen/SplashScreen.module.scss b/ethernet-view/src/components/SplashScreen/SplashScreen.module.scss deleted file mode 100644 index 985ce37b6..000000000 --- a/ethernet-view/src/components/SplashScreen/SplashScreen.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -@use "src/styles/colors" as colors; - -.loadingView { - flex: 1 1 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - gap: 2rem; - font-size: 6rem; - font-weight: 900; - color: colors.getThemeColor("secondary"); -} - -.monkey { - font-family: NotoColorEmoji; -} diff --git a/ethernet-view/src/components/SplashScreen/SplashScreen.tsx b/ethernet-view/src/components/SplashScreen/SplashScreen.tsx deleted file mode 100644 index c40ef6101..000000000 --- a/ethernet-view/src/components/SplashScreen/SplashScreen.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import styles from './SplashScreen.module.scss'; -import { animated, useSpring } from '@react-spring/web'; - -// TODO: change for common front SplashScreen -export const SplashScreen = () => { - const springs = useSpring({ - from: { fontSize: '0rem' }, - to: { fontSize: '16rem' }, - config: { - mass: 5, - }, - delay: 150, - }); - - return ( -
- - ๐Ÿ’ - -
- ); -}; diff --git a/ethernet-view/src/components/ThemeToggle/ThemeToggle.module.scss b/ethernet-view/src/components/ThemeToggle/ThemeToggle.module.scss deleted file mode 100644 index 476b8d932..000000000 --- a/ethernet-view/src/components/ThemeToggle/ThemeToggle.module.scss +++ /dev/null @@ -1,34 +0,0 @@ -@use "../../styles/colors.scss" as colors; - -.themeToggle { - display: flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - border: 1px solid colors.getThemeColor("border"); - border-radius: 8px; - background-color: colors.getThemeColor("surface"); - color: colors.getThemeColor("text-primary"); - cursor: pointer; - transition: all 0.2s ease; - - &:hover { - background-color: colors.getThemeColor("surface-hover"); - border-color: colors.getThemeColor("border-hover"); - transform: translateY(-1px); - box-shadow: colors.getThemeColor("shadow-sm"); - } - - &:active { - transform: translateY(0); - } -} - -.icon { - font-size: 20px; - line-height: 1; - display: flex; - align-items: center; - justify-content: center; -} \ No newline at end of file diff --git a/ethernet-view/src/components/ThemeToggle/ThemeToggle.tsx b/ethernet-view/src/components/ThemeToggle/ThemeToggle.tsx deleted file mode 100644 index 61fecf92c..000000000 --- a/ethernet-view/src/components/ThemeToggle/ThemeToggle.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect, useState } from "react"; -import styles from "./ThemeToggle.module.scss"; - -export function ThemeToggle() { - const [theme, setTheme] = useState<"light" | "dark">(() => { - const savedTheme = localStorage.getItem("theme"); - return (savedTheme as "light" | "dark") || "light"; - }); - - useEffect(() => { - document.documentElement.setAttribute("data-theme", theme); - localStorage.setItem("theme", theme); - }, [theme]); - - const toggleTheme = () => { - setTheme((prev) => (prev === "light" ? "dark" : "light")); - }; - - return ( - - ); -} \ No newline at end of file diff --git a/ethernet-view/src/hooks/useBounds.ts b/ethernet-view/src/hooks/useBounds.ts deleted file mode 100644 index d2d20dedb..000000000 --- a/ethernet-view/src/hooks/useBounds.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useLayoutEffect, useRef, useState } from "react"; - -export function useBounds() { - const ref = useRef(null); - const [rect, setRect] = useState({ - x: 0, - y: 0, - height: 0, - width: 0, - bottom: 0, - left: 0, - right: 0, - top: 0, - toJSON: () => {}, - }); - const resizeObserverRef = useRef(); - - useLayoutEffect(() => { - setRect((prevRect) => { - if (ref.current) { - return ref.current.getBoundingClientRect(); - } else { - return { ...prevRect }; - } - }); - - resizeObserverRef.current = new ResizeObserver((entries) => { - setRect(entries[0].target.getBoundingClientRect()); - }); - - resizeObserverRef.current.observe(ref.current!); - - return () => { - resizeObserverRef.current?.disconnect(); - }; - }, []); - - return [ref, rect] as const; -} diff --git a/ethernet-view/src/hooks/useInterval.ts b/ethernet-view/src/hooks/useInterval.ts deleted file mode 100644 index fe85cf5bf..000000000 --- a/ethernet-view/src/hooks/useInterval.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useRef, useEffect } from "react"; - -export function useInterval(callback: () => void, delay: number) { - const savedCallback = useRef<() => void>(); - - useEffect(() => { - savedCallback.current = callback; - }); - - useEffect(() => { - function tick() { - savedCallback.current!(); - } - - let id = setInterval(tick, delay); - return () => clearInterval(id); - }, [delay]); -} diff --git a/ethernet-view/src/hooks/useSplit/InitialState.ts b/ethernet-view/src/hooks/useSplit/InitialState.ts deleted file mode 100644 index 33fa5c276..000000000 --- a/ethernet-view/src/hooks/useSplit/InitialState.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Orientation } from "./Orientation"; -import { Size, SplitElement } from "./useSplit"; - -export class InitialState { - public readonly initialMousePos: [number, number]; - public readonly initialElements: SplitElement[]; - public readonly direction: Orientation; - public readonly separatorIndex: number; - public readonly separatorSize: Size; - public readonly wrapperSize: Size; - - constructor( - mousePos: [number, number], - elements: SplitElement[], - direction: Orientation, - separatorIndex: number, - separatorSize: Size, - wrapperSize: Size - ) { - this.initialMousePos = mousePos; - this.initialElements = elements; - this.direction = direction; - this.separatorIndex = separatorIndex; - this.separatorSize = separatorSize; - this.wrapperSize = wrapperSize; - } -} diff --git a/ethernet-view/src/hooks/useSplit/Orientation.ts b/ethernet-view/src/hooks/useSplit/Orientation.ts deleted file mode 100644 index a9f887111..000000000 --- a/ethernet-view/src/hooks/useSplit/Orientation.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum Orientation { - VERTICAL, - HORIZONTAL, -} diff --git a/ethernet-view/src/hooks/useSplit/SeparatorEventHandler.ts b/ethernet-view/src/hooks/useSplit/SeparatorEventHandler.ts deleted file mode 100644 index 4caf3ca57..000000000 --- a/ethernet-view/src/hooks/useSplit/SeparatorEventHandler.ts +++ /dev/null @@ -1,23 +0,0 @@ -export class SeparatorEventHandler { - public onMouseDown?: (ev: React.MouseEvent, separatorIndex: number) => void; - public onMove?: (clientX: number, clientY: number) => void; - public onMouseUp?: () => void; - - public handleMouseDown = (separatorIndex: number, ev: React.MouseEvent) => { - ev.preventDefault(); - this.onMouseDown?.(ev, separatorIndex); - document.addEventListener("mousemove", this.handleMouseMove); - document.addEventListener("mouseup", this.handleMouseUp); - }; - - private handleMouseMove = (ev: MouseEvent) => { - ev.preventDefault(); - this.onMove?.(ev.clientX, ev.clientY); - }; - - private handleMouseUp = (ev: MouseEvent) => { - ev.preventDefault(); - document.removeEventListener("mousemove", this.handleMouseMove); - document.removeEventListener("mouseup", this.handleMouseUp); - }; -} diff --git a/ethernet-view/src/hooks/useSplit/useSplit.ts b/ethernet-view/src/hooks/useSplit/useSplit.ts deleted file mode 100644 index 059bcf940..000000000 --- a/ethernet-view/src/hooks/useSplit/useSplit.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { useState, useEffect, useMemo } from "react"; -import { SeparatorEventHandler } from "./SeparatorEventHandler"; -import { Orientation } from "./Orientation"; -import { InitialState as InitialState } from "./InitialState"; - -export type Size = { - width: number; - height: number; -}; - -export type SplitElement = { - length: number; - minLength: number; -}; - -/** - * The `useSplit` function in TypeScript is used to manage resizable elements with specified initial - * lengths and minimum lengths in a given direction. - * @param {number[] | undefined} initialLengths - The `initialLengths` parameter is an array of numbers - * representing the initial lengths of the elements to be split. It can be either `undefined` or an - * array of numbers. If it is `undefined`, if its length is not equal to the length of the - * `minLengths` array or if its sum is not equal to 1, the `initialLengths` will be set proportionally equal. - * @param {number[]} minLengths - The `minLengths` parameter in the `useSplit` function represents an - * array of minimum lengths for each split element. These minimum lengths determine the smallest size - * each element can be resized to. If a user tries to resize an element below its minimum length, the - * element will be collapsed. - * @param {Orientation} direction - The `direction` parameter in the `useSplit` function is used to - * determine the orientation of the split elements. It is of type `Orientation`, which is likely an - * enum or type that specifies whether the split should be horizontal or vertical. This helps in - * calculating the resizing of elements based on the direction - * @returns The `useSplit` function returns an array containing the `elements` state and the - * `handleMouseDown` function from the `separatorEventHandler`. - */ -export function useSplit(minLengths: number[], direction: Orientation, initialLengths?: number[]) { - - if(!initialLengths || initialLengths.length != minLengths.length || initialLengths.reduce((prev, curr) => prev + curr, 0) != 1) { - initialLengths = minLengths.map(() => 1 / minLengths.length); - } - - const [elements, setElements] = useState( - initialLengths.map((length, index) => ({ - length, - minLength: minLengths[index], - })) - ); - - const separatorEventHandler = useMemo( - () => new SeparatorEventHandler(), - [] - ); - - useEffect(() => { - separatorEventHandler.onMouseDown = (ev, sepIndex) => { - const separator = ev.currentTarget as HTMLElement; - const wrapper = separator.parentElement!; - - const initialState = new InitialState( - [ev.clientX, ev.clientY], - [...elements], - direction, - sepIndex, - getSize(separator), - getSize(wrapper) - ); - - separatorEventHandler.onMove = (clientX, clientY) => { - const normDistace = getNormDistance( - clientX, - clientY, - direction, - initialState - ); - - setElements(() => { - const newElements = getResizedElements( - initialState.initialElements, - normDistace, - initialState.separatorIndex - ); - - return newElements; - }); - }; - }; - - separatorEventHandler.onMouseUp = () => { - separatorEventHandler.onMove = () => {}; - }; - }, [elements]); - - return [elements, separatorEventHandler.handleMouseDown] as const; -} - -function getResizedElements( - elements: SplitElement[], - normDistance: number, - sepIndex: number -) { - const direction = Math.sign(normDistance) as 1 | 0 | -1; - - const { affected, affectedIndex, unaffected, unaffectedIndex } = - getAffectedAndUnaffectedElements(elements, direction, sepIndex); - - const shrinkedElements = substractDisplacement(affected, normDistance); - - const mainElementIndex = direction == 1 ? sepIndex : sepIndex + 1; - - const newMainElement = getNewMainElement(elements[mainElementIndex], [ - ...shrinkedElements, - ...unaffected, - ]); - - return getFinalElements( - newMainElement, - mainElementIndex, - shrinkedElements, - affectedIndex, - unaffected, - unaffectedIndex, - direction - ); -} - -function getFinalElements( - newMainElement: SplitElement, - newMainElementIndex: number, - shrinkedElements: SplitElement[], - shrinkedElementsIndex: number, - unaffectedElements: SplitElement[], - unaffectedElementsIndex: number, - direction: 1 | 0 | -1 -) { - return direction == 1 || direction == 0 - ? Object.assign( - [] as SplitElement[], - { - [unaffectedElementsIndex]: unaffectedElements, - }, - { - [newMainElementIndex]: newMainElement, - }, - { - [shrinkedElementsIndex]: shrinkedElements, - } - ).flat() - : Object.assign( - [] as SplitElement[], - { - [shrinkedElementsIndex]: shrinkedElements, - }, - { - [newMainElementIndex]: newMainElement, - }, - - { - [unaffectedElementsIndex]: unaffectedElements, - } - ).flat(); -} - -function getAffectedAndUnaffectedElements( - elements: SplitElement[], - direction: 1 | 0 | -1, - separatorIndex: number -): { - affected: SplitElement[]; - affectedIndex: number; - unaffected: SplitElement[]; - unaffectedIndex: number; -} { - if (direction == 1) { - return { - affected: elements.slice(separatorIndex + 1), - affectedIndex: separatorIndex + 1, - unaffected: elements.slice(0, separatorIndex), - unaffectedIndex: 0, - }; - } else if (direction == -1) { - return { - affected: elements.slice(0, separatorIndex + 1), - affectedIndex: 0, - unaffected: elements.slice(separatorIndex + 2), - unaffectedIndex: separatorIndex + 2, - }; - } else { - return { - affected: [], - affectedIndex: 1, - unaffected: elements, - unaffectedIndex: 0, - }; - } -} - -function getNewMainElement( - mainElement: SplitElement, - elements: SplitElement[] -): SplitElement { - const mainElementLength = - 1 - - elements.reduce((prevValue, currentElement) => { - return prevValue + currentElement.length; - }, 0); - - return { - length: mainElementLength, - minLength: mainElement.minLength, - }; -} - -function substractDisplacement( - elements: SplitElement[], - distance: number -): SplitElement[] { - const orderedElements = - distance >= 0 ? [...elements] : [...elements].reverse(); - - let remaindingDistance = Math.abs(distance); - - for (let i = 0; i < orderedElements.length; i++) { - let newLength = orderedElements[i].length - remaindingDistance; - if(newLength < orderedElements[i].minLength) newLength = 0; - remaindingDistance = Math.max( - remaindingDistance - (orderedElements[i].length - newLength), - 0 - ); - - orderedElements[i] = { - length: newLength, - minLength: orderedElements[i].minLength, - }; - - if (remaindingDistance < 0.00001) break; - } - - return distance > 0 ? orderedElements : orderedElements.reverse(); -} - -function getNormDistance( - x: number, - y: number, - direction: Orientation, - state: InitialState -) { - const distance = - direction == Orientation.HORIZONTAL - ? x - state.initialMousePos[0] - : y - state.initialMousePos[1]; - - const wrapperLength = getLength(state.wrapperSize, state.direction); - - const wrapperLengthWithoutSeparators = - wrapperLength - - getLength(state.separatorSize, state.direction) * - (state.initialElements.length - 1); - - return distance / wrapperLengthWithoutSeparators; -} - -function getLength(size: Size, direction: Orientation): number { - if (direction == Orientation.HORIZONTAL) { - return size.width; - } else { - return size.height; - } -} - -function getSize(element: HTMLElement): Size { - const rect = element.getBoundingClientRect(); - return { width: rect.width, height: rect.height }; -} diff --git a/ethernet-view/src/index.scss b/ethernet-view/src/index.scss deleted file mode 100644 index 6d74c522f..000000000 --- a/ethernet-view/src/index.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "src/styles/styles"; - -body { - margin: 0; - padding: 0; - /*TODO: cambiar width y height para que si haces la ventana mas pequeรฑa, los componentes se hacen */ - height: 100vh; - background-color: styles.$background-color; -} -* { - box-sizing: border-box; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; -} - -#root { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} diff --git a/ethernet-view/src/layouts/AppLayout/AppLayout.module.scss b/ethernet-view/src/layouts/AppLayout/AppLayout.module.scss deleted file mode 100644 index 7b0dd2f59..000000000 --- a/ethernet-view/src/layouts/AppLayout/AppLayout.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -@use "../../styles/styles.scss"; - -.appLayout { - display: flex; - height: 100%; - width: 100%; - gap: styles.$normal-padding; - padding: styles.$normal-padding; -} \ No newline at end of file diff --git a/ethernet-view/src/layouts/AppLayout/AppLayout.tsx b/ethernet-view/src/layouts/AppLayout/AppLayout.tsx deleted file mode 100644 index dfaf3717e..000000000 --- a/ethernet-view/src/layouts/AppLayout/AppLayout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import styles from "./AppLayout.module.scss"; -import Testing from "assets/svg/testing.svg"; -import Logger from "assets/svg/logger.svg"; -import Camera from "assets/svg/camera.svg"; -import { Navbar } from "components/Navbar/Navbar"; -import { ReactNode } from "react"; - -interface Props { - children: ReactNode; - pageShown: string; - setPageShown: (page: string) => void; -} - -export const AppLayout = ({ children, pageShown, setPageShown }: Props) => { - return ( -
- - {children} -
- ); -}; diff --git a/ethernet-view/src/layouts/SplitLayout/Pane/Pane.module.scss b/ethernet-view/src/layouts/SplitLayout/Pane/Pane.module.scss deleted file mode 100644 index 8cd0af2ef..000000000 --- a/ethernet-view/src/layouts/SplitLayout/Pane/Pane.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.wrapper { - min-width: 0; - min-height: 0; -} - -.collapsed { - padding: .6rem; - background-color: colors.getThemeColor("primary"); - border-radius: .8rem; -} - -.icon { - width: 2rem; - - img { - filter: invert(1); - width: 100%; - color: colors.getThemeColor("primary-text"); - } -} \ No newline at end of file diff --git a/ethernet-view/src/layouts/SplitLayout/Pane/Pane.tsx b/ethernet-view/src/layouts/SplitLayout/Pane/Pane.tsx deleted file mode 100644 index 8d7deb717..000000000 --- a/ethernet-view/src/layouts/SplitLayout/Pane/Pane.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import styles from "layouts/SplitLayout/Pane/Pane.module.scss"; - -type Props = { - component: React.ReactNode; - normalizedLength: number; - collapsedIcon: string; -}; - -export const Pane = ({ component, normalizedLength, collapsedIcon }: Props) => { - - const isCollapsed = normalizedLength === 0; - - return ( -
-
- collapsed -
- -
- {component} -
- -
- ); -}; diff --git a/ethernet-view/src/layouts/SplitLayout/Separator/Separator.module.scss b/ethernet-view/src/layouts/SplitLayout/Separator/Separator.module.scss deleted file mode 100644 index dc0d0d853..000000000 --- a/ethernet-view/src/layouts/SplitLayout/Separator/Separator.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.wrapper { - flex: 0 1 0rem; - padding: 0.5rem; - .line { - width: 100%; - height: 100%; - padding: 1px; - border-radius: 1rem; - background-color: none; - transition: styles.$background-color-transition; - } - - &:hover { - .line { - background-color: colors.getThemeColor("border-hover"); - } - } -} diff --git a/ethernet-view/src/layouts/SplitLayout/Separator/Separator.tsx b/ethernet-view/src/layouts/SplitLayout/Separator/Separator.tsx deleted file mode 100644 index 69b798c25..000000000 --- a/ethernet-view/src/layouts/SplitLayout/Separator/Separator.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Orientation } from "hooks/useSplit/Orientation"; -import styles from "layouts/SplitLayout/Separator/Separator.module.scss"; -import { MouseEvent } from "react"; - -type Props = { - orientation: Orientation; - onMouseDown: (ev: MouseEvent) => void; -}; - -export const Separator = ({ orientation, onMouseDown }: Props) => { - return ( -
{ - onMouseDown(ev); - }} - > -
-
- ); -}; diff --git a/ethernet-view/src/layouts/SplitLayout/SplitLayout.module.scss b/ethernet-view/src/layouts/SplitLayout/SplitLayout.module.scss deleted file mode 100644 index 7d2ec976e..000000000 --- a/ethernet-view/src/layouts/SplitLayout/SplitLayout.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.wrapper { - width: 100%; - height: 100%; - min-height: 0; - display: flex; - justify-content: space-between; -} diff --git a/ethernet-view/src/layouts/SplitLayout/SplitLayout.tsx b/ethernet-view/src/layouts/SplitLayout/SplitLayout.tsx deleted file mode 100644 index 1d17b24e2..000000000 --- a/ethernet-view/src/layouts/SplitLayout/SplitLayout.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from "react"; -import styles from "layouts/SplitLayout/SplitLayout.module.scss"; -import { useSplit } from "hooks/useSplit/useSplit"; -import { Pane } from "layouts/SplitLayout/Pane/Pane"; -import { Separator } from "layouts/SplitLayout/Separator/Separator"; -import { Orientation } from "hooks/useSplit/Orientation"; - -type Props = { - components: { - component: React.ReactNode; - collapsedIcon: string; - }[]; - orientation?: Orientation; - initialLengths?: number[]; -}; - -export const SplitLayout = ({ initialLengths, components, orientation = Orientation.HORIZONTAL }: Props) => { - - const minLengths = components.map(() => 0.05); - const [splitElements, onSeparatorMouseDown] = useSplit(minLengths, orientation, initialLengths); - - return ( -
- {components.map((component, index) => { - return ( - - - {index < components.length - 1 && ( - - onSeparatorMouseDown(index, ev) - } - /> - )} - - ); - })} -
- ); -}; diff --git a/ethernet-view/src/layouts/TabLayout/Header/Header.module.scss b/ethernet-view/src/layouts/TabLayout/Header/Header.module.scss deleted file mode 100644 index 590c94c07..000000000 --- a/ethernet-view/src/layouts/TabLayout/Header/Header.module.scss +++ /dev/null @@ -1,19 +0,0 @@ -@use "src/styles/styles"; - -.headerWrapper { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - gap: 1rem; - flex: 0 0 2.5rem; - overflow-x: clip; - - margin-bottom: 1.2rem; - .name { - display: flex; - align-items: center; - @include styles.title-text; - line-height: 95%; - } -} diff --git a/ethernet-view/src/layouts/TabLayout/Header/Header.tsx b/ethernet-view/src/layouts/TabLayout/Header/Header.tsx deleted file mode 100644 index b8245ab0f..000000000 --- a/ethernet-view/src/layouts/TabLayout/Header/Header.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import styles from "./Header.module.scss"; -import { TabBar } from "./TabBar/TabBar"; -import { TabItem } from "layouts/TabLayout/TabItem"; - -type Props = { - tabs: TabItem[]; - visibleTab: TabItem; - onTabClick: (tab: TabItem) => void; -}; - -export const Header = ({ tabs, visibleTab, onTabClick }: Props) => { - return ( -
-
{visibleTab.name}
- {tabs.length > 1 && ( - - )} -
- ); -}; diff --git a/ethernet-view/src/layouts/TabLayout/Header/TabBar/Tab/Tab.module.scss b/ethernet-view/src/layouts/TabLayout/Header/TabBar/Tab/Tab.module.scss deleted file mode 100644 index 64e5e5f8e..000000000 --- a/ethernet-view/src/layouts/TabLayout/Header/TabBar/Tab/Tab.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -.wrapper { - display: grid; - grid-template: "icon title" / auto auto; - align-items: center; - gap: 0.5rem; - height: auto; - padding: 0.5rem; - //border: 2px solid styles.$alternate-text-color; - background-color: colors.getThemeColor("surface-variant"); - border-radius: 0.8rem; - cursor: pointer; -} - -.icon { - grid-area: icon; - - align-self: center; - justify-self: center; - font-size: 1.5rem; -} diff --git a/ethernet-view/src/layouts/TabLayout/Header/TabBar/Tab/Tab.tsx b/ethernet-view/src/layouts/TabLayout/Header/TabBar/Tab/Tab.tsx deleted file mode 100644 index 32b3ceff8..000000000 --- a/ethernet-view/src/layouts/TabLayout/Header/TabBar/Tab/Tab.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import styles from "./Tab.module.scss"; - -type Props = { - name: string; - className?: string; - icon?: React.ReactNode; - onClick: () => void; -}; -export const Tab = ({ name, icon, onClick, className = "" }: Props) => { - return ( -
- {icon} -
{name}
-
- ); -}; diff --git a/ethernet-view/src/layouts/TabLayout/Header/TabBar/TabBar.module.scss b/ethernet-view/src/layouts/TabLayout/Header/TabBar/TabBar.module.scss deleted file mode 100644 index 9141c4373..000000000 --- a/ethernet-view/src/layouts/TabLayout/Header/TabBar/TabBar.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.wrapper { - display: flex; - flex-direction: row; - gap: 0.5rem; -} diff --git a/ethernet-view/src/layouts/TabLayout/Header/TabBar/TabBar.tsx b/ethernet-view/src/layouts/TabLayout/Header/TabBar/TabBar.tsx deleted file mode 100644 index 65e1bae37..000000000 --- a/ethernet-view/src/layouts/TabLayout/Header/TabBar/TabBar.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import styles from "./TabBar.module.scss"; -import { Tab } from "./Tab/Tab"; -import { TabItem } from "layouts/TabLayout/TabItem"; - -type Props = { - tabs: TabItem[]; - onTabClick: (tab: TabItem) => void; - visibleTabId: string; -}; - -export const TabBar = ({ tabs, onTabClick, visibleTabId }: Props) => { - return ( -
- {tabs.map((tab) => { - return ( - onTabClick(tab)} - > - ); - })} -
- ); -}; diff --git a/ethernet-view/src/layouts/TabLayout/TabItem.ts b/ethernet-view/src/layouts/TabLayout/TabItem.ts deleted file mode 100644 index aa3a5643a..000000000 --- a/ethernet-view/src/layouts/TabLayout/TabItem.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type TabItem = { - id: string; - name: string; - //FIXME: cambiar a obligatorio cuando arregle el bug de aria-hidden - icon?: React.ReactNode; - component: React.ReactNode; -}; diff --git a/ethernet-view/src/layouts/TabLayout/TabLayout.module.scss b/ethernet-view/src/layouts/TabLayout/TabLayout.module.scss deleted file mode 100644 index 7adc57f6a..000000000 --- a/ethernet-view/src/layouts/TabLayout/TabLayout.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -@use "src/styles/styles"; - -.body { - width: 100%; - min-width: 0; - min-height: 0; - flex-grow: 1; -} - -.componentWrapper { - width: 100%; - height: 100%; - overflow: hidden; -} - -.visibilityWrapper { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; -} diff --git a/ethernet-view/src/layouts/TabLayout/TabLayout.tsx b/ethernet-view/src/layouts/TabLayout/TabLayout.tsx deleted file mode 100644 index 395915439..000000000 --- a/ethernet-view/src/layouts/TabLayout/TabLayout.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import styles from "layouts/TabLayout/TabLayout.module.scss"; -import { TabItem } from "layouts/TabLayout/TabItem"; -import { Header } from "layouts/TabLayout/Header/Header"; -import { Island } from "components/Island/Island"; -import { useState } from "react"; -type Props = { - tabs: TabItem[]; -}; - -export const TabLayout = ({ tabs }: Props) => { - const [visibleTab, setVisibleTab] = useState(tabs[0]); - - function onTabClick(tab: TabItem) { - setVisibleTab(tab); - } - - return ( - -
-
-
- {tabs.map((tab) => { - return ( -
- {tab.component} -
- ); - })} -
-
- - ); -}; diff --git a/ethernet-view/src/main.tsx b/ethernet-view/src/main.tsx deleted file mode 100644 index c0b448232..000000000 --- a/ethernet-view/src/main.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import "common/dist/style.css"; -import "styles/fonts.scss"; -import "./index.scss"; -import { ConfigProvider, GlobalTicker } from "common"; -import App from "App"; - -ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - - - - - -); \ No newline at end of file diff --git a/ethernet-view/src/pages/CamerasPage/CamerasPage.module.scss b/ethernet-view/src/pages/CamerasPage/CamerasPage.module.scss deleted file mode 100644 index d679f0475..000000000 --- a/ethernet-view/src/pages/CamerasPage/CamerasPage.module.scss +++ /dev/null @@ -1,23 +0,0 @@ -.camerasPage { - display: flex; - gap: 1rem; -} - -.camerasRow { - display: flex; - flex: 1; - gap: 1rem; -} - -.cameraColumn { - display: flex; - flex: 1; - flex-direction: column; - gap: 1rem; -} - -.cameraInput { - display: flex; - gap: 1rem; - align-items: center; -} \ No newline at end of file diff --git a/ethernet-view/src/pages/CamerasPage/CamerasPage.tsx b/ethernet-view/src/pages/CamerasPage/CamerasPage.tsx deleted file mode 100644 index 7edc867f3..000000000 --- a/ethernet-view/src/pages/CamerasPage/CamerasPage.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Button, LiveStreamPlayer, TextInput } from 'common'; -import styles from './CamerasPage.module.scss'; -import { useState } from 'react'; - -export const CamerasPage = () => { - const [cameras, setCameras] = useState>(new Map()); - const [cameraLeftURL, setCameraLeftURL] = useState(''); - const [cameraRightURL, setCameraRightURL] = useState(''); - - const handleSetCamera = (cameraId: number, cameraURL: string) => () => { - setCameras((prev) => new Map(prev).set(cameraId, cameraURL)); - }; - - return ( -
-
-
-
- setCameraLeftURL(e.target.value)} - /> -
- -
- -
-
- setCameraRightURL(e.target.value)} - /> -
- -
-
-
- ); -}; diff --git a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartCanvas.tsx b/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartCanvas.tsx deleted file mode 100644 index fc6100748..000000000 --- a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartCanvas.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { - ColorType, - createChart, - IChartApi, - UTCTimestamp, -} from 'lightweight-charts'; -import { ChartPoint } from 'pages/LoggerPage/LogsColumn/LogLoader/LogsProcessor'; -import { useEffect, useRef } from 'react'; -import { MeasurementLogger } from './ChartElement'; - -const CHART_HEIGHT = 300; - -interface Props { - measurementsInChart: MeasurementLogger[]; - getDataFromLogSession: (measurement: string) => ChartPoint[]; -} - -export const ChartCanvas = ({ - measurementsInChart, - getDataFromLogSession, -}: Props) => { - const chart = useRef(null); - const chartContainerRef = useRef(null); - - // Helper function to get theme-aware chart options - const getThemeOptions = () => { - const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; - return { - layout: { - background: { - type: ColorType.Solid, - color: isDark ? 'black' : 'white' - }, - textColor: isDark ? 'white' : 'black', - }, - grid: { - vertLines: { - color: isDark ? '#1f1f1f' : '#f0f0f0', - }, - horzLines: { - color: isDark ? '#1f1f1f' : '#f0f0f0', - }, - }, - }; - }; - - useEffect(() => { - const handleResize = () => { - if (chartContainerRef.current) - if (chart) - chart.current?.applyOptions({ - width: chartContainerRef.current.clientWidth, - }); - }; - - const resizeObserver = new ResizeObserver(handleResize); - if (chartContainerRef.current) - resizeObserver.observe(chartContainerRef.current); - - let themeObserver: MutationObserver | null = null; - - if (chartContainerRef.current) { - if (chart) - chart.current = createChart(chartContainerRef.current, { - ...getThemeOptions(), - width: chartContainerRef.current.clientWidth, - height: CHART_HEIGHT, - timeScale: { - timeVisible: true, - fixLeftEdge: true, - fixRightEdge: true, - lockVisibleTimeRangeOnResize: true, - tickMarkFormatter: (time: UTCTimestamp) => { - const date = new Date(time * 1000); - return date.toLocaleTimeString() + '.' + date.getMilliseconds(); - }, - }, - localization: { - timeFormatter: (time: UTCTimestamp) => { - const date = new Date(time * 1000); - return date.toLocaleTimeString() + '.' + date.getMilliseconds(); - } - } - }); - - // Set up MutationObserver to watch for theme changes - themeObserver = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') { - chart.current?.applyOptions(getThemeOptions()); - } - }); - }); - - themeObserver.observe(document.documentElement, { - attributes: true, - attributeFilter: ['data-theme'] - }); - } - - for (const measurement of measurementsInChart) { - const data = getDataFromLogSession(measurement.id); - const series = chart.current?.addLineSeries({ - color: measurement.color, - priceFormat: { - type: 'price', - precision: 3, - minMove: 0.001, - }, - }); - for (const point of data) { - series?.update({ - time: (point.time / 1000) as UTCTimestamp, - value: point.value, - }); - } - } - - return () => { - resizeObserver.disconnect(); - themeObserver?.disconnect(); - chart.current?.remove(); - }; - }, [measurementsInChart]); - - return
; -}; diff --git a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartElement.tsx b/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartElement.tsx deleted file mode 100644 index dcf6fecca..000000000 --- a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartElement.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { ChartId } from "components/ChartMenu/ChartMenu" -import { AiOutlineCloseCircle } from "react-icons/ai" -import styles from "components/ChartMenu/ChartElement/ChartElement.module.scss" -import { ChartCanvas } from "./ChartCanvas"; -import { ChartPoint } from "pages/LoggerPage/LogsColumn/LogLoader/LogsProcessor"; -import { useState } from "react"; -import { MeasurementId, } from "common"; -import { ChartLegend } from "./ChartLegend"; - -interface Props { - chartId: ChartId; - initialMeasurementId: MeasurementId - removeChart: (chartId: ChartId) => void; - getDataFromLogSession: (measurement: ChartId) => ChartPoint[]; -} - -export interface MeasurementLogger { - id: string; - color: string; -} - -export const ChartElement = ({ chartId, initialMeasurementId, removeChart, getDataFromLogSession }: Props) => { - - const [measurementsInChart, setMeasurementsInChart] = useState([{id: initialMeasurementId, color: 'red'}]); - - const addMeasurementToChart = (measurement: MeasurementLogger) => { - if(!measurementsInChart.some(measurementInChart => measurementInChart.id === measurement.id)) { - setMeasurementsInChart([...measurementsInChart, measurement]); - } - } - - const handleDrop = (ev: React.DragEvent) => { - ev.stopPropagation(); - const id = ev.dataTransfer.getData("id"); - addMeasurementToChart({id, color: getRandomColor()}); - }; - - return ( -
ev.preventDefault()} - onDragOver={(ev) => ev.preventDefault()} - > -
- removeChart(chartId)} - /> - - setMeasurementsInChart(measurementsInChart.filter(measurement => measurement.id !== measurementId))} - /> -
-
- ) -} - -function getRandomColor() { - var r = Math.floor(Math.random() * 256); - var g = Math.floor(Math.random() * 256); - var b = Math.floor(Math.random() * 256); - var hexR = r.toString(16).padStart(2, '0'); - var hexG = g.toString(16).padStart(2, '0'); - var hexB = b.toString(16).padStart(2, '0'); - return '#' + hexR + hexG + hexB; - } \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartLegend.tsx b/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartLegend.tsx deleted file mode 100644 index efea169f9..000000000 --- a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartElement/ChartLegend.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { MeasurementId } from "common"; -import { ChartId } from "components/ChartMenu/ChartMenu"; -import { useEffect, useRef } from "react"; -import styles from "components/ChartMenu/ChartElement/ChartLegend/ChartLegend.module.scss" -import { MeasurementLogger } from "./ChartElement"; - -interface Props { - chartId: ChartId; - measurementsInChart: MeasurementLogger[]; - removeMeasurementFromChart: (measurementId: MeasurementId) => void; - removeChart: (chartId: ChartId) => void; -} - -export const ChartLegend = ({ chartId, measurementsInChart, removeMeasurementFromChart, removeChart }: Props) => { - - const legendRef = useRef(null); - - const onRemoveMeasurement = (measurementId: MeasurementId) => { - removeMeasurementFromChart(measurementId); - }; - - useEffect(() => { - if(measurementsInChart.length == 0) removeChart(chartId); - }, [measurementsInChart.length]) - - useEffect(() => { - if (legendRef.current) { - while (legendRef.current.firstChild) { - legendRef.current.removeChild(legendRef.current.firstChild); - } - measurementsInChart.forEach((measurement) => { - const newChartLegendItem = createChartLegendItem(measurement); - newChartLegendItem.onclick = (_) => onRemoveMeasurement(measurement.id); - legendRef.current?.appendChild(newChartLegendItem); - }); - } - }); - - return
; -}; - -function createChartLegendItem(measurement: MeasurementLogger) { - const legendItem = document.createElement("div"); - legendItem.setAttribute("data-id", measurement.id); - legendItem.className = styles.chartLegendItem; - const seriesColor = document.createElement("div"); - seriesColor.className = styles.chartLegendItemColor; - seriesColor.style.backgroundColor = measurement.color; - const seriesName = document.createElement("p"); - seriesName.innerText = measurement.id; - legendItem.appendChild(seriesColor); - legendItem.appendChild(seriesName); - return legendItem; -} diff --git a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartMenu.tsx b/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartMenu.tsx deleted file mode 100644 index 6df6cd5ab..000000000 --- a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartMenu/ChartMenu.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { ChartId, ChartInfo } from "components/ChartMenu/ChartMenu" -import styles from "components/ChartMenu/ChartMenu.module.scss" -import { Item } from "components/ChartMenu/Sidebar/Section/Subsection/Subsection/Items/Item/ItemView" -import Sidebar from "components/ChartMenu/Sidebar/Sidebar" -import { useLogStore } from "pages/LoggerPage/useLogStore" -import { DragEvent, useCallback, useState } from "react" -import { ChartElement } from "./ChartElement/ChartElement" -import { nanoid } from "nanoid" -import { MeasurementId } from "common" - -export const ChartMenu = () => { - const openLogSession = useLogStore((state) => state.openLogSession) - const logSessions = useLogStore((state) => state.logSessions) - const logSession = logSessions.find((logSession) => logSession.name === openLogSession) - const data = logSession ? Array.from(logSession.measurementLogs.keys()).sort() : [] - const sidebarItems: Item[] = data.map(measurement => ({ - id: measurement, - name: measurement, - })) - - const getDataFromLogSession = (measurement: string) => { - return logSession?.measurementLogs.get(measurement) || []; - } - - const [charts, setCharts] = useState([]); - - const addChart = ((chartId: ChartId, initialMeasurementId: MeasurementId) => { - setCharts([...charts, { chartId, initialMeasurementId }]); - }); - - const removeChart = useCallback((chartId: ChartId) => { - setCharts(prevCharts => prevCharts.filter(c => chartId !== c.chartId)); - }, []); - - const handleDrop = (ev: DragEvent) => { - ev.preventDefault(); - const id = ev.dataTransfer.getData("id"); - addChart(nanoid(), id); - }; - - return ( -
- -
ev.preventDefault()} - onDragOver={(ev) => ev.preventDefault()} - > - {charts.map((chart) => ( - removeChart(chartId)} - getDataFromLogSession={getDataFromLogSession} - /> - ))} -
-
- ) -} diff --git a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartsColumn.tsx b/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartsColumn.tsx deleted file mode 100644 index 2d9b00222..000000000 --- a/ethernet-view/src/pages/LoggerPage/ChartsColumn/ChartsColumn.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { TabLayout } from "layouts/TabLayout/TabLayout"; -import { ChartMenu } from "./ChartMenu/ChartMenu"; -import { ReactComponent as Chart } from "assets/svg/chart.svg"; - -export const ChartsColumn = () => { - const chartsColumnTabItems = [ - { - id: "charts", - name: "Charts", - icon: , - component: , - }, - ] - - return ; -}; \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LoggerPage.module.scss b/ethernet-view/src/pages/LoggerPage/LoggerPage.module.scss deleted file mode 100644 index 7841138b2..000000000 --- a/ethernet-view/src/pages/LoggerPage/LoggerPage.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.LoggerPage { - display: flex; - width: 100%; - gap: 1rem; -} - -.LogsColumn { - flex: 1; -} - -.ChartsColumn { - flex: 3; -} \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LoggerPage.tsx b/ethernet-view/src/pages/LoggerPage/LoggerPage.tsx deleted file mode 100644 index da3b0b974..000000000 --- a/ethernet-view/src/pages/LoggerPage/LoggerPage.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { ChartsColumn } from "pages/LoggerPage/ChartsColumn/ChartsColumn"; -import { LogsColumn } from "./LogsColumn/LogsColumn"; -import { SplitLayout } from "layouts/SplitLayout/SplitLayout"; -import logs from "assets/svg/logs.svg"; -import chart from "assets/svg/chart.svg"; - -export const LoggerPage = () => { - return ( - , - collapsedIcon: logs - }, - { - component: , - collapsedIcon: chart - } - ]} - initialLengths={[0.3, 0.7]} - /> - ); -}; diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Controls/Controls.module.scss b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Controls/Controls.module.scss deleted file mode 100644 index a3eb366d5..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Controls/Controls.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.controlsWrapper { - width: 100%; - display: flex; - padding: 1rem; - gap: 2rem; -} - -.button { - flex: 1; - padding: 0.2rem; - font-size: large; - border-style: none; -} \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Controls/Controls.tsx b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Controls/Controls.tsx deleted file mode 100644 index f1e550d65..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Controls/Controls.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Button } from "components/FormComponents/Button/Button" -import styles from "./Controls.module.scss" -import { UploadInformation, UploadState } from "../../LogLoader"; - -type Props = { - uploadInformation: UploadInformation; - onLoad: () => void; - onRemove: () => void; -} - -export const Controls = ({uploadInformation, onLoad, onRemove}: Props) => { - return ( -
- -
- ) -} diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Dropzone.module.scss b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Dropzone.module.scss deleted file mode 100644 index dad168642..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Dropzone.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -@use "src/styles/styles.scss"; -@use "src/styles/colors" as colors; - -.dropZone { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - padding: 3rem 2rem; - border: dashed colors.getThemeColor("border"); - border-radius: styles.$large-border-radius; - - &Text { - font-weight: styles.$normal-font-weight; - font-size: styles.$normal-font-size; - color: colors.getThemeColor("text-secondary"); - font-family: styles.$code-font; - } - - &.Active { - background-color: colors.getThemeColor("primary-surface"); - } - - &.Uploading { - background-color: colors.getThemeColor("primary-surface"); - } - - &.Success { - background-color: colors.getThemeColor("success"); - opacity: 0.15; - } - - &.Error { - background-color: colors.getThemeColor("error"); - opacity: 0.15; - } -} - -.selectLabel { - cursor: pointer; - font-weight: bold; - color: colors.getThemeColor("primary"); -} \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Dropzone.tsx b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Dropzone.tsx deleted file mode 100644 index 4f0d117eb..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/Dropzone.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { useState } from "react"; -import { UploadInformation, UploadState } from "../LogLoader"; -import styles from "./Dropzone.module.scss" -import { extractLoggerSession } from "../LogsProcessor"; -import { Controls } from "./Controls/Controls"; -import { useDropzone } from "./useDropzone"; -import { LogSession, useLogStore } from "pages/LoggerPage/useLogStore"; - -export const Dropzone = () => { - - const [uploadInformation, setUploadInformation] = useState({state: UploadState.IDLE}); - const [isDraggingOver, setIsDraggingOver] = useState(false); - const {dropZoneText, draftSession, setDraftSession} = useDropzone({uploadInformation}); - const addLogSession = useLogStore(state => state.addLogSession); - - const uploadSession = async (directory: FileSystemDirectoryEntry) => { - setUploadInformation({state: UploadState.UPLOADING}); - try { - const loggerSession = await extractLoggerSession(directory); - setDraftSession({name: directory.name, measurementLogs: loggerSession} as LogSession); - setUploadInformation({state: UploadState.SUCCESS}); - } catch(err) { - if(err instanceof Error) { - setUploadInformation({state: UploadState.ERROR, errorMessage: err.message}); - } else { - setUploadInformation({state: UploadState.ERROR, errorMessage: "An unexpected error occurred"}); - } - } - }; - - const handleDrop = async (e: React.DragEvent) => { - e.preventDefault(); - try { - validateEntry(e.dataTransfer.items); - const directory = e.dataTransfer.items[0].webkitGetAsEntry(); - console.log(directory) - await uploadSession(directory as FileSystemDirectoryEntry); - } catch(err) { - if (err instanceof Error){ - setUploadInformation({state: UploadState.ERROR, errorMessage: err.cause as string}); - } else { - setUploadInformation({state: UploadState.ERROR, errorMessage: "An unexpected error occurred"}); - } - } - }; - - return ( -
-
{ - e.preventDefault(); - setIsDraggingOver(true); - }} - onDragLeave={e => { - e.preventDefault(); - if (e.currentTarget.contains(e.relatedTarget as Node)) return; - setIsDraggingOver(false); - }} - onDragOver={e => e.preventDefault()} - onDrop={handleDrop} - > -
-

- {dropZoneText} - {uploadInformation.state === UploadState.IDLE && -

- - { - if(ev.target.files && ev.target.files.length === 1) { - - } - }} - > - -
- } - {uploadInformation.errorMessage} -

-
-
- - - { - if(draftSession) { - addLogSession(draftSession); - setUploadInformation({state: UploadState.IDLE}); - setDraftSession(undefined); - } - }} - onRemove={() => { - setUploadInformation({state: UploadState.IDLE}); - setDraftSession(undefined); - }} - /> - -
- ) -} - -/** - * The function `validateEntry` checks if only one directory is selected from a list of files. - * @param {DataTransferItemList} files - The `files` parameter in the `validateEntry` function is of - * type `DataTransferItemList`, which represents a list of items. - */ -function validateEntry(files: DataTransferItemList) { - if(files.length != 1) throw new Error("Only one file or directory is allowed"); - let file = files[0].webkitGetAsEntry(); - if(!file) throw new Error("Invalid file"); - if(!file.isDirectory) throw new Error("Only directories are allowed"); -} \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/useDropzone.ts b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/useDropzone.ts deleted file mode 100644 index a0ba6822d..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/Dropzone/useDropzone.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useEffect, useState } from "react"; -import { UploadInformation, UploadState } from "../LogLoader"; -import { LogSession } from "pages/LoggerPage/useLogStore"; - -interface Props { - uploadInformation: UploadInformation; -} - -export const useDropzone = ({uploadInformation}: Props) => { - - const [dropZoneText, setDropZoneText] = useState(); - const [draftSession, setDraftSession] = useState(); - - useEffect(() => { - setDropZoneText(`${ - uploadInformation.state === UploadState.UPLOADING ? "Uploading..." : - uploadInformation.state === UploadState.SUCCESS ? "Upload successful" : - uploadInformation.state === UploadState.ERROR ? "Upload failed" : - "Drop a logger session directory here" - }`); - }, [uploadInformation]); - - return { - dropZoneText, - setDraftSession, - draftSession - } - -} diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogItem/LogItem.module.scss b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogItem/LogItem.module.scss deleted file mode 100644 index c350adbec..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogItem/LogItem.module.scss +++ /dev/null @@ -1,34 +0,0 @@ -@use "src/styles/styles.scss"; -@use "src/styles/colors" as colors; - -.logItemWrapper { - display: flex; - align-items: center; - justify-content: center; - gap: .8rem; - width: 100%; - - &:hover { - cursor: pointer; - } -} - -.icon { - img { - width: 1.6rem; - } -} - -.title { - color: styles.$title-color; - font-size: 1.6rem; - font-family: styles.$code-font; - font-style: italic; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} - -.active { - color: colors.getThemeColor("secondary"); -} \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogItem/LogItem.tsx b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogItem/LogItem.tsx deleted file mode 100644 index 05c45db70..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogItem/LogItem.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useLogStore } from "pages/LoggerPage/useLogStore"; -import styles from "./LogItem.module.scss" -import folderClosed from "assets/svg/folder-closed.svg" -import folderOpen from "assets/svg/folder-open.svg" -import cross from "assets/svg/cross.svg" - -interface Props { - logName: string; -} - -export const LogItem = ({logName}: Props) => { - - const openLogSession = useLogStore(state => state.openLogSession); - const setOpenLogSession = useLogStore(state => state.setOpenLogSession); - const removeLogSession = useLogStore(state => state.removeLogSession); - const isOpen = openLogSession === logName; - - const handleRemoveLog = () => { - removeLogSession(logName); - } - - return ( -
setOpenLogSession(logName)}> -
- log-icon -
-
- {logName} -
-
- Remove log -
-
- ) -} diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogList.module.scss b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogList.module.scss deleted file mode 100644 index e7855b329..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogList.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.logListWrapper { - display: flex; - flex-direction: column; - align-items: center; -} \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogList.tsx b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogList.tsx deleted file mode 100644 index eb6c450f7..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogList/LogList.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { LogItem } from "./LogItem/LogItem"; -import styles from "./LogList.module.scss" - -interface Props { - logNames: string[]; -} - -export const LogList = ({logNames}: Props) => { - return ( -
- {logNames.map((logName) => ( - - ))} -
- ) -} diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogLoader.module.scss b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogLoader.module.scss deleted file mode 100644 index 09d378956..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogLoader.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.logLoaderWrapper { - height: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; -} \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogLoader.tsx b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogLoader.tsx deleted file mode 100644 index a0e70d54b..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogLoader.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Dropzone } from "./Dropzone/Dropzone" -import styles from "./LogLoader.module.scss" -import { LogList } from "./LogList/LogList"; -import { useLogStore } from "pages/LoggerPage/useLogStore"; - -export enum UploadState { - IDLE = "idle", - UPLOADING = "uploading", - SUCCESS = "success", - ERROR = "error", -} - -export interface UploadInformation { - state: UploadState; - errorMessage?: string; -} - -export const LogLoader = () => { - - const logSessions = useLogStore(state => state.logSessions); - - return ( -
- - logSession.name)} /> - - - -
- ) -} diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogsProcessor.ts b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogsProcessor.ts deleted file mode 100644 index 6e722fc8d..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogLoader/LogsProcessor.ts +++ /dev/null @@ -1,108 +0,0 @@ -import Papa from 'papaparse'; - -/** - * The function `processLoggerSession` reads CSV files from a directory, parses the data, filters out - * invalid logs, and returns a map of file names to arrays of valid log entries with timestamps and - * values. - * @param {FileSystemDirectoryEntry} directory - The `directory` parameter in the - * `processLoggerSession` function is expected to be a FileSystemDirectoryEntry object representing a - * directory in the file system. This directory is used to retrieve files for processing in the - * function. - * @returns The `processLoggerSession` function returns a Promise that resolves to a Map object. This map contains entries where the key is the name of a CSV file representing - * a Measurement, and the value is an array of objects with a `time` property of type Date and - * a `value` property of type number, representing an array of points with all the data - * retrieved from the CSV file. - */ - -export type ChartPoint = {time: number, value: number}; -export type Session = Map; - -export async function extractLoggerSession (directory: FileSystemDirectoryEntry): Promise { - const session: Session = new Map(); - const files = await getFilesFromDirectory(directory); - if(files.length === 0) throw new Error("No files found in the directory."); - - for(const file of files) { - try { - if(!file.isFile) throw new Error(`Invalid entry ${file.name}. Expected a file.`); - if(!file.name.endsWith(".csv")) throw new Error(`Invalid file ${file.name}. Expected a CSV file.`); - (file as FileSystemFileEntry).file((file) => { - Papa.parse(file, { - complete: (result) => { - const measurementPoints = [] as ChartPoint[]; - let lastTimestamp = 0; - for(const row of result.data) { - if(isValidLog(row)) { - const [timestamp, , , value] = row as [string, string, string, string]; - if(parseInt(timestamp) <= lastTimestamp) continue; - measurementPoints.push({ - time: parseFloat(timestamp), - value: parseFloat(value) - }); - lastTimestamp = parseInt(timestamp); - } - } - session.set(file.name.replace(".csv", ""), measurementPoints); - }, - error: (err) => { - throw new Error(`Error parsing file ${file.name}. ${err}`); - }, - header: false - }); - }); - } catch(err) { - if(err instanceof Error) { - console.error(err.message); - } else { - console.error("An unexpected error occurred"); - } - } - }; - - return session; -}; - -/** - * The function `getFilesFromDirectory` asynchronously retrieves the entries (files and directories) - * from a given directory in a file system. - * @param {FileSystemDirectoryEntry} folder - FileSystemDirectoryEntry - Represents a directory in the - * file system. - * @returns The function `getFilesFromDirectory` returns a Promise that resolves to an array of - * `FileSystemEntry` objects representing the files in the specified directory. - */ - -export async function getFilesFromDirectory(folder: FileSystemDirectoryEntry): Promise { - const entries: FileSystemEntry[] = []; - - async function readEntries(reader: FileSystemDirectoryReader) { - const result = await new Promise((resolve, reject) => { - reader.readEntries((results) => { - resolve(results); - }, (err) => { - reject(err); - }); - }); - - if (result.length > 0) { - entries.push(...result); - await readEntries(reader); - } - } - - await readEntries(folder.createReader()); - - return entries; -} - - -function isValidLog(row: unknown) { - return ( - Array.isArray(row) && - row.length === 4 && - // new Date(row[0]).to() !== "Invalid Date" && - typeof row[1] === "string" && - typeof row[2] === "string" && - !isNaN(parseFloat(row[3])) - ); -} \ No newline at end of file diff --git a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogsColumn.tsx b/ethernet-view/src/pages/LoggerPage/LogsColumn/LogsColumn.tsx deleted file mode 100644 index e66c5c414..000000000 --- a/ethernet-view/src/pages/LoggerPage/LogsColumn/LogsColumn.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { TabItem } from "layouts/TabLayout/TabItem"; -import { TabLayout } from "layouts/TabLayout/TabLayout" -import { LogLoader } from "./LogLoader/LogLoader"; - -export const LogsColumn = () => { - - const logsColumnTabItems : TabItem[] = [ - { - id: "logs", - name: "Logs", - icon: null, - component: - } - ]; - - return -} diff --git a/ethernet-view/src/pages/LoggerPage/useLogStore.ts b/ethernet-view/src/pages/LoggerPage/useLogStore.ts deleted file mode 100644 index 48e0c9303..000000000 --- a/ethernet-view/src/pages/LoggerPage/useLogStore.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { create } from "zustand"; -import { ChartPoint } from "./LogsColumn/LogLoader/LogsProcessor"; - -export interface LogSession { - name: string; - measurementLogs: Map; -} -export type LogSessionCollection = LogSession[]; - -type LogStore = { - logSessions: LogSessionCollection; - openLogSession: string; - setOpenLogSession: (logSession: string) => void; - addLogSession: (log: LogSession) => void; - clearLogSessions: () => void; - removeLogSession: (logName: string) => void; -}; - - -/* - Zustand store for managing all the logger sessions uploaded. - It is the nexo between the LogLoader and the LogsColumn components. -*/ -export const useLogStore = create((set, get) => ({ - logSessions: [], - openLogSession: "", - setOpenLogSession: (logSession: string) => { - set((state) => ({ ...state, openLogSession: logSession })); - }, - addLogSession: (log: LogSession) => { - if (get().logSessions.find((logSession) => logSession.name === log.name)) return; - set((state) => ({ logSessions: [...state.logSessions, log] })); - }, - clearLogSessions: () => set({ logSessions: [] }), - removeLogSession: (logName: string) => { - set((state) => ({ logSessions: state.logSessions.filter((logSession) => logSession.name !== logName) })); - } -})); diff --git a/ethernet-view/src/pages/TestingPage/ChartsColumn/ChartsColumn.tsx b/ethernet-view/src/pages/TestingPage/ChartsColumn/ChartsColumn.tsx deleted file mode 100644 index b7b3db212..000000000 --- a/ethernet-view/src/pages/TestingPage/ChartsColumn/ChartsColumn.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { TabLayout } from "layouts/TabLayout/TabLayout"; -import { ChartMenu } from "components/ChartMenu/ChartMenu"; -import { ReactComponent as Chart } from "assets/svg/chart.svg"; -import { useMemo } from "react"; -import { useMeasurementsStore, usePodDataStore, useSubscribe } from "common"; -import { createSidebarSections } from "components/ChartMenu/sidebar"; - -export const ChartsColumn = () => { - const podData = usePodDataStore(state => state.podData); - const updatePodData = usePodDataStore(state => state.updatePodData) - const updateMeasurements = useMeasurementsStore(state => state.updateMeasurements) - - useSubscribe("podData/update", (update) => { - updatePodData(update); - updateMeasurements(update); - }); - - const sections = useMemo(() => { - return createSidebarSections(podData); - }, []); - - const chartsColumnTabItems = [ - { - id: "charts", - name: "Charts", - icon: , - component: , - }, - ]; - - return ; -}; \ No newline at end of file diff --git a/ethernet-view/src/pages/TestingPage/MessagesColumn/MessagesColumn.module.scss b/ethernet-view/src/pages/TestingPage/MessagesColumn/MessagesColumn.module.scss deleted file mode 100644 index afd765e76..000000000 --- a/ethernet-view/src/pages/TestingPage/MessagesColumn/MessagesColumn.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.messageColumnWrapper { - height: 100%; - display: flex; - flex-direction: column; - gap: 1rem; -} diff --git a/ethernet-view/src/pages/TestingPage/MessagesColumn/MessagesColumn.tsx b/ethernet-view/src/pages/TestingPage/MessagesColumn/MessagesColumn.tsx deleted file mode 100644 index 88aec79d0..000000000 --- a/ethernet-view/src/pages/TestingPage/MessagesColumn/MessagesColumn.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import styles from "pages/TestingPage/MessagesColumn/MessagesColumn.module.scss"; -import { SplitLayout } from "layouts/SplitLayout/SplitLayout"; -import { Orientation } from "hooks/useSplit/Orientation"; -import { TabLayout } from "layouts/TabLayout/TabLayout"; -import { BiLineChart } from "react-icons/bi"; -import { nanoid } from "nanoid"; -import { MessagesContainer } from "components/MessagesContainer/MessagesContainer"; -import { Logger } from "components/Logger/Logger"; -import { useRef } from "react"; -import { Connections } from "common"; -import { BootloaderContainer } from "components/BootloaderContainer/BootloaderContainer"; -import letter from "assets/svg/letter.svg" -import connection from "assets/svg/connection.svg" - -export const MessagesColumn = () => { - const messagesTabItems = useRef([ - { - id: nanoid(), - name: "Messages", - icon: , - - component: , - }, - ]); - - const connectionsTabItems = useRef([ - { - id: nanoid(), - name: "Connections", - icon: , - - component: , - } - ]) - - return ( -
- , - collapsedIcon: letter, - }, - { - component: , - collapsedIcon: connection, - }, - ]} - orientation={Orientation.VERTICAL} - > - - -
- ); -}; diff --git a/ethernet-view/src/pages/TestingPage/OrderColumn/OrderColumn.tsx b/ethernet-view/src/pages/TestingPage/OrderColumn/OrderColumn.tsx deleted file mode 100644 index 145c64c58..000000000 --- a/ethernet-view/src/pages/TestingPage/OrderColumn/OrderColumn.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { TabLayout } from "layouts/TabLayout/TabLayout"; -import { nanoid } from "nanoid"; -import { OrdersContainer } from "components/OrdersContainer/OrdersContainer"; -import { useRef } from "react"; -import { ReactComponent as OutgoingMessage } from "assets/svg/outgoing-message.svg"; - -export const OrderColumn = () => { - const orderTabItems = useRef([ - { - id: nanoid(), - name: "Orders", - icon: , - component: , - }, - ]); - - return ; -}; diff --git a/ethernet-view/src/pages/TestingPage/ReceiveColumn/ReceiveColumn.module.scss b/ethernet-view/src/pages/TestingPage/ReceiveColumn/ReceiveColumn.module.scss deleted file mode 100644 index ff1a5f0df..000000000 --- a/ethernet-view/src/pages/TestingPage/ReceiveColumn/ReceiveColumn.module.scss +++ /dev/null @@ -1,8 +0,0 @@ -.loadingMessages { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - font-size: 2rem; -} diff --git a/ethernet-view/src/pages/TestingPage/ReceiveColumn/ReceiveColumn.tsx b/ethernet-view/src/pages/TestingPage/ReceiveColumn/ReceiveColumn.tsx deleted file mode 100644 index fe40bbb8b..000000000 --- a/ethernet-view/src/pages/TestingPage/ReceiveColumn/ReceiveColumn.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { TabLayout } from "layouts/TabLayout/TabLayout"; -import { ReactComponent as IncomingMessage } from "assets/svg/incoming-message.svg"; -import { ReceiveTable } from "components/ReceiveTable/ReceiveTable"; -import { usePodDataStore } from "common"; - -export const ReceiveColumn = () => { - const podData = usePodDataStore(state => state.podData) - - const receiveColumnTabItems = [ - { - id: "receiveTable", - name: "Packets", - icon: , - component: ( - - ), - }, - ] - - return ; -}; \ No newline at end of file diff --git a/ethernet-view/src/pages/TestingPage/TestingPage.module.scss b/ethernet-view/src/pages/TestingPage/TestingPage.module.scss deleted file mode 100644 index e91380518..000000000 --- a/ethernet-view/src/pages/TestingPage/TestingPage.module.scss +++ /dev/null @@ -1,19 +0,0 @@ -@use "src/styles/styles"; - -#wrapper { - width: 100%; - height: 100%; - display: grid; - grid-template: ". body" / auto 1fr; -} - -#wrapper > * { - min-height: 0; - min-width: 0; -} - -#body { - grid-area: body; - width: 100%; - height: 100%; -} diff --git a/ethernet-view/src/pages/TestingPage/TestingPage.tsx b/ethernet-view/src/pages/TestingPage/TestingPage.tsx deleted file mode 100644 index e16f6eee3..000000000 --- a/ethernet-view/src/pages/TestingPage/TestingPage.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { SplitLayout } from "layouts/SplitLayout/SplitLayout"; -import { Orientation } from "hooks/useSplit/Orientation"; -import { ReceiveColumn } from "pages/TestingPage/ReceiveColumn/ReceiveColumn"; -import { OrderColumn } from "pages/TestingPage/OrderColumn/OrderColumn"; -import { MessagesColumn } from "pages/TestingPage/MessagesColumn/MessagesColumn"; -import { ChartsColumn } from "./ChartsColumn/ChartsColumn"; -import styles from "pages/TestingPage/TestingPage.module.scss"; -import incomingMessage from "assets/svg/incoming-message.svg"; -import paperAirplane from "assets/svg/paper-airplane.svg"; -import outgoingMessage from "assets/svg/outgoing-message.svg"; -import chart from "assets/svg/chart.svg"; - -export const TestingPage = () => { - const components = [ - { - component: , - collapsedIcon: chart, - }, - { - component: , - collapsedIcon: incomingMessage, - }, - { - component: , - collapsedIcon: paperAirplane, - }, - { - component: , - collapsedIcon: outgoingMessage, - }, - ]; - - return ( -
-
- -
-
- ); -}; diff --git a/ethernet-view/src/services/public/images/logo-white.png b/ethernet-view/src/services/public/images/logo-white.png deleted file mode 100644 index d11903fc9..000000000 Binary files a/ethernet-view/src/services/public/images/logo-white.png and /dev/null differ diff --git a/ethernet-view/src/services/public/svg/tab-edge.svg b/ethernet-view/src/services/public/svg/tab-edge.svg deleted file mode 100644 index c3380d31d..000000000 --- a/ethernet-view/src/services/public/svg/tab-edge.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - diff --git a/ethernet-view/src/services/useBootloader.ts b/ethernet-view/src/services/useBootloader.ts deleted file mode 100644 index ef317b445..000000000 --- a/ethernet-view/src/services/useBootloader.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { useWsHandler } from "common"; -import { nanoid } from "nanoid"; - -export type BootloaderUpload = { board: string; file: File }; - -export function useBootloader( - onDownloadSuccess: (file: File) => void, - onDownloadFailure: () => void, - onSendSuccess: () => void, - onSendFailure: () => void, - onProgress: (progress: number) => void // progress between 0 and 100 -) { - const handler = useWsHandler(); - - //TODO: timeout if it takes to long - const uploader = (board: string, file: string) => { - const id = nanoid(); - - handler.exchange("blcu/upload", { board, file }, id, (res, _, end) => { - if (res.percentage == 100) { - onSendSuccess(); - end(); - } else if (res.failure) { - onSendFailure(); - end(); - } else { - onProgress(res.percentage); - } - }); - }; - - //TODO: timeout if it takes to long - const downloader = (board: string) => { - const id = nanoid(); - handler.exchange("blcu/download", { board }, id, (res, _, end) => { - if (res.percentage == 100) { - onDownloadSuccess(new File([res.file], "program")); - end(); - } else if (res.failure) { - onDownloadFailure(); - end(); - } else { - onProgress(res.percentage); - } - }); - }; - - return { uploader, downloader } as const; -} diff --git a/ethernet-view/src/store/columnsStore.ts b/ethernet-view/src/store/columnsStore.ts deleted file mode 100644 index 0aa1fc3ed..000000000 --- a/ethernet-view/src/store/columnsStore.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { create } from "zustand"; - -export interface ColumnsStore { - columnSizes: string[]; - setColumnSizes: (setColumnSizes: string[]) => void; -}; - -// Zustand store for keeping track of column sizes. -// It is useful to the layout of the application. -export const useColumnsStore = create((set) => ({ - columnSizes: ["30%", "10%", "20%", "20%", "20%"] as string[], - setColumnSizes: (columnSizes: string[]) => set({ columnSizes }), -})); \ No newline at end of file diff --git a/ethernet-view/src/styles/colors.scss b/ethernet-view/src/styles/colors.scss deleted file mode 100644 index 427958611..000000000 --- a/ethernet-view/src/styles/colors.scss +++ /dev/null @@ -1,149 +0,0 @@ -@use "sass:color"; -@use "sass:map"; - -// Base colors -$colors: ( - "primary": hsl(212, 65%, 50%), - "secondary": hsl(25, 86%, 53%), - "surface": hsl(212, 22%, 88%), - "neutral": hsl(212, 15%, 50%), - "success": hsl(120, 60%, 50%), - "warning": hsl(45, 90%, 50%), - "error": hsl(0, 70%, 50%), - "info": hsl(200, 70%, 50%), -); - -// Generate color variations -@each $name, $color in $colors { - @for $i from 0 through 100 { - :root { - --color-#{$name}-#{$i}: #{color.adjust( - $color, - $lightness: $i * 1% - 50% - )}; - } - } -} - -// Light theme -:root, -:root[data-theme="light"] { - // Backgrounds - --theme-background: #dce3eb; - --theme-surface: #ffffff; - --theme-surface-variant: #f5f5f5; - --theme-surface-hover: #f0f0f0; - - // Text - --theme-text-primary: var(--color-neutral-15); - --theme-text-secondary: var(--color-neutral-30); - --theme-text-tertiary: var(--color-neutral-45); - --theme-text-disabled: var(--color-neutral-60); - - // Borders - --theme-border: var(--color-neutral-85); - --theme-border-variant: var(--color-neutral-90); - --theme-border-hover: var(--color-neutral-75); - - // Primary colors - --theme-primary: var(--color-primary-45); - --theme-primary-hover: var(--color-primary-40); - --theme-primary-surface: var(--color-primary-95); - --theme-primary-text: var(--color-primary-99); - - // Secondary colors - --theme-secondary: var(--color-secondary-50); - --theme-secondary-hover: var(--color-secondary-45); - --theme-secondary-surface: var(--color-secondary-90); - - // Status colors - --theme-success: var(--color-success-40); - --theme-warning: var(--color-warning-45); - --theme-error: var(--color-error-45); - --theme-info: var(--color-info-45); - - // Shadows - --theme-shadow-color: rgba(0, 0, 0, 0.1); - --theme-shadow-sm: 0 1px 2px var(--theme-shadow-color); - --theme-shadow-md: 0 4px 6px var(--theme-shadow-color); - --theme-shadow-lg: 0 10px 15px var(--theme-shadow-color); - - // Components - --theme-navbar-bg: #f8f9fa; - --theme-island-bg: var(--theme-surface); - --theme-input-bg: var(--theme-surface); - --theme-button-bg: var(--theme-primary); - --theme-button-text: var(--theme-primary-text); - - // Charts - --theme-chart-grid: var(--color-neutral-90); - --theme-chart-text: var(--theme-text-secondary); -} - -// Dark theme -:root[data-theme="dark"] { - // Backgrounds - --theme-background: var(--color-neutral-10); - --theme-surface: var(--color-neutral-15); - --theme-surface-variant: var(--color-neutral-18); - --theme-surface-hover: var(--color-neutral-20); - - // Text - --theme-text-primary: var(--color-neutral-90); - --theme-text-secondary: var(--color-neutral-75); - --theme-text-tertiary: var(--color-neutral-60); - --theme-text-disabled: var(--color-neutral-40); - - // Borders - --theme-border: var(--color-neutral-25); - --theme-border-variant: var(--color-neutral-20); - --theme-border-hover: var(--color-neutral-35); - - // Primary colors - --theme-primary: var(--color-primary-60); - --theme-primary-hover: var(--color-primary-65); - --theme-primary-surface: var(--color-primary-20); - --theme-primary-text: var(--color-neutral-10); - - // Secondary colors - --theme-secondary: var(--color-secondary-60); - --theme-secondary-hover: var(--color-secondary-65); - --theme-secondary-surface: var(--color-secondary-20); - - // Status colors - --theme-success: var(--color-success-60); - --theme-warning: var(--color-warning-60); - --theme-error: var(--color-error-60); - --theme-info: var(--color-info-60); - - // Shadows - --theme-shadow-color: rgba(0, 0, 0, 0.3); - --theme-shadow-sm: 0 1px 2px var(--theme-shadow-color); - --theme-shadow-md: 0 4px 6px var(--theme-shadow-color); - --theme-shadow-lg: 0 10px 15px var(--theme-shadow-color); - - // Components - --theme-navbar-bg: rgba(10, 25, 47, 0.85); - --theme-island-bg: var(--theme-surface); - --theme-input-bg: var(--color-neutral-20); - --theme-button-bg: var(--theme-primary); - --theme-button-text: var(--theme-primary-text); - - // Charts - --theme-chart-grid: var(--color-neutral-25); - --theme-chart-text: var(--theme-text-secondary); -} - -// Helper functions -@function getThemeColor($name) { - @return var(--theme-#{$name}); -} - -@function getColor($name, $lightness) { - @return var(--color-#{$name}-#{$lightness}); -} - -// Export for use in other files -:export { - theme: true; -} diff --git a/ethernet-view/src/styles/fonts.scss b/ethernet-view/src/styles/fonts.scss deleted file mode 100644 index e6900a754..000000000 --- a/ethernet-view/src/styles/fonts.scss +++ /dev/null @@ -1,3 +0,0 @@ -@forward "/src/assets/fonts/Inter/Inter.scss"; -@forward "/src/assets/fonts/Consolas/Consolas.scss"; -@forward "/src/assets/fonts/NotoColorEmoji/NotoColorEmoji.scss"; diff --git a/ethernet-view/src/styles/globalOverride.scss b/ethernet-view/src/styles/globalOverride.scss deleted file mode 100644 index 7bf719ae4..000000000 --- a/ethernet-view/src/styles/globalOverride.scss +++ /dev/null @@ -1,64 +0,0 @@ -@use "src/styles/styles"; -@use "src/styles/colors" as colors; - -input { - margin: 0; -} - -td { - padding: 0; -} - -ul { - padding-left: 0; - margin: 0; -} - -hr { - margin-top: 0rem; - margin-bottom: 0rem; - height: 1px; - border: none; - background-color: colors.getThemeColor("border"); -} - -code { - @include styles.alternate-code-text; - font-weight: bold; - background-color: colors.getThemeColor("surface-variant"); - padding: 0.1rem 0.2rem; - border-radius: 0.3rem; -} - -//If it's only :root, font-size changes the base rem. If it's :root *, it affects everything, skipping inheritance. -:root > * { - box-sizing: border-box; - @include styles.normal-text; - scrollbar-track-color: transparent; - scrollbar-color: colors.getThemeColor("text-tertiary") none; - user-select: none; -} - -:root > * input { - outline: none; - border-style: solid; -} - -::-webkit-scrollbar { - width: 7px; - height: 7px; - background-color: colors.getThemeColor("surface"); -} - -::-webkit-scrollbar-track { - background-color: transparent; -} - -::-webkit-scrollbar-thumb { - background: colors.getThemeColor("border"); - border-radius: 1rem; -} - -::-webkit-scrollbar-thumb:hover { - background: colors.getThemeColor("border-hover"); -} diff --git a/ethernet-view/src/styles/styles.scss b/ethernet-view/src/styles/styles.scss deleted file mode 100644 index a7b6171a9..000000000 --- a/ethernet-view/src/styles/styles.scss +++ /dev/null @@ -1,123 +0,0 @@ -@use "sass:color"; -@use "./colors.scss" as colors; - -// COLORS (now using theme system) -$background-color: colors.getThemeColor("background"); -$title-color: colors.getThemeColor("text-primary"); -$normal-text-color: colors.getThemeColor("text-primary"); -$alternate-text-color: colors.getThemeColor("text-secondary"); -$orange: colors.getColor("secondary", 50); -$blue: colors.getColor("primary", 50); -$base-color: colors.getColor("primary", 50); - -$dark-normal-text-color: colors.getThemeColor("text-secondary"); - -@function getColor($name, $lightness) { - @return colors.getColor($name, $lightness); -} - -// FONTS -$sans-font: Inter; -$code-font: Consolas, Cascadia Code, Cascadia Mono, Monospace; -$alternate-code-font: Consolas, Monospace; - -// FONT-SIZE -$title-font-size: 1.9rem; -$normal-font-size: 1rem; -$small-font-size: x-small; - -// FONT-WEIGHT -$bold-font-weight: 700; -$normal-font-weight: 400; - -// PADDING -$large-padding: 2rem; -$normal-padding: 1.2rem; - -// BORDER-RADIUS -$large-border-radius: 1rem; -$normal-border-radius: 0.2rem; - -// BORDER-WIDTH -$normal-border-width: 1px; - -// TRANSITIONS -$normal-transition-time: 0.08s; -$opacity-transition: opacity $normal-transition-time linear; -$background-color-transition: background-color $normal-transition-time linear; - -// MIXINS -@mixin code-text { - font-family: $code-font; - font-size: $normal-font-size; - color: $normal-text-color; -} - -@mixin alternate-code-text { - font-family: $alternate-code-font; - font-size: $normal-font-size; - color: $alternate-text-color; -} - -@mixin title-text { - font-family: $sans-font; - font-size: $title-font-size; - color: $title-color; - font-weight: $bold-font-weight; -} - -@mixin normal-text { - font-family: $sans-font; - font-size: $normal-font-size; - color: $alternate-text-color; - font-weight: $normal-font-weight; -} - -@mixin subtitle-text { - font-family: $code-font; - font-size: $normal-font-size; - font-style: italic; - color: colors.getThemeColor("text-tertiary"); -} - -@mixin tab-text { - font-family: $sans-font; - font-size: $normal-font-size; - font-weight: 500; - color: $title-color; -} - -@mixin inherit-text { - font-family: inherit; - font-size: inherit; - color: inherit; - font-weight: inherit; -} - -@mixin undraggable { - user-drag: none; - user-select: none; - -moz-user-select: none; - -webkit-user-drag: none; - -webkit-user-select: none; - -ms-user-select: none; -} - -@mixin shadow { - box-shadow: colors.getThemeColor("shadow-md"); -} - -// CLASSES - -// FUNCTIONS -@function transparency($color, $transparency) { - @return color.adjust($color, $alpha: $transparency); -} - -@function lightness($color, $lightness) { - @return color.adjust($color, $lightness: $lightness); -} - -@function saturation($color, $saturation) { - @return color.adjust($color, $saturation: $saturation); -} diff --git a/ethernet-view/src/types/ConfigData.ts b/ethernet-view/src/types/ConfigData.ts deleted file mode 100644 index 71b81fb0d..000000000 --- a/ethernet-view/src/types/ConfigData.ts +++ /dev/null @@ -1,38 +0,0 @@ -export type ConfigData = { - vehicle: { - boards: string[]; - }; - adj: { - branch: string; - }; - network: { - manual: boolean; - }; - transport: { - propagate_fault: boolean; - }; - tcp: { - backoff_min_ms: number; - backoff_max_ms: number; - backoff_multiplier: number; - max_retries: number; - connection_timeout_ms: number; - keep_alive_ms: number; - }; - blcu: { - ip: string; - download_order_id: number; - upload_order_id: number; - }; - tftp: { - block_size: number; - retries: number; - timeout_ms: number; - backoff_factor: number; - enable_progress: boolean; - }; - logging: { - time_unit: string; - logging_path: string; - }; -}; diff --git a/ethernet-view/src/utils/array.ts b/ethernet-view/src/utils/array.ts deleted file mode 100644 index a62cbb9c2..000000000 --- a/ethernet-view/src/utils/array.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function mustFindIndex( - elements: T[], - predicate: (element: T) => boolean -): number { - const index = elements.findIndex((element) => predicate(element)); - - if (index == -1) { - console.error("element not found"); - } - - return index; -} diff --git a/ethernet-view/src/utils/color.ts b/ethernet-view/src/utils/color.ts deleted file mode 100644 index d8212daf3..000000000 --- a/ethernet-view/src/utils/color.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { clamp } from "utils/math"; - -export type HSLAColor = { - h: number; - s: number; - l: number; - a: number; -}; - -type RGBAColor = { - r: number; - g: number; - b: number; - a: number; -}; - -export function hslaToHex({ h, s, l, a }: HSLAColor): RGBAColor { - l /= 100; - const a2 = (s * Math.min(l, 1 - l)) / 100; - const f = (n: number) => { - const k = (n + h / 30) % 12; - const color = l - a2 * Math.max(Math.min(k - 3, 9 - k, 1), -1); - return Math.round(255 * color) - .toString(16) - .padStart(2, "0"); // convert to Hex and prefix "0" if needed - }; - return { - r: parseInt(f(0)), - g: parseInt(f(8)), - b: parseInt(f(4)), - a: a, - }; -} - -export function getSofterHSLAColor( - { h, s, l, a }: HSLAColor, - lightnessOffset: number -): HSLAColor { - return { h, s, l: clamp(l + lightnessOffset, 0, 100), a: a } as HSLAColor; -} - -export function hslaToString({ h, s, l, a }: HSLAColor): string { - return `hsl(${h},${s}%,${l}%,${a})`; -} - -export function parseHSL(colorStr: string): HSLAColor { - const matches = colorStr - .replaceAll(" ", "") - .match(/hsl\((\d{1,3}),(\d{1,3})%,(\d{1,3})%\)/)!; - const h = parseInt(matches[1]); - const s = parseInt(matches[2]); - const l = parseInt(matches[3]); - - return { h, s, l, a: 1 }; -} - -export function lightenHSL(color: string, lOffset: number): string { - const matches = color - .replaceAll(" ", "") - .match(/hsl\((\d{1,3}),(\d{1,3})%,(\d{1,3})%\)/)!; - const h = matches[1]; - const s = matches[2]; - const l = matches[3]; - const newLightness = Math.min(parseInt(l) + lOffset, 100); - return `hsl(${h}, ${s}%, ${newLightness}%)`; -} \ No newline at end of file diff --git a/ethernet-view/src/utils/math.ts b/ethernet-view/src/utils/math.ts deleted file mode 100644 index 256e703e0..000000000 --- a/ethernet-view/src/utils/math.ts +++ /dev/null @@ -1,29 +0,0 @@ -export function getVectorLimits(vector: number[]): [number, number] { - let min = Infinity; - let max = -Infinity; - - vector.forEach((value) => { - min = value < min ? value : min; - max = value > max ? value : max; - }); - - return [min, max]; -} - -export function getMultipleVectorsLimits(vectors: number[][]) { - let [minOfLines, maxOfLines] = [Infinity, -Infinity]; - for (let vector of vectors) { - const [localMin, localMax] = getVectorLimits(vector); - if (localMin < minOfLines) { - minOfLines = localMin; - } - if (localMax > maxOfLines) { - maxOfLines = localMax; - } - } - return [minOfLines, maxOfLines]; -} - -export function clamp(value: number, min: number, max: number): number { - return Math.min(Math.max(min, value), max); -} diff --git a/ethernet-view/src/vite-env.d.ts b/ethernet-view/src/vite-env.d.ts deleted file mode 100644 index 866caf79b..000000000 --- a/ethernet-view/src/vite-env.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -/// -/// - -import type { ConfigData } from "./types/ConfigData"; - -interface ElectronAPI { - saveConfig: (config: ConfigData) => Promise; - getConfig: () => Promise; - importConfig: () => Promise; - selectFolder: () => Promise; -} - -declare global { - interface Window { - electronAPI?: ElectronAPI; - } -} - -export {}; diff --git a/ethernet-view/tsconfig.json b/ethernet-view/tsconfig.json deleted file mode 100644 index 7785f9244..000000000 --- a/ethernet-view/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "baseUrl": "src", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/ethernet-view/tsconfig.node.json b/ethernet-view/tsconfig.node.json deleted file mode 100644 index 9d31e2aed..000000000 --- a/ethernet-view/tsconfig.node.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "module": "ESNext", - "moduleResolution": "Node", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/ethernet-view/vite.config.ts b/ethernet-view/vite.config.ts deleted file mode 100644 index 78bed8cc4..000000000 --- a/ethernet-view/vite.config.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// -import { defineConfig } from "vite"; -import path from "path"; -import react from "@vitejs/plugin-react"; -import tsconfigPaths from "vite-tsconfig-paths"; -import svgr from "vite-plugin-svgr"; - -// https://vitejs.dev/config/ -export default defineConfig({ - base: "./", // Add this line for relative paths - build: { - sourcemap: true, - outDir: "static", - minify: false, - }, - plugins: [react(), tsconfigPaths(), svgr()], - test: { - globals: true, - environment: "jsdom", - setupFiles: "./src/tests/setup.ts", - }, - resolve: { - alias: { - common: path.resolve(__dirname, "../common-front"), - }, - }, -}); diff --git a/frontend/README.md b/frontend/README.md index 1a9792b7f..f867ea4ac 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,97 +1,222 @@ # Frontend -This is a monorepo workspace managed with **pnpm** and **Turborepo**. +> **Note:** For general setup, prerequisites, and root-level commands, see the [main README](../README.md). -## โš ๏ธ Important: Package Manager +This directory contains the frontend workspace for the Hyperloop Control Station, built with React, TypeScript, and Vite. -**You MUST use `pnpm` for this project.** Do not use `npm`, `yarn`, or `bun`. +## Architecture Overview -The project is configured to enforce pnpm usage. If you don't have pnpm installed, install it first: +The frontend is organized as 6 workspaces out of 9 in the whole monorepo, divided into 3 main areas: -```bash -npm install -g pnpm +### Workspaces + +| Workspace | Description | +| :----------------------------------------------------------------- | :---------------------------------------------------- | +| `testing-view` | Primary telemetry testing and debugging interface | +| `competition-view` | Competition-focused UI (simplified view) | +| `frontend-kit/ui` or `@workspace/ui` | Component library built on shadcn/ui and Radix UI | +| `frontend-kit/core` or `@workspace/core` | Shared business logic, WebSocket utilities, and types | +| `frontend-kit/eslint-config` or `@workspace/eslint-config` | Common ESLint configurations | +| `frontend-kit/typescript-config` or `@workspace/typescript-config` | Common TypeScript configurations | + +## Key Technologies + +- **React 19** with TypeScript +- **Vite** for build tooling +- **Zustand** for state management +- **React Router** for navigation +- **Radix UI / shadcn/ui** for UI components +- **WebSocket** for real-time backend communication +- **@dnd-kit** for drag-and-drop functionality + +## State Management + +The application uses **Zustand** with a slice-based architecture: + +- `workspacesSlice` - Manages workspaces, filters, charts, and tabs +- `catalogSlice` - Stores telemetry and command catalogs +- `telemetrySlice` - Real-time telemetry data +- `messagesSlice` - System messages and logs +- `appSlice` - Application mode and settings +- `rightSidebarSlice` - UI state for sidebar panels + +### Workspace System + +Testing View supports multiple workspaces to organize different testing scenarios. Each workspace has: + +- Independent filters for commands and telemetry +- Separate chart configurations +- Isolated tab state and expanded items +- Persistent configuration + +## WebSocket Integration + +The frontend connects to the Go backend via WebSocket for real-time communication: + +- **Connection**: `useWebSocket` hook from `@workspace/core` +- **Topics**: Subscribe to specific data streams using `useTopic` + - `podData/update` - Telemetry data + - `connection/update` - Backend connection status + - `message/update` - System messages +- **Sending packets**: Send a message through websocket using `post` method from `socketService` + +Example: + +```tsx +import { useTopic, useWebSocket } from "@workspace/ui/hooks"; +import { socketService } from "@workspace/core"; + +const { isConnected } = useWebSocket(); + +useTopic("podData/update", (data) => { + // Handle telemetry data +}); + +socketService.post("order/send", ); ``` -OR +## Component Library (@workspace/ui) -```bash -npm install --global corepack@latest -corepack enable pnpm +The shared UI package provides: + +- shadcn/ui components (Button, Dialog, Dropdown, etc.) +- Custom components (Sidebar, Charts, Filters) +- Hooks (useWebSocket, useTopic, useLogger) +- Icons from Lucide React + +Import components from `@workspace/ui`: + +```tsx +import { Button, Dialog } from "@workspace/ui"; +import { Plus, Settings } from "@workspace/ui/icons"; ``` -For other cases, read: [Official pnpm installation guide](https://pnpm.io/installation) +## Development Patterns -## Getting Started +### Styling & Theming -All commands should be executed from the `frontend` folder. +- **CSS Variables** for theming (defined in `globals.css`) +- **Tailwind CSS** for utility classes +- **Dark mode** support via CSS class toggling +- Multiple color schemes (default, pink, etc.) -### Installation +### Adding Icons -Install all dependencies: +To add a new Lucide icon to the shared UI library: -```bash -pnpm install +1. Look up the icon on [Lucide Icons](https://lucide.dev/icons) +2. Find the **first category** listed for that icon +3. Add the import to the corresponding category file in `frontend-kit/ui/src/icons/` +4. If the category file doesn't exist, create it and add its export to `index.ts` +5. Keep the same alphabetical order as the icon categories + +**Example:** The `Axe` icon's first category is `Tools`, so you would add its import to `tools.ts`: + +```js +// frontend-kit/ui/src/icons/tools.ts +export { Axe, Hammer } from "lucide-react"; ``` -### Available Scripts +## Project Structure -Run these commands from the `frontend` folder: +``` +frontend/ +โ”œโ”€โ”€ frontend-kit/ +โ”‚ โ”œโ”€โ”€ ui/ # Shared UI components +โ”‚ โ”‚ โ”œโ”€โ”€ src/components/ # React components +โ”‚ โ”‚ โ”œโ”€โ”€ src/hooks/ # Custom hooks +โ”‚ โ”‚ โ””โ”€โ”€ src/styles/ # Global styles +โ”‚ โ”œโ”€โ”€ core/ # Business logic +โ”‚ โ”‚ โ””โ”€โ”€ src/ # WebSocket, utilities, types +โ”‚ โ”œโ”€โ”€ eslint-config/ # ESLint configs +โ”‚ โ””โ”€โ”€ typescript-config/ # TS configs +โ”œโ”€โ”€ testing-view/ +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ assets/ # Assets (images, gifs, etc.) +โ”‚ โ”‚ โ”œโ”€โ”€ components/ # UI components +โ”‚ โ”‚ โ”œโ”€โ”€ layout/ # App layout +โ”‚ โ”‚ โ”œโ”€โ”€ pages/ # Route pages +โ”‚ โ”‚ โ”œโ”€โ”€ store/ # Zustand store slices +โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ # Custom hooks +โ”‚ โ”‚ โ”œโ”€โ”€ constants/ # Config and constants +โ”‚ โ”‚ โ”œโ”€โ”€ types/ # TypeScript types +โ”‚ โ”‚ โ”œโ”€โ”€ mocks/ # Mocks +โ”‚ โ”‚ โ””โ”€โ”€ lib/ # Utilities +โ”‚ โ””โ”€โ”€ public/ # Static assets +โ””โ”€โ”€ competition-view/ + โ””โ”€โ”€ src/ # Similar structure +``` -- **`pnpm dev`** - Start development servers for all packages -- **`pnpm build`** - Build all packages -- **`pnpm lint`** - Lint all packages -- **`pnpm format`** - Format code using Prettier -- **`pnpm preview`** - Preview built versions (requires `pnpm build` run first) +## Common Tasks -### Installing Dependencies +### Adding a Dependency to a Specific Workspace -To add a new dependency to a specific project, use: +Use pnpm's `--filter` flag (run from root or frontend directory): ```bash -pnpm add --filter +pnpm add --filter testing-view ``` -For example, to add a dependency to `testing-view`: +### Running a Specific Workspace + +From root: ```bash -pnpm add --filter testing-view +pnpm dev --filter testing-view ``` -To add a dev dependency: +### Linting & Formatting ```bash -pnpm add -D --filter +pnpm lint +pnpm format ``` -### Installing shadcn/ui Components +## Testing + +- Test framework: **Vitest** +- Component testing: **@testing-library/react** -To install shadcn/ui components, use `pnpm dlx` (pnpm's equivalent to npx) with the `-c` flag to specify the components configuration file location: +Run tests: ```bash -pnpm dlx shadcn@latest add -c frontend-kit/ui +pnpm test ``` -For example, to add a button component: +## Build + +Build all frontend packages: ```bash -pnpm dlx shadcn@latest add button -c frontend-kit/ui +pnpm build ``` -The components will be installed in `frontend-kit/ui/src/components/shadcn/`. +Preview production builds: -## Project Structure +```bash +pnpm preview +``` + +## Troubleshooting + +### WebSocket Connection Issues + +- Ensure backend is running on the expected port +- Check `.env.development` for `VITE_BACKEND_URL` configuration + +### Store State Issues + +- Use Redux DevTools extension for debugging Zustand state +- Ensure getters return stable references (use constants for empty arrays/objects) -This monorepo contains: +### Component Re-render Issues -- **`frontend-kit/`** - Shared UI components and utilities - - `ui/` - shadcn/ui components and custom components - - `core/` - Core utilities - - `esling-config/` - Shared ESLint configurations - - `typescript-config/` - Shared TypeScript configurations -- **`testing-view/`** - Testing application -- **`competition-view/`** - Competition view application +- Use React DevTools Profiler to identify excessive re-renders +- Ensure Zustand selectors are properly scoped -## Requirements +## Additional Resources -- Node.js >= 20 -- pnpm >= 10.24.0 +- [React Documentation](https://react.dev/) +- [Zustand Documentation](https://github.com/pmndrs/zustand) +- [shadcn/ui Documentation](https://ui.shadcn.com/) +- [Vite Documentation](https://vitejs.dev/) diff --git a/frontend/frontend-kit/core/package.json b/frontend/frontend-kit/core/package.json index 75654f21c..38738ae85 100644 --- a/frontend/frontend-kit/core/package.json +++ b/frontend/frontend-kit/core/package.json @@ -18,5 +18,8 @@ }, "exports": { ".": "./src/index.ts" + }, + "dependencies": { + "rxjs": "^7.8.2" } } diff --git a/frontend/frontend-kit/core/src/index.ts b/frontend/frontend-kit/core/src/index.ts index c47f0b72c..c1f7d87f3 100644 --- a/frontend/frontend-kit/core/src/index.ts +++ b/frontend/frontend-kit/core/src/index.ts @@ -1 +1,4 @@ +export * from "./logger"; +export * from "./minMaxDownsample"; +export * from "./types"; export * from "./websocket"; diff --git a/frontend/frontend-kit/core/src/logger.ts b/frontend/frontend-kit/core/src/logger.ts new file mode 100644 index 000000000..23f64cdbf --- /dev/null +++ b/frontend/frontend-kit/core/src/logger.ts @@ -0,0 +1,27 @@ +type LoggerModule = "testing-view" | "competition-view" | "core" | "ui"; + +const colors = { + "testing-view": "\x1b[36m", // Cyan + "competition-view": "\x1b[35m", // Magenta + core: "\x1b[33m", // Yellow + ui: "\x1b[32m", // Green + reset: "\x1b[0m", +}; + +function createLogger(module: LoggerModule) { + const color = colors[module]; + const prefix = `[${module.toUpperCase()}]`; + + return { + log: console.log.bind(console, `${color}${prefix}${colors.reset}`), + warn: console.warn.bind(console, `${color}${prefix}${colors.reset}`), + error: console.error.bind(console, `${color}${prefix}${colors.reset}`), + }; +} + +export const logger = { + testingView: createLogger("testing-view"), + competitionView: createLogger("competition-view"), + core: createLogger("core"), + ui: createLogger("ui"), +}; diff --git a/frontend/frontend-kit/core/src/minMaxDownsample.ts b/frontend/frontend-kit/core/src/minMaxDownsample.ts new file mode 100644 index 000000000..9b4cb8685 --- /dev/null +++ b/frontend/frontend-kit/core/src/minMaxDownsample.ts @@ -0,0 +1,25 @@ +export const minMaxDownsample = (buffer: any[]) => { + let minIdx = 0; + let maxIdx = 0; + + buffer.forEach((packet, i) => { + const measurements = packet.measurementUpdates || {}; + const firstKey = Object.keys(measurements)[0]; + const val = measurements[firstKey as keyof typeof measurements]; + + if (typeof val === "number") { + if (val < (buffer[minIdx].measurementUpdates[firstKey] ?? Infinity)) + minIdx = i; + if (val > (buffer[maxIdx].measurementUpdates[firstKey] ?? -Infinity)) + maxIdx = i; + } + }); + + // 4. Return them in chronological order to maintain X-axis integrity + const result = + minIdx < maxIdx + ? [buffer[minIdx], buffer[maxIdx]] + : [buffer[maxIdx], buffer[minIdx]]; + + return result; +}; diff --git a/frontend/frontend-kit/core/src/types.ts b/frontend/frontend-kit/core/src/types.ts new file mode 100644 index 000000000..fb7538dde --- /dev/null +++ b/frontend/frontend-kit/core/src/types.ts @@ -0,0 +1,4 @@ +export interface TopicOptions { + downsample?: "min-max" | "none"; + throttle?: number; +} diff --git a/frontend/frontend-kit/core/src/websocket.ts b/frontend/frontend-kit/core/src/websocket.ts new file mode 100644 index 000000000..b8bd59dbc --- /dev/null +++ b/frontend/frontend-kit/core/src/websocket.ts @@ -0,0 +1,108 @@ +import { + asyncScheduler, + BehaviorSubject, + bufferTime, + concatMap, + filter, + from, + map, + Observable, + ReplaySubject, + shareReplay, + switchMap, + throttleTime, +} from "rxjs"; +import { webSocket, WebSocketSubject } from "rxjs/webSocket"; +import { logger } from "./logger"; +import { minMaxDownsample } from "./minMaxDownsample"; +import { type TopicOptions } from "./types"; + +class SocketService { + private socketSource$ = new ReplaySubject>(1); + public status$ = new BehaviorSubject< + "connected" | "disconnected" | "connecting" + >("disconnected"); + + public messages$: Observable = this.socketSource$.pipe( + switchMap((socket) => socket), + shareReplay(1), + ); + + private ws: WebSocketSubject | null = null; + + connect(port: number = 4000) { + if (this.ws) return; + + logger.core.log("Connecting to WebSocket..."); + this.status$.next("connecting"); + + this.ws = webSocket({ + url: `ws://127.0.0.1:${port}/backend`, + deserializer: (e) => JSON.parse(e.data), + openObserver: { + next: () => { + this.status$.next("connected"); + logger.core.log("WebSocket connected"); + }, + }, + }); + + this.ws.subscribe({ + error: (err) => { + logger.core.error("WebSocket Error:", err); + this.status$.next("disconnected"); + this.cleanup(); + }, + complete: () => { + logger.core.log("WebSocket Connection Closed. Cleaning up..."); + this.cleanup(); + }, + }); + + this.socketSource$.next(this.ws); + } + + private cleanup() { + this.ws = null; + this.status$.next("disconnected"); + } + + onTopic(topic: string, options: TopicOptions = {}) { + let pipe$ = this.messages$.pipe( + filter((msg) => msg.topic === topic), + map((msg) => msg.payload), + ); + + if (options.downsample == "min-max") { + pipe$ = pipe$.pipe( + bufferTime(options.throttle || 100), + filter((buffer) => buffer.length > 0), + concatMap((buffer) => { + if (buffer.length <= 2) return from(buffer); + + const result = minMaxDownsample(buffer); + logger.core.log( + `[Downsample] ${topic}: ${buffer.length} in -> ${result.length} out`, + ); + return from(result); + }), + ); + } + + if (options.throttle) { + pipe$ = pipe$.pipe(throttleTime(options.throttle, asyncScheduler)); + } + + return pipe$; + } + + post(topic: string, payload: any) { + if (!this.ws) { + logger.core.error("Cannot post: Socket not connected."); + return; + } + this.ws.next({ topic, payload }); + } +} + +export const socketService = new SocketService(); diff --git a/frontend/frontend-kit/core/src/websocket/connect.ts b/frontend/frontend-kit/core/src/websocket/connect.ts deleted file mode 100644 index ac6ea00fc..000000000 --- a/frontend/frontend-kit/core/src/websocket/connect.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function connect() { - console.log("[TEST] connecting to websocket"); -} diff --git a/frontend/frontend-kit/core/src/websocket/index.ts b/frontend/frontend-kit/core/src/websocket/index.ts deleted file mode 100644 index 2e22528f7..000000000 --- a/frontend/frontend-kit/core/src/websocket/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./connect"; diff --git a/frontend/frontend-kit/ui/components.json b/frontend/frontend-kit/ui/components.json index 814866f90..29e8e6d99 100644 --- a/frontend/frontend-kit/ui/components.json +++ b/frontend/frontend-kit/ui/components.json @@ -11,9 +11,9 @@ }, "iconLibrary": "lucide", "aliases": { - "components": "@/components", - "hooks": "@/hooks", - "lib": "@/lib", + "components": "@workspace/ui/components", + "hooks": "@workspace/ui/hooks/shadcn", + "lib": "@workspace/ui/lib", "utils": "@workspace/ui/lib/utils", "ui": "@workspace/ui/components/shadcn" } diff --git a/frontend/frontend-kit/ui/package.json b/frontend/frontend-kit/ui/package.json index 08c9a6a50..cb789c7ca 100644 --- a/frontend/frontend-kit/ui/package.json +++ b/frontend/frontend-kit/ui/package.json @@ -8,23 +8,37 @@ "lint": "eslint ." }, "dependencies": { - "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@workspace/core": "workspace:*", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.475.0", "next-themes": "^0.4.4", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-resizable-panels": "^3.0.6", + "rxjs": "^7.8.2", "tailwind-merge": "^3.0.1", "tw-animate-css": "^1.2.4", - "zod": "^3.24.2" + "zod": "^3.24.2", + "zustand": "^5.0.9" }, "devDependencies": { "@tailwindcss/postcss": "^4.0.8", "@turbo/gen": "^2.4.2", "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", "@workspace/eslint-config": "workspace:*", "@workspace/typescript-config": "workspace:*", "eslint": "^9.39.1", @@ -40,6 +54,8 @@ "./hooks": "./src/hooks/index.ts", "./lib/*": "./src/lib/*.ts", "./components/*": "./src/components/*.tsx", - "./hooks/*": "./src/hooks/*.ts" + "./hooks/*": "./src/hooks/*.ts", + "./store": "./src/store/index.ts", + "./icons": "./src/icons/index.ts" } } diff --git a/frontend/frontend-kit/ui/src/components/shadcn/button.tsx b/frontend/frontend-kit/ui/src/components/shadcn/button.tsx index 35c86efde..b25fb8e68 100644 --- a/frontend/frontend-kit/ui/src/components/shadcn/button.tsx +++ b/frontend/frontend-kit/ui/src/components/shadcn/button.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; import { cn } from "@workspace/ui/lib/utils"; @@ -13,11 +13,11 @@ const buttonVariants = cva( destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + "border bg-background text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + "text-foreground hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", }, size: { diff --git a/frontend/frontend-kit/ui/src/components/shadcn/card.tsx b/frontend/frontend-kit/ui/src/components/shadcn/card.tsx new file mode 100644 index 000000000..2e7392a65 --- /dev/null +++ b/frontend/frontend-kit/ui/src/components/shadcn/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@workspace/ui/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/frontend/frontend-kit/ui/src/components/shadcn/checkbox.tsx b/frontend/frontend-kit/ui/src/components/shadcn/checkbox.tsx new file mode 100644 index 000000000..bca5ecca4 --- /dev/null +++ b/frontend/frontend-kit/ui/src/components/shadcn/checkbox.tsx @@ -0,0 +1,32 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "lucide-react" + +import { cn } from "@workspace/ui/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/frontend/frontend-kit/ui/src/components/shadcn/collapsible.tsx b/frontend/frontend-kit/ui/src/components/shadcn/collapsible.tsx new file mode 100644 index 000000000..ae9fad04a --- /dev/null +++ b/frontend/frontend-kit/ui/src/components/shadcn/collapsible.tsx @@ -0,0 +1,33 @@ +"use client" + +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +function Collapsible({ + ...props +}: React.ComponentProps) { + return +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/frontend/frontend-kit/ui/src/components/shadcn/dialog.tsx b/frontend/frontend-kit/ui/src/components/shadcn/dialog.tsx new file mode 100644 index 000000000..b905322cf --- /dev/null +++ b/frontend/frontend-kit/ui/src/components/shadcn/dialog.tsx @@ -0,0 +1,143 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@workspace/ui/lib/utils" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/frontend/frontend-kit/ui/src/components/shadcn/dropdown-menu.tsx b/frontend/frontend-kit/ui/src/components/shadcn/dropdown-menu.tsx new file mode 100644 index 000000000..6c011e181 --- /dev/null +++ b/frontend/frontend-kit/ui/src/components/shadcn/dropdown-menu.tsx @@ -0,0 +1,257 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" + +import { cn } from "@workspace/ui/lib/utils" + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +} diff --git a/frontend/frontend-kit/ui/src/components/shadcn/field.tsx b/frontend/frontend-kit/ui/src/components/shadcn/field.tsx new file mode 100644 index 000000000..7d7b87450 --- /dev/null +++ b/frontend/frontend-kit/ui/src/components/shadcn/field.tsx @@ -0,0 +1,248 @@ +"use client" + +import { useMemo } from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@workspace/ui/lib/utils" +import { Label } from "@workspace/ui/components/shadcn/label" +import { Separator } from "@workspace/ui/components/shadcn/separator" + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...props} + /> + ) +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ) +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
[data-slot=field-group]]:gap-4", + className + )} + {...props} + /> + ) +} + +const fieldVariants = cva( + "group/field flex w-full gap-3 data-[invalid=true]:text-destructive", + { + variants: { + orientation: { + vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], + horizontal: [ + "flex-row items-center", + "[&>[data-slot=field-label]]:flex-auto", + "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + responsive: [ + "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto", + "@md/field-group:[&>[data-slot=field-label]]:flex-auto", + "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + }, + }, + defaultVariants: { + orientation: "vertical", + }, + } +) + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +