diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ae8aae3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.env +.git +node_modules +dist +.yarn diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9853df7 --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# Copy to .env to override compose defaults. Every value here has a working default, +# so the stack runs with no .env at all. + +# Ports published on the host. Change these if something already holds them. +TRAEFIK_HTTP_PORT=88 +TRAEFIK_DASHBOARD_PORT=8088 +API_EXTERNAL_PORT=3005 +POSTGRES_EXTERNAL_PORT=5433 +TEST_POSTGRES_EXTERNAL_PORT=5444 +KEYCLOAK_EXTERNAL_PORT=8280 + +POSTGRES_USER=postgres +POSTGRES_PASSWORD=psql +POSTGRES_DB=postgres + +KEYCLOAK_ADMIN=admin +KEYCLOAK_ADMIN_PASSWORD=admin + +DEPLOYMENT=dev +LOG_LEVEL=info,deepreefmap_api=debug + +# Off by default locally so repeated enrolment attempts while testing the desktop +# sync path do not trip the per-IP limiter. +DISABLE_RATE_LIMITING=true diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index ed68028..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:react/jsx-runtime", - "plugin:react-hooks/recommended", - "prettier" - ], - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "env": { - "browser": true, - "es2021": true - }, - "settings": { - "react": { - "version": "detect" - } - } -} diff --git a/.github/workflows/publish-container.yml b/.github/workflows/publish-container.yml index b730ce5..2c66b75 100644 --- a/.github/workflows/publish-container.yml +++ b/.github/workflows/publish-container.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v7 # Install the cosign tool # https://github.com/sigstore/cosign-installer diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..81aa3cd --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,119 @@ +name: CI + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + # Mirrors node:22.23.2-alpine in both Dockerfiles. + NODE_VERSION: '22.23.2' + +jobs: + checks: + name: type-check, lint, format, build + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out ui repository + uses: actions/checkout@v7 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + + # After setup-node, so the shims land on the Node this job runs. + - name: Enable corepack + run: corepack enable + + # Yarn 4 caches globally under ~/.yarn/berry, so ask rather than assume a path. + - name: Resolve Yarn cache folder + id: yarn-cache + run: echo "dir=$(yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" + + - name: Restore Yarn cache + uses: actions/cache@v6 + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: ${{ runner.os }}-yarn- + + # Fails when yarn.lock does not already satisfy package.json. + - name: Install dependencies + run: yarn install --immutable + + - name: Type check + run: yarn type-check + + # Not `yarn lint`, which carries --fix. CI must not rewrite the tree. + - name: Lint + run: yarn eslint ./src + + - name: Format check + run: yarn prettier --check ./src + + - name: Build + run: yarn build + + contract: + # A stale api.d.ts means the console's types disagree with the server it calls. + name: contract types are current + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out ui repository + uses: actions/checkout@v7 + + # Public repo, so the default token suffices. Only contract/ is needed. + - name: Check out api contract + uses: actions/checkout@v7 + with: + repository: eceo-epfl/deepreefmap-api + ref: main + path: api + sparse-checkout: contract + sparse-checkout-cone-mode: false + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Enable corepack + run: corepack enable + + - name: Resolve Yarn cache folder + id: yarn-cache + run: echo "dir=$(yarn config get cacheFolder)" >> "$GITHUB_OUTPUT" + + - name: Restore Yarn cache + uses: actions/cache@v6 + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: ${{ runner.os }}-yarn- + + - name: Install dependencies + run: yarn install --immutable + + # The committed file is generator output passed through prettier, so do both. + - name: Regenerate contract types + env: + DRM_API_DIR: ${{ github.workspace }}/api + run: | + yarn contract-types + yarn prettier --write src/contract/api.d.ts + + - name: Check the checked-in types are current + run: | + if ! git diff --exit-code -- src/contract/api.d.ts; then + echo "::error::src/contract/api.d.ts is stale. Run 'yarn contract-types && yarn format' and commit the result." + exit 1 + fi diff --git a/.gitignore b/.gitignore index 7ceb59f..3602499 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,11 @@ dist dist-ssr *.local +# Yarn keeps its install state here. Only patches and plugins are ours to commit. +.yarn/* +!.yarn/patches +!.yarn/plugins + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..87cba18 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,14 @@ +# Vite resolves from a real node_modules tree, so PnP is not used. +nodeLinker: node-modules + +# Rolldown and lightningcss ship per-platform binaries. The build image is alpine, +# so musl builds must be in the lockfile beside the host's glibc ones. +supportedArchitectures: + os: + - linux + cpu: + - x64 + - arm64 + libc: + - glibc + - musl diff --git a/Dockerfile b/Dockerfile index fee1299..ce466fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,20 @@ -FROM node:20.8.1-alpine as builder +FROM node:22.23.2-alpine AS builder # Set the working directory in the container WORKDIR /app -COPY package.json yarn.lock ./ -RUN yarn install +RUN corepack enable + +COPY package.json yarn.lock .yarnrc.yml ./ +RUN yarn install --immutable # Copy the rest of your application source code to the container COPY . . RUN yarn build -FROM nginx:1.12-alpine +FROM nginx:1.31-alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /app/dist /usr/share/nginx/html # Expose the port your application will listen on (if applicable) EXPOSE 80 diff --git a/Dockerfile.dev b/Dockerfile.dev index 114f77c..420343f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,9 +1,14 @@ -FROM node:20.8.1-alpine as builder +FROM node:22.23.2-alpine AS builder # Set the working directory in the container WORKDIR /app -COPY package.json yarn.lock ./ +RUN corepack enable + +# Install before copying the source, so a source edit does not re-run yarn. +COPY package.json yarn.lock .yarnrc.yml ./ +RUN yarn install --immutable + COPY . . # Start your Yarn application diff --git a/README.md b/README.md index fd6c30f..a2f77c2 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,47 @@ -# deepreefmap-frontend +# deepreefmap-ui -## Installation +**The web console for the DeepReefMap metadata registry.** -Install the application dependencies by running: +Administrators define the sites, campaigns and transects that field laptops download, +enrol those laptops, and browse the survey metadata they upload. -```sh -yarn +## Quick Start + +The console needs the registry and a Keycloak realm. The compose stack in this repository +brings up all three: + +```bash +docker compose up -d ``` -## Development +The console is then at `http://localhost:88`, and everything binds to loopback only. -Start the application in development mode by running: +For a local dev server against a registry that is already running: -```sh +```bash +yarn install yarn dev ``` -## Production +## Types from the contract -Build the application in production mode by running: +Every entity type is generated from the registry's published `OpenAPI` document, so a +field the server renamed becomes a compile error here: -```sh -yarn build +```bash +yarn contract-types ``` -## DataProvider +It reads `../deepreefmap-api/contract/openapi.json`, or `$DRM_API_DIR/contract` when the +registry lives elsewhere. -The included data provider use [FakeREST](https://github.com/marmelab/fakerest) to simulate a backend. -You'll find a `data.json` file in the `src` directory that includes some fake data for testing purposes. +## Checks -It includes two resources, posts and comments. -Posts have the following properties: `id`, `title` and `content`. -Comments have the following properties: `id`, `post_id` and `content`. - -## Authentication +```bash +yarn type-check +yarn build +``` -The included auth provider should only be used for development and test purposes. -You'll find a `users.json` file in the `src` directory that includes the users you can use. +## Licence -You can sign in to the application with the following usernames and password: -- janedoe / password -- johndoe / password +MIT diff --git a/docker-compose.yaml b/docker-compose.yaml index 4c980e4..eeb55cf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,159 +1,277 @@ +# Local development stack: Postgres, Keycloak, the Rust API with hot reload, and the +# web interface, behind one traefik so everything shares an origin. +# +# docker compose up -d +# open http://localhost:88 the interface +# open http://localhost:88/docs the API reference +# open http://localhost:8280 Keycloak (admin / admin) +# +# Sign in as admin/admin or user/user. The `norole` user exists to check that an +# authenticated person without a project role is refused. +# +# Integration tests run in their own containers against a throwaway database: +# docker compose up -d deepreefmap-test-watcher +# docker compose logs -f deepreefmap-test-watcher + services: traefik: - image: traefik:v2.9.6 + image: traefik:v3.7 command: - "--api.insecure=true" - "--providers.docker=true" + - "--providers.docker.exposedByDefault=false" + # Declared rather than inherited, so the published port below is unambiguous. + - "--entryPoints.web.address=:80" ports: - - "88:80" - - "8088:8080" + - "127.0.0.1:${TRAEFIK_HTTP_PORT:-88}:80" + - "127.0.0.1:${TRAEFIK_DASHBOARD_PORT:-8088}:8080" volumes: - - /var/run/docker.sock:/var/run/docker.sock + - /var/run/docker.sock:/var/run/docker.sock:ro deepreefmap-api: build: context: ../deepreefmap-api + dockerfile: Dockerfile.dev environment: + - DB_USER=${POSTGRES_USER:-postgres} + - DB_PASSWORD=${POSTGRES_PASSWORD:-psql} - DB_HOST=deepreefmap-db - DB_PORT=5432 - - DB_USER=postgres - - DB_PASSWORD=psql - - DB_NAME=postgres - - DB_PREFIX=postgresql+asyncpg - - S3_URL=${S3_URL} - - S3_BUCKET_ID=${S3_BUCKET_ID} - - S3_ACCESS_KEY=${S3_ACCESS_KEY} - - S3_SECRET_KEY=${S3_SECRET_KEY} - - S3_PREFIX=${S3_PREFIX} - - KUBERNETES_SERVICE_HOST=caas-test.rcp.epfl.ch - - KUBERNETES_SERVICE_PORT=443 - - KUBECONFIG=/root/.kube/config.yaml - - INCOMPLETE_OBJECT_CHECK_INTERVAL=${INCOMPLETE_OBJECT_CHECK_INTERVAL} - - INCOMPLETE_OBJECT_TIMEOUT_SECONDS=${INCOMPLETE_OBJECT_TIMEOUT_SECONDS} - - INCOMPLETE_OBJECT_CONSIDER_ABANDONED=${INCOMPLETE_OBJECT_CONSIDER_ABANDONED} - - DEEPREEFMAP_IMAGE_TAG=${DEEPREEFMAP_IMAGE_TAG} - - DEEPREEFMAP_IMAGE=${DEEPREEFMAP_IMAGE} - - NAMESPACE=${NAMESPACE} - - PROJECT=${PROJECT} - - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID} - - KEYCLOAK_API_ID=${KEYCLOAK_API_ID} - - KEYCLOAK_API_SECRET=${KEYCLOAK_API_SECRET} - - KEYCLOAK_REALM=${KEYCLOAK_REALM} - - KEYCLOAK_URL=${KEYCLOAK_URL} - - DEEPREEFMAP_API_URL=http://deepreefmap-api:8000 - - SERIALIZER_SECRET_KEY=${SERIALIZER_SECRET_KEY} - - DEPLOYMENT=local - - CACHE_URL=deepreefmap-cache - - CACHE_PORT=6379 - - CACHE_DB=0 - - CACHE_SECRET=${CACHE_SECRET} - - CACHE_PASSWORD=${REDIS_PASSWORD} + - DB_NAME=${POSTGRES_DB:-postgres} + - API_HOST=0.0.0.0 + - API_PORT=${API_PORT:-3000} + - DEPLOYMENT=${DEPLOYMENT:-dev} + - RUST_LOG=${LOG_LEVEL:-info,deepreefmap_api=debug} + # Container-internal URL: this is where the API validates tokens. The browser + # uses the host-published URL instead, which is why the two differ. + - KEYCLOAK_URL=http://deepreefmap-keycloak:8080/ + # What /api/config/keycloak advertises: the browser is on the host and cannot + # resolve the container name above. + - KEYCLOAK_BROWSER_URL=http://localhost:${KEYCLOAK_EXTERNAL_PORT:-8280}/ + - KEYCLOAK_REALM=deepreefmap + - KEYCLOAK_CLIENT_ID=deepreefmap-ui-local + # Embedded in every connect code, so it must be an address the desktop + # application can reach from the host, not a container name. + - PUBLIC_BASE_URL=http://localhost:${TRAEFIK_HTTP_PORT:-88}/api + - CORS_ALLOWED_ORIGINS=http://localhost:${TRAEFIK_HTTP_PORT:-88},http://localhost:5173 + - DISABLE_RATE_LIMITING=${DISABLE_RATE_LIMITING:-true} + # The blob archive. In-network endpoint for the API's own bucket operations. + # Internal network only, matching the production constraint: the store is + # never reachable by a client, every byte flows through the API. + - S3_URL=http://minio:9000 + # What presigned URLs are signed against: the browser is on the host and + # cannot resolve the container name above. MinIO publishes this port. + - S3_BUCKET_ID=deepreefmap + - S3_ACCESS_KEY=${MINIO_ROOT_USER:-minioadmin} + - S3_SECRET_KEY=${MINIO_ROOT_PASSWORD:-minioadmin} + - S3_PREFIX=dev + ports: + - "127.0.0.1:${API_EXTERNAL_PORT:-3005}:${API_PORT:-3000}" depends_on: - - deepreefmap-db + deepreefmap-db: + condition: service_healthy + deepreefmap-keycloak: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:${API_PORT:-3000}/healthz || exit 1"] + interval: 10s + timeout: 5s + retries: 30 + # Generous: the first run compiles the whole dependency tree. + start_period: 300s volumes: - - ../deepreefmap-api/app:/app/app - - ../config.yaml:/root/.kube/config.yaml - ports: - - 5001:8000 + - ../deepreefmap-api/src:/app/src + - ../deepreefmap-api/tests:/app/tests + - ../deepreefmap-api/migration:/app/migration + # The exporter writes here and the contract test diffs it. + - ../deepreefmap-api/contract:/app/contract + - ../deepreefmap-api/Cargo.toml:/app/Cargo.toml + - ../deepreefmap-api/Cargo.lock:/app/Cargo.lock + - ../deepreefmap-api/bacon.toml:/app/bacon.toml + # Named volume, so cargo's build cache survives a container rebuild. + - deepreefmap-rust-data:/app/target labels: - - "traefik.http.routers.deepreefmap-bff.rule=Host(`deepreefmap`) && PathPrefix(`/api`)" - - "traefik.http.services.deepreefmap-bff.loadbalancer.server.port=8000" + - "traefik.enable=true" + - "traefik.http.routers.deepreefmap-api.rule=PathPrefix(`/api`) || PathPrefix(`/docs`)" + - "traefik.http.routers.deepreefmap-api.priority=100" + - "traefik.http.services.deepreefmap-api.loadbalancer.server.port=${API_PORT:-3000}" deepreefmap-ui: build: context: . - dockerfile: Dockerfile.dev # Uses the dockerfile containing yarn dev + dockerfile: Dockerfile.dev + depends_on: + deepreefmap-api: + condition: service_started labels: - - "traefik.http.routers.deepreefmap-ui.rule=Host(`deepreefmap`)" + - "traefik.enable=true" + - "traefik.http.routers.deepreefmap-ui.rule=PathPrefix(`/`)" + - "traefik.http.routers.deepreefmap-ui.priority=10" - "traefik.http.services.deepreefmap-ui.loadbalancer.server.port=5173" volumes: - - ../deepreefmap-ui:/app + - ./src:/app/src + - ./index.html:/app/index.html + - ./vite.config.mts:/app/vite.config.mts + - ./public:/app/public deepreefmap-db: - image: postgis/postgis:16-master + image: postgres:18-alpine environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=psql - - POSTGRES_DB=postgres + - POSTGRES_USER=${POSTGRES_USER:-postgres} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-psql} + - POSTGRES_DB=${POSTGRES_DB:-postgres} + ports: + - "127.0.0.1:${POSTGRES_EXTERNAL_PORT:-5433}:5432" + volumes: + # Postgres 18 puts the cluster in /var/lib/postgresql/18/docker and declares the + # volume a level up, so mounting .../data loses it. + - deepreefmap-db-data:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"] + interval: 5s + timeout: 5s + retries: 5 + + deepreefmap-keycloak: + image: quay.io/keycloak/keycloak:26.7 + command: start-dev --import-realm + environment: + KC_DB: dev-file + KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN:-admin} + KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin} + KC_HOSTNAME_STRICT: false + ports: + - "127.0.0.1:${KEYCLOAK_EXTERNAL_PORT:-8280}:8080" + volumes: + - ./keycloak-realm-dev.json:/opt/keycloak/data/import/realm.json:ro + healthcheck: + test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080"] + interval: 10s + timeout: 5s + retries: 15 + start_period: 30s + + # ── Blob archive ───────────────────────────────────────────────────────── + minio: + image: minio/minio + command: server /data --console-address ":9001" + environment: + - MINIO_ROOT_USER=${MINIO_ROOT_USER:-minioadmin} + - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-minioadmin} ports: - - 5433:5432 + # Loopback only, for the API's gated S3 tests running on the host. No client + # in the stack reaches MinIO: the dev topology matches the intranet-only + # store in production. + - "127.0.0.1:${MINIO_EXTERNAL_PORT:-9000}:9000" volumes: - - deepreefmap-db-data:/var/lib/postgresql/data + - deepreefmap-minio-data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 5s + timeout: 5s + retries: 10 + # One-shot: the API expects its bucket to exist rather than creating it, so a typo + # in S3_BUCKET_ID fails loudly instead of silently making a second bucket. + minio-seed: + image: minio/mc + depends_on: + minio: + condition: service_healthy + entrypoint: ["sh", "-c"] + command: + - | + mc alias set local http://minio:9000 ${MINIO_ROOT_USER:-minioadmin} ${MINIO_ROOT_PASSWORD:-minioadmin} + mc mb --ignore-existing local/deepreefmap + restart: "no" + + # ── Dev connect-code seeder (one-shot) ────────────────────────────────── + # Minting a connect code normally needs an interactive login, which makes + # testing the desktop sync path a click-through. This seeds a fixed code so + # `POST /api/enrol` can be exercised straight away: + # + # curl -X POST http://localhost:88/api/enrol \ + # -H 'Content-Type: application/json' \ + # -d '{"code":"'$(printf 'ab%.0s' {1..32})'"}' + # + # Single use, like any connect code: re-run this service to get another. + dev-seed-connect-code: + image: postgres:18-alpine + # Opt in with `--profile seed`: the secret below is public, so an unattended stack + # must not carry a live enrolment credential. + profiles: ["seed"] + depends_on: + deepreefmap-api: + condition: service_healthy + environment: + - PGPASSWORD=${POSTGRES_PASSWORD:-psql} + entrypoint: ["sh", "-c"] + command: + - | + until pg_isready -h deepreefmap-db -U ${POSTGRES_USER:-postgres}; do sleep 1; done + psql -h deepreefmap-db -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} <<'SQL' + CREATE EXTENSION IF NOT EXISTS pgcrypto; + -- sha256 of the 64-hex secret 'abab...ab', matching the curl above. The + -- server stores only this digest, so the seeded row is indistinguishable + -- from one the mint endpoint wrote. + INSERT INTO connect_code (id, code_hash, created_by, device_name, expires_at, created_at) + VALUES ( + gen_random_uuid(), + encode(digest(repeat('ab', 32), 'sha256'), 'hex'), + 'dev-local-admin', + 'Dev laptop', + now() + interval '15 minutes', + now() + ) + ON CONFLICT (code_hash) DO NOTHING; + SQL + echo "Seeded dev connect code: $(printf 'ab%.0s' $(seq 32))" + restart: "no" + + # ── Test infrastructure ───────────────────────────────────────────────── deepreefmap-test-db: - image: postgis/postgis:16-master + image: postgres:18-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=psql - - POSTGRES_DB=postgres + - POSTGRES_DB=deepreefmap_test ports: - - 5444:5432 - - deepreefmap-test-cache: - image: redis:7.4.0 - command: ["redis-server", "--requirepass", "test"] + - "127.0.0.1:${TEST_POSTGRES_EXTERNAL_PORT:-5444}:5432" + # In memory: the suite recreates the schema, so nothing here needs to survive. + tmpfs: + - /var/lib/postgresql + # Quiet, so a failing test is not buried under statement logs in the watcher. + command: ["postgres", "-c", "log_min_messages=fatal", "-c", "log_statement=none"] + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 - deepreefmap-test-api: + deepreefmap-test-watcher: build: context: ../deepreefmap-api - dockerfile: Dockerfile.dev # Uses the dockerfile just for pytest + dockerfile: Dockerfile.dev environment: - - DB_HOST=deepreefmap-test-db - - DB_PORT=5432 - - DB_USER=postgres - - DB_PASSWORD=psql - - DB_NAME=postgres - - DB_PREFIX=postgresql+asyncpg - - S3_URL=https://s3.example.com - - S3_BUCKET_ID=1234 - - S3_ACCESS_KEY=1234 - - S3_SECRET_KEY=1234 - - S3_PREFIX=test-only - - KUBERNETES_SERVICE_HOST=test-only - - KUBERNETES_SERVICE_PORT=443 - - KUBECONFIG=/dev/null - - INCOMPLETE_OBJECT_CHECK_INTERVAL=${INCOMPLETE_OBJECT_CHECK_INTERVAL} - - INCOMPLETE_OBJECT_TIMEOUT_SECONDS=${INCOMPLETE_OBJECT_TIMEOUT_SECONDS} - - DEEPREEFMAP_IMAGE_TAG=${DEEPREEFMAP_IMAGE_TAG} - - DEEPREEFMAP_IMAGE=${DEEPREEFMAP_IMAGE} - - NAMESPACE=test - - PROJECT=test - - KEYCLOAK_CLIENT_ID=test - - KEYCLOAK_API_ID=test - - KEYCLOAK_API_SECRET=test - - KEYCLOAK_REALM=test - - KEYCLOAK_URL=test - - SERIALIZER_SECRET_KEY=test + - DATABASE_URL=postgresql://postgres:psql@deepreefmap-test-db:5432/deepreefmap_test + - RUST_LOG=${LOG_LEVEL:-warn} - DEPLOYMENT=local - - CACHE_URL=deepreefmap-test-cache - - CACHE_PORT=6379 - - CACHE_DB=1 - - CACHE_SECRET=test - - CACHE_PASSWORD=test depends_on: - - deepreefmap-test-db - - deepreefmap-test-cache + deepreefmap-test-db: + condition: service_healthy + entrypoint: ["bacon", "--headless", "test-integration"] volumes: - - ../deepreefmap-api:/app - ports: - - 5445:8000 - - deepreefmap-cache: - image: redis:7.4.0 - ports: - - 6379:6379 - command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"] - - deepreefmap-tus: - image: tusproject/tusd:latest - environment: - - AWS_ACCESS_KEY_ID=${S3_ACCESS_KEY} - - AWS_SECRET_ACCESS_KEY=${S3_SECRET_KEY} - - AWS_REGION=us-east-1 - command: -s3-bucket ${S3_BUCKET_ID} -s3-endpoint https://${S3_URL} -s3-object-prefix deepreefmap-local -behind-proxy -port 8080 -hooks-http http://deepreefmap-api:8000/tus_hooks - labels: - - "traefik.http.routers.deepreefmap-tus.rule=Host(`deepreefmap`) && PathPrefix(`/files`)" - - "traefik.http.services.deepreefmap-tus.loadbalancer.server.port=8080" + - ../deepreefmap-api/src:/app/src + - ../deepreefmap-api/tests:/app/tests + - ../deepreefmap-api/migration:/app/migration + - ../deepreefmap-api/contract:/app/contract + - ../deepreefmap-api/Cargo.toml:/app/Cargo.toml + - ../deepreefmap-api/Cargo.lock:/app/Cargo.lock + - ../deepreefmap-api/bacon.toml:/app/bacon.toml + - deepreefmap-test-rust-data:/app/target volumes: deepreefmap-db-data: + deepreefmap-minio-data: + deepreefmap-rust-data: + deepreefmap-test-rust-data: diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..9609792 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,46 @@ +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import react from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import prettier from 'eslint-config-prettier/flat'; +import globals from 'globals'; + +export default tseslint.config( + { + // node_modules and .git are ignored already. These two are ours. + ignores: ['dist/**', 'src/contract/api.d.ts'], + }, + { + // A bare directory on the CLI expands to .js/.mjs/.cjs without this. + files: ['**/*.{js,jsx,ts,tsx}'], + extends: [ + js.configs.recommended, + // Turns off the base rules that misread TypeScript type syntax, `no-unused-vars` + // on an interface signature and `no-redeclare` on a type import among them. + tseslint.configs.recommended, + react.configs.flat.recommended, + react.configs.flat['jsx-runtime'], + // Last, so formatting rules lose to prettier. + prettier, + ], + languageOptions: { + globals: globals.browser, + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + }, + settings: { + // Pinned: 'detect' crashes, eslint-plugin-react probes for a context API + // that eslint 10 removed. + react: { version: '19.2' }, + }, + plugins: { + 'react-hooks': reactHooks, + }, + rules: { + // The v7 flat preset also runs the React Compiler lints at error. + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + }, + } +); diff --git a/index.html b/index.html index 9543582..95e370c 100644 --- a/index.html +++ b/index.html @@ -7,149 +7,19 @@ - - - - - DeepReefMap - - - - -
-
-
Loading...
-
-
+
+ - - \ No newline at end of file + diff --git a/keycloak-realm-dev.json b/keycloak-realm-dev.json new file mode 100644 index 0000000..2a7b053 --- /dev/null +++ b/keycloak-realm-dev.json @@ -0,0 +1,112 @@ +{ + "realm": "deepreefmap", + "displayName": "DeepReefMap Development", + "enabled": true, + "registrationAllowed": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "verifyEmail": false, + "bruteForceProtected": true, + "accessTokenLifespan": 3600, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "clients": [ + { + "clientId": "deepreefmap-ui-local", + "name": "DeepReefMap UI", + "description": "Public client for the web interface. The desktop application deliberately has no client here: it authenticates with a device token traded for a connect code, so no realm details ship inside a public application.", + "enabled": true, + "publicClient": true, + "fullScopeAllowed": true, + "clientAuthenticatorType": "client-secret", + "directAccessGrantsEnabled": true, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "redirectUris": [ + "http://localhost:*", + "http://localhost:88/*", + "http://localhost:5173/*", + "http://localhost:3000/*" + ], + "webOrigins": ["*"], + "protocol": "openid-connect", + "defaultClientScopes": ["web-origins", "profile", "roles", "email"], + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "protocolMappers": [ + { + "name": "User ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "id", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "sub", + "jsonType.label": "String" + } + }, + { + "name": "account-audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "account", + "id.token.claim": "false", + "access.token.claim": "true" + } + } + ] + } + ], + "users": [ + { + "username": "admin", + "enabled": true, + "emailVerified": true, + "email": "admin@local.dev", + "firstName": "Admin", + "lastName": "User", + "credentials": [{ "type": "password", "value": "admin", "temporary": false }], + "realmRoles": ["deepreefmap-admin", "deepreefmap-member"] + }, + { + "username": "user", + "enabled": true, + "emailVerified": true, + "email": "user@local.dev", + "firstName": "Regular", + "lastName": "User", + "credentials": [{ "type": "password", "value": "user", "temporary": false }], + "realmRoles": ["deepreefmap-member"] + }, + { + "username": "norole", + "enabled": true, + "emailVerified": true, + "email": "norole@local.dev", + "firstName": "No", + "lastName": "Role", + "credentials": [{ "type": "password", "value": "norole", "temporary": false }], + "realmRoles": [] + } + ], + "roles": { + "realm": [ + { + "name": "deepreefmap-admin", + "description": "May revoke anyone's devices and hard-delete rows" + }, + { + "name": "deepreefmap-member", + "description": "May read and write survey metadata, and enrol their own devices" + } + ] + }, + "requiredCredentials": ["password"] +} diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..1221220 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,27 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # react-admin routes on the fragment, so only index.html is ever requested. + # The fallback keeps a reload working should the router move to history mode. + location / { + try_files $uri $uri/ /index.html; + } + + # Vite fingerprints these, so they can be cached indefinitely. + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Carries the asset hashes, so it must never be cached. + location = /index.html { + add_header Cache-Control "no-cache"; + } + + gzip on; + gzip_types text/css application/javascript application/json image/svg+xml; + gzip_min_length 1024; +} diff --git a/package.json b/package.json index 33157cc..2cb68ef 100644 --- a/package.json +++ b/package.json @@ -1,58 +1,44 @@ { "name": "deepreefmap-frontend", "private": true, + "packageManager": "yarn@4.9.2", "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview", "type-check": "tsc --noEmit", - "lint": "eslint --fix --ext .js,.jsx,.ts,.tsx ./src", + "contract-types": "openapi-typescript ${DRM_API_DIR:-../deepreefmap-api}/contract/openapi.json -o src/contract/api.d.ts && cp ${DRM_API_DIR:-../deepreefmap-api}/contract/preset-schema.json src/contract/preset-schema.json", + "lint": "eslint --fix ./src", "format": "prettier --write ./src" }, "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.6.0", - "@fortawesome/free-brands-svg-icons": "^6.6.0", - "@fortawesome/free-solid-svg-icons": "^6.6.0", - "@fortawesome/react-fontawesome": "^0.2.2", - "axios": "^1.6.0", - "filepond": "^4.31.2", - "filepond-plugin-file-validate-type": "^1.2.9", - "keycloak-js": "^22.0.4", + "@mui/icons-material": "^9.3.1", + "@mui/material": "^9.3.1", + "keycloak-js": "^26.2.4", "leaflet": "^1.9.4", - "leaflet-draw": "^1.0.4", - "leaflet-tilelayer-swiss": "^2.3.0", - "ol": "^8.1.0", - "plotly.js": "^2.32.0", - "ra-data-fakerest": "^4.14.0", - "ra-data-simple-rest": "^4.15.2", - "ra-input-rich-text": "^4.15.1", - "ra-keycloak": "^1.0.1", - "ra-language-french": "^4.15.1", - "react": "^18.2.0", - "react-admin": "^5.1.3", - "react-dom": "^18.2.0", - "react-dropzone": "^14.2.3", - "react-dropzone-uploader": "^2.11.0", - "react-filepond": "^7.1.2", - "react-leaflet": "^4.2.1", - "react-leaflet-draw": "^0.20.4", - "react-plotly.js": "^2.6.0", - "recharts": "^2.9.1", - "tus-js-client": "^4.2.3" + "react": "^19.2.8", + "react-admin": "^5.15.1", + "react-dom": "^19.2.8", + "react-leaflet": "^5.0.0", + "react-router-dom": "^7.18.2", + "three": "^0.185.1" }, "devDependencies": { - "@types/node": "^18.16.1", - "@types/react": "^18.0.22", - "@types/react-dom": "^18.0.7", - "@typescript-eslint/eslint-plugin": "^5.60.1", - "@typescript-eslint/parser": "^5.60.1", - "@vitejs/plugin-react": "^4.0.1", - "eslint": "^8.43.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", - "prettier": "^2.8.8", - "typescript": "^5.1.6", - "vite": "^4.3.9" + "@eslint/js": "^10.0.1", + "@types/leaflet": "^1.9.12", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@types/three": "^0.185.4", + "@vitejs/plugin-react": "^6.0.5", + "eslint": "^10.8.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", + "globals": "^17.11.0", + "openapi-typescript": "^7.13.0", + "prettier": "^3.9.6", + "typescript": "^5.9.3", + "typescript-eslint": "^8.67.0", + "vite": "^8.2.1" } -} \ No newline at end of file +} diff --git a/prettier.config.js b/prettier.config.js index 7c6d6c7..0eee8f8 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1 +1,6 @@ -module.exports = {} \ No newline at end of file +module.exports = { + tabWidth: 4, + singleQuote: true, + printWidth: 95, + arrowParens: 'avoid', +}; diff --git a/public/eceo.png b/public/eceo.png deleted file mode 100644 index 75459cc..0000000 Binary files a/public/eceo.png and /dev/null differ diff --git a/public/epfl.png b/public/epfl.png deleted file mode 100644 index 96fc8dc..0000000 Binary files a/public/epfl.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json index 890137a..fb042ab 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "deepreefmap-frontend", - "name": "{{name}}", + "short_name": "DeepReefMap", + "name": "DeepReefMap", "icons": [ { "src": "favicon.ico", @@ -12,4 +12,4 @@ "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" -} \ No newline at end of file +} diff --git a/public/trsc.png b/public/trsc.png deleted file mode 100644 index 82ea53e..0000000 Binary files a/public/trsc.png and /dev/null differ diff --git a/src/App.tsx b/src/App.tsx index 97195ec..356291b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,82 +1,118 @@ /* eslint react/jsx-key: off */ -import React, { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect } from 'react'; import { Admin, + CustomRoutes, Resource, AuthProvider, - DataProvider, defaultLightTheme, defaultDarkTheme, + fetchUtils, } from 'react-admin'; import { Route } from 'react-router-dom'; -import simpleRestProvider from './dataProvider/index' -import Keycloak, { - KeycloakConfig, - KeycloakTokenParsed, - KeycloakInitOptions, -} from 'keycloak-js'; -import { httpClient } from 'ra-keycloak'; -import { keycloakAuthProvider } from './authProvider'; +import { deepmerge } from '@mui/utils'; +import simpleRestProvider, { DrmDataProvider } from './dataProvider/index'; +import Keycloak, { KeycloakTokenParsed, KeycloakInitOptions } from 'keycloak-js'; +import { keycloakAuthProvider, httpClient } from './authProvider'; import MyLayout from './Layout'; -import axios from 'axios'; -import SubmissionJobLogsShow from './submissions/SubmissionJobLogsShow'; import Dashboard from './Dashboard'; -import users from './users'; -import submissions from './submissions'; -import objects from './objects'; -import status from './status'; +import sites from './sites'; +import campaigns from './campaigns'; import transects from './transects'; -import { deepmerge } from '@mui/utils'; - -const initOptions: KeycloakInitOptions = { onLoad: 'login-required' }; +import passes from './passes'; +import passGroups from './passGroups'; +import videos from './videos'; +import runs from './runs'; +import cover from './cover'; +import devices from './devices'; +import presets from './presets'; +import archive from './archive'; +import PerformancePage from './performance/PerformancePage'; + +// MUI names Roboto by default, which nothing here ships or fetches. +const SYSTEM_FONTS = [ + '-apple-system', + 'BlinkMacSystemFont', + 'Segoe UI', + 'Helvetica', + 'Arial', + 'sans-serif', +].join(', '); + +// `responseMode: 'query'` and a fragment-free `redirectUri` keep the authorisation +// code out of the URL fragment, which react-admin's router owns. Returning it in the +// fragment loses it to the router and Keycloak redirects forever. +const initOptions: KeycloakInitOptions = { + onLoad: 'login-required', + checkLoginIframe: false, + pkceMethod: 'S256', + responseMode: 'query', + redirectUri: `${window.location.origin}/`, + enableLogging: true, +}; const getPermissions = (decoded: KeycloakTokenParsed) => { const roles = decoded?.realm_access?.roles; if (!roles) { return false; } - if (roles.includes('admin')) return 'admin'; - if (roles.includes('user')) return 'user'; + if (roles.includes('deepreefmap-admin')) return 'admin'; + if (roles.includes('deepreefmap-member')) return 'user'; return false; }; -const apiKeycloakConfigUrl = '/api/config'; +const apiKeycloakConfigUrl = '/api/config/keycloak'; export const apiUrl = '/api'; const App = () => { - const [keycloak, setKeycloak] = useState(); + const [keycloak, setKeycloak] = useState(); const [loading, setLoading] = useState(true); - const authProvider = useRef(); - const dataProvider = useRef(); - const [deployment, setDeployment] = useState(undefined); - + const authProvider = useRef(undefined); + const dataProvider = useRef(undefined); + const [deployment, setDeployment] = useState(); + const [startupError, setStartupError] = useState(); + // StrictMode runs effects twice in development, and a second `Keycloak.init` on a + // second client redirects into a loop. + const started = useRef(false); useEffect(() => { + if (started.current) return; + started.current = true; + async function fetchData() { try { - const response = await axios.get(apiKeycloakConfigUrl); - const keycloakConfig = response.data; + const { json: keycloakConfig } = + await fetchUtils.fetchJson(apiKeycloakConfigUrl); setDeployment(keycloakConfig.deployment); - - // Initialize Keycloak here, once you have the configuration - const keycloakClient = new Keycloak(keycloakConfig); + // The API resolves the browser-facing Keycloak URL itself, since in a + // container network the address it validates against is not one the + // browser can reach. + const keycloakClient = new Keycloak({ + url: keycloakConfig.url, + realm: keycloakConfig.realm, + clientId: keycloakConfig.clientId, + }); await keycloakClient.init(initOptions); authProvider.current = keycloakAuthProvider(keycloakClient, { onPermissions: getPermissions, }); - dataProvider.current = simpleRestProvider( - apiUrl, - httpClient(keycloakClient) - ); + dataProvider.current = simpleRestProvider(apiUrl, httpClient(keycloakClient)); setKeycloak(keycloakClient); setLoading(false); } catch (error) { - console.error('Error fetching data:', error); + // Rendering Admin without its providers reports the failure as + // "Unknown dataProvider function", which says nothing about the cause. + console.error('Startup failed:', error); + setStartupError( + error instanceof Error + ? error.message + : `could not reach ${apiKeycloakConfigUrl} or initialise Keycloak`, + ); setLoading(false); } } @@ -84,61 +120,66 @@ const App = () => { fetchData(); }, []); - - const lightTheme = deepmerge( - defaultLightTheme, { - sidebar: { - width: 170, - }, - }); - const darkTheme = deepmerge( - defaultDarkTheme, { - sidebar: { - width: 170, - }, - }); - - if (!keycloak & loading) return

Loading...

; + // Merged, not spread: a shallow spread would replace the default themes' + // typography object rather than add a font family to it. + const branding = { + sidebar: { width: 170 }, + typography: { fontFamily: SYSTEM_FONTS }, + }; + const lightTheme = deepmerge(defaultLightTheme, branding); + const darkTheme = deepmerge(defaultDarkTheme, branding); + + if (loading) return

Loading...

; + if (startupError || !keycloak) { + return ( +
+

Cannot start

+

{startupError ?? 'Keycloak did not initialise.'}

+

Check that the API is reachable and Keycloak is configured.

+
+ ); + } return ( } + layout={props => } theme={lightTheme} darkTheme={darkTheme} > - {permissions => ( - <> - { // only show the resources if the user has been approved; ie. is either 'user' or 'admin' role - (permissions === 'admin' || permissions === 'user') ? ( - <> - - - - - - - ) : null} - {permissions ? ( - <> - {permissions === 'admin' ? ( - <> - - - ) : null} - - ) : null} - - )} - + {permissions => { + if (permissions !== 'admin' && permissions !== 'user') return null; + return ( + <> + + + + + + + + + {/* A member enrols their own laptop, so the connect page is + theirs too. Revoking somebody else's stays with an admin. */} + + + + {/* No list view, so no menu entry: registered only for the + passes views to resolve their video references. */} + + {/* Likewise, registered only for the run show page's + archived outputs panel. */} + + {/* Not a resource: one aggregate the registry computes. */} + + } /> + + + ); + }} + ); }; export default App; diff --git a/src/Dashboard.tsx b/src/Dashboard.tsx index 9797656..c337edb 100644 --- a/src/Dashboard.tsx +++ b/src/Dashboard.tsx @@ -1,162 +1,160 @@ -import React from 'react'; -import './css/css.css'; -import './css/academicons.min.css'; -import './css/bulma.min.css'; -import './css/bulma-carousel.min.css'; -import './css/bulma-slider.min.css'; -import { usePermissions, useGetList, Link } from 'react-admin'; -import { Typography, Button, Container, Box, Divider } from '@mui/material'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faFilePdf, faSheetPlastic } from '@fortawesome/free-solid-svg-icons'; -import { faGithub } from '@fortawesome/free-brands-svg-icons'; +import { + Datagrid, + DateField, + ListContextProvider, + ReferenceField, + ResourceContextProvider, + TextField, + Title, + useGetList, + useList, + usePermissions, +} from 'react-admin'; +import { Box, Card, CardContent, Stack, Typography } from '@mui/material'; -const GettingStartedTooltip = () => { - const { data, total, isPending, error } = useGetList('transects'); - if (isPending || error) return null; - if (total > 0) return null; - return ( - - - To get started, go to Transects, and create a transect. +import type { RunRecord, Transect } from './contract'; +import Overview from './maps/Overview'; +import StatusField from './runs/StatusField'; + +// A Keycloak login without a deepreefmap realm role. The API answers every route with a +// 403, so there is nothing to show and nothing worth requesting. +const NoAccess = () => ( + + + + You are signed in without access - - ); -}; + + Ask an administrator for the member or administrator role, then sign in again. + + + +); -const Dashboard = () => { - const { permissions } = usePermissions(); +const RECENT = 8; - return ( - - {(permissions === 'admin' || permissions === 'user') ? ( - - ) : ( - - To access the DeepReefMap portal, please contact the administrator to request access. - - )} +const Panel = ({ title, children }: { title: string; children: React.ReactNode }) => ( + + + + {title} + + {children} + + +); - - - - DeepReefMap: -
- Scalable Semantic 3D Mapping of Coral Reefs with Deep Learning -
+const Nothing = ({ what }: { what: string }) => ( + + No {what} yet. + +); - - Jonathan Sauder,{' '} - Guilhem Banc-Prandi,{' '} - Gabriela Perna,{' '} - Anders Meibom,{' '} - Devis Tuia - +const LatestRuns = () => { + const { data, isPending } = useGetList('runs', { + pagination: { page: 1, perPage: RECENT }, + sort: { field: 'started_at', order: 'DESC' }, + }); + const context = useList({ data, isPending, perPage: RECENT }); + if (!isPending && !data?.length) return ; - - - - - - - EPFL Logo - TRSC Logo - ECEO Logo - -
-
+ return ( + // The resource context is what lets rowClick resolve a show route from here. + + + + + + + + + + + + ); +}; - - - - - - - +const RecentlyChanged = () => { + const { data, isPending } = useGetList('transects', { + pagination: { page: 1, perPage: RECENT }, + sort: { field: 'updated_at', order: 'DESC' }, + }); + const context = useList({ data, isPending, perPage: RECENT }); + if (!isPending && !data?.length) return ; - - - - - - - + return ( + + + + + + + + + + + + ); +}; - - - Interface developed by Evan Thomas for the DeepReefMap project - in collaboration
- with the ECEO lab and - the Transnational Red Sea Center. -
+const Dashboard = () => { + const { permissions, isPending } = usePermissions(); + if (isPending) return null; -
+ if (permissions !== 'admin' && permissions !== 'user') { + return ( + <> + + <NoAccess /> + </> + ); + } - </Container> - </Box> - </Box> + return ( + <> + <Title title="DeepReefMap" /> + <Stack spacing={2} sx={{ mt: 2 }}> + <Box sx={{ '& .leaflet-container': { borderRadius: 1 } }}> + <Overview /> + </Box> + <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}> + <Panel title="Latest uploads"> + <LatestRuns /> + </Panel> + <Panel title="Recently changed transects"> + <RecentlyChanged /> + </Panel> + </Stack> + </Stack> + </> ); }; -export default Dashboard; \ No newline at end of file +export default Dashboard; diff --git a/src/Layout.tsx b/src/Layout.tsx index a59f354..0cfb433 100644 --- a/src/Layout.tsx +++ b/src/Layout.tsx @@ -1,117 +1,51 @@ -import * as React from 'react'; -import { Layout, AppBar, TitlePortal, useDataProvider, usePermissions } from 'react-admin'; -import { CssBaseline } from '@mui/material'; -import { useState, useEffect } from 'react'; -import { Typography, Box } from '@mui/material'; -import Brightness1TwoToneIcon from '@mui/icons-material/Brightness1TwoTone'; +import type { ReactNode } from 'react'; +import { AppBar, Layout, TitlePortal } from 'react-admin'; +import { Chip, CssBaseline } from '@mui/material'; +import DrmMenu from './layout/Menu'; -const StatusIcon = ({ gpu, storage }) => { - return ( - <Box sx={{ display: 'flex', alignItems: 'center' }}> - <Brightness1TwoToneIcon sx={{ - width: 16, - marginRight: 1, - }} color={gpu ? 'success' : 'error'} /> - <Typography variant="body1" color={gpu ? 'textPrimary' : 'textSecondary'}> - GPU - </Typography> +type Severity = 'warning' | 'info' | 'default'; - {/* Add a vertical separator line */} - <Box - sx={{ - height: 24, - width: 2, - bgcolor: 'gray', - marginX: 2, - }} - /> - <Brightness1TwoToneIcon sx={{ - width: 16, - marginRight: 1, - }} color={storage ? 'success' : 'error'} /> - <Typography variant="body1" color={storage ? 'textPrimary' : 'textSecondary'}> - Storage - </Typography> - </Box> - ); +// Anything other than production is worth calling out, so nobody edits staging by accident. +const DEPLOYMENT_LABELS: Record<string, { label: string; colour: Severity }> = { + local: { label: 'Local development', colour: 'default' }, + dev: { label: 'Development', colour: 'info' }, + stage: { label: 'Staging', colour: 'warning' }, }; - -const MyAppBar = (props) => { - const [systemStatus, setSystemStatus] = useState( - { - "kubernetes": [], - "s3_local": { - "total_object_count": 0, - "input_object_count": 0, - "output_object_count": 0, - "total_size": 0, - "input_size": 0, - "output_size": 0 - }, - "s3_global": null, - "s3_status": true, - "kubernetes_status": true - }); - const dataProvider = useDataProvider(); - const { isPending, permissions } = usePermissions(); - const appBarText = () => { - if (props.deployment) { - if (props.deployment == 'local') { - return "⭐Local Development⭐" - } - if (props.deployment == 'dev') { - return "⭐Development⭐" - } - if (props.deployment == 'stage') { - return "⭐Staging⭐" - } - } - } - - useEffect(() => { - const fetchData = async () => { - const statusData = await dataProvider.getStatus(); - setSystemStatus(statusData.data); - }; - - fetchData(); - - }, []); - if (isPending) return null; - +const DeploymentChip = ({ deployment }: { deployment?: string }) => { + const banner = deployment ? DEPLOYMENT_LABELS[deployment] : undefined; + if (!banner) return null; return ( - <AppBar color="primary" > - <TitlePortal /> - <Typography - variant="h6" - color='#FF69B4' - id="react-admin-title" - > - {props.deployment ? appBarText() : ""}   - - </Typography> - - - {(permissions === 'user' || permissions === 'admin') ? ( - <StatusIcon - gpu={systemStatus.kubernetes_status} - storage={systemStatus.s3_status} - /> - ) : null} - </AppBar> - ) + <Chip + size="small" + label={banner.label} + color={banner.colour} + sx={{ mr: 2, fontWeight: 600 }} + /> + ); }; -export const MyLayout = ({ children, deployment }) => ( +const DrmAppBar = ({ deployment }: { deployment?: string }) => ( + <AppBar color="primary"> + <TitlePortal /> + <DeploymentChip deployment={deployment} /> + </AppBar> +); + +export const MyLayout = ({ + children, + deployment, +}: { + children?: ReactNode; + deployment?: string; +}) => ( <> <CssBaseline /> - <Layout appBar={() => <MyAppBar deployment={deployment} />} > + <Layout appBar={() => <DrmAppBar deployment={deployment} />} menu={DrmMenu}> {children} </Layout> - </> ); -export default MyLayout; \ No newline at end of file +export default MyLayout; diff --git a/src/NothingToSee.tsx b/src/NothingToSee.tsx deleted file mode 100644 index 4cf6e87..0000000 --- a/src/NothingToSee.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// In your resource file (e.g., posts.js) -import React from 'react'; -import { List, Datagrid, TextField, SimpleList } from 'react-admin'; - -const CustomEmptyList = () => ( - <div style={{ padding: '20px', textAlign: 'center' }}> - Nothing here just yet. - </div> -); - -export const NothingList = (props) => ( - <div style={{ padding: '20px', textAlign: 'center' }}> - Nothing here just yet. - </div> - // <List {...props} empty={<CustomEmptyList />}> - // {/* Your list fields go here */} - // <Datagrid> - // <TextField source="id" /> - // <TextField source="title" /> - // {/* Add other fields as needed */} - // </Datagrid> - // </List> -); - -export default NothingList; \ No newline at end of file diff --git a/src/archive/ArchiveChip.tsx b/src/archive/ArchiveChip.tsx new file mode 100644 index 0000000..916896f --- /dev/null +++ b/src/archive/ArchiveChip.tsx @@ -0,0 +1,92 @@ +import { MouseEvent } from 'react'; +import { Link } from 'react-router-dom'; +import { Chip, Tooltip, Typography } from '@mui/material'; + +import { relativeTime } from '../devices/RelativeDateField'; +import { useArchiveProbe } from './useArchiveProbe'; + +// Both the by-hash and the batch probe answer this shape. +export type ArchiveState = { + status: string; + completed_at?: string | null; + object_id?: string; +}; + +const Dash = () => ( + <Typography + variant="body2" + component="span" + sx={{ + color: 'text.disabled', + }} + > + — + </Typography> +); + +// Inside a datagrid the row's own click would swallow the link. +const stopRowClick = (event: MouseEvent) => event.stopPropagation(); + +/** + * One archive state as a chip. + * + * `state` is `undefined` while the probe is in flight, `null` when nothing is + * archived under the hash. A complete state links to its stored object. + */ +export const ArchiveStateChip = ({ + state, + error, +}: { + state: ArchiveState | null | undefined; + error?: string; +}) => { + if (error) { + return ( + <Tooltip title={error}> + <Chip size="small" label="Archive unavailable" variant="outlined" /> + </Tooltip> + ); + } + if (state === undefined) { + return <Chip size="small" label="Checking…" variant="outlined" />; + } + if (state === null) { + return <Chip size="small" label="Not archived" variant="outlined" />; + } + switch (state.status) { + case 'complete': { + const label = state.completed_at + ? `Archived ${relativeTime(state.completed_at)}` + : 'Archived'; + if (state.object_id) { + return ( + <Chip + size="small" + color="success" + label={label} + clickable + component={Link} + to={`/stored_objects/${state.object_id}/show`} + onClick={stopRowClick} + /> + ); + } + return <Chip size="small" color="success" label={label} />; + } + case 'failed': + return <Chip size="small" color="error" label="Archive failed" />; + case 'pending': + return <Chip size="small" color="warning" variant="outlined" label="Uploading" />; + default: + return <Chip size="small" color="warning" variant="outlined" label="Verifying" />; + } +}; + +/** Whether a clip's bytes are in the archive, from one probe of its hash. */ +const ArchiveChip = ({ contentHash }: { contentHash: string | null | undefined }) => { + const { probe, error } = useArchiveProbe(contentHash); + if (!contentHash) return <Dash />; + return <ArchiveStateChip state={probe} error={error} />; +}; + +export default ArchiveChip; diff --git a/src/archive/ArchivedOutputs.tsx b/src/archive/ArchivedOutputs.tsx new file mode 100644 index 0000000..95eae57 --- /dev/null +++ b/src/archive/ArchivedOutputs.tsx @@ -0,0 +1,81 @@ +import { + Datagrid, + FunctionField, + Pagination, + ReferenceField, + ReferenceManyField, + useRecordContext, +} from 'react-admin'; +import { Typography } from '@mui/material'; + +import { asColumn } from '../components'; +import type { RunArtifact, StoredObject } from '../contract'; +import { SizeField } from '../videos/VideoFields'; +import DownloadButton from './DownloadButton'; +import StatusField from './StatusField'; + +const SizeColumn = asColumn(SizeField); + +const NoArtifacts = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + p: 1, + }} + > + No outputs archived for this run yet. Artefacts appear once a client uploads the run + directory. + </Typography> +); + +// The download route answers 409 until the linked object is complete. +const CompleteDownload = () => { + const object = useRecordContext<StoredObject>(); + if (!object || object.status !== 'complete') return null; + return <DownloadButton objectId={object.id} />; +}; + +/** The run directory files a client archived, each through its stored object. */ +const ArchivedOutputs = () => ( + <ReferenceManyField + reference="run_artifacts" + target="run_id" + sort={{ field: 'relpath', order: 'ASC' }} + perPage={25} + pagination={<Pagination />} + > + <Datagrid bulkActionButtons={false} empty={<NoArtifacts />} rowClick={false}> + <FunctionField<RunArtifact> + label="Path" + render={artifact => ( + <Typography variant="body2" sx={{ fontFamily: 'monospace' }}> + {artifact.relpath} + </Typography> + )} + /> + <SizeColumn label="Size" source="size_bytes" sortable={false} /> + <ReferenceField + source="stored_object_id" + reference="stored_objects" + link="show" + label="Archive" + sortable={false} + emptyText="—" + > + <StatusField /> + </ReferenceField> + <ReferenceField + source="stored_object_id" + reference="stored_objects" + link={false} + label={false} + sortable={false} + > + <CompleteDownload /> + </ReferenceField> + </Datagrid> + </ReferenceManyField> +); + +export default ArchivedOutputs; diff --git a/src/archive/DownloadButton.tsx b/src/archive/DownloadButton.tsx new file mode 100644 index 0000000..61df781 --- /dev/null +++ b/src/archive/DownloadButton.tsx @@ -0,0 +1,30 @@ +import { MouseEvent } from 'react'; +import { Button, useDataProvider, useNotify } from 'react-admin'; +import DownloadIcon from '@mui/icons-material/Download'; + +import type { DrmDataProvider } from '../dataProvider'; + +/** Mints a short-lived signed fetch link on click, so the link can never be stale. */ +const DownloadButton = ({ objectId }: { objectId: string }) => { + const dataProvider = useDataProvider<DrmDataProvider>(); + const notify = useNotify(); + const download = async (event: MouseEvent<HTMLButtonElement>) => { + // The datagrid row would otherwise navigate away on the same click. + event.stopPropagation(); + try { + const { url } = await dataProvider.archiveDownload(objectId); + window.open(url, '_blank', 'noopener'); + } catch (error) { + notify(error instanceof Error ? error.message : 'The download URL was refused', { + type: 'warning', + }); + } + }; + return ( + <Button label="Download" onClick={download}> + <DownloadIcon /> + </Button> + ); +}; + +export default DownloadButton; diff --git a/src/archive/StatusField.tsx b/src/archive/StatusField.tsx new file mode 100644 index 0000000..8becede --- /dev/null +++ b/src/archive/StatusField.tsx @@ -0,0 +1,51 @@ +import { Chip } from '@mui/material'; +import { useRecordContext } from 'react-admin'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import ErrorIcon from '@mui/icons-material/Error'; +import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; + +import { StoredObjectStatus } from '../contract'; + +const STATUS_LABELS: Record<StoredObjectStatus, string> = { + pending: 'Pending', + complete: 'Complete', + failed: 'Failed', +}; + +const STATUS_COLOURS: Record<StoredObjectStatus, 'success' | 'warning' | 'error'> = { + pending: 'warning', + complete: 'success', + failed: 'error', +}; + +const STATUS_ICONS: Record<StoredObjectStatus, typeof CheckCircleIcon> = { + pending: HourglassEmptyIcon, + complete: CheckCircleIcon, + failed: ErrorIcon, +}; + +/** How a stored object's status reads everywhere it is shown. */ +export const ObjectStatusChip = ({ status }: { status: string }) => { + if (!(status in STATUS_LABELS)) return <span>{status}</span>; + const known = status as StoredObjectStatus; + const Icon = STATUS_ICONS[known]; + return ( + <Chip + size="small" + icon={<Icon fontSize="small" />} + label={STATUS_LABELS[known]} + color={STATUS_COLOURS[known]} + variant={known === 'complete' || known === 'failed' ? 'filled' : 'outlined'} + /> + ); +}; + +// `label` is read by the Datagrid header, not here. +const StatusField = ({ emptyText = '—' }: { label?: string; emptyText?: string }) => { + const record = useRecordContext(); + const status = record?.status as string | undefined; + if (!status) return <span>{emptyText}</span>; + return <ObjectStatusChip status={status} />; +}; + +export default StatusField; diff --git a/src/archive/StoredObjectList.tsx b/src/archive/StoredObjectList.tsx new file mode 100644 index 0000000..4dfa781 --- /dev/null +++ b/src/archive/StoredObjectList.tsx @@ -0,0 +1,102 @@ +import { + Datagrid, + List, + ReferenceField, + SelectInput, + TextField, + useRecordContext, +} from 'react-admin'; +import { Box, Typography } from '@mui/material'; + +import { AccountField, asColumn, HashField } from '../components'; +import type { StoredObject } from '../contract'; +import { STORED_OBJECT_STATUS_VALUES } from '../contract'; +import RelativeDateField from '../devices/RelativeDateField'; +import { SizeField } from '../videos/VideoFields'; +import StatusField from './StatusField'; + +const HashColumn = asColumn(HashField); +const SizeColumn = asColumn(SizeField); +const StatusColumn = asColumn(StatusField); +const RelativeDateColumn = asColumn(RelativeDateField); + +const statusChoices = STORED_OBJECT_STATUS_VALUES.map(id => ({ id, name: id })); + +const archiveFilters = [ + <SelectInput key="status" source="status" label="Status" choices={statusChoices} />, + <SelectInput + key="kind" + source="kind" + label="Kind" + choices={[ + { id: 'video', name: 'video' }, + { id: 'artifact', name: 'artifact' }, + ]} + />, +]; + +/** A blob is sent by a device or by a person through the console, never both. */ +export const UploaderField = ({ + emptyText = '—', +}: { + label?: string; + sortable?: boolean; + emptyText?: string; +}) => { + const record = useRecordContext<StoredObject>(); + if (!record) return null; + if (record.uploaded_by_device_id) { + return ( + <ReferenceField source="uploaded_by_device_id" reference="devices" link="show"> + <TextField source="name" /> + </ReferenceField> + ); + } + return <AccountField source="uploaded_by" emptyText={emptyText} />; +}; + +const ArchiveEmpty = () => ( + <Box + sx={{ + textAlign: 'center', + m: 4, + }} + > + <Typography variant="h6" gutterBottom> + Nothing archived + </Typography> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Blobs appear here once a device or the upload page sends them. + </Typography> + </Box> +); + +const StoredObjectList = () => ( + <List + filters={archiveFilters} + sort={{ field: 'created_at', order: 'DESC' }} + perPage={50} + empty={<ArchiveEmpty />} + > + <Datagrid rowClick="show" bulkActionButtons={false}> + <HashColumn label="Content hash" source="content_hash" /> + <TextField source="kind" sortable={false} /> + <StatusColumn label="Status" sortable={false} /> + <SizeColumn label="Size" source="size_bytes" sortable={false} /> + <RelativeDateColumn + label="Archived" + source="completed_at" + emptyText="—" + sortable={false} + /> + <UploaderField label="Uploaded by" sortable={false} /> + </Datagrid> + </List> +); + +export default StoredObjectList; diff --git a/src/archive/StoredObjectShow.tsx b/src/archive/StoredObjectShow.tsx new file mode 100644 index 0000000..ac23f5e --- /dev/null +++ b/src/archive/StoredObjectShow.tsx @@ -0,0 +1,174 @@ +import { + DateField, + Labeled, + Show, + TextField, + TopToolbar, + useGetList, + useRecordContext, +} from 'react-admin'; +import { Link } from 'react-router-dom'; +import { Alert, Stack, Typography } from '@mui/material'; + +import { HashField } from '../components'; +import type { RunArtifact, StoredObject, VideoAsset } from '../contract'; +import { SizeField } from '../videos/VideoFields'; +import DownloadButton from './DownloadButton'; +import StatusField from './StatusField'; +import { UploaderField } from './StoredObjectList'; + +const StoredObjectActions = () => { + const record = useRecordContext<StoredObject>(); + return ( + <TopToolbar> + {/* The download route answers 409 until the object is complete. */} + {record?.status === 'complete' && <DownloadButton objectId={record.id} />} + </TopToolbar> + ); +}; + +const FailureNotice = () => { + const record = useRecordContext<StoredObject>(); + if (record?.status !== 'failed') return null; + return <Alert severity="error">{record.failure ?? 'The upload failed.'}</Alert>; +}; + +const NoConsumers = ({ children }: { children: string }) => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + {children} + </Typography> +); + +// Clips reference the archive by content hash, not by object id, so the join +// runs over `hash` here. +const VideoConsumers = ({ contentHash }: { contentHash: string }) => { + const { data, isPending } = useGetList<VideoAsset>('videos', { + pagination: { page: 1, perPage: 25 }, + sort: { field: 'file_name', order: 'ASC' }, + filter: { hash: contentHash }, + }); + if (isPending) return null; + if (!data?.length) { + return <NoConsumers>No registered clip carries this hash.</NoConsumers>; + } + return ( + <Stack spacing={0.5}> + {data.map(video => ( + <Typography key={video.id} variant="body2"> + <Link to={`/videos/${video.id}/show`}>{video.file_name}</Link> + </Typography> + ))} + </Stack> + ); +}; + +const ArtifactConsumers = ({ objectId }: { objectId: string }) => { + const { data, isPending } = useGetList<RunArtifact>('run_artifacts', { + pagination: { page: 1, perPage: 25 }, + sort: { field: 'relpath', order: 'ASC' }, + filter: { stored_object_id: objectId }, + }); + if (isPending) return null; + if (!data?.length) { + return <NoConsumers>No run artefact links this object.</NoConsumers>; + } + return ( + <Stack spacing={0.5}> + {data.map(artifact => ( + <Stack + key={artifact.id} + direction="row" + spacing={1} + useFlexGap + sx={{ alignItems: 'baseline', flexWrap: 'wrap' }} + > + <Typography variant="body2"> + <Link to={`/runs/${artifact.run_id}/show`}>Run</Link> + </Typography> + <Typography variant="body2" sx={{ fontFamily: 'monospace' }}> + {artifact.relpath} + </Typography> + </Stack> + ))} + </Stack> + ); +}; + +/** The rows that reference this blob, so an object explains why it is kept. */ +const UsedByPanel = () => { + const record = useRecordContext<StoredObject>(); + if (!record) return null; + return ( + <Labeled label="Used by"> + {record.kind === 'video' ? ( + <VideoConsumers contentHash={record.content_hash} /> + ) : ( + <ArtifactConsumers objectId={record.id} /> + )} + </Labeled> + ); +}; + +const ObjectTitle = () => { + const record = useRecordContext<StoredObject>(); + return <span>{record ? `Object ${record.content_hash}` : 'Object'}</span>; +}; + +// The bucket layout is the server's business, so `s3_key` stays off the page. +const StoredObjectShow = () => ( + <Show title={<ObjectTitle />} actions={<StoredObjectActions />}> + <Stack spacing={2} sx={{ p: 2 }}> + <FailureNotice /> + <Labeled label="Content hash"> + <HashField source="content_hash" abbreviate={false} /> + </Labeled> + <Stack + direction="row" + spacing={3} + useFlexGap + sx={{ + flexWrap: 'wrap', + }} + > + <Labeled label="Status"> + <StatusField /> + </Labeled> + <Labeled label="Kind"> + <TextField source="kind" /> + </Labeled> + <Labeled label="Size"> + <SizeField /> + </Labeled> + <Labeled label="Uploaded by"> + <UploaderField /> + </Labeled> + </Stack> + <Stack + direction="row" + spacing={3} + useFlexGap + sx={{ + flexWrap: 'wrap', + }} + > + <Labeled label="Created"> + <DateField source="created_at" showTime /> + </Labeled> + <Labeled label="Last part"> + <DateField source="last_part_at" showTime emptyText="—" /> + </Labeled> + <Labeled label="Verified"> + <DateField source="completed_at" showTime emptyText="—" /> + </Labeled> + </Stack> + <UsedByPanel /> + </Stack> + </Show> +); + +export default StoredObjectShow; diff --git a/src/archive/UploadPage.tsx b/src/archive/UploadPage.tsx new file mode 100644 index 0000000..f1a7288 --- /dev/null +++ b/src/archive/UploadPage.tsx @@ -0,0 +1,189 @@ +import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { HttpError, Title, useDataProvider, useGetList } from 'react-admin'; +import { Link } from 'react-router-dom'; +import { + Alert, + Box, + Button, + Card, + CardContent, + LinearProgress, + Stack, + Typography, +} from '@mui/material'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; + +import type { VideoAsset } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; +import { formatBytes } from '../videos/VideoFields'; +import { imohashOfFile } from './hash'; +import { uploadVideo } from './upload'; + +type Phase = + | { name: 'idle' } + | { name: 'uploading'; sent: number; total: number } + | { name: 'complete'; deduplicated: boolean } + | { name: 'error'; message: string; unconfigured: boolean }; + +/** Whether the registry already knows a clip with this content, and a link if so. */ +const VideoMatch = ({ contentHash }: { contentHash: string }) => { + const { data, isPending } = useGetList<VideoAsset>('videos', { + filter: { hash: contentHash }, + pagination: { page: 1, perPage: 1 }, + sort: { field: 'created_at', order: 'DESC' }, + }); + if (isPending) return null; + const video = data?.[0]; + if (!video) { + return ( + <Typography variant="body2"> + Stored, but no registered clip carries this hash. It attaches to one when a + device that holds the same file syncs. + </Typography> + ); + } + return ( + <Typography variant="body2"> + This content is registered as the clip{' '} + <Link to={`/videos/${video.id}/show`}>{video.file_name}</Link>. + </Typography> + ); +}; + +const Progress = ({ phase }: { phase: Phase }) => { + switch (phase.name) { + case 'uploading': + return ( + <Box> + <Typography variant="body2" gutterBottom> + Uploading part {Math.min(phase.sent + 1, phase.total)} of {phase.total} + </Typography> + <LinearProgress + variant="determinate" + value={(phase.sent / phase.total) * 100} + /> + </Box> + ); + default: + return null; + } +}; + +const UploadPage = () => { + const dataProvider = useDataProvider<DrmDataProvider>(); + const [file, setFile] = useState<File | null>(null); + const [contentHash, setContentHash] = useState<string | null>(null); + const [phase, setPhase] = useState<Phase>({ name: 'idle' }); + // The hash-upload-poll chain outlives renders, so state writes after unmount + // are gated on this rather than surfacing React warnings. + const alive = useRef(true); + useEffect(() => { + alive.current = true; + return () => { + alive.current = false; + }; + }, []); + + const send = async (picked: File) => { + setFile(picked); + setContentHash(null); + if (picked.size === 0) { + setPhase({ + name: 'error', + message: 'The file is empty, and the archive refuses empty content.', + unconfigured: false, + }); + return; + } + try { + // Sampled, so this is instant even for a 4 GB chapter. + const hash = await imohashOfFile(picked); + if (!alive.current) return; + setContentHash(hash); + setPhase({ name: 'uploading', sent: 0, total: 1 }); + const outcome = await uploadVideo(dataProvider, picked, hash, (sent, total) => { + if (alive.current) setPhase({ name: 'uploading', sent, total }); + }); + if (!alive.current) return; + setPhase({ name: 'complete', deduplicated: outcome.deduplicated }); + } catch (error) { + if (!alive.current) return; + if (error instanceof HttpError && error.status === 503) { + setPhase({ name: 'error', message: error.message, unconfigured: true }); + return; + } + setPhase({ + name: 'error', + message: error instanceof Error ? error.message : 'The upload failed.', + unconfigured: false, + }); + } + }; + + const pick = (event: ChangeEvent<HTMLInputElement>) => { + const picked = event.target.files?.[0]; + // The same file can be picked again after a failure. + event.target.value = ''; + if (picked) send(picked); + }; + + const busy = phase.name === 'uploading'; + + return ( + <> + <Title title="Upload to the archive" /> + <Card sx={{ mt: 2, maxWidth: 760 }}> + <CardContent> + <Stack spacing={2}> + <Typography variant="h6">Upload to the archive</Typography> + + <Box> + <Button + component="label" + variant="contained" + startIcon={<CloudUploadIcon />} + disabled={busy} + > + Choose a video file + <input type="file" hidden onChange={pick} /> + </Button> + </Box> + + {file && ( + <Typography variant="body2" sx={{ fontFamily: 'monospace' }}> + {file.name} · {formatBytes(file.size)} + {contentHash ? ` · ${contentHash}` : ''} + </Typography> + )} + + <Progress phase={phase} /> + + {phase.name === 'complete' && ( + <> + <Alert severity="success"> + {phase.deduplicated + ? 'Already archived, so nothing was sent.' + : 'Stored.'} + </Alert> + {contentHash && <VideoMatch contentHash={contentHash} />} + </> + )} + + {phase.name === 'error' && + (phase.unconfigured ? ( + <Alert severity="warning"> + The archive is not configured on this registry, so nothing + can be uploaded. An administrator has to set the S3 + environment on the server first. + </Alert> + ) : ( + <Alert severity="error">{phase.message}</Alert> + ))} + </Stack> + </CardContent> + </Card> + </> + ); +}; + +export default UploadPage; diff --git a/src/archive/hash.ts b/src/archive/hash.ts new file mode 100644 index 0000000..fa6cfd3 --- /dev/null +++ b/src/archive/hash.ts @@ -0,0 +1,142 @@ +// imohash, the identity a device gives every clip it ingests. The archive keys +// blobs on it, so the console has to compute the same value for a file picked +// here or an upload could never meet the clip it belongs to. +// +// It reads three 16 KiB samples and the file size rather than the whole file, so +// this is instant even for a 4 GB chapter. It is a dedup identity, not a +// checksum: integrity of the transfer is the store's own, which answers every +// part with the MD5 it computed of what it stored. + +const SAMPLE_THRESHOLD = 128 * 1024; +const SAMPLE_SIZE = 16 * 1024; + +const C1 = 0x87c37b91114253d5n; +const C2 = 0x4cf5ad432745937fn; +const MASK = 0xffffffffffffffffn; + +const mul = (a: bigint, b: bigint) => (a * b) & MASK; +const add = (a: bigint, b: bigint) => (a + b) & MASK; +const rotl = (v: bigint, n: bigint) => ((v << n) | (v >> (64n - n))) & MASK; + +const fmix64 = (input: bigint) => { + let k = input; + k ^= k >> 33n; + k = mul(k, 0xff51afd7ed558ccdn); + k ^= k >> 33n; + k = mul(k, 0xc4ceb9fe1a85ec53n); + k ^= k >> 33n; + return k; +}; + +const readLE64 = (bytes: Uint8Array, at: number) => { + let value = 0n; + for (let i = 7; i >= 0; i -= 1) { + value = (value << 8n) | BigInt(bytes[at + i] ?? 0); + } + return value; +}; + +/** MurmurHash3 x64 128, as the two 64-bit halves it produces. */ +const murmur3x64 = (data: Uint8Array): [bigint, bigint] => { + let h1 = 0n; + let h2 = 0n; + const blocks = Math.floor(data.length / 16); + + for (let i = 0; i < blocks; i += 1) { + let k1 = readLE64(data, i * 16); + let k2 = readLE64(data, i * 16 + 8); + + k1 = mul(rotl(mul(k1, C1), 31n), C2); + h1 ^= k1; + h1 = add(mul(rotl(h1, 27n), 1n), h2); + h1 = add(mul(h1, 5n), 0x52dce729n); + + k2 = mul(rotl(mul(k2, C2), 33n), C1); + h2 ^= k2; + h2 = add(mul(rotl(h2, 31n), 1n), h1); + h2 = add(mul(h2, 5n), 0x38495ab5n); + } + + const tail = data.subarray(blocks * 16); + let k1 = 0n; + let k2 = 0n; + for (let i = tail.length - 1; i >= 8; i -= 1) { + k2 = (k2 << 8n) | BigInt(tail[i]); + } + for (let i = Math.min(tail.length, 8) - 1; i >= 0; i -= 1) { + k1 = (k1 << 8n) | BigInt(tail[i]); + } + if (tail.length > 8) { + k2 = mul(rotl(mul(k2, C2), 33n), C1); + h2 ^= k2; + } + if (tail.length > 0) { + k1 = mul(rotl(mul(k1, C1), 31n), C2); + h1 ^= k1; + } + + const length = BigInt(data.length); + h1 ^= length; + h2 ^= length; + h1 = add(h1, h2); + h2 = add(h2, h1); + h1 = fmix64(h1); + h2 = fmix64(h2); + h1 = add(h1, h2); + h2 = add(h2, h1); + return [h1, h2]; +}; + +/** Protocol-buffer style unsigned varint, which is how imohash carries the size. */ +const varint = (value: number): number[] => { + const out: number[] = []; + let rest = BigInt(value); + while (rest >= 0x80n) { + out.push(Number((rest & 0x7fn) | 0x80n)); + rest >>= 7n; + } + out.push(Number(rest)); + return out; +}; + +const bigEndianBytes = (value: bigint) => { + const out = new Uint8Array(8); + for (let i = 7; i >= 0; i -= 1) { + out[i] = Number((value >> BigInt((7 - i) * 8)) & 0xffn); + } + return out; +}; + +/** The digest of an already-sampled buffer. Exported so a test can pin it. */ +export const imohashOfSample = (sample: Uint8Array, size: number): string => { + const [h1, h2] = murmur3x64(sample); + // The two halves big-endian, then the size varint written over the front. + const digest = new Uint8Array(16); + digest.set(bigEndianBytes(h1), 0); + digest.set(bigEndianBytes(h2), 8); + digest.set(varint(size), 0); + return Array.from(digest) + .map(b => b.toString(16).padStart(2, '0')) + .join(''); +}; + +/** imohash of a file as 32 lowercase hex characters. */ +export const imohashOfFile = async (file: File): Promise<string> => { + const size = file.size; + let sample: Uint8Array; + if (size < SAMPLE_THRESHOLD || size < 4 * SAMPLE_SIZE) { + sample = new Uint8Array(await file.arrayBuffer()); + } else { + const middle = Math.floor(size / 2); + const slices = await Promise.all([ + file.slice(0, SAMPLE_SIZE).arrayBuffer(), + file.slice(middle, middle + SAMPLE_SIZE).arrayBuffer(), + file.slice(size - SAMPLE_SIZE, size).arrayBuffer(), + ]); + sample = new Uint8Array(SAMPLE_SIZE * 3); + slices.forEach((slice, index) => { + sample.set(new Uint8Array(slice), index * SAMPLE_SIZE); + }); + } + return imohashOfSample(sample, size); +}; diff --git a/src/archive/index.tsx b/src/archive/index.tsx new file mode 100644 index 0000000..5c5b970 --- /dev/null +++ b/src/archive/index.tsx @@ -0,0 +1,20 @@ +import { Route } from 'react-router-dom'; +import Inventory2Icon from '@mui/icons-material/Inventory2'; + +import type { StoredObject } from '../contract'; +import StoredObjectList from './StoredObjectList'; +import StoredObjectShow from './StoredObjectShow'; +import UploadPage from './UploadPage'; + +// Read-only: rows exist because a client uploaded bytes, never because the console +// said so. The upload page under `upload` is the console's only write path. +export default { + list: StoredObjectList, + show: StoredObjectShow, + recordRepresentation: (record: StoredObject) => record.content_hash, + icon: Inventory2Icon, + options: { + label: 'Archive', + }, + children: <Route path="upload" element={<UploadPage />} />, +}; diff --git a/src/archive/upload.ts b/src/archive/upload.ts new file mode 100644 index 0000000..e0a6726 --- /dev/null +++ b/src/archive/upload.ts @@ -0,0 +1,60 @@ +import type { CompletedPart } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; + +export type UploadOutcome = { + objectId: string; + // `complete` answered by dedup: the content was already archived, nothing travelled. + deduplicated: boolean; +}; + +/** + * Send one video file into the archive: initiate, PUT the missing parts through the + * registry, complete. + * + * Resumes an interrupted upload of the same content wherever it stopped: `initiate` + * reports the parts already stored and only the rest are sent. The server assembles + * the object from its own part listing, so completion carries only the receipts + * collected this session, which may be none on a resumed upload. + */ +export const uploadVideo = async ( + dataProvider: DrmDataProvider, + file: File, + contentHash: string, + onParts: (sent: number, total: number) => void, +): Promise<UploadOutcome> => { + const initiated = await dataProvider.archiveInitiate({ + content_hash: contentHash, + size_bytes: file.size, + kind: 'video', + }); + if (initiated.status === 'complete') { + return { objectId: initiated.object_id, deduplicated: true }; + } + + const partSize = Number(initiated.part_size_bytes); + if (!partSize) { + throw new Error('The server answered pending without a part size.'); + } + const done = new Set(initiated.parts_done ?? []); + const total = Math.ceil(file.size / partSize); + const parts: CompletedPart[] = []; + let sent = done.size; + onParts(sent, total); + + for (let partNumber = 1; partNumber <= total; partNumber += 1) { + if (done.has(partNumber)) continue; + const begin = (partNumber - 1) * partSize; + const slice = file.slice(begin, Math.min(begin + partSize, file.size)); + const receipt = await dataProvider.archiveUploadPart( + initiated.object_id, + partNumber, + slice, + ); + parts.push({ part_number: receipt.part_number, etag: receipt.etag }); + sent += 1; + onParts(sent, total); + } + + await dataProvider.archiveComplete(initiated.object_id, parts); + return { objectId: initiated.object_id, deduplicated: false }; +}; diff --git a/src/archive/useArchiveProbe.ts b/src/archive/useArchiveProbe.ts new file mode 100644 index 0000000..af93043 --- /dev/null +++ b/src/archive/useArchiveProbe.ts @@ -0,0 +1,40 @@ +import { useEffect, useState } from 'react'; +import { useDataProvider } from 'react-admin'; + +import type { ArchiveProbe } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; + +/** + * One probe of the archive for a content hash. + * + * `probe` is `undefined` while loading or without a hash, `null` when nothing is + * archived under it, and the object's state otherwise. + */ +export const useArchiveProbe = (contentHash: string | null | undefined) => { + const dataProvider = useDataProvider<DrmDataProvider>(); + const [probe, setProbe] = useState<ArchiveProbe | null>(); + const [error, setError] = useState<string>(); + + useEffect(() => { + if (!contentHash) { + setProbe(undefined); + return; + } + let current = true; + dataProvider + .archiveByHash(contentHash) + .then(result => { + if (current) setProbe(result); + }) + .catch((e: unknown) => { + if (current) { + setError(e instanceof Error ? e.message : 'Could not probe the archive'); + } + }); + return () => { + current = false; + }; + }, [dataProvider, contentHash]); + + return { probe, error }; +}; diff --git a/src/archive/useBatchProbe.ts b/src/archive/useBatchProbe.ts new file mode 100644 index 0000000..13c9fb1 --- /dev/null +++ b/src/archive/useBatchProbe.ts @@ -0,0 +1,69 @@ +import { useQuery } from '@tanstack/react-query'; +import { useDataProvider } from 'react-admin'; + +import type { BatchProbeState, RunArchiveState } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; + +// The registry caps a probe body, so a larger page goes over in slices. +const HASH_CHUNK = 500; +const RUN_CHUNK = 200; + +const chunked = (keys: string[], size: number): string[][] => { + const slices: string[][] = []; + for (let start = 0; start < keys.length; start += size) { + slices.push(keys.slice(start, start + size)); + } + return slices; +}; + +const useBatchProbe = <State>( + scope: string, + keys: (string | null | undefined)[], + chunk: number, + fetchStates: (keys: string[]) => Promise<{ [key: string]: State }>, +) => { + // Rows hand in a fresh array every render. Sorted and deduplicated it is one query + // key, which is what turns a whole column into a single POST, and what lets the + // Refresh button re-probe rather than read a cache that never expires. + const wanted = Array.from( + new Set(keys.filter((key): key is string => Boolean(key))), + ).sort(); + + const { data, error } = useQuery({ + queryKey: ['archiveProbe', scope, wanted], + queryFn: async () => { + const results = await Promise.all(chunked(wanted, chunk).map(fetchStates)); + // Absent from the response means nothing archived, and saying so keeps a + // row from reading as still checking. + const states = new Map<string, State | null>(wanted.map(key => [key, null])); + for (const result of results) { + for (const [key, state] of Object.entries(result)) states.set(key, state); + } + return states; + }, + enabled: wanted.length > 0, + staleTime: Infinity, + retry: false, + }); + + return { + states: data ?? new Map<string, State | null>(), + error: error ? error.message || 'Could not probe the archive' : undefined, + }; +}; + +/** Archive state per content hash for a whole page, from one POST. */ +export const useArchiveProbeBatch = (hashes: (string | null | undefined)[]) => { + const dataProvider = useDataProvider<DrmDataProvider>(); + return useBatchProbe<BatchProbeState>('hashes', hashes, HASH_CHUNK, keys => + dataProvider.archiveProbe(keys).then(response => response.states), + ); +}; + +/** Archived-output counts per run for a whole page, from one POST. */ +export const useRunsProbeBatch = (runIds: (string | null | undefined)[]) => { + const dataProvider = useDataProvider<DrmDataProvider>(); + return useBatchProbe<RunArchiveState>('runs', runIds, RUN_CHUNK, keys => + dataProvider.archiveRunsProbe(keys).then(response => response.states), + ); +}; diff --git a/src/authProvider.tsx b/src/authProvider.tsx index f62a153..1c27a0c 100644 --- a/src/authProvider.tsx +++ b/src/authProvider.tsx @@ -1,119 +1,74 @@ -import { AuthProvider } from 'react-admin'; +import { AuthProvider, fetchUtils } from 'react-admin'; import Keycloak, { KeycloakTokenParsed } from 'keycloak-js'; -import jwt_decode from 'jwt-decode'; -// import { getKeycloakHeaders } from 'ra-keycloak'; +export type PermissionsFunction = (decoded: KeycloakTokenParsed) => unknown; -export type PermissionsFunction = (decoded: KeycloakTokenParsed) => any; +export const getKeycloakHeaders = ( + token: string | undefined, + options: fetchUtils.Options | undefined, +): Headers => { + const headers = ((options && options.headers) || + new Headers({ Accept: 'application/json' })) as Headers; + if (token) { + headers.set('Authorization', `Bearer ${token}`); + } + return headers; +}; + +export const httpClient = + (keycloak: Keycloak) => (url: string, options?: fetchUtils.Options) => + fetchUtils.fetchJson(url, { + ...options, + headers: getKeycloakHeaders(keycloak.token, options), + }); -/** - * Returns an authProvider for react-admin which authenticates - * against a Keycloak instance. - * ``` - * - * @param client the keycloak client - * @param options.onPermissions function used to transform the permissions fetched from Keycloak into a permissions object in the form of what your react-admin app expects - * @param options.loginRedirectUri URI used to override the redirect URI after successful login - * @param options.logoutRedirectUri URI used to override the redirect URI after successful logout - * - * @returns an authProvider ready to be used by React-Admin. - */ +// Local rather than from `ra-keycloak`, whose own keycloak-js dependency would be a +// second copy alongside the one the application constructs. export const keycloakAuthProvider = ( client: Keycloak, - options: { - onPermissions?: PermissionsFunction; - loginRedirectUri?: string; - logoutRedirectUri?: string; - } = {} + options: { onPermissions?: PermissionsFunction } = {}, ): AuthProvider => ({ async login() { - return client.login({ - redirectUri: options.loginRedirectUri ?? window.location.origin, - }); + return client.login({ redirectUri: window.location.origin }); }, + async logout() { - return client.logout({ - redirectUri: options.logoutRedirectUri ?? window.location.origin, - }); - }, - async checkError() { - return Promise.resolve(); + return client.logout({ redirectUri: window.location.origin }); }, - async checkAuth() { - try { - if (!client.authenticated || !client.token) { - throw new Error('Authentication failed.'); - } - // Check if the token is expired or needs refreshing - const isTokenValid = await this.isTokenValid(client.token); - - if (isTokenValid) { - console.log("Token is valid"); - // Token is valid, proceed with the request - return Promise.resolve(); - } else { - // Token is expired or needs refreshing, initiate token refresh - await this.refreshToken(); - console.log("Token refreshed") - // Token refreshed successfully, proceed with the request - return Promise.resolve(); - } - } catch (error) { - return Promise.reject(error); + // A 401 means the session went; a 403 means the account lacks the role, which + // signing in again cannot fix. + async checkError({ status }: { status?: number }) { + if (status === 401) { + await client.updateToken(30).catch(() => client.login()); } }, - isTokenValid(token) { - try { - const decodedToken = jwt_decode(token); - - // Check if the token has an expiration time - if (!decodedToken.exp) { - return false; // Token is considered invalid if there's no expiration time - } - // Convert expiration time to milliseconds and compare with the current time - const expirationTime = new Date(decodedToken.exp * 1000); // Convert seconds to milliseconds - const currentTime = new Date(Date.now()); - - return (currentTime < expirationTime); - } catch (error) { - console.error('Error decoding token:', error); - return false; // Consider the token invalid in case of decoding errors - } - }, - async refreshToken() { - // Update the Keycloak client with the new token - try { - const refreshed = await client.updateToken(); - if (refreshed) { - console.log('Token was successfully refreshed'); - } else { - console.log('Token is still valid'); - } - } catch (error) { - console.log('Failed to refresh the token, or the session has expired', error); + async checkAuth() { + if (!client.authenticated || !client.token) { + throw new Error('Not signed in'); } + // Refreshes when the token has under 30 seconds left, and no-ops otherwise. + await client.updateToken(30); }, + async getPermissions() { - if (!client.token) { - return Promise.resolve(false); + const claims = client.tokenParsed; + if (!claims) { + return false; } - const decoded = jwt_decode<KeycloakTokenParsed>(client.token); - return Promise.resolve( - options.onPermissions ? options.onPermissions(decoded) : decoded - ); + return options.onPermissions ? options.onPermissions(claims) : claims; }, + async getIdentity() { - if (client.token) { - const decoded = jwt_decode<KeycloakTokenParsed>(client.token); - const id = decoded.sub || ''; - const fullName = decoded.preferred_username; - return Promise.resolve({ id, fullName }); + const claims = client.tokenParsed; + if (!claims) { + throw new Error('Not signed in'); } - return Promise.reject('Failed to get identity.'); + return { id: claims.sub ?? '', fullName: claims.preferred_username }; }, + getToken() { return client.token; - } + }, }); diff --git a/src/campaigns/CampaignCreate.tsx b/src/campaigns/CampaignCreate.tsx new file mode 100644 index 0000000..8597d1f --- /dev/null +++ b/src/campaigns/CampaignCreate.tsx @@ -0,0 +1,13 @@ +import { Create, SimpleForm } from 'react-admin'; + +import CampaignInputs, { validateCampaign } from './CampaignInputs'; + +const CampaignCreate = () => ( + <Create redirect="show"> + <SimpleForm defaultValues={{ description: '' }} validate={validateCampaign}> + <CampaignInputs /> + </SimpleForm> + </Create> +); + +export default CampaignCreate; diff --git a/src/campaigns/CampaignEdit.tsx b/src/campaigns/CampaignEdit.tsx new file mode 100644 index 0000000..6d37ef3 --- /dev/null +++ b/src/campaigns/CampaignEdit.tsx @@ -0,0 +1,20 @@ +import { Edit, SaveButton, SimpleForm, Toolbar } from 'react-admin'; + +import CampaignInputs, { validateCampaign } from './CampaignInputs'; + +// Rows are tombstoned by the sync contract, so the default toolbar's delete is wrong here. +const CampaignEditToolbar = () => ( + <Toolbar> + <SaveButton /> + </Toolbar> +); + +const CampaignEdit = () => ( + <Edit redirect="show" mutationMode="pessimistic"> + <SimpleForm toolbar={<CampaignEditToolbar />} validate={validateCampaign}> + <CampaignInputs /> + </SimpleForm> + </Edit> +); + +export default CampaignEdit; diff --git a/src/campaigns/CampaignInputs.tsx b/src/campaigns/CampaignInputs.tsx new file mode 100644 index 0000000..a424356 --- /dev/null +++ b/src/campaigns/CampaignInputs.tsx @@ -0,0 +1,56 @@ +import { DateInput, required, TextInput } from 'react-admin'; +import { Grid, Typography } from '@mui/material'; + +type CampaignFormValues = { + begin_date?: string | null; + end_date?: string | null; +}; + +export const validateCampaign = ({ begin_date, end_date }: CampaignFormValues) => { + if (!begin_date || !end_date || end_date >= begin_date) return {}; + return { end_date: 'End date cannot precede the begin date' }; +}; + +const CampaignInputs = () => ( + <> + <Typography variant="h6" gutterBottom> + Expedition + </Typography> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <TextInput + source="name" + validate={required()} + helperText="Archive folder name, for example 2025_10_eritrea. Unique, ignoring case." + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 3, + }} + > + <DateInput source="begin_date" fullWidth /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 3, + }} + > + <DateInput source="end_date" fullWidth /> + </Grid> + <Grid size={12}> + <TextInput source="description" multiline rows={3} fullWidth /> + </Grid> + </Grid> + </> +); + +export default CampaignInputs; diff --git a/src/campaigns/CampaignList.tsx b/src/campaigns/CampaignList.tsx new file mode 100644 index 0000000..e11f47b --- /dev/null +++ b/src/campaigns/CampaignList.tsx @@ -0,0 +1,65 @@ +import { + CreateButton, + Datagrid, + DateField, + ExportButton, + List, + SearchInput, + TextField, + TopToolbar, +} from 'react-admin'; +import { Box, Typography } from '@mui/material'; + +import { useCanAuthor } from '../permissions'; + +const campaignFilters = [<SearchInput source="q" alwaysOn key="q" />]; + +const CampaignListActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + {canAuthor && <CreateButton />} + <ExportButton /> + </TopToolbar> + ); +}; + +const CampaignEmpty = () => { + const canAuthor = useCanAuthor(); + return ( + <Box + sx={{ + textAlign: 'center', + m: 4, + }} + > + <Typography variant="h6" gutterBottom> + No campaigns yet + </Typography> + <Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}> + A campaign is one expedition, named after its archive folder, for example + 2025_10_eritrea. One expedition visits many sites, and every pass the field + team records is filed against it. + </Typography> + {canAuthor && <CreateButton label="Create the first campaign" />} + </Box> + ); +}; + +const CampaignList = () => ( + <List + actions={<CampaignListActions />} + filters={campaignFilters} + sort={{ field: 'begin_date', order: 'DESC' }} + perPage={25} + empty={<CampaignEmpty />} + > + <Datagrid rowClick="show" bulkActionButtons={false}> + <TextField source="name" /> + <DateField source="begin_date" emptyText="—" /> + <DateField source="end_date" emptyText="—" /> + </Datagrid> + </List> +); + +export default CampaignList; diff --git a/src/campaigns/CampaignShow.tsx b/src/campaigns/CampaignShow.tsx new file mode 100644 index 0000000..4fa865b --- /dev/null +++ b/src/campaigns/CampaignShow.tsx @@ -0,0 +1,202 @@ +import { + Datagrid, + DateField, + EditButton, + FunctionField, + Labeled, + Link, + Pagination, + ReferenceField, + ReferenceManyField, + Show, + SimpleShowLayout, + TextField, + TopToolbar, + useCreatePath, + useGetList, + useGetMany, + useRecordContext, +} from 'react-admin'; +import { Box, Chip, Divider, Grid, Stack, Typography } from '@mui/material'; + +import { formatSeconds, QualityField, SyncFields, TombstoneButton } from '../components'; +import { useCanAuthor } from '../permissions'; +import type { Campaign, Transect, TransectPass } from '../contract'; + +const CampaignShowActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + {canAuthor && <EditButton />} + <TombstoneButton noun="campaign" /> + </TopToolbar> + ); +}; + +const PASS_PAGE = 500; + +/** + * The distinct lines this expedition swam, as links. Derived from the passes + * client-side because the registry keeps no campaign-to-transect table. + */ +const TransectsSurveyed = () => { + const record = useRecordContext<Campaign>(); + const createPath = useCreatePath(); + const { data: passes } = useGetList<TransectPass>('passes', { + filter: { campaign_id: record?.id }, + pagination: { page: 1, perPage: PASS_PAGE }, + sort: { field: 'created_at', order: 'DESC' }, + }); + + const transectIds = Array.from( + new Set( + (passes ?? []).map(pass => pass.transect_id).filter((id): id is string => !!id), + ), + ); + const { data: transects } = useGetMany<Transect>( + 'transects', + { ids: transectIds }, + { enabled: transectIds.length > 0 }, + ); + + if (!transects?.length) return null; + const named = [...transects].sort((a, b) => a.name.localeCompare(b.name)); + return ( + <Box> + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + Transects surveyed + </Typography> + <Stack + direction="row" + spacing={1} + useFlexGap + sx={{ + flexWrap: 'wrap', + }} + > + {named.map(transect => ( + <Chip + key={transect.id} + component={Link} + to={createPath({ + resource: 'transects', + type: 'show', + id: transect.id, + })} + label={transect.name} + size="small" + clickable + /> + ))} + </Stack> + </Box> + ); +}; + +const NoPasses = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + No passes recorded against this campaign. Passes arrive from the desktop clients when + they sync, so there is nothing to add here. + </Typography> +); + +const CampaignPasses = () => ( + <Box> + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + Passes + </Typography> + <ReferenceManyField + reference="passes" + target="campaign_id" + sort={{ field: 'created_at', order: 'DESC' }} + perPage={25} + pagination={<Pagination />} + > + <Datagrid rowClick="show" bulkActionButtons={false} empty={<NoPasses />}> + <TextField source="label" emptyText="unnamed" sortable={false} /> + <ReferenceField + source="transect_id" + reference="transects" + link="show" + sortable={false} + > + <TextField source="name" /> + </ReferenceField> + <FunctionField<TransectPass> + label="Window" + render={record => + `${formatSeconds(record.begin_s)} – ${formatSeconds(record.end_s)}` + } + /> + <TextField source="direction" /> + <QualityField source="quality" /> + </Datagrid> + </ReferenceManyField> + </Box> +); + +const CampaignShow = () => ( + <Show actions={<CampaignShowActions />}> + <SimpleShowLayout> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <Labeled label="Name"> + <TextField source="name" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <Labeled label="Begin date"> + <DateField source="begin_date" emptyText="—" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <Labeled label="End date"> + <DateField source="end_date" emptyText="—" /> + </Labeled> + </Grid> + <Grid size={12}> + <Labeled label="Description"> + <TextField source="description" emptyText="—" /> + </Labeled> + </Grid> + </Grid> + <TransectsSurveyed /> + <Divider /> + <CampaignPasses /> + <Divider /> + <SyncFields /> + </SimpleShowLayout> + </Show> +); + +export default CampaignShow; diff --git a/src/campaigns/index.tsx b/src/campaigns/index.tsx new file mode 100644 index 0000000..e54951a --- /dev/null +++ b/src/campaigns/index.tsx @@ -0,0 +1,17 @@ +import EventIcon from '@mui/icons-material/Event'; + +import CampaignCreate from './CampaignCreate'; +import CampaignEdit from './CampaignEdit'; +import CampaignList from './CampaignList'; +import CampaignShow from './CampaignShow'; + +export default { + list: CampaignList, + show: CampaignShow, + edit: CampaignEdit, + create: CampaignCreate, + icon: EventIcon, + options: { + label: 'Campaigns', + }, +}; diff --git a/src/components/AccountField.tsx b/src/components/AccountField.tsx new file mode 100644 index 0000000..b0cbb75 --- /dev/null +++ b/src/components/AccountField.tsx @@ -0,0 +1,43 @@ +import { useGetIdentity, useRecordContext } from 'react-admin'; +import { Tooltip, Typography } from '@mui/material'; + +// The registry stores a Keycloak subject and no name, so the whole uuid would only imply +// there is a person behind it to look up. +const AccountField = ({ + source, + emptyText = '—', + variant = 'body2', +}: { + source: string; + label?: string; + emptyText?: string; + sortable?: boolean; + variant?: 'body2' | 'caption'; +}) => { + const record = useRecordContext(); + const { identity } = useGetIdentity(); + const value = record?.[source] as string | null | undefined; + if (!value) { + return ( + <Typography + variant={variant} + component="span" + sx={{ + color: 'text.disabled', + }} + > + {emptyText} + </Typography> + ); + } + const label = identity?.id === value ? 'you' : `account ${value.slice(0, 8)}`; + return ( + <Tooltip title={`Account id ${value}`}> + <Typography variant={variant} component="span"> + {label} + </Typography> + </Tooltip> + ); +}; + +export default AccountField; diff --git a/src/components/CoordinateField.tsx b/src/components/CoordinateField.tsx new file mode 100644 index 0000000..9cf75e4 --- /dev/null +++ b/src/components/CoordinateField.tsx @@ -0,0 +1,24 @@ +import { Link, useRecordContext } from 'react-admin'; + +/** A lat/lon pair as a Google Maps link, so an operator can check a position in one click. */ +const CoordinateField = ({ + latSource, + lonSource, + emptyText = '—', +}: { + latSource: string; + lonSource: string; + emptyText?: string; +}) => { + const record = useRecordContext(); + const lat = record?.[latSource] as number | null | undefined; + const lon = record?.[lonSource] as number | null | undefined; + if (lat == null || lon == null) return <span>{emptyText}</span>; + return ( + <Link to={`https://www.google.com/maps?q=${lat},${lon}`} target="_blank"> + {`${lat}°, ${lon}°`} + </Link> + ); +}; + +export default CoordinateField; diff --git a/src/components/DurationField.tsx b/src/components/DurationField.tsx new file mode 100644 index 0000000..6f4cf96 --- /dev/null +++ b/src/components/DurationField.tsx @@ -0,0 +1,33 @@ +import { useRecordContext } from 'react-admin'; + +export const formatSeconds = (seconds: number) => { + const total = Math.max(0, Math.round(seconds)); + return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, '0')}`; +}; + +/** + * A begin/end window as `m:ss – m:ss (m:ss)`. With only `source`, a single `m:ss`. + */ +const DurationField = ({ + source, + endSource, + emptyText = '—', +}: { + source: string; + endSource?: string; + emptyText?: string; +}) => { + const record = useRecordContext(); + const begin = record?.[source] as number | null | undefined; + if (begin == null) return <span>{emptyText}</span>; + if (!endSource) return <span>{formatSeconds(begin)}</span>; + const end = record?.[endSource] as number | null | undefined; + if (end == null) return <span>{formatSeconds(begin)}</span>; + return ( + <span>{`${formatSeconds(begin)} – ${formatSeconds(end)} (${formatSeconds( + end - begin, + )})`}</span> + ); +}; + +export default DurationField; diff --git a/src/components/HashField.tsx b/src/components/HashField.tsx new file mode 100644 index 0000000..0d6a0c7 --- /dev/null +++ b/src/components/HashField.tsx @@ -0,0 +1,52 @@ +import { MouseEvent } from 'react'; +import { useNotify, useRecordContext } from 'react-admin'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import { Box, IconButton, Stack, Tooltip } from '@mui/material'; + +/** A content hash is an identity, so it needs to be copyable in full. */ +const HashField = ({ + source = 'hash', + abbreviate = true, + emptyText = 'Not hashed', +}: { + label?: string; + sortable?: boolean; + source?: string; + abbreviate?: boolean; + emptyText?: string; +}) => { + const record = useRecordContext(); + const notify = useNotify(); + const hash = record?.[source] as string | null | undefined; + if (!hash) return <span>{emptyText}</span>; + const copy = (event: MouseEvent<HTMLButtonElement>) => { + // The datagrid row would otherwise navigate away on the same click. + event.stopPropagation(); + navigator.clipboard.writeText(hash).then( + () => notify('Hash copied', { type: 'info' }), + () => notify('Could not copy the hash', { type: 'warning' }), + ); + }; + return ( + <Stack + direction="row" + spacing={0.5} + sx={{ + alignItems: 'center', + }} + > + <Tooltip title={hash}> + <Box component="span" sx={{ fontFamily: 'monospace' }}> + {abbreviate ? `${hash.slice(0, 12)}…` : hash} + </Box> + </Tooltip> + <Tooltip title="Copy the full hash"> + <IconButton size="small" onClick={copy}> + <ContentCopyIcon fontSize="inherit" /> + </IconButton> + </Tooltip> + </Stack> + ); +}; + +export default HashField; diff --git a/src/components/QualityField.tsx b/src/components/QualityField.tsx new file mode 100644 index 0000000..e72a0a3 --- /dev/null +++ b/src/components/QualityField.tsx @@ -0,0 +1,41 @@ +import { SelectInput, SelectInputProps, useRecordContext } from 'react-admin'; +import { Chip } from '@mui/material'; + +import { QUALITY_VALUES, Quality } from '../contract'; + +const QUALITY_LABELS: Record<Quality, string> = { + excellent: 'Excellent', + very_good: 'Very good', + good: 'Good', + meh: 'Meh', + bad: 'Bad', + very_bad: 'Very bad', +}; + +const QUALITY_COLOURS: Record<Quality, 'success' | 'info' | 'warning' | 'error'> = { + excellent: 'success', + very_good: 'success', + good: 'info', + meh: 'warning', + bad: 'error', + very_bad: 'error', +}; + +export const qualityChoices = QUALITY_VALUES.map(id => ({ id, name: QUALITY_LABELS[id] })); + +export const QualityField = ({ + source = 'quality', + emptyText = '—', +}: { + source?: string; + emptyText?: string; +}) => { + const record = useRecordContext(); + const value = record?.[source] as Quality | null | undefined; + if (!value || !(value in QUALITY_LABELS)) return <span>{emptyText}</span>; + return <Chip size="small" label={QUALITY_LABELS[value]} color={QUALITY_COLOURS[value]} />; +}; + +export const QualityInput = (props: Omit<SelectInputProps, 'choices'>) => ( + <SelectInput source="quality" label="Quality" choices={qualityChoices} {...props} /> +); diff --git a/src/components/SyncFields.tsx b/src/components/SyncFields.tsx new file mode 100644 index 0000000..de9218c --- /dev/null +++ b/src/components/SyncFields.tsx @@ -0,0 +1,39 @@ +import { ReferenceField, TextField, useRecordContext } from 'react-admin'; +import { Typography } from '@mui/material'; + +import RelativeDateField from '../devices/RelativeDateField'; + +/** + * One caption line of provenance at the foot of a Show page. + * + * A pushed row names its device. A row authored here names nobody: the schema + * deliberately records no person against survey rows, only the laptop. + */ +const SyncFields = () => { + const record = useRecordContext(); + if (!record) return null; + return ( + <Typography + variant="caption" + component="div" + sx={{ + color: 'text.secondary', + }} + > + {record.device_id ? ( + <> + Uploaded by{' '} + <ReferenceField source="device_id" reference="devices" link="show"> + <TextField source="name" variant="caption" /> + </ReferenceField> + {', '} + </> + ) : ( + <>Created in this console, </> + )} + changed <RelativeDateField source="updated_at" variant="caption" /> + </Typography> + ); +}; + +export default SyncFields; diff --git a/src/components/TombstoneButton.tsx b/src/components/TombstoneButton.tsx new file mode 100644 index 0000000..93b7227 --- /dev/null +++ b/src/components/TombstoneButton.tsx @@ -0,0 +1,26 @@ +import { DeleteWithConfirmButton } from 'react-admin'; + +import { useIsAdmin } from '../permissions'; + +/** + * Administrator-only delete, worded as the tombstone the registry actually writes. + * + * `noun` names the row in the dialog title, e.g. `site`. + */ +const TombstoneButton = ({ noun }: { noun: string }) => { + const admin = useIsAdmin(); + if (!admin) return null; + return ( + <DeleteWithConfirmButton + confirmTitle={`Delete this ${noun}?`} + confirmContent={ + `The ${noun} is not erased, it is tombstoned. It disappears from this ` + + 'console at once, and every field laptop holding a copy drops it on its ' + + 'next sync. Nothing here brings it back.' + } + confirmColor="warning" + /> + ); +}; + +export default TombstoneButton; diff --git a/src/components/TriStateField.tsx b/src/components/TriStateField.tsx new file mode 100644 index 0000000..6c5b1e4 --- /dev/null +++ b/src/components/TriStateField.tsx @@ -0,0 +1,44 @@ +import { useRecordContext } from 'react-admin'; +import CheckIcon from '@mui/icons-material/Check'; +import CloseIcon from '@mui/icons-material/Close'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutlined'; + +import { TRI_STATE_VALUES, TriState } from '../contract'; + +const TRI_STATE_LABELS: Record<TriState, string> = { + yes: 'Yes', + no: 'No', + unknown: 'Unknown', +}; + +const TRI_STATE_ICONS: Record<TriState, typeof CheckIcon> = { + yes: CheckIcon, + no: CloseIcon, + unknown: HelpOutlineIcon, +}; + +const TRI_STATE_COLOURS: Record<TriState, 'success' | 'error' | 'disabled'> = { + yes: 'success', + no: 'error', + unknown: 'disabled', +}; + +export const triStateChoices = TRI_STATE_VALUES.map(id => ({ + id, + name: TRI_STATE_LABELS[id], +})); + +/** The video `gravity` / `gps` columns, which are yes|no|unknown rather than booleans. */ +export const TriStateField = ({ source }: { source: string }) => { + const record = useRecordContext(); + const value = (record?.[source] ?? 'unknown') as TriState; + const key = value in TRI_STATE_LABELS ? value : 'unknown'; + const Icon = TRI_STATE_ICONS[key]; + return ( + <Icon + fontSize="small" + color={TRI_STATE_COLOURS[key]} + titleAccess={TRI_STATE_LABELS[key]} + /> + ); +}; diff --git a/src/components/column.ts b/src/components/column.ts new file mode 100644 index 0000000..efaf0a5 --- /dev/null +++ b/src/components/column.ts @@ -0,0 +1,9 @@ +import { ComponentType } from 'react'; + +type ColumnProps = { label?: string; sortable?: boolean }; + +/** Datagrid reads a column header from `label`, which the shared fields do not declare. */ +const asColumn = <P extends object>(Field: ComponentType<P>): ComponentType<P & ColumnProps> => + Field as ComponentType<P & ColumnProps>; + +export default asColumn; diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..8766bfe --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,9 @@ +export { default as AccountField } from './AccountField'; +export { default as asColumn } from './column'; +export { default as SyncFields } from './SyncFields'; +export { default as TombstoneButton } from './TombstoneButton'; +export { default as CoordinateField } from './CoordinateField'; +export { default as DurationField, formatSeconds } from './DurationField'; +export { default as HashField } from './HashField'; +export { QualityField, QualityInput, qualityChoices } from './QualityField'; +export { TriStateField, triStateChoices } from './TriStateField'; diff --git a/src/contract/api.d.ts b/src/contract/api.d.ts new file mode 100644 index 0000000..cadbbc0 --- /dev/null +++ b/src/contract/api.d.ts @@ -0,0 +1,8461 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + '/admin/erase-subject': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Null every reference to a Keycloak subject. Administrators only. + * @description Touches only the onboarding and upload audit columns. None of them sync, so the + * erasure is complete on the server and nothing propagates to devices. + */ + post: operations['erase_subject']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/archive/by-hash/{content_hash}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Whether content with this hash is archived. Cheap, for badges. */ + get: operations['by_hash']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/archive/initiate': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Begin or resume an upload, deduplicated by content hash. + * @description Content already archived answers `complete` with no upload at all. An unfinished + * upload of the same content resumes wherever it stopped, whoever started it. + */ + post: operations['initiate']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/archive/probe': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Archive state for many hashes at once, so badges cost one request per page. */ + post: operations['probe']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/archive/runs-probe': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Archive state for many runs at once: artefact counts, grouped in one query. */ + post: operations['runs_probe']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/archive/{object_id}/complete': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Assemble the uploaded parts into the finished object and verify them. + * @description S3 checked every part against the `ETag` it answered as the part arrived, so an + * assembly it accepts is the bytes the client sent. Whether those bytes are the + * content the client claimed is checked here: the stored size must match the + * initiated one, and the imohash re-computed from the stored object must match the + * claimed hash, before the object counts as `complete`. A mismatch deletes the + * object, fails the row and answers 409, so a wrong upload can never poison a + * content-addressed key another device would dedup against. + */ + post: operations['complete']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/archive/{object_id}/download': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * A short-lived download URL for a verified object. + * @description The URL points back at this registry's own `/archive/{id}/fetch` route with an + * HMAC signature in the query, so a browser navigation needs no bearer header + * while the object store stays unreachable. + */ + get: operations['download']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/archive/{object_id}/fetch': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Stream a verified object against a signed fetch link. */ + get: operations['fetch']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/archive/{object_id}/parts/{part_number}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Store one part's raw bytes. + * @description The body streams through to the object store under the registry's own + * credential: clients never reach the store themselves, so every byte arrives + * under the caller's authenticated identity. Parts may arrive in any order and + * re-sending one overwrites it, which is how a retry works. + */ + put: operations['upload_part']; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/campaigns': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all campaigns + * @description Retrieves all campaigns. + * + * This resource manages campaign items + * + * Additional sortable columns: + * - name + * - begin_date + * - end_date + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - name + * - begin_date + * - end_date + * - deleted_at + * - device_id. + */ + get: operations['get_all_campaigns']; + put?: never; + /** + * Create one campaign + * @description Creates a new campaign. + * + * This resource manages campaign items + */ + post: operations['create_one_campaign']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/campaigns/batch': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create many campaigns + * @description Creates multiple campaigns in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages campaign items + */ + post: operations['create_many_campaigns']; + /** + * Delete many campaigns + * @description Deletes many campaigns by their IDs and returns array of deleted UUIDs. + * + * Use `?partial=true` for partial success mode (deletes valid items even if some fail). + * + * This resource manages campaign items + */ + delete: operations['delete_many_campaigns']; + options?: never; + head?: never; + /** + * Update many campaigns + * @description Updates multiple campaigns in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages campaign items + */ + patch: operations['update_many_campaigns']; + trace?: never; + }; + '/campaigns/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one campaign + * @description Retrieves one campaign by its ID. + * + * This resource manages campaign items + */ + get: operations['get_one_campaign']; + /** + * Update one campaign + * @description Updates one campaign by its ID. + * + * This resource manages campaign items + */ + put: operations['update_one_campaign']; + post?: never; + /** + * Delete one campaign + * @description Deletes one campaign by its ID. + * + * This resource manages campaign items + */ + delete: operations['delete_one_campaign']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/config/class-groups': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * The benthic class groups and the colour each is drawn in. + * @description Served so the console colours a cover figure the same way the desktop viewer does. + */ + get: operations['get_class_groups']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/config/keycloak': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Keycloak details for the web interface. */ + get: operations['get_keycloak_config']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/cover_rows': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all cover_rows + * @description Retrieves all cover_rows. + * + * This resource manages cover_row items + * + * Additional sortable columns: + * - level + * - class_group + * - fraction + * - point_count + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - run_id + * - level + * - class_group + * - estimator + * - metric_source + * - deleted_at + * - device_id. + */ + get: operations['get_all_cover_rows']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/cover_rows/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one cover_row + * @description Retrieves one cover_row by its ID. + * + * This resource manages cover_row items + */ + get: operations['get_one_cover_row']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/devices': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all devices + * @description Retrieves all devices. + * + * This resource manages device items + * + * Additional sortable columns: + * - name + * - versions_changed_at + * - profile_reported_at + * - assigned_at + * - active_preset_reported_at + * - created_at + * - last_seen_at + * - revoked_at. + * + * Additional filterable columns: + * - id + * - enrolled_by + * - name + * - platform + * - gui_version + * - library_version + * - preset_schema_version + * - assigned_preset_id + * - revoked_at. + */ + get: operations['get_all_devices']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/devices/connect-codes': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Mint a connect code for a desktop installation. + * @description Interactive login only, so a device cannot invite further devices. + */ + post: operations['mint_code']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/devices/{device_id}/assign-preset': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Choose a device's default preset. Members may assign to their own, administrators + * to anyone's. + * @description The assignment travels in the next heartbeat response, and the device reports the + * preset it actually runs under in the request after that, so `active_preset_*` on + * the device row says whether the assignment was acknowledged. + */ + post: operations['assign_preset']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/devices/{device_id}/rename': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Rename a device. Members may rename their own, administrators anyone's. + * @description Interactive login only. A device that could rename itself would make its own + * attribution editable. + */ + post: operations['rename']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/devices/{device_id}/revoke': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Revoke a device. Members may revoke their own, administrators anyone's. */ + post: operations['revoke']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/devices/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one device + * @description Retrieves one device by its ID. + * + * This resource manages device items + */ + get: operations['get_one_device']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/enrol': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Trade a connect code for a long-lived device token. + * @description Unauthenticated, since the code is the credential: rate limited per IP, and the + * code is spent in the same transaction that creates the device. + */ + post: operations['enrol']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/healthz': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Liveness: the process is up. */ + get: operations['healthz']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/me': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Who the caller is, by whichever credential they presented. */ + get: operations['get_me']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/pass_groups': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all pass_groups + * @description Retrieves all pass_groups. + * + * This resource manages pass_group items + * + * Additional sortable columns: + * - name + * - period_label + * - created_at + * - updated_at + * - deleted_at. + * + * Additional filterable columns: + * - id + * - name + * - period_label + * - deleted_at. + */ + get: operations['get_all_pass_groups']; + put?: never; + /** + * Create one pass_group + * @description Creates a new pass_group. + * + * This resource manages pass_group items + */ + post: operations['create_one_pass_group']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/pass_groups/batch': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create many pass_groups + * @description Creates multiple pass_groups in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages pass_group items + */ + post: operations['create_many_pass_groups']; + /** + * Delete many pass_groups + * @description Deletes many pass_groups by their IDs and returns array of deleted UUIDs. + * + * Use `?partial=true` for partial success mode (deletes valid items even if some fail). + * + * This resource manages pass_group items + */ + delete: operations['delete_many_pass_groups']; + options?: never; + head?: never; + /** + * Update many pass_groups + * @description Updates multiple pass_groups in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages pass_group items + */ + patch: operations['update_many_pass_groups']; + trace?: never; + }; + '/pass_groups/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one pass_group + * @description Retrieves one pass_group by its ID. + * + * This resource manages pass_group items + */ + get: operations['get_one_pass_group']; + /** + * Update one pass_group + * @description Updates one pass_group by its ID. + * + * This resource manages pass_group items + */ + put: operations['update_one_pass_group']; + post?: never; + /** + * Delete one pass_group + * @description Deletes one pass_group by its ID. + * + * This resource manages pass_group items + */ + delete: operations['delete_one_pass_group']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/pass_videos': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all pass_videos + * @description Retrieves all pass_videos. + * + * This resource manages pass_video items + * + * Additional sortable columns: + * - ordinal + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - pass_id + * - video_id + * - deleted_at + * - device_id. + */ + get: operations['get_all_pass_videos']; + put?: never; + /** + * Create one pass_video + * @description Creates a new pass_video. + * + * This resource manages pass_video items + */ + post: operations['create_one_pass_video']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/pass_videos/batch': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create many pass_videos + * @description Creates multiple pass_videos in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages pass_video items + */ + post: operations['create_many_pass_videos']; + /** + * Delete many pass_videos + * @description Deletes many pass_videos by their IDs and returns array of deleted UUIDs. + * + * Use `?partial=true` for partial success mode (deletes valid items even if some fail). + * + * This resource manages pass_video items + */ + delete: operations['delete_many_pass_videos']; + options?: never; + head?: never; + /** + * Update many pass_videos + * @description Updates multiple pass_videos in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages pass_video items + */ + patch: operations['update_many_pass_videos']; + trace?: never; + }; + '/pass_videos/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one pass_video + * @description Retrieves one pass_video by its ID. + * + * This resource manages pass_video items + */ + get: operations['get_one_pass_video']; + /** + * Update one pass_video + * @description Updates one pass_video by its ID. + * + * This resource manages pass_video items + */ + put: operations['update_one_pass_video']; + post?: never; + /** + * Delete one pass_video + * @description Deletes one pass_video by its ID. + * + * This resource manages pass_video items + */ + delete: operations['delete_one_pass_video']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/passes': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all passes + * @description Retrieves all passes. + * + * This resource manages pass items + * + * Additional sortable columns: + * - quality + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - transect_id + * - campaign_id + * - survey_group_id + * - direction + * - upside_down + * - label + * - quality + * - deleted_at + * - device_id. + */ + get: operations['get_all_passes']; + put?: never; + /** + * Create one pass + * @description Creates a new pass. + * + * This resource manages pass items + */ + post: operations['create_one_pass']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/passes/batch': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create many passes + * @description Creates multiple passes in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages pass items + */ + post: operations['create_many_passes']; + /** + * Delete many passes + * @description Deletes many passes by their IDs and returns array of deleted UUIDs. + * + * Use `?partial=true` for partial success mode (deletes valid items even if some fail). + * + * This resource manages pass items + */ + delete: operations['delete_many_passes']; + options?: never; + head?: never; + /** + * Update many passes + * @description Updates multiple passes in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages pass items + */ + patch: operations['update_many_passes']; + trace?: never; + }; + '/passes/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one pass + * @description Retrieves one pass by its ID. + * + * This resource manages pass items + */ + get: operations['get_one_pass']; + /** + * Update one pass + * @description Updates one pass by its ID. + * + * This resource manages pass items + */ + put: operations['update_one_pass']; + post?: never; + /** + * Delete one pass + * @description Deletes one pass by its ID. + * + * This resource manages pass items + */ + delete: operations['delete_one_pass']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/performance/summary': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Resource statistics per device, preset, model combination and processing + * configuration, the grain the desktop application keys its own history by. + * @description One run contributes one observation per metric: the largest value across that run's + * stages. Mean, sample standard deviation, min, max and n are then taken across the + * group's runs over those per-run peaks. The standard deviation is null below two + * observations, and n is per metric, so a fleet of machines without discrete GPUs + * reports a `vram_n` of zero beside a full `ram_n`. + * + * Covers the non-deleted runs that recorded per-stage peaks, and only those. A failed + * run counts once it recorded them, which is the point: an out-of-memory run's peaks are + * exactly the interesting ones. A run that recorded none contributes nothing at all, not + * even to `run_count`: a run synced by a build that never reported peaks, one that died + * before a single stage finished, and one whose `stage_peaks` arrived empty or as + * something other than a map of stages are all absent here rather than counted as runs + * with nothing to show. Revoked devices keep their history for the same reason failed + * runs do. Within a stage map `stage_peaks` is untrusted JSON, so a non-numeric or + * absent value is ignored rather than refused, and the run still counts with that metric + * unobserved. + * + * The device hardware figures (`gpu_name`, `total_ram_bytes`, `total_vram_bytes`) come + * from the device's current profile, not from its profile when the runs happened, so a + * machine that has since been upgraded reports its new hardware against its old runs. + */ + get: operations['performance_summary']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/presets': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all presets + * @description Retrieves all presets. + * + * This resource manages preset items + * + * Additional sortable columns: + * - name + * - version + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - name + * - version + * - deleted_at + * - device_id. + */ + get: operations['get_all_presets']; + put?: never; + /** + * Create one preset + * @description Creates a new preset. + * + * This resource manages preset items + */ + post: operations['create_one_preset']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/presets/batch': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create many presets + * @description Creates multiple presets in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages preset items + */ + post: operations['create_many_presets']; + /** + * Delete many presets + * @description Deletes many presets by their IDs and returns array of deleted UUIDs. + * + * Use `?partial=true` for partial success mode (deletes valid items even if some fail). + * + * This resource manages preset items + */ + delete: operations['delete_many_presets']; + options?: never; + head?: never; + /** + * Update many presets + * @description Updates multiple presets in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages preset items + */ + patch: operations['update_many_presets']; + trace?: never; + }; + '/presets/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one preset + * @description Retrieves one preset by its ID. + * + * This resource manages preset items + */ + get: operations['get_one_preset']; + /** + * Update one preset + * @description Updates one preset by its ID. + * + * This resource manages preset items + */ + put: operations['update_one_preset']; + post?: never; + /** + * Delete one preset + * @description Deletes one preset by its ID. + * + * This resource manages preset items + */ + delete: operations['delete_one_preset']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/presets/{preset_id}/assign-all': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Assign a preset to every active device. Administrators only. + * @description Member semantics ("devices I enrolled") would make a bulk route mean a different + * fleet per caller, so this one is admin-only. Each assignment travels in that + * device's next heartbeat response, exactly as the single route's does. + */ + post: operations['assign_all']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/run_artifacts': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all run_artifacts + * @description Retrieves all run_artifacts. + * + * This resource manages run_artifact items + * + * Additional sortable columns: + * - relpath + * - size_bytes + * - created_at + * - updated_at. + * + * Additional filterable columns: + * - id + * - run_id + * - relpath + * - content_hash + * - stored_object_id. + */ + get: operations['get_all_run_artifacts']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/run_artifacts/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one run_artifact + * @description Retrieves one run_artifact by its ID. + * + * This resource manages run_artifact items + */ + get: operations['get_one_run_artifact']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/runs': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all runs + * @description Retrieves all runs. + * + * This resource manages run items + * + * Additional sortable columns: + * - status + * - started_at + * - finished_at + * - run_duration_s + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - pass_id + * - status + * - started_at + * - finished_at + * - gui_version + * - library_version + * - segmentation_model + * - mapping_backend + * - processing_width + * - processing_height + * - fps + * - preprocess_batch_size + * - taxonomy_version + * - preset_name + * - preset_version + * - deleted_at + * - device_id. + */ + get: operations['get_all_runs']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/runs/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one run + * @description Retrieves one run by its ID. + * + * This resource manages run items + */ + get: operations['get_one_run']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/sites': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all sites + * @description Retrieves all sites. + * + * This resource manages site items + * + * Additional sortable columns: + * - name + * - country + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - name + * - country + * - region + * - deleted_at + * - device_id. + */ + get: operations['get_all_sites']; + put?: never; + /** + * Create one site + * @description Creates a new site. + * + * This resource manages site items + */ + post: operations['create_one_site']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/sites/batch': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create many sites + * @description Creates multiple sites in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages site items + */ + post: operations['create_many_sites']; + /** + * Delete many sites + * @description Deletes many sites by their IDs and returns array of deleted UUIDs. + * + * Use `?partial=true` for partial success mode (deletes valid items even if some fail). + * + * This resource manages site items + */ + delete: operations['delete_many_sites']; + options?: never; + head?: never; + /** + * Update many sites + * @description Updates multiple sites in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages site items + */ + patch: operations['update_many_sites']; + trace?: never; + }; + '/sites/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one site + * @description Retrieves one site by its ID. + * + * This resource manages site items + */ + get: operations['get_one_site']; + /** + * Update one site + * @description Updates one site by its ID. + * + * This resource manages site items + */ + put: operations['update_one_site']; + post?: never; + /** + * Delete one site + * @description Deletes one site by its ID. + * + * This resource manages site items + */ + delete: operations['delete_one_site']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/stored_objects': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all stored_objects + * @description Retrieves all stored_objects. + * + * This resource manages stored_object items + * + * Additional sortable columns: + * - size_bytes + * - status + * - created_at + * - updated_at + * - last_part_at + * - completed_at. + * + * Additional filterable columns: + * - id + * - content_hash + * - kind + * - status + * - uploaded_by_device_id + * - uploaded_by. + */ + get: operations['get_all_stored_objects']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/stored_objects/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one stored_object + * @description Retrieves one stored_object by its ID. + * + * This resource manages stored_object items + */ + get: operations['get_one_stored_object']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/sync/heartbeat': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Update the calling device's own record and learn its assigned preset. + * @description Identity comes from the credential and never from the body, so a device cannot + * report on a sibling's behalf. + */ + post: operations['heartbeat']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/sync/pull': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Download rows changed since a cursor. */ + get: operations['pull']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/sync/push': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Apply a document of survey metadata. + * @description One transaction, sections in foreign-key order, so a client need not order its + * own writes. + */ + post: operations['push']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/transects': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all transects + * @description Retrieves all transects. + * + * This resource manages transect items + * + * Additional sortable columns: + * - name + * - length_m + * - depth_m + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - site_id + * - name + * - length_m + * - depth_m + * - deleted_at + * - device_id. + */ + get: operations['get_all_transects']; + put?: never; + /** + * Create one transect + * @description Creates a new transect. + * + * This resource manages transect items + */ + post: operations['create_one_transect']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/transects/batch': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create many transects + * @description Creates multiple transects in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages transect items + */ + post: operations['create_many_transects']; + /** + * Delete many transects + * @description Deletes many transects by their IDs and returns array of deleted UUIDs. + * + * Use `?partial=true` for partial success mode (deletes valid items even if some fail). + * + * This resource manages transect items + */ + delete: operations['delete_many_transects']; + options?: never; + head?: never; + /** + * Update many transects + * @description Updates multiple transects in a batch. Limited to 100 items per request. + * + * Use `?partial=true` for partial success mode (commits successful items even if some fail). + * + * This resource manages transect items + */ + patch: operations['update_many_transects']; + trace?: never; + }; + '/transects/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one transect + * @description Retrieves one transect by its ID. + * + * This resource manages transect items + */ + get: operations['get_one_transect']; + /** + * Update one transect + * @description Updates one transect by its ID. + * + * This resource manages transect items + */ + put: operations['update_one_transect']; + post?: never; + /** + * Delete one transect + * @description Deletes one transect by its ID. + * + * This resource manages transect items + */ + delete: operations['delete_one_transect']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/transects/{id}/cover': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Cover pooled across a transect's passes. + * @description Collapses reruns to the latest succeeded run per pass, so a pass processed twice counts + * once. + */ + get: operations['pooled_cover']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/transects/{id}/cover-series': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Cover per survey event along one transect. + * @description Collapses reruns to the latest succeeded run per pass, so a pass processed twice counts + * once. + */ + get: operations['cover_series']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/videos': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get all videos + * @description Retrieves all videos. + * + * This resource manages video items + * + * Additional sortable columns: + * - file_name + * - size_bytes + * - duration_s + * - captured_at + * - created_at + * - updated_at + * - deleted_at + * - server_seq. + * + * Additional filterable columns: + * - id + * - hash + * - file_name + * - codec + * - captured_at + * - gravity + * - gps + * - deleted_at + * - device_id. + */ + get: operations['get_all_videos']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/videos/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one video + * @description Retrieves one video by its ID. + * + * This resource manages video items + */ + get: operations['get_one_video']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/videos/{id}/runs': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Runs that consumed a video, newest first. + * @description Follows `pass_video` to `transect_pass` to `run_record`, so every rerun of every + * pass the clip played in is listed. + */ + get: operations['runs_for_video']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record<string, never>; +export interface components { + schemas: { + AssignAllResponse: { + /** Format: date-time */ + assigned_at: string; + /** + * Format: int64 + * @description Active devices reached. Revoked devices are skipped. + */ + assigned_count: number; + /** Format: uuid */ + preset_id: string; + }; + AssignPresetRequest: { + /** + * Format: uuid + * @description Null clears the assignment. + */ + preset_id?: string | null; + }; + AssignPresetResponse: { + /** Format: date-time */ + assigned_at?: string | null; + /** Format: uuid */ + assigned_preset_id?: string | null; + /** Format: uuid */ + device_id: string; + }; + /** @description The server-chosen default preset, named well enough to select locally. */ + AssignedPreset: { + /** Format: uuid */ + id: string; + name: string; + /** Format: int32 */ + version: number; + }; + /** + * @description Wrapper type for batch update request items. + * Each item contains an `id` field and the update fields flattened into the same object. + */ + BatchUpdateRequest: components['schemas']['SiteUpdate'] & { + /** @description The ID of the resource to update */ + id: string; + }; + ByHashResponse: { + /** Format: date-time */ + completed_at?: string | null; + /** Format: uuid */ + object_id: string; + status: string; + }; + CampaignCreate: { + /** Format: date */ + begin_date?: string | null; + description: string; + /** Format: date */ + end_date?: string | null; + /** Format: uuid */ + id?: string | null; + name: string; + }; + CampaignList: { + /** Format: date */ + begin_date?: string | null; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + description: string; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: date */ + end_date?: string | null; + /** Format: uuid */ + id: string; + name: string; + /** Format: int64 */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + CampaignResponse: { + /** Format: date */ + begin_date?: string | null; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + description: string; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: date */ + end_date?: string | null; + /** Format: uuid */ + id: string; + name: string; + /** Format: int64 */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + CampaignUpdate: { + /** Format: date */ + begin_date?: string | null; + description?: string | null; + /** Format: date */ + end_date?: string | null; + name?: string | null; + }; + /** @description One benthic class group and the colour every viewer draws it in. */ + ClassGroup: { + /** @description `#rrggbb`, so a browser can use it without conversion. */ + colour: string; + level: string; + name: string; + }; + CompleteRequest: { + /** + * @description Accepted for compatibility and ignored: the server assembles from its + * own `ListParts`, because a resuming client cannot know the `ETag`s of + * parts an earlier attempt sent. + */ + parts?: components['schemas']['CompletedPartBody'][]; + }; + CompleteResponse: { + /** Format: uuid */ + object_id: string; + /** @description `complete`: S3 assembled the parts into the finished object. */ + status: string; + }; + CompletedPartBody: { + etag: string; + /** Format: int32 */ + part_number: number; + }; + CoverRowList: { + class_group: string; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** Format: double */ + denominator?: number | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** @description `per_pass` for one pass observed, `pooled` for the count-weighted estimate. */ + estimator: string; + /** Format: double */ + fraction: number; + /** Format: uuid */ + id: string; + /** @description Level of the class hierarchy the group sits at. */ + level: string; + /** @description Which cloud the fractions were measured on: the choice changes their meaning. */ + metric_source?: string | null; + /** Format: double */ + point_count?: number | null; + /** Format: uuid */ + run_id: string; + /** Format: int64 */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + CoverRowResponse: { + class_group: string; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** Format: double */ + denominator?: number | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** @description `per_pass` for one pass observed, `pooled` for the count-weighted estimate. */ + estimator: string; + /** Format: double */ + fraction: number; + /** Format: uuid */ + id: string; + /** @description Level of the class hierarchy the group sits at. */ + level: string; + /** @description Which cloud the fractions were measured on: the choice changes their meaning. */ + metric_source?: string | null; + /** Format: double */ + point_count?: number | null; + /** Format: uuid */ + run_id: string; + /** Format: int64 */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + CoverSeries: { + /** + * @description Named survey events first, ordered by period label then name, then campaign + * buckets, then the passes with neither. + */ + entries: components['schemas']['CoverSeriesEntry'][]; + level: string; + /** Format: uuid */ + transect_id: string; + }; + CoverSeriesEntry: { + /** Format: uuid */ + campaign_id?: string | null; + campaign_name?: string | null; + /** + * Format: int64 + * @description Passes that contributed a run with counts. + */ + contributing_passes: number; + /** + * Format: double + * @description Points the entry's fractions are measured over. + */ + denominator: number; + /** + * Format: uuid + * @description The curator's survey event, or null for a campaign or unbucketed entry. + */ + group_id?: string | null; + group_name?: string | null; + /** @description Largest fraction first. */ + groups: components['schemas']['SeriesGroupCover'][]; + period_label?: string | null; + }; + DeviceList: { + /** + * @description The preset the device says it runs under, from its heartbeat. Beside the + * assignment, this is the acknowledgement. + */ + active_preset_name?: string | null; + /** Format: date-time */ + active_preset_reported_at?: string | null; + /** Format: int32 */ + active_preset_version?: number | null; + /** Format: date-time */ + assigned_at?: string | null; + /** + * Format: uuid + * @description The server-chosen default preset, set through `/api/devices/{id}/assign-preset` + * and delivered in the heartbeat response. + */ + assigned_preset_id?: string | null; + /** Format: date-time */ + created_at: string; + /** + * @description Keycloak subject that minted this device's connect code. Audit only: it grants + * nothing and attributes nothing. Nullable: subject erasure scrubs it. + */ + enrolled_by?: string | null; + gui_version?: string | null; + /** Format: uuid */ + id: string; + /** Format: date-time */ + last_seen_at?: string | null; + library_version?: string | null; + /** + * @description The installation's durable identity, shown as `uploaded_by` on what it pushes. + * Renamed by a human through `/api/devices/{id}/rename`, never by the device. + */ + name: string; + platform?: string | null; + /** + * Format: int32 + * @description Which `preset-schema.json` revision the installation understands, from its + * heartbeat. + */ + preset_schema_version?: number | null; + /** + * Format: date-time + * @description When the device last reported on itself, so a stale profile reads as stale. + */ + profile_reported_at?: string | null; + /** + * Format: date-time + * @description Set to revoke. Kept, so a revoked device stays visible in the audit trail. + */ + revoked_at?: string | null; + /** + * Format: date-time + * @description When a heartbeat last reported a different `gui_version` or `library_version`. + * Enrolment does not stamp it, so null reads as unchanged since enrolment. + */ + versions_changed_at?: string | null; + }; + DeviceResponse: { + /** + * @description The preset the device says it runs under, from its heartbeat. Beside the + * assignment, this is the acknowledgement. + */ + active_preset_name?: string | null; + /** Format: date-time */ + active_preset_reported_at?: string | null; + /** Format: int32 */ + active_preset_version?: number | null; + /** Format: date-time */ + assigned_at?: string | null; + /** + * Format: uuid + * @description The server-chosen default preset, set through `/api/devices/{id}/assign-preset` + * and delivered in the heartbeat response. + */ + assigned_preset_id?: string | null; + /** Format: date-time */ + created_at: string; + /** + * @description Keycloak subject that minted this device's connect code. Audit only: it grants + * nothing and attributes nothing. Nullable: subject erasure scrubs it. + */ + enrolled_by?: string | null; + gui_version?: string | null; + /** Format: uuid */ + id: string; + /** Format: date-time */ + last_seen_at?: string | null; + library_version?: string | null; + /** + * @description The installation's durable identity, shown as `uploaded_by` on what it pushes. + * Renamed by a human through `/api/devices/{id}/rename`, never by the device. + */ + name: string; + platform?: string | null; + /** + * Format: int32 + * @description Which `preset-schema.json` revision the installation understands, from its + * heartbeat. + */ + preset_schema_version?: number | null; + /** + * Format: date-time + * @description When the device last reported on itself, so a stale profile reads as stale. + */ + profile_reported_at?: string | null; + /** + * Format: date-time + * @description Set to revoke. Kept, so a revoked device stays visible in the audit trail. + */ + revoked_at?: string | null; + /** + * @description Hardware and driver survey the device reports about itself, stored as sent. + * Detail view only. + */ + system_profile?: unknown; + /** + * Format: date-time + * @description When a heartbeat last reported a different `gui_version` or `library_version`. + * Enrolment does not stamp it, so null reads as unchanged since enrolment. + */ + versions_changed_at?: string | null; + }; + DownloadResponse: { + /** + * @description A fetch link on this registry itself, signed for one object and a few + * minutes. The object store is never addressed by a client. + */ + url: string; + }; + EnrolRequest: { + /** @description The whole `drm1.…` string or its bare secret. */ + code: string; + gui_version?: string | null; + library_version?: string | null; + platform?: string | null; + }; + EnrolResponse: { + /** + * Format: int32 + * @description Version agreed for this exchange, so a fresh installation learns it before its + * first push. + */ + contract_version: number; + /** Format: uuid */ + device_id: string; + /** + * @description This installation's durable name, chosen when its connect code was minted and + * shown as `uploaded_by` on what it pushes. + */ + device_name: string; + /** @description Bearer token for every later sync request. Returned once; only its hash is kept. */ + token: string; + }; + EraseSubjectRequest: { + /** @description The Keycloak subject to scrub. */ + subject: string; + }; + EraseSubjectResponse: { + /** @description Rows scrubbed, per table. */ + scrubbed: { + [key: string]: number; + }; + }; + GroupCover: { + class_group: string; + /** @description `#rrggbb`, from the published class group table. */ + colour?: string | null; + /** + * Format: double + * @description Share of the pooled denominator, 0 to 1. + */ + fraction: number; + /** Format: double */ + point_count: number; + }; + HeartbeatRequest: { + /** @description The preset the device currently runs under, acknowledging an assignment. */ + active_preset_name?: string | null; + /** Format: int32 */ + active_preset_version?: number | null; + gui_version?: string | null; + library_version?: string | null; + platform?: string | null; + /** + * Format: int32 + * @description Which `preset-schema.json` revision this installation understands. + */ + preset_schema_version?: number | null; + /** @description Arbitrary JSON object describing the hardware, stored as sent. */ + system_profile?: unknown; + }; + HeartbeatResponse: { + assigned_preset?: null | components['schemas']['AssignedPreset']; + }; + InitiateRequest: { + /** + * @description imohash of the file, 32 lowercase hex characters. A device already holds this + * for every clip it has ingested, which is what a blob and a `video_asset` meet on. + */ + content_hash: string; + /** @description `video` or `artifact`. */ + kind: string; + /** @description Required for kind `artifact`. Path inside the run directory. */ + relpath?: string | null; + /** + * Format: uuid + * @description Required for kind `artifact`. + */ + run_id?: string | null; + /** Format: int64 */ + size_bytes: number; + }; + InitiateResponse: { + /** Format: uuid */ + object_id: string; + /** Format: int64 */ + part_size_bytes?: number | null; + /** + * @description Part numbers already stored, which a resuming client skips. The rest are + * PUT to `/archive/{object_id}/parts/{part_number}` in any order. + */ + parts_done: number[]; + /** + * @description `pending` with parts to upload, or `complete` when the content is already + * archived and nothing need be sent. + */ + status: string; + upload_id?: string | null; + }; + KeycloakConfigResponse: { + /** @description Camel case so the object drops straight into a `keycloak-js` constructor. */ + clientId?: string | null; + /** @description Drives the environment banner, so nobody edits stage thinking it is local. */ + deployment: string; + /** @description False when no realm is configured, which only local and dev may do. */ + enabled: boolean; + realm?: string | null; + url?: string | null; + }; + MeResponse: { + /** Format: uuid */ + device_id?: string | null; + /** @description The installation's name, which its pushed rows are attributed to. */ + device_name?: string | null; + email?: string | null; + is_admin: boolean; + /** @description True when the caller is a desktop installation rather than a person. */ + is_device: boolean; + /** @description Keycloak subject. Null for a device, which is an application and not a person. */ + sub?: string | null; + }; + MintConnectCodeRequest: { + /** + * @description The name the redeeming device takes, so a device's name has one origin: the + * person minting the code names the installation they are about to enrol. + */ + device_name: string; + }; + MintConnectCodeResponse: { + /** @description The string to paste into the desktop application. Shown once. */ + code: string; + /** Format: date-time */ + expires_at: string; + }; + PassCreate: { + /** Format: double */ + begin_s: number; + /** Format: uuid */ + campaign_id?: string | null; + direction?: string | null; + /** Format: double */ + end_s: number; + /** Format: uuid */ + id?: string | null; + label: string; + notes: string; + quality?: string | null; + /** Format: uuid */ + survey_group_id?: string | null; + /** Format: uuid */ + transect_id?: string | null; + upside_down: boolean; + }; + PassGroupCreate: { + description: string; + /** Format: uuid */ + id?: string | null; + name: string; + period_label?: string | null; + }; + PassGroupList: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach. Accepting it on create or update would let a member delete through a + * plain edit. + */ + deleted_at?: string | null; + description: string; + /** Format: uuid */ + id: string; + name: string; + /** @description Where the event sits on a timeline (`2024 spring`), and what orders a series. */ + period_label?: string | null; + /** Format: date-time */ + updated_at: string; + }; + PassGroupResponse: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach. Accepting it on create or update would let a member delete through a + * plain edit. + */ + deleted_at?: string | null; + description: string; + /** Format: uuid */ + id: string; + name: string; + /** @description Where the event sits on a timeline (`2024 spring`), and what orders a series. */ + period_label?: string | null; + /** Format: date-time */ + updated_at: string; + }; + PassGroupUpdate: { + description?: string | null; + name?: string | null; + period_label?: string | null; + }; + PassList: { + /** Format: double */ + begin_s: number; + /** Format: uuid */ + campaign_id?: string | null; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** @description Null means the direction was never recorded, which no code stands for. */ + direction?: string | null; + /** Format: double */ + end_s: number; + /** Format: uuid */ + id: string; + /** @description Empty means unnamed, which clients render as their generated default. */ + label: string; + notes: string; + /** @description Diver's assessment, on the scale the field spreadsheets map onto. */ + quality?: string | null; + /** Format: int64 */ + server_seq: number; + /** + * Format: uuid + * @description The curator's survey event, assigned in the console. Deliberately outside the + * sync contract, so a device re-pushing this pass can never clobber it. + */ + survey_group_id?: string | null; + /** + * Format: uuid + * @description Nullable: footage is not always laid against a tape, and such a run is unscaled. + */ + transect_id?: string | null; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + upside_down: boolean; + }; + PassResponse: { + /** Format: double */ + begin_s: number; + /** Format: uuid */ + campaign_id?: string | null; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** @description Null means the direction was never recorded, which no code stands for. */ + direction?: string | null; + /** Format: double */ + end_s: number; + /** Format: uuid */ + id: string; + /** @description Empty means unnamed, which clients render as their generated default. */ + label: string; + notes: string; + /** @description Diver's assessment, on the scale the field spreadsheets map onto. */ + quality?: string | null; + /** Format: int64 */ + server_seq: number; + /** + * Format: uuid + * @description The curator's survey event, assigned in the console. Deliberately outside the + * sync contract, so a device re-pushing this pass can never clobber it. + */ + survey_group_id?: string | null; + /** + * Format: uuid + * @description Nullable: footage is not always laid against a tape, and such a run is unscaled. + */ + transect_id?: string | null; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + upside_down: boolean; + }; + PassUpdate: { + /** Format: double */ + begin_s?: number | null; + /** Format: uuid */ + campaign_id?: string | null; + direction?: string | null; + /** Format: double */ + end_s?: number | null; + label?: string | null; + notes?: string | null; + quality?: string | null; + /** Format: uuid */ + survey_group_id?: string | null; + /** Format: uuid */ + transect_id?: string | null; + upside_down?: boolean | null; + }; + PassVideoCreate: { + /** Format: uuid */ + id?: string | null; + /** Format: int32 */ + ordinal: number; + /** Format: uuid */ + pass_id: string; + /** Format: uuid */ + video_id: string; + }; + PassVideoList: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: uuid */ + id: string; + /** + * Format: int32 + * @description Playing order within the pass, zero-based. + */ + ordinal: number; + /** Format: uuid */ + pass_id: string; + /** Format: int64 */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + /** Format: uuid */ + video_id: string; + }; + PassVideoResponse: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: uuid */ + id: string; + /** + * Format: int32 + * @description Playing order within the pass, zero-based. + */ + ordinal: number; + /** Format: uuid */ + pass_id: string; + /** Format: int64 */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + /** Format: uuid */ + video_id: string; + }; + PassVideoUpdate: { + /** Format: int32 */ + ordinal?: number | null; + /** Format: uuid */ + pass_id?: string | null; + /** Format: uuid */ + video_id?: string | null; + }; + PerformanceGroup: { + /** + * Format: uuid + * @description Null when the runs arrived under an interactive login rather than a device + * token: provenance binds from the credential, and a human push carries no device. + */ + device_id?: string | null; + device_name?: string | null; + /** Format: double */ + duration_max_s?: number | null; + /** + * Format: double + * @description Wall-clock seconds, over the runs that report a duration. + */ + duration_mean_s?: number | null; + /** Format: double */ + duration_min_s?: number | null; + /** Format: int64 */ + duration_n: number; + /** Format: double */ + duration_std_s?: number | null; + /** + * Format: int64 + * @description The failed subset of `run_count`. + */ + failed_count: number; + /** Format: int32 */ + fps?: number | null; + /** + * @description From the device's current profile, as are the two totals below: what the + * hardware is now, not what it was when the runs happened. + */ + gpu_name?: string | null; + /** + * Format: date-time + * @description Latest `started_at` in the group. + */ + last_run_at?: string | null; + mapping_backend?: string | null; + /** Format: int32 */ + preprocess_batch_size?: number | null; + preset_hash?: string | null; + preset_name?: string | null; + /** Format: int32 */ + preset_version?: number | null; + /** Format: int32 */ + processing_height?: number | null; + /** + * Format: int32 + * @description Processing configuration, part of the group key: resolution and fps set the + * memory regime, batch size gates VRAM. All null for a run pushed by a build that + * did not report them. + */ + processing_width?: number | null; + /** Format: int64 */ + ram_max_bytes?: number | null; + /** + * Format: double + * @description Mean of the per-run RAM peaks. Every metric below carries the same five figures. + */ + ram_mean_bytes?: number | null; + /** Format: int64 */ + ram_min_bytes?: number | null; + /** + * Format: int64 + * @description Runs that observed a RAM peak. Counted per metric rather than per group, so it + * can sit below `run_count`: a machine with no discrete GPU reports no VRAM at all, + * and a run that reported junk is a run with that metric unobserved. + */ + ram_n: number; + /** + * Format: double + * @description Sample standard deviation, null below two observations. + */ + ram_std_bytes?: number | null; + /** + * Format: int64 + * @description Every run in the group that recorded peaks, whatever its status. A run whose + * `stage_peaks` is absent, empty, or something other than a map of stages is not + * in the group at all. + */ + run_count: number; + segmentation_model?: string | null; + /** Format: int64 */ + swap_max_bytes?: number | null; + /** Format: double */ + swap_mean_bytes?: number | null; + /** Format: int64 */ + swap_min_bytes?: number | null; + /** Format: int64 */ + swap_n: number; + /** Format: double */ + swap_std_bytes?: number | null; + /** Format: int64 */ + total_ram_bytes?: number | null; + /** Format: int64 */ + total_vram_bytes?: number | null; + /** Format: int64 */ + vram_max_bytes?: number | null; + /** Format: double */ + vram_mean_bytes?: number | null; + /** Format: int64 */ + vram_min_bytes?: number | null; + /** Format: int64 */ + vram_n: number; + /** Format: double */ + vram_std_bytes?: number | null; + }; + PerformanceSummary: { + groups: components['schemas']['PerformanceGroup'][]; + }; + PooledCover: { + /** Format: uuid */ + campaign_id?: string | null; + /** + * Format: int64 + * @description Passes that contributed a run with counts. + */ + contributing_passes: number; + /** + * Format: double + * @description Points the fractions are measured over. + */ + denominator: number; + /** + * Format: int64 + * @description Passes on this transect, so a partial figure is visible as partial. + */ + expected_passes: number; + /** @description Largest fraction first. */ + groups: components['schemas']['GroupCover'][]; + level: string; + /** Format: uuid */ + transect_id: string; + }; + PresetCreate: { + description: string; + /** Format: uuid */ + id?: string | null; + name: string; + settings: unknown; + /** Format: int32 */ + version: number; + }; + PresetList: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + description: string; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: uuid */ + id: string; + name: string; + /** Format: int64 */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + /** Format: int32 */ + version: number; + }; + PresetResponse: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + description: string; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: uuid */ + id: string; + name: string; + /** Format: int64 */ + server_seq: number; + /** + * @description The settings document itself, opaque to the registry. Detail view only: a page + * of presets does not carry every document. + */ + settings: unknown; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + /** Format: int32 */ + version: number; + }; + PresetUpdate: { + description?: string | null; + name?: string | null; + settings?: unknown; + /** Format: int32 */ + version?: number | null; + }; + ProbeRequest: { + /** @description Content hashes to look up, 32 lowercase hex each, at most 500. */ + hashes: string[]; + }; + ProbeResponse: { + /** @description One entry per hash that has a row. Hashes never seen are simply absent. */ + states: { + [key: string]: components['schemas']['ProbeState']; + }; + }; + ProbeState: { + /** Format: date-time */ + completed_at?: string | null; + /** Format: uuid */ + object_id: string; + status: string; + }; + PullResponse: { + /** Format: int32 */ + contract_version: number; + /** + * Format: int64 + * @description Feed this back as `since` next time. + */ + cursor: number; + /** @description Whether rows wait beyond `cursor`. Keep pulling while true. */ + has_more: boolean; + /** + * @description Sections this build cannot read, held back by the client's own + * `Deepreefmap-Sections`. Upload-only sections are absent by design and not listed. + */ + omitted_sections: string[]; + sections: Record<string, never>; + }; + PushRequest: { + /** + * Format: int32 + * @description Contract version this document was built against, which must be the one negotiated + * for the exchange. + */ + contract_version: number; + /** @description Rows keyed by section name, as listed in `contract/sync-contract.json`. */ + sections: Record<string, never>; + }; + PushResponse: { + /** + * Format: int32 + * @description Version agreed for this exchange, which is what the document had to declare. + */ + contract_version: number; + /** + * Format: int64 + * @description Sequence position after this push, for the next pull's `since`. + */ + cursor: number; + sections: Record<string, never>; + }; + RenameDeviceRequest: { + name: string; + }; + RenameDeviceResponse: { + /** Format: uuid */ + device_id: string; + name: string; + }; + RevokeResponse: { + /** Format: uuid */ + device_id: string; + /** Format: date-time */ + revoked_at: string; + }; + RunArchiveState: { + /** + * Format: int64 + * @description How many artefact rows the run has. + */ + artifacts: number; + /** + * Format: int64 + * @description How many of them link a stored object in status `complete`. + */ + complete: number; + /** + * Format: int64 + * @description And how many link one in status `failed`. + */ + failed: number; + }; + RunArtifactList: { + content_hash: string; + /** Format: date-time */ + created_at: string; + /** Format: uuid */ + id: string; + kind?: string | null; + /** @description Path inside the run directory, validated against traversal on the way in. */ + relpath: string; + /** Format: uuid */ + run_id: string; + /** Format: int64 */ + size_bytes?: number | null; + /** Format: uuid */ + stored_object_id?: string | null; + /** Format: date-time */ + updated_at: string; + }; + RunArtifactResponse: { + content_hash: string; + /** Format: date-time */ + created_at: string; + /** Format: uuid */ + id: string; + kind?: string | null; + /** @description Path inside the run directory, validated against traversal on the way in. */ + relpath: string; + /** Format: uuid */ + run_id: string; + /** Format: int64 */ + size_bytes?: number | null; + /** Format: uuid */ + stored_object_id?: string | null; + /** Format: date-time */ + updated_at: string; + }; + RunList: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + error: string; + /** Format: date-time */ + finished_at?: string | null; + /** Format: int32 */ + fps?: number | null; + gui_version?: string | null; + /** Format: uuid */ + id: string; + library_version?: string | null; + mapping_backend?: string | null; + /** Format: uuid */ + pass_id: string; + /** Format: int32 */ + preprocess_batch_size?: number | null; + /** @description Settings that departed from the preset, separating "unchanged" from "unrecorded". */ + preset_deviations?: unknown; + /** @description Digest of the preset definition, so a claimed version can be checked. */ + preset_hash?: string | null; + preset_name?: string | null; + /** Format: int32 */ + preset_version?: number | null; + /** Format: int32 */ + processing_height?: number | null; + /** + * Format: int32 + * @description The processing configuration the run used: resolution and fps set the memory + * regime, batch size gates VRAM. Integer fps, as the preset schema defines it. + */ + processing_width?: number | null; + /** @description Relative to the producing device's output root, not a server path. */ + run_dir_name: string; + /** + * Format: double + * @description Wall-clock seconds for the whole run. + */ + run_duration_s?: number | null; + segmentation_model?: string | null; + /** Format: int64 */ + server_seq: number; + /** Format: date-time */ + started_at?: string | null; + status: string; + /** @description Digest of the class-groups definition, so a claimed version can be checked. */ + taxonomy_hash?: string | null; + /** Format: int32 */ + taxonomy_version?: number | null; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + RunResponse: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + error: string; + /** Format: date-time */ + finished_at?: string | null; + /** Format: int32 */ + fps?: number | null; + gui_version?: string | null; + /** Format: uuid */ + id: string; + library_version?: string | null; + mapping_backend?: string | null; + /** + * @description Repository to upstream revision present at launch. Best-effort: the version + * available, not proof it was loaded. Detail view only. + */ + model_revisions?: unknown; + /** Format: uuid */ + pass_id: string; + /** Format: int32 */ + preprocess_batch_size?: number | null; + /** @description Settings that departed from the preset, separating "unchanged" from "unrecorded". */ + preset_deviations?: unknown; + /** @description Digest of the preset definition, so a claimed version can be checked. */ + preset_hash?: string | null; + preset_name?: string | null; + /** Format: int32 */ + preset_version?: number | null; + /** Format: int32 */ + processing_height?: number | null; + /** + * Format: int32 + * @description The processing configuration the run used: resolution and fps set the memory + * regime, batch size gates VRAM. Integer fps, as the preset schema defines it. + */ + processing_width?: number | null; + /** @description Relative to the producing device's output root, not a server path. */ + run_dir_name: string; + /** + * Format: double + * @description Wall-clock seconds for the whole run. + */ + run_duration_s?: number | null; + segmentation_model?: string | null; + /** Format: int64 */ + server_seq: number; + /** + * @description Stage name to wall-clock seconds, so a slow run names its slow stage. Detail + * view only. + */ + stage_durations?: unknown; + /** + * @description Stage name to peak resource use, so an out-of-memory run stays explicable. + * Detail view only. + */ + stage_peaks?: unknown; + /** Format: date-time */ + started_at?: string | null; + status: string; + /** @description Digest of the class-groups definition, so a claimed version can be checked. */ + taxonomy_hash?: string | null; + /** Format: int32 */ + taxonomy_version?: number | null; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + RunsProbeRequest: { + /** @description Runs to look up, at most 200. */ + run_ids: string[]; + }; + RunsProbeResponse: { + /** @description One entry per run id with artefact rows. Runs without any are absent. */ + states: { + [key: string]: components['schemas']['RunArchiveState']; + }; + }; + SectionOutcome: { + /** @description Rows written, inserted or updated. */ + applied: number; + /** + * @description Ids the database would not take: a unique collision, a missing parent, or a value + * outside its allowed set. Re-sending the same row cannot help. + */ + conflicted: string[]; + received: number; + /** + * @description Ids left untouched because this origin does not author them: another origin's row, + * or any row of a section this origin may not write. A human reconciles these. + */ + refused: string[]; + /** + * @description Ids this origin owns but the server holds at an equal or newer `updated_at`; pull + * them to see what won. + */ + skipped: string[]; + }; + SeriesGroupCover: { + class_group: string; + /** @description `#rrggbb`, from the published class group table. */ + colour?: string | null; + /** + * Format: double + * @description Share of the entry's pooled denominator, 0 to 1. + */ + fraction: number; + /** + * Format: double + * @description Largest per-run fraction among the contributing passes. + */ + max_fraction: number; + /** + * Format: double + * @description Smallest per-run fraction among the contributing passes. + */ + min_fraction: number; + /** Format: double */ + point_count: number; + }; + SiteCreate: { + country?: string | null; + description: string; + /** Format: uuid */ + id?: string | null; + /** Format: double */ + latitude?: number | null; + /** Format: double */ + longitude?: number | null; + name: string; + region?: string | null; + }; + SiteList: { + country?: string | null; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + description: string; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: uuid */ + id: string; + /** + * Format: double + * @description Representative point, not a boundary. + */ + latitude?: number | null; + /** Format: double */ + longitude?: number | null; + name: string; + region?: string | null; + /** + * Format: int64 + * @description Sync ordering, stamped by trigger. Never accepted from a client. + */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + SiteResponse: { + country?: string | null; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + description: string; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: uuid */ + id: string; + /** + * Format: double + * @description Representative point, not a boundary. + */ + latitude?: number | null; + /** Format: double */ + longitude?: number | null; + name: string; + region?: string | null; + /** + * Format: int64 + * @description Sync ordering, stamped by trigger. Never accepted from a client. + */ + server_seq: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + SiteUpdate: { + country?: string | null; + description?: string | null; + /** Format: double */ + latitude?: number | null; + /** Format: double */ + longitude?: number | null; + name?: string | null; + region?: string | null; + }; + StoredObjectList: { + /** + * Format: date-time + * @description When S3 assembled the parts into the finished object. + */ + completed_at?: string | null; + /** @description imohash of the file, 32 lowercase hex. The join key to `video_asset.hash`. */ + content_hash: string; + /** Format: date-time */ + created_at: string; + /** @description Why `status` is `failed`, for the operator. */ + failure?: string | null; + /** Format: uuid */ + id: string; + /** @description `video` or `artifact`, deciding the key layout. */ + kind: string; + /** + * Format: date-time + * @description Last part activity, so the reaper measures idleness rather than age. + */ + last_part_at?: string | null; + /** Format: int64 */ + part_size_bytes?: number | null; + s3_key: string; + /** Format: int64 */ + size_bytes: number; + /** @description `pending`, `failed` or `complete`. */ + status: string; + /** Format: date-time */ + updated_at: string; + /** @description Keycloak subject, when a person uploaded through the console. */ + uploaded_by?: string | null; + /** Format: uuid */ + uploaded_by_device_id?: string | null; + }; + StoredObjectResponse: { + /** + * Format: date-time + * @description When S3 assembled the parts into the finished object. + */ + completed_at?: string | null; + /** @description imohash of the file, 32 lowercase hex. The join key to `video_asset.hash`. */ + content_hash: string; + /** Format: date-time */ + created_at: string; + /** @description Why `status` is `failed`, for the operator. */ + failure?: string | null; + /** Format: uuid */ + id: string; + /** @description `video` or `artifact`, deciding the key layout. */ + kind: string; + /** + * Format: date-time + * @description Last part activity, so the reaper measures idleness rather than age. + */ + last_part_at?: string | null; + /** Format: int64 */ + part_size_bytes?: number | null; + s3_key: string; + /** Format: int64 */ + size_bytes: number; + /** @description `pending`, `failed` or `complete`. */ + status: string; + /** Format: date-time */ + updated_at: string; + /** @description Keycloak subject, when a person uploaded through the console. */ + uploaded_by?: string | null; + /** Format: uuid */ + uploaded_by_device_id?: string | null; + }; + TransectCreate: { + /** Format: double */ + depth_m?: number | null; + description: string; + /** Format: double */ + end_accuracy_m?: number | null; + /** Format: double */ + end_lat: number; + /** Format: double */ + end_lon: number; + /** Format: uuid */ + id?: string | null; + /** Format: double */ + length_m?: number | null; + name: string; + /** Format: uuid */ + site_id?: string | null; + /** Format: double */ + start_accuracy_m?: number | null; + /** Format: double */ + start_lat: number; + /** Format: double */ + start_lon: number; + }; + TransectList: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** Format: double */ + depth_m?: number | null; + description: string; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: double */ + end_accuracy_m?: number | null; + /** Format: double */ + end_lat: number; + /** Format: double */ + end_lon: number; + /** Format: uuid */ + id: string; + /** Format: double */ + length_m?: number | null; + /** @description Unique per site, not globally. */ + name: string; + /** Format: int64 */ + server_seq: number; + /** Format: uuid */ + site_id?: string | null; + /** + * Format: double + * @description Accuracy is per end point, as the field records have it. + */ + start_accuracy_m?: number | null; + /** Format: double */ + start_lat: number; + /** Format: double */ + start_lon: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + TransectResponse: { + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** Format: double */ + depth_m?: number | null; + description: string; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: double */ + end_accuracy_m?: number | null; + /** Format: double */ + end_lat: number; + /** Format: double */ + end_lon: number; + /** Format: uuid */ + id: string; + /** Format: double */ + length_m?: number | null; + /** @description Unique per site, not globally. */ + name: string; + /** Format: int64 */ + server_seq: number; + /** Format: uuid */ + site_id?: string | null; + /** + * Format: double + * @description Accuracy is per end point, as the field records have it. + */ + start_accuracy_m?: number | null; + /** Format: double */ + start_lat: number; + /** Format: double */ + start_lon: number; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + }; + TransectUpdate: { + /** Format: double */ + depth_m?: number | null; + description?: string | null; + /** Format: double */ + end_accuracy_m?: number | null; + /** Format: double */ + end_lat?: number | null; + /** Format: double */ + end_lon?: number | null; + /** Format: double */ + length_m?: number | null; + name?: string | null; + /** Format: uuid */ + site_id?: string | null; + /** Format: double */ + start_accuracy_m?: number | null; + /** Format: double */ + start_lat?: number | null; + /** Format: double */ + start_lon?: number | null; + }; + UploadPartResponse: { + /** + * @description The `ETag` the store recorded, which is the part's MD5 on every store this + * registry deploys against, so the sender can verify what landed. + */ + etag: string; + /** Format: int32 */ + part_number: number; + }; + VideoList: { + /** Format: date-time */ + captured_at?: string | null; + /** @description Where `captured_at` came from: a container stamp and an mtime differ in trust. */ + captured_source?: string | null; + codec?: string | null; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: double */ + duration_s?: number | null; + file_name: string; + /** Format: double */ + fps?: number | null; + gps: string; + /** @description Tri-state `yes`/`no`/`unknown`: a camera that recorded none differs from unread. */ + gravity: string; + /** + * @description The sampled imohash a device computes at ingest. Nullable, because an + * unreadable clip still deserves a row, and unique when present. Also what + * the archive keys this clip's blob on, so a stored object and this row + * meet without either side reading the whole file. + */ + hash?: string | null; + /** Format: int32 */ + height?: number | null; + /** Format: uuid */ + id: string; + /** Format: int64 */ + server_seq: number; + /** Format: int64 */ + size_bytes?: number | null; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + /** Format: int32 */ + width?: number | null; + }; + VideoResponse: { + /** Format: date-time */ + captured_at?: string | null; + /** @description Where `captured_at` came from: a container stamp and an mtime differ in trust. */ + captured_source?: string | null; + codec?: string | null; + /** Format: date-time */ + created_at: string; + /** + * Format: date-time + * @description The tombstone. Written only by the delete route, which administrators alone + * reach, and by `/api/sync/push`. Accepting it on create or update would let a + * member delete through a plain edit. + */ + deleted_at?: string | null; + /** + * Format: uuid + * @description Provenance, stamped from the credential by `/api/sync/push`. Never accepted + * from a client, which would otherwise forge another device. + */ + device_id?: string | null; + /** Format: double */ + duration_s?: number | null; + file_name: string; + /** Format: double */ + fps?: number | null; + gps: string; + /** @description Tri-state `yes`/`no`/`unknown`: a camera that recorded none differs from unread. */ + gravity: string; + /** + * @description The sampled imohash a device computes at ingest. Nullable, because an + * unreadable clip still deserves a row, and unique when present. Also what + * the archive keys this clip's blob on, so a stored object and this row + * meet without either side reading the whole file. + */ + hash?: string | null; + /** Format: int32 */ + height?: number | null; + /** Format: uuid */ + id: string; + /** Format: int64 */ + server_seq: number; + /** Format: int64 */ + size_bytes?: number | null; + /** + * Format: date-time + * @description The conflict key last-write-wins resolves on. Server-stamped: `on_update` only + * fires for a field the update model excludes, and a client-set stamp could pin a + * row against every later push. `/api/sync/push` writes it directly instead. + */ + updated_at: string; + /** Format: int32 */ + width?: number | null; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record<string, never>; +export interface operations { + erase_subject: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['EraseSubjectRequest']; + }; + }; + responses: { + /** @description Rows scrubbed per table */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EraseSubjectResponse']; + }; + }; + /** @description Empty subject */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Requires the deepreefmap-admin role */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + by_hash: { + parameters: { + query?: never; + header?: never; + path: { + /** @description imohash, 32 lowercase hex */ + content_hash: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The object's state */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ByHashResponse']; + }; + }; + /** @description Malformed hash */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Nothing archived under this hash */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The archive is not configured */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + initiate: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['InitiateRequest']; + }; + }; + responses: { + /** @description Upload state and any URLs still needed */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['InitiateResponse']; + }; + }; + /** @description Malformed hash, size, kind or relpath */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description No such run */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Conflicting concurrent initiate */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The archive is not configured */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + probe: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ProbeRequest']; + }; + }; + responses: { + /** @description State per known hash */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ProbeResponse']; + }; + }; + /** @description Malformed hash or too many of them */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The archive is not configured */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + runs_probe: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['RunsProbeRequest']; + }; + }; + responses: { + /** @description Counts per run with artefacts */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['RunsProbeResponse']; + }; + }; + /** @description Too many run ids */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The archive is not configured */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + complete: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Object being uploaded */ + object_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['CompleteRequest']; + }; + }; + responses: { + /** @description Parts assembled and verified against the claimed hash */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CompleteResponse']; + }; + }; + /** @description No such object */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The object is not pending, or its content does not match the claimed size or hash */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The archive is not configured */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + download: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Object to download */ + object_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Signed fetch URL on this registry */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['DownloadResponse']; + }; + }; + /** @description No such object */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The object is not complete yet */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The archive is not configured */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + fetch: { + parameters: { + query: { + /** @description Unix timestamp the signature lapses at. */ + expires: number; + /** @description HMAC over the object id and expiry, from `/archive/{id}/download`. */ + sig: string; + }; + header?: never; + path: { + /** @description Object to stream */ + object_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The object's bytes, as an attachment */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The signature is wrong or has lapsed */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description No such object */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The archive is not configured */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + upload_part: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Pending object the part belongs to */ + object_id: string; + /** @description 1-based part number */ + part_number: number; + }; + cookie?: never; + }; + /** @description The part's raw bytes */ + requestBody: { + content: { + 'application/octet-stream': number[]; + }; + }; + responses: { + /** @description Part stored */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['UploadPartResponse']; + }; + }; + /** @description Part number out of range */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The object is not pending */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Content-Length is required */ + 411: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Larger than the negotiated part size */ + 413: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description The archive is not configured */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_campaigns: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CampaignList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_one_campaign: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['CampaignCreate']; + }; + }; + responses: { + /** @description Resource created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CampaignResponse']; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + create_many_campaigns: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['CampaignCreate'][]; + }; + }; + responses: { + /** @description Resources created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CampaignResponse'][]; + }; + }; + /** @description Partial success - some items created, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_many_campaigns: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': string[]; + }; + }; + responses: { + /** @description Resources deleted successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': string[]; + }; + }; + /** @description Partial success - some items deleted, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + update_many_campaigns: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['BatchUpdateRequest'][]; + }; + }; + responses: { + /** @description Resources updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CampaignResponse'][]; + }; + }; + /** @description Partial success - some items updated, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description One or more resources not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + get_one_campaign: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CampaignResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_one_campaign: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['CampaignUpdate']; + }; + }; + responses: { + /** @description Resource updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CampaignResponse']; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_one_campaign: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Resource deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_class_groups: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Class groups by level */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ClassGroup'][]; + }; + }; + }; + }; + get_keycloak_config: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Realm details */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['KeycloakConfigResponse']; + }; + }; + }; + }; + get_all_cover_rows: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CoverRowList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_one_cover_row: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CoverRowResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_devices: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['DeviceList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + mint_code: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['MintConnectCodeRequest']; + }; + }; + responses: { + /** @description Code minted; shown to the operator once */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['MintConnectCodeResponse']; + }; + }; + /** @description Empty device name */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Requires an interactive login */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description PUBLIC_BASE_URL is not configured */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + assign_preset: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Device to assign a preset to */ + device_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['AssignPresetRequest']; + }; + }; + responses: { + /** @description Assignment stored */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['AssignPresetResponse']; + }; + }; + /** @description Not your device, or a device token */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description No such device, or no such preset */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + rename: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Device to rename */ + device_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['RenameDeviceRequest']; + }; + }; + responses: { + /** @description Device renamed */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['RenameDeviceResponse']; + }; + }; + /** @description Empty name */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Not your device, or a device token */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description No such device */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + revoke: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Device to revoke */ + device_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Device revoked */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['RevokeResponse']; + }; + }; + /** @description Not your device */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description No such device */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_one_device: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['DeviceResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + enrol: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['EnrolRequest']; + }; + }; + responses: { + /** @description Device enrolled */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['EnrolResponse']; + }; + }; + /** @description Malformed request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Code unknown, expired, or already used */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + healthz: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_me: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Caller identity */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['MeResponse']; + }; + }; + /** @description No valid credential */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_pass_groups: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassGroupList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_one_pass_group: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassGroupCreate']; + }; + }; + responses: { + /** @description Resource created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassGroupResponse']; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + create_many_pass_groups: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassGroupCreate'][]; + }; + }; + responses: { + /** @description Resources created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassGroupResponse'][]; + }; + }; + /** @description Partial success - some items created, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_many_pass_groups: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': string[]; + }; + }; + responses: { + /** @description Resources deleted successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': string[]; + }; + }; + /** @description Partial success - some items deleted, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + update_many_pass_groups: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['BatchUpdateRequest'][]; + }; + }; + responses: { + /** @description Resources updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassGroupResponse'][]; + }; + }; + /** @description Partial success - some items updated, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description One or more resources not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + get_one_pass_group: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassGroupResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_one_pass_group: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassGroupUpdate']; + }; + }; + responses: { + /** @description Resource updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassGroupResponse']; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_one_pass_group: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Resource deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_pass_videos: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassVideoList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_one_pass_video: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassVideoCreate']; + }; + }; + responses: { + /** @description Resource created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassVideoResponse']; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + create_many_pass_videos: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassVideoCreate'][]; + }; + }; + responses: { + /** @description Resources created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassVideoResponse'][]; + }; + }; + /** @description Partial success - some items created, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_many_pass_videos: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': string[]; + }; + }; + responses: { + /** @description Resources deleted successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': string[]; + }; + }; + /** @description Partial success - some items deleted, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + update_many_pass_videos: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['BatchUpdateRequest'][]; + }; + }; + responses: { + /** @description Resources updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassVideoResponse'][]; + }; + }; + /** @description Partial success - some items updated, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description One or more resources not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + get_one_pass_video: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassVideoResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_one_pass_video: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassVideoUpdate']; + }; + }; + responses: { + /** @description Resource updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassVideoResponse']; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_one_pass_video: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Resource deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_passes: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_one_pass: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassCreate']; + }; + }; + responses: { + /** @description Resource created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassResponse']; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + create_many_passes: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassCreate'][]; + }; + }; + responses: { + /** @description Resources created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassResponse'][]; + }; + }; + /** @description Partial success - some items created, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_many_passes: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': string[]; + }; + }; + responses: { + /** @description Resources deleted successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': string[]; + }; + }; + /** @description Partial success - some items deleted, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + update_many_passes: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['BatchUpdateRequest'][]; + }; + }; + responses: { + /** @description Resources updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassResponse'][]; + }; + }; + /** @description Partial success - some items updated, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description One or more resources not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + get_one_pass: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_one_pass: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PassUpdate']; + }; + }; + responses: { + /** @description Resource updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PassResponse']; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_one_pass: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Resource deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + performance_summary: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description One row per device, preset, model combination and processing configuration */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PerformanceSummary']; + }; + }; + }; + }; + get_all_presets: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PresetList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_one_preset: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PresetCreate']; + }; + }; + responses: { + /** @description Resource created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PresetResponse']; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + create_many_presets: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PresetCreate'][]; + }; + }; + responses: { + /** @description Resources created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PresetResponse'][]; + }; + }; + /** @description Partial success - some items created, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_many_presets: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': string[]; + }; + }; + responses: { + /** @description Resources deleted successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': string[]; + }; + }; + /** @description Partial success - some items deleted, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + update_many_presets: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['BatchUpdateRequest'][]; + }; + }; + responses: { + /** @description Resources updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PresetResponse'][]; + }; + }; + /** @description Partial success - some items updated, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description One or more resources not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + get_one_preset: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PresetResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_one_preset: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PresetUpdate']; + }; + }; + responses: { + /** @description Resource updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PresetResponse']; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_one_preset: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Resource deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + assign_all: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Preset to assign fleet-wide */ + preset_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Assignment stored on every active device */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['AssignAllResponse']; + }; + }; + /** @description Requires the deepreefmap-admin role */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description No such preset */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_run_artifacts: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['RunArtifactList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_one_run_artifact: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['RunArtifactResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_runs: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['RunList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_one_run: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['RunResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_sites: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SiteList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_one_site: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SiteCreate']; + }; + }; + responses: { + /** @description Resource created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SiteResponse']; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + create_many_sites: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SiteCreate'][]; + }; + }; + responses: { + /** @description Resources created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SiteResponse'][]; + }; + }; + /** @description Partial success - some items created, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_many_sites: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': string[]; + }; + }; + responses: { + /** @description Resources deleted successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': string[]; + }; + }; + /** @description Partial success - some items deleted, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + update_many_sites: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['BatchUpdateRequest'][]; + }; + }; + responses: { + /** @description Resources updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SiteResponse'][]; + }; + }; + /** @description Partial success - some items updated, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description One or more resources not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + get_one_site: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SiteResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_one_site: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SiteUpdate']; + }; + }; + responses: { + /** @description Resource updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SiteResponse']; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_one_site: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Resource deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_stored_objects: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['StoredObjectList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_one_stored_object: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['StoredObjectResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + heartbeat: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['HeartbeatRequest']; + }; + }; + responses: { + /** @description Report stored; the response names the assigned preset */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HeartbeatResponse']; + }; + }; + /** @description system_profile is not a JSON object */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Requires a device token */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + pull: { + parameters: { + query?: { + /** @description Cursor from the previous pull or push. Omit for a full download. */ + since?: number | null; + /** @description Maximum rows across all sections, capped at 5000. */ + limit?: number | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Changed rows in foreign-key order */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PullResponse']; + }; + }; + }; + }; + push: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PushRequest']; + }; + }; + responses: { + /** @description Document applied */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PushResponse']; + }; + }; + /** @description Unknown section, bad contract version, or malformed row */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description A row references something that does not exist */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_transects: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['TransectList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_one_transect: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['TransectCreate']; + }; + }; + responses: { + /** @description Resource created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['TransectResponse']; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + create_many_transects: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['TransectCreate'][]; + }; + }; + responses: { + /** @description Resources created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['TransectResponse'][]; + }; + }; + /** @description Partial success - some items created, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_many_transects: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': string[]; + }; + }; + responses: { + /** @description Resources deleted successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': string[]; + }; + }; + /** @description Partial success - some items deleted, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + update_many_transects: { + parameters: { + query?: { + /** + * @description Enable partial success mode for batch operations. + * + * When `true`, the operation processes each item independently instead of + * using all-or-nothing semantics. Items that succeed are committed even if + * other items fail. + * + * Default: `false` (all-or-nothing) + * @example false + */ + partial?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['BatchUpdateRequest'][]; + }; + }; + responses: { + /** @description Resources updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['TransectResponse'][]; + }; + }; + /** @description Partial success - some items updated, some failed */ + 207: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad request - batch size exceeded or validation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description One or more resources not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + get_one_transect: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['TransectResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_one_transect: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['TransectUpdate']; + }; + }; + responses: { + /** @description Resource updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['TransectResponse']; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Duplicate record */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + 'text/plain': string; + }; + }; + }; + }; + delete_one_transect: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Resource deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + pooled_cover: { + parameters: { + query?: { + /** @description Class hierarchy level: `fine`, `intermediate` or `coarse`. */ + level?: string; + /** @description Narrow to the passes swum during one expedition. Omit for the whole transect. */ + campaign_id?: string | null; + }; + header?: never; + path: { + /** @description Transect id */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Pooled cover for the transect */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['PooledCover']; + }; + }; + /** @description Unknown level */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + cover_series: { + parameters: { + query?: { + /** @description Class hierarchy level: `fine`, `intermediate` or `coarse`. */ + level?: string; + }; + header?: never; + path: { + /** @description Transect id */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Pooled cover per survey event */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CoverSeries']; + }; + }; + /** @description Unknown level */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_all_videos: { + parameters: { + query?: { + /** + * @description JSON-encoded filter for querying resources. + * + * This parameter supports various filtering options: + * - Free text search: `{"q": "search text"}` + * - Filtering by a single ID: `{"id": "550e8400-e29b-41d4-a716-446655440000"}` + * - Filtering by multiple IDs: `{"id": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"]}` + * - Filtering on other columns: `{"name": "example"}` + * @example { + * "id": "550e8400-e29b-41d4-a716-446655440000", + * "name": "example", + * "q": "search text" + * } + */ + filter?: string; + /** + * @description Range for pagination in the format "[start, end]". + * + * Example: `[0,9]` + * @example [0,9] + */ + range?: string; + /** + * @description Page number for standard REST pagination (1-based). + * + * Example: `1` + * @example 1 + */ + page?: number; + /** + * @description Number of items per page for standard REST pagination. + * + * Example: `10` + * @example 10 + */ + per_page?: number; + /** + * @description Sort order for the results in the format `["column", "order"]`. + * + * Example: `["id", "ASC"]` + * @example ["id", "ASC"] + */ + sort?: string; + /** + * @description Sort column for standard REST format. + * + * Example: `title` + * @example title + */ + sort_by?: string; + /** + * @description Sort order for standard REST format (ASC or DESC). + * + * Example: `ASC` + * @example ASC + */ + order?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['VideoList'][]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_one_video: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Resource identifier */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description The requested resource */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['VideoResponse']; + }; + }; + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + runs_for_video: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Video id */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Runs that consumed the video, newest first */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['RunResponse'][]; + }; + }; + }; + }; +} diff --git a/src/contract/index.ts b/src/contract/index.ts new file mode 100644 index 0000000..796707d --- /dev/null +++ b/src/contract/index.ts @@ -0,0 +1,111 @@ +import type { components } from './api'; + +type Schemas = components['schemas']; + +// crudcrate stores these vocabularies as plain text columns, so the generated schemas +// say `string`. Narrowing them here is what turns a typo into a compile error. +export const QUALITY_VALUES = [ + 'excellent', + 'very_good', + 'good', + 'meh', + 'bad', + 'very_bad', +] as const; +export const DIRECTION_VALUES = ['forward', 'reverse'] as const; +export const TRI_STATE_VALUES = ['yes', 'no', 'unknown'] as const; +export const RUN_STATUS_VALUES = [ + 'pending', + 'running', + 'succeeded', + 'failed', + 'cancelled', + 'interrupted', +] as const; +export const COVER_LEVEL_VALUES = ['fine', 'intermediate', 'coarse'] as const; +export const COVER_ESTIMATOR_VALUES = ['per_pass', 'pooled'] as const; +export const METRIC_SOURCE_VALUES = ['unprojected', 'tsdf'] as const; +export const STORED_OBJECT_STATUS_VALUES = ['pending', 'complete', 'failed'] as const; + +export type Quality = (typeof QUALITY_VALUES)[number]; +export type Direction = (typeof DIRECTION_VALUES)[number]; +export type TriState = (typeof TRI_STATE_VALUES)[number]; +export type RunStatus = (typeof RUN_STATUS_VALUES)[number]; +export type CoverLevel = (typeof COVER_LEVEL_VALUES)[number]; +export type CoverEstimator = (typeof COVER_ESTIMATOR_VALUES)[number]; +export type MetricSource = (typeof METRIC_SOURCE_VALUES)[number]; +export type StoredObjectStatus = (typeof STORED_OBJECT_STATUS_VALUES)[number]; + +export type Site = Schemas['SiteResponse']; +export type Campaign = Schemas['CampaignResponse']; +export type Transect = Schemas['TransectResponse']; +export type Device = Schemas['DeviceResponse']; + +export type VideoAsset = Omit<Schemas['VideoResponse'], 'gravity' | 'gps'> & { + gravity: TriState; + gps: TriState; +}; + +export type TransectPass = Omit<Schemas['PassResponse'], 'direction' | 'quality'> & { + direction?: Direction | null; + quality?: Quality | null; +}; + +export type RunRecord = Omit<Schemas['RunResponse'], 'status'> & { + status: RunStatus; +}; + +export type CoverRow = Omit< + Schemas['CoverRowResponse'], + 'level' | 'estimator' | 'metric_source' +> & { + level: CoverLevel; + estimator: CoverEstimator; + metric_source?: MetricSource | null; +}; + +export type PassGroup = Schemas['PassGroupResponse']; +export type Preset = Schemas['PresetResponse']; + +export type PooledCover = Schemas['PooledCover']; +export type ClassGroup = Schemas['ClassGroup']; +export type GroupCover = Schemas['GroupCover']; +export type CoverSeries = Schemas['CoverSeries']; +export type CoverSeriesEntry = Schemas['CoverSeriesEntry']; +export type SeriesGroupCover = Schemas['SeriesGroupCover']; + +export type ConnectCode = Schemas['MintConnectCodeResponse']; +export type DeviceRevocation = Schemas['RevokeResponse']; +export type DeviceRename = Schemas['RenameDeviceResponse']; +export type PresetAssignment = Schemas['AssignPresetResponse']; + +export type StoredObject = Omit<Schemas['StoredObjectResponse'], 'status'> & { + status: StoredObjectStatus; +}; +export type RunArtifact = Schemas['RunArtifactResponse']; + +export type ArchiveInitiateRequest = Schemas['InitiateRequest']; +export type ArchiveInitiate = Omit<Schemas['InitiateResponse'], 'status'> & { + status: StoredObjectStatus; +}; +export type CompletedPart = Schemas['CompletedPartBody']; +export type ArchivePartReceipt = Schemas['UploadPartResponse']; +export type ArchiveComplete = Schemas['CompleteResponse']; +export type ArchiveProbe = Omit<Schemas['ByHashResponse'], 'status'> & { + status: StoredObjectStatus; +}; +export type ArchiveDownload = Schemas['DownloadResponse']; + +export type BatchProbeState = Omit<Schemas['ProbeState'], 'status'> & { + status: StoredObjectStatus; +}; +export type BatchProbe = Omit<Schemas['ProbeResponse'], 'states'> & { + states: { [hash: string]: BatchProbeState }; +}; +export type RunArchiveState = Schemas['RunArchiveState']; +export type RunsProbe = Schemas['RunsProbeResponse']; + +export type AssignAllResponse = Schemas['AssignAllResponse']; + +export type PerformanceGroup = Schemas['PerformanceGroup']; +export type PerformanceSummary = Schemas['PerformanceSummary']; diff --git a/src/contract/preset-schema.json b/src/contract/preset-schema.json new file mode 100644 index 0000000..ffcec16 --- /dev/null +++ b/src/contract/preset-schema.json @@ -0,0 +1,381 @@ +{ + "choices": { + "camera": ["gopro_hero_10"], + "mapping": [ + { + "approx_size_mb": 326, + "description": "SC-SfMLearner depth + pose estimation", + "gated": false, + "gpu_only": false, + "hf_repos": ["EPFL-ECEO/deepreefmap-sfm-net"], + "name": "scsfmlearner" + }, + { + "approx_size_mb": 4787, + "description": "LoGeR depth + pose estimation (GPU required)", + "gated": false, + "gpu_only": true, + "hf_repos": ["Junyi42/LoGeR"], + "name": "loger" + }, + { + "approx_size_mb": 4787, + "description": "LoGeR* (longer-context variant, GPU required)", + "gated": false, + "gpu_only": true, + "hf_repos": ["Junyi42/LoGeR"], + "name": "loger_star" + } + ], + "resolution": ["Native", "Half", "Quarter", "Custom"], + "segmentation": [ + { + "approx_size_mb": 110, + "description": "SegFormer B2 (lightweight, no auth required)", + "gated": false, + "gpu_only": false, + "hf_repos": ["EPFL-ECEO/segformer-b2-finetuned-coralscapes-1024-1024"], + "name": "segformer-b2" + }, + { + "approx_size_mb": 339, + "description": "SegFormer B5 (larger, no auth required)", + "gated": false, + "gpu_only": false, + "hf_repos": ["EPFL-ECEO/segformer-b5-finetuned-coralscapes-1024-1024"], + "name": "segformer-b5" + }, + { + "approx_size_mb": 257, + "description": "DINOv3 ViT-S DPT (requires HF login)", + "gated": true, + "gpu_only": false, + "hf_repos": [ + "EPFL-ECEO/coralscapes-vit-s-dpt", + "facebook/dinov3-vits16-pretrain-lvd1689m" + ], + "name": "coralscapes-vit-s-dpt" + }, + { + "approx_size_mb": 786, + "description": "DINOv3 ViT-B DPT (requires HF login)", + "gated": true, + "gpu_only": false, + "hf_repos": [ + "EPFL-ECEO/coralscapes-vit-b-dpt", + "facebook/dinov3-vitb16-pretrain-lvd1689m" + ], + "name": "coralscapes-vit-b-dpt" + }, + { + "approx_size_mb": 2542, + "description": "DINOv3 ViT-L DPT (largest, requires HF login)", + "gated": true, + "gpu_only": false, + "hf_repos": [ + "EPFL-ECEO/coralscapes-vit-l-dpt", + "facebook/dinov3-vitl16-pretrain-lvd1689m" + ], + "name": "coralscapes-vit-l-dpt" + } + ] + }, + "fields": [ + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": 5, + "key": "fps", + "kind": "int", + "label": "frames per second", + "maximum": 60.0, + "minimum": 1.0, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "segmentation", + "decimals": null, + "default": "coralscapes-vit-b-dpt", + "key": "segmentation_name", + "kind": "enum", + "label": "coral identification model", + "maximum": null, + "minimum": null, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "mapping", + "decimals": null, + "default": "loger_star", + "key": "mapping_name", + "kind": "enum", + "label": "processing method", + "maximum": null, + "minimum": null, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "camera", + "decimals": null, + "default": "gopro_hero_10", + "key": "camera_profile_name", + "kind": "enum", + "label": "camera", + "maximum": null, + "minimum": null, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": 2, + "default": 1.0, + "key": "transect_crop_width", + "kind": "float", + "label": "transect width", + "maximum": 50.0, + "minimum": 0.0, + "nullable": false, + "step": 0.1, + "unit": "m" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": false, + "key": "enable_tsdf", + "kind": "bool", + "label": "surface fusion", + "maximum": null, + "minimum": null, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": false, + "key": "skip_segmentation", + "kind": "bool", + "label": "skipping coral identification", + "maximum": null, + "minimum": null, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "resolution", + "decimals": null, + "default": "Native", + "key": "resolution_preset", + "kind": "enum", + "label": "image resolution", + "maximum": null, + "minimum": null, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": null, + "key": "processing_width", + "kind": "int", + "label": "image width", + "maximum": 3840.0, + "minimum": 256.0, + "nullable": true, + "step": 32.0, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": null, + "key": "processing_height", + "kind": "int", + "label": "image height", + "maximum": 2160.0, + "minimum": 256.0, + "nullable": true, + "step": 32.0, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": 4, + "key": "preprocess_batch_size", + "kind": "int", + "label": "frames processed at once", + "maximum": 16.0, + "minimum": 1.0, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": 2000, + "key": "grid_bins", + "kind": "int", + "label": "map detail", + "maximum": 10000.0, + "minimum": 100.0, + "nullable": false, + "step": 100.0, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": false, + "key": "require_gravity_telemetry", + "kind": "bool", + "label": "requiring camera tilt data", + "maximum": null, + "minimum": null, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": 2, + "default": 0.0, + "key": "replacement_radius_factor", + "kind": "float", + "label": "replacement radius factor", + "maximum": 10.0, + "minimum": 0.0, + "nullable": false, + "step": 0.1, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": 30, + "key": "replacement_radius_estimation_frames", + "kind": "int", + "label": "replacement radius estimation frames", + "maximum": 200.0, + "minimum": 1.0, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": 4, + "default": 0.0, + "key": "replacement_radius_override", + "kind": "float", + "label": "replacement radius override", + "maximum": 10.0, + "minimum": 0.0, + "nullable": false, + "step": 0.001, + "unit": "m" + }, + { + "applies_when": ["loger", "loger_star"], + "choices": "", + "decimals": null, + "default": 32, + "key": "loger_window_size", + "kind": "int", + "label": "loger window size", + "maximum": 256.0, + "minimum": 1.0, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": ["loger", "loger_star"], + "choices": "", + "decimals": null, + "default": 3, + "key": "loger_overlap_size", + "kind": "int", + "label": "loger overlap size", + "maximum": 64.0, + "minimum": 0.0, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": [], + "choices": "", + "decimals": null, + "default": false, + "key": "refine_intrinsics_from_mapper", + "kind": "bool", + "label": "camera lens refinement", + "maximum": null, + "minimum": null, + "nullable": false, + "step": null, + "unit": "" + }, + { + "applies_when": ["scsfmlearner"], + "choices": "", + "decimals": null, + "default": 512, + "key": "scs_target_width", + "kind": "int", + "label": "scs target width", + "maximum": 2048.0, + "minimum": 64.0, + "nullable": false, + "step": 32.0, + "unit": "" + }, + { + "applies_when": ["scsfmlearner"], + "choices": "", + "decimals": null, + "default": 256, + "key": "scs_target_height", + "kind": "int", + "label": "scs target height", + "maximum": 2048.0, + "minimum": 64.0, + "nullable": false, + "step": 32.0, + "unit": "" + } + ], + "preset_schema_version": 1, + "unpublishable_keys": ["loger_model_path", "scs_checkpoint_path"] +} diff --git a/src/cover/CoverFigure.tsx b/src/cover/CoverFigure.tsx new file mode 100644 index 0000000..8244895 --- /dev/null +++ b/src/cover/CoverFigure.tsx @@ -0,0 +1,117 @@ +import { + Alert, + Box, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Typography, +} from '@mui/material'; + +import type { PooledCover } from '../contract'; +import { formatCount, formatPercent } from './usePooledCover'; + +const FALLBACK = '#9e9e9e'; + +type BarSegment = { + class_group: string; + colour?: string | null; + fraction: number; +}; + +/** A stacked bar in the class colours the desktop viewer uses. */ +export const CoverBar = ({ groups }: { groups: BarSegment[] }) => ( + <Box sx={{ display: 'flex', height: 22, borderRadius: 1, overflow: 'hidden' }}> + {groups + .filter(group => group.fraction > 0) + .map(group => ( + <Box + key={group.class_group} + title={`${group.class_group} ${formatPercent(group.fraction)}`} + sx={{ + width: `${group.fraction * 100}%`, + backgroundColor: group.colour ?? FALLBACK, + }} + /> + ))} + </Box> +); + +/** How much of the transect the figure rests on, stated rather than implied. */ +const Coverage = ({ cover }: { cover: PooledCover }) => { + const partial = cover.contributing_passes < cover.expected_passes; + const text = `${cover.contributing_passes} of ${ + cover.expected_passes + } passes over ${formatCount(cover.denominator)} points`; + return partial ? ( + <Alert severity="warning" sx={{ py: 0 }}> + {text} + </Alert> + ) : ( + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + }} + > + {text} + </Typography> + ); +}; + +const CoverFigure = ({ cover }: { cover: PooledCover }) => { + if (!cover.groups.length) { + return <Alert severity="info">No cover reported for these passes yet.</Alert>; + } + + return ( + <Stack spacing={1}> + <CoverBar groups={cover.groups} /> + <Coverage cover={cover} /> + <Table size="small"> + <TableHead> + <TableRow> + <TableCell>Class</TableCell> + <TableCell align="right">Cover</TableCell> + <TableCell align="right">Points</TableCell> + </TableRow> + </TableHead> + <TableBody> + {cover.groups.map(group => ( + <TableRow key={group.class_group}> + <TableCell> + <Stack + direction="row" + spacing={1} + sx={{ + alignItems: 'center', + }} + > + <Box + sx={{ + width: 12, + height: 12, + borderRadius: '2px', + backgroundColor: group.colour ?? FALLBACK, + }} + /> + <span>{group.class_group}</span> + </Stack> + </TableCell> + <TableCell align="right"> + {formatPercent(group.fraction)} + </TableCell> + <TableCell align="right"> + {formatCount(group.point_count)} + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </Stack> + ); +}; + +export default CoverFigure; diff --git a/src/cover/CoverSeriesChart.tsx b/src/cover/CoverSeriesChart.tsx new file mode 100644 index 0000000..532b4b9 --- /dev/null +++ b/src/cover/CoverSeriesChart.tsx @@ -0,0 +1,261 @@ +import { Box, Stack, Typography, useTheme } from '@mui/material'; + +import type { CoverSeriesEntry } from '../contract'; +import { formatPercent } from './usePooledCover'; + +const FALLBACK = '#9e9e9e'; + +const AXIS_WIDTH = 48; +const TOP_PAD = 10; +const PLOT_HEIGHT = 200; +const LABEL_BAND = 26; +const BAR_WIDTH = 14; +const BAR_GAP = 2; +const GROUP_GAP = 28; +const WHISKER_CAP = 6; + +/** What one series entry is called, wherever it appears. */ +export const entryLabel = (entry: CoverSeriesEntry) => { + if (entry.group_name) { + return entry.period_label + ? `${entry.group_name} (${entry.period_label})` + : entry.group_name; + } + return entry.campaign_name ?? 'Ungrouped'; +}; + +export const entryKey = (entry: CoverSeriesEntry) => + entry.group_id ?? entry.campaign_id ?? 'ungrouped'; + +// Every entry orders its own classes by size, so a shared order is needed for the +// bars to line up across groups. +const classOrder = (entries: CoverSeriesEntry[]) => { + const totals = new Map<string, number>(); + for (const entry of entries) { + for (const group of entry.groups) { + totals.set( + group.class_group, + (totals.get(group.class_group) ?? 0) + group.fraction, + ); + } + } + return [...totals.entries()] + .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) + .map(([name]) => name); +}; + +// The axis top is the next gridline above the tallest whisker, so the lines land on +// round percentages. +const niceScale = (maxValue: number) => { + const steps = [0.01, 0.02, 0.05, 0.1, 0.2, 0.25, 0.5]; + const step = steps.find(s => maxValue / s <= 5) ?? 0.5; + const top = Math.max(step, step * Math.ceil(maxValue / step)); + const ticks: number[] = []; + for (let i = 0; i * step <= top + step / 2; i += 1) ticks.push(i * step); + return { top, ticks }; +}; + +const tickLabel = (tick: number) => `${Math.round(tick * 1000) / 10}%`; + +// Rounded at the data end only: a rect's rx would round the baseline too. +const barPath = (x: number, y: number, width: number, height: number) => { + const r = Math.min(3, width / 2, height); + const bottom = y + height; + return [ + `M${x},${bottom}`, + `V${y + r}`, + `Q${x},${y} ${x + r},${y}`, + `H${x + width - r}`, + `Q${x + width},${y} ${x + width},${y + r}`, + `V${bottom}`, + 'Z', + ].join(' '); +}; + +const truncate = (label: string, maxChars: number) => + label.length <= maxChars ? label : `${label.slice(0, Math.max(1, maxChars - 1))}…`; + +/** Grouped bars per survey event, whiskered with each class's per-pass spread. */ +const CoverSeriesChart = ({ + entries, + colours, +}: { + entries: CoverSeriesEntry[]; + colours: Map<string, string>; +}) => { + const theme = useTheme(); + const classes = classOrder(entries); + if (!classes.length) return null; + + const maxValue = Math.max( + ...entries.flatMap(entry => + entry.groups.map(group => Math.max(group.fraction, group.max_fraction)), + ), + ); + const { top, ticks } = niceScale(maxValue); + const y = (value: number) => TOP_PAD + (PLOT_HEIGHT - TOP_PAD) * (1 - value / top); + const baseline = y(0); + + const groupWidth = classes.length * (BAR_WIDTH + BAR_GAP) - BAR_GAP; + const slotWidth = groupWidth + GROUP_GAP; + const groupX = (index: number) => AXIS_WIDTH + GROUP_GAP / 2 + index * slotWidth; + const width = AXIS_WIDTH + entries.length * slotWidth; + const height = PLOT_HEIGHT + LABEL_BAND; + + const gridStroke = theme.palette.divider; + const inkFaint = theme.palette.text.secondary; + const ink = theme.palette.text.primary; + + // The series payload carries colours too, which covers the moment before the + // class-group table has loaded. + const seedColours = new Map<string, string>(); + for (const entry of entries) { + for (const group of entry.groups) { + if (group.colour && !seedColours.has(group.class_group)) { + seedColours.set(group.class_group, group.colour); + } + } + } + const colourOf = (className: string) => + colours.get(className) ?? seedColours.get(className) ?? FALLBACK; + + return ( + <Stack spacing={1}> + <Box sx={{ overflowX: 'auto' }}> + <svg + width={width} + height={height} + role="img" + aria-label="Cover per survey event, one bar per class with its min to max spread" + > + {ticks.map(tick => ( + <g key={tick}> + <line + x1={AXIS_WIDTH} + x2={width} + y1={y(tick)} + y2={y(tick)} + stroke={gridStroke} + strokeWidth={1} + /> + <text + x={AXIS_WIDTH - 6} + y={y(tick) + 3.5} + textAnchor="end" + fontSize={11} + fill={inkFaint} + > + {tickLabel(tick)} + </text> + </g> + ))} + + {entries.map((entry, entryIndex) => { + const x0 = groupX(entryIndex); + const label = entryLabel(entry); + const byClass = new Map( + entry.groups.map(group => [group.class_group, group]), + ); + return ( + <g key={entryKey(entry)}> + {classes.map((className, classIndex) => { + const group = byClass.get(className); + if (!group) return null; + const x = x0 + classIndex * (BAR_WIDTH + BAR_GAP); + const centre = x + BAR_WIDTH / 2; + const spread = group.max_fraction > group.min_fraction; + return ( + <g key={className}> + <title> + {`${label}\n${className}: ${formatPercent( + group.fraction, + )} (min ${formatPercent( + group.min_fraction, + )}, max ${formatPercent(group.max_fraction)})`} + + {group.fraction > 0 && ( + + )} + {spread && ( + + + + + + )} + + ); + })} + + {label} + {truncate(label, Math.floor(slotWidth / 6.5))} + + + ); + })} + + + +
+ + + {classes.map(className => ( + + + {className} + + ))} + + + ); +}; + +export default CoverSeriesChart; diff --git a/src/cover/LevelToggle.tsx b/src/cover/LevelToggle.tsx new file mode 100644 index 0000000..5f1e6cc --- /dev/null +++ b/src/cover/LevelToggle.tsx @@ -0,0 +1,28 @@ +import { ToggleButton, ToggleButtonGroup } from '@mui/material'; + +import { COVER_LEVEL_VALUES, CoverLevel } from '../contract'; + +/** The fine/intermediate/coarse selector every cover view shares. */ +const LevelToggle = ({ + value, + onChange, +}: { + value: CoverLevel; + onChange: (level: CoverLevel) => void; +}) => ( + next && onChange(next as CoverLevel)} + sx={{ alignSelf: 'flex-start' }} + > + {COVER_LEVEL_VALUES.map(level => ( + + {level} + + ))} + +); + +export default LevelToggle; diff --git a/src/cover/RunCoverTable.tsx b/src/cover/RunCoverTable.tsx new file mode 100644 index 0000000..b929d1f --- /dev/null +++ b/src/cover/RunCoverTable.tsx @@ -0,0 +1,91 @@ +import { useState } from 'react'; +import { Loading, useGetList, useRecordContext } from 'react-admin'; +import { + Alert, + Box, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from '@mui/material'; + +import { CoverLevel, CoverRow, RunRecord } from '../contract'; +import LevelToggle from './LevelToggle'; +import { useClassColours } from './useClassGroups'; +import { formatCount, formatPercent } from './usePooledCover'; + +const ROW_PAGE = 200; +const FALLBACK = '#9e9e9e'; + +/** The cover rows this run reported, as it reported them. */ +const RunCoverTable = () => { + const run = useRecordContext(); + const [level, setLevel] = useState('coarse'); + const colours = useClassColours(level); + const { data, isPending } = useGetList('cover_rows', { + filter: { run_id: run?.id, level, estimator: 'per_pass' }, + pagination: { page: 1, perPage: ROW_PAGE }, + sort: { field: 'fraction', order: 'DESC' }, + }); + + if (!run || isPending) return ; + const rows = data ?? []; + + return ( + + + + {rows.length === 0 ? ( + This run reported no cover at this level. + ) : ( + + + + Class + Cover + Points + + + + {rows.map(row => ( + + + + + {row.class_group} + + + + {formatPercent(row.fraction)} + + + {row.point_count == null + ? '—' + : formatCount(row.point_count)} + + + ))} + +
+ )} +
+ ); +}; + +export default RunCoverTable; diff --git a/src/cover/Statistics.tsx b/src/cover/Statistics.tsx new file mode 100644 index 0000000..28e7ba4 --- /dev/null +++ b/src/cover/Statistics.tsx @@ -0,0 +1,152 @@ +import { useState } from 'react'; +import { Loading, useRecordContext } from 'react-admin'; +import { + Alert, + Box, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Typography, +} from '@mui/material'; + +import { CoverLevel, CoverSeriesEntry, Transect } from '../contract'; +import { CoverBar } from './CoverFigure'; +import CoverSeriesChart, { entryKey, entryLabel } from './CoverSeriesChart'; +import LevelToggle from './LevelToggle'; +import { useClassColours } from './useClassGroups'; +import { useCoverSeries } from './useCoverSeries'; +import { formatCount, formatPercent } from './usePooledCover'; + +const FALLBACK = '#9e9e9e'; + +const passCount = (entry: CoverSeriesEntry) => + `${entry.contributing_passes} ${entry.contributing_passes === 1 ? 'pass' : 'passes'}`; + +/** One entry's composition at a glance, in the stacked form the run pages use. */ +const EntryBar = ({ entry }: { entry: CoverSeriesEntry }) => ( + + {entryLabel(entry)} + + + {passCount(entry)} over {formatCount(entry.denominator)} points + + +); + +/** The numbers behind one entry, in the layout of the desktop analysis table. */ +const EntryTable = ({ + entry, + colours, +}: { + entry: CoverSeriesEntry; + colours: Map; +}) => ( + + {entryLabel(entry)} + + + + Class + Cover + Min + Max + Passes + + + + {entry.groups.map(group => ( + + + + + {group.class_group} + + + {formatPercent(group.fraction)} + + {formatPercent(group.min_fraction)} + + + {formatPercent(group.max_fraction)} + + {entry.contributing_passes} + + ))} + +
+
+); + +/** + * Cover along this transect, one series entry per survey event. + * + * The registry orders the series itself: named groups by period label, then campaign + * buckets, then the passes with neither. Grouping happens on the passes list. + */ +const Statistics = () => { + const transect = useRecordContext(); + const [level, setLevel] = useState('coarse'); + const { series, error, pending } = useCoverSeries(transect?.id, level); + const colours = useClassColours(level); + + if (!transect) return ; + const entries = series?.entries ?? []; + + return ( + + + + {error && {error}} + {pending && } + {series && !pending && !entries.length && ( + + No processed passes on this transect yet. The series fills in once a + desktop client reconstructs a pass and syncs. + + )} + + {!pending && entries.length > 0 && ( + <> + + + + {entries.map(entry => ( + + ))} + + + + {entries.map(entry => ( + + ))} + + + )} + + ); +}; + +export default Statistics; diff --git a/src/cover/index.tsx b/src/cover/index.tsx new file mode 100644 index 0000000..4b109e2 --- /dev/null +++ b/src/cover/index.tsx @@ -0,0 +1,3 @@ +// No views: cover rows are pipeline output, read in aggregate by the run and +// transect pages rather than browsed directly. +export default {}; diff --git a/src/cover/useClassGroups.ts b/src/cover/useClassGroups.ts new file mode 100644 index 0000000..41d4871 --- /dev/null +++ b/src/cover/useClassGroups.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; +import { useDataProvider } from 'react-admin'; + +import type { ClassGroup } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; + +/** `class group` to `#rrggbb` at one level, as the registry publishes it. */ +export const useClassColours = (level: string) => { + const dataProvider = useDataProvider(); + const [groups, setGroups] = useState(); + + useEffect(() => { + let current = true; + dataProvider + .classGroups() + .then(result => { + if (current) setGroups(result); + }) + .catch(() => { + if (current) setGroups([]); + }); + return () => { + current = false; + }; + }, [dataProvider]); + + return new Map( + (groups ?? []) + .filter(group => group.level === level) + .map(group => [group.name, group.colour]), + ); +}; diff --git a/src/cover/useCoverSeries.ts b/src/cover/useCoverSeries.ts new file mode 100644 index 0000000..0846c11 --- /dev/null +++ b/src/cover/useCoverSeries.ts @@ -0,0 +1,40 @@ +import { useEffect, useState } from 'react'; +import { useDataProvider } from 'react-admin'; + +import type { CoverSeries } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; + +/** Every survey event's pooled figure for one transect, in one call. */ +export const useCoverSeries = (transectId: string | undefined, level: string) => { + const dataProvider = useDataProvider(); + const [series, setSeries] = useState(); + const [error, setError] = useState(); + const [pending, setPending] = useState(false); + + useEffect(() => { + if (!transectId) { + setSeries(undefined); + return; + } + let current = true; + setPending(true); + setError(undefined); + dataProvider + .transectCoverSeries(transectId, level) + .then(result => { + if (current) setSeries(result); + }) + .catch((e: unknown) => { + if (current) + setError(e instanceof Error ? e.message : 'Could not read the series'); + }) + .finally(() => { + if (current) setPending(false); + }); + return () => { + current = false; + }; + }, [dataProvider, transectId, level]); + + return { series, error, pending }; +}; diff --git a/src/cover/usePooledCover.ts b/src/cover/usePooledCover.ts new file mode 100644 index 0000000..7c6d13e --- /dev/null +++ b/src/cover/usePooledCover.ts @@ -0,0 +1,48 @@ +import { useEffect, useState } from 'react'; +import { useDataProvider } from 'react-admin'; + +import type { PooledCover } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; + +export const formatPercent = (fraction: number, digits = 1) => + `${(fraction * 100).toFixed(digits)}%`; + +export const formatCount = (count: number) => Math.round(count).toLocaleString(); + +/** The registry's pooled figure for one transect, optionally for one campaign. */ +export const usePooledCover = ( + transectId: string | undefined, + level: string, + campaignId?: string, +) => { + const dataProvider = useDataProvider(); + const [cover, setCover] = useState(); + const [error, setError] = useState(); + const [pending, setPending] = useState(false); + + useEffect(() => { + if (!transectId) { + setCover(undefined); + return; + } + let current = true; + setPending(true); + setError(undefined); + dataProvider + .transectCover(transectId, level, campaignId) + .then(result => { + if (current) setCover(result); + }) + .catch((e: unknown) => { + if (current) setError(e instanceof Error ? e.message : 'Could not read cover'); + }) + .finally(() => { + if (current) setPending(false); + }); + return () => { + current = false; + }; + }, [dataProvider, transectId, level, campaignId]); + + return { cover, error, pending }; +}; diff --git a/src/css/academicons.min.css b/src/css/academicons.min.css deleted file mode 100644 index 29a4f84..0000000 --- a/src/css/academicons.min.css +++ /dev/null @@ -1 +0,0 @@ - @font-face {font-family: 'Academicons';font-style: normal;font-weight: 400;font-display: block;src:url('../fonts/academicons.eot');src:url('../fonts/academicons.eot') format('embedded-opentype'), url('../fonts/academicons.ttf') format('truetype'), url('../fonts/academicons.woff') format('woff'), url('../fonts/academicons.svg') format('svg');}.ai {font-family: 'Academicons';font-weight: 400;-moz-osx-font-smoothing: grayscale;-webkit-font-smoothing: antialiased;display: inline-block;font-style: normal;font-variant: normal;text-rendering: auto;line-height: 1;}.ai-academia:before {content: "\e9af";}.ai-academia-square:before {content: "\e93d";}.ai-acclaim:before {content: "\e92e";}.ai-acclaim-square:before {content: "\e93a";}.ai-acm:before {content: "\e93c";}.ai-acm-square:before {content: "\e95d";}.ai-acmdl:before {content: "\e96a";}.ai-acmdl-square:before {content: "\e9d3";}.ai-ads:before {content: "\e9cb";}.ai-ads-square:before {content: "\e94a";}.ai-africarxiv:before {content: "\e91b";}.ai-africarxiv-square:before {content: "\e90b";}.ai-archive:before {content: "\e955";}.ai-archive-square:before {content: "\e956";}.ai-arxiv:before {content: "\e974";}.ai-arxiv-square:before {content: "\e9a6";}.ai-biorxiv:before {content: "\e9a2";}.ai-biorxiv-square:before {content: "\e98b";}.ai-ceur:before {content: "\e96d";}.ai-ceur-square:before {content: "\e92f";}.ai-ciencia-vitae:before {content: "\e912";}.ai-ciencia-vitae-square:before {content: "\e913";}.ai-clarivate:before {content: "\e924";}.ai-clarivate-square:before {content: "\e925";}.ai-closed-access:before {content: "\e942";}.ai-closed-access-square:before {content: "\e943";}.ai-conversation:before {content: "\e94c";}.ai-conversation-square:before {content: "\e915";}.ai-coursera:before {content: "\e95f";}.ai-coursera-square:before {content: "\e97f";}.ai-crossref:before {content: "\e918";}.ai-crossref-square:before {content: "\e919";}.ai-cv:before {content: "\e9a5";}.ai-cv-square:before {content: "\e90a";}.ai-datacite:before {content: "\e91c";}.ai-datacite-square:before {content: "\e91d";}.ai-dataverse:before {content: "\e9f7";}.ai-dataverse-square:before {content: "\e9e4";}.ai-dblp:before {content: "\e94f";}.ai-dblp-square:before {content: "\e93f";}.ai-depsy:before {content: "\e97a";}.ai-depsy-square:before {content: "\e94b";}.ai-doi:before {content: "\e97e";}.ai-doi-square:before {content: "\e98f";}.ai-dryad:before {content: "\e97c";}.ai-dryad-square:before {content: "\e98c";}.ai-elsevier:before {content: "\e961";}.ai-elsevier-square:before {content: "\e910";}.ai-figshare:before {content: "\e981";}.ai-figshare-square:before {content: "\e9e7";}.ai-google-scholar:before {content: "\e9d4";}.ai-google-scholar-square:before {content: "\e9f9";}.ai-hal:before {content: "\e92c";}.ai-hal-square:before {content: "\e92d";}.ai-hypothesis:before {content: "\e95a";}.ai-hypothesis-square:before {content: "\e95b";}.ai-ideas-repec:before {content: "\e9ed";}.ai-ideas-repec-square:before {content: "\e9f8";}.ai-ieee:before {content: "\e929";}.ai-ieee-square:before {content: "\e9b9";}.ai-impactstory:before {content: "\e9cf";}.ai-impactstory-square:before {content: "\e9aa";}.ai-inaturalist:before {content: "\e900";}.ai-inaturalist-square:before {content: "\e901";}.ai-inpn:before {content: "\e902";}.ai-inpn-square:before {content: "\e903";}.ai-inspire:before {content: "\e9e9";}.ai-inspire-square:before {content: "\e9fe";}.ai-isidore:before {content: "\e936";}.ai-isidore-square:before {content: "\e954";}.ai-isni:before {content: "\e957";}.ai-isni-square:before {content: "\e958";}.ai-jstor:before {content: "\e938";}.ai-jstor-square:before {content: "\e944";}.ai-lattes:before {content: "\e9b3";}.ai-lattes-square:before {content: "\e99c";}.ai-mathoverflow:before {content: "\e9f6";}.ai-mathoverflow-square:before {content: "\e97b";}.ai-mendeley:before {content: "\e9f0";}.ai-mendeley-square:before {content: "\e9f3";}.ai-moodle:before {content: "\e907";}.ai-moodle-square:before {content: "\e908";}.ai-mtmt:before {content: "\e950";}.ai-mtmt-square:before {content: "\e951";}.ai-nakala:before {content: "\e940";}.ai-nakala-square:before {content: "\e941";}.ai-obp:before {content: "\e92a";}.ai-obp-square:before {content: "\e92b";}.ai-open-access:before {content: "\e939";}.ai-open-access-square:before {content: "\e9f4";}.ai-open-data:before {content: "\e966";}.ai-open-data-square:before {content: "\e967";}.ai-open-materials:before {content: "\e968";}.ai-open-materials-square:before {content: "\e969";}.ai-openedition:before {content: "\e946";}.ai-openedition-square:before {content: "\e947";}.ai-orcid:before {content: "\e9d9";}.ai-orcid-square:before {content: "\e9c3";}.ai-osf:before {content: "\e9ef";}.ai-osf-square:before {content: "\e931";}.ai-overleaf:before {content: "\e914";}.ai-overleaf-square:before {content: "\e98d";}.ai-philpapers:before {content: "\e98a";}.ai-philpapers-square:before {content: "\e96f";}.ai-piazza:before {content: "\e99a";}.ai-piazza-square:before {content: "\e90c";}.ai-preregistered:before {content: "\e906";}.ai-preregistered-square:before {content: "\e96b";}.ai-protocols:before {content: "\e952";}.ai-protocols-square:before {content: "\e953";}.ai-psyarxiv:before {content: "\e90e";}.ai-psyarxiv-square:before {content: "\e90f";}.ai-publons:before {content: "\e937";}.ai-publons-square:before {content: "\e94e";}.ai-pubmed:before {content: "\e99f";}.ai-pubmed-square:before {content: "\e97d";}.ai-pubpeer:before {content: "\e922";}.ai-pubpeer-square:before {content: "\e923";}.ai-researcherid:before {content: "\e91a";}.ai-researcherid-square:before {content: "\e95c";}.ai-researchgate:before {content: "\e95e";}.ai-researchgate-square:before {content: "\e99e";}.ai-ror:before {content: "\e948";}.ai-ror-square:before {content: "\e949";}.ai-sci-hub:before {content: "\e959";}.ai-sci-hub-square:before {content: "\e905";}.ai-scirate:before {content: "\e98e";}.ai-scirate-square:before {content: "\e99d";}.ai-scopus:before {content: "\e91e";}.ai-scopus-square:before {content: "\e91f";}.ai-semantic-scholar:before {content: "\e96e";}.ai-semantic-scholar-square:before {content: "\e96c";}.ai-springer:before {content: "\e928";}.ai-springer-square:before {content: "\e99b";}.ai-ssrn:before {content: "\e916";}.ai-ssrn-square:before {content: "\e917";}.ai-stackoverflow:before {content: "\e920";}.ai-stackoverflow-square:before {content: "\e921";}.ai-viaf:before {content: "\e933";}.ai-viaf-square:before {content: "\e934";}.ai-wiley:before {content: "\e926";}.ai-wiley-square:before {content: "\e927";}.ai-zenodo:before {content: "\e911";}.ai-zotero:before {content: "\e962";}.ai-zotero-square:before {content: "\e932";}.ai-lg {font-size: 1.33333em;line-height: 0.75em;vertical-align: -.0667em;}.ai-xs {font-size: .75em;}.ai-sm {font-size: .875em;}.ai-1x {font-size: 1em;}.ai-2x {font-size: 2em;}.ai-3x {font-size: 3em;}.ai-4x {font-size: 4em;}.ai-5x {font-size: 5em;}.ai-6x {font-size: 6em;}.ai-7x {font-size: 7em;}.ai-8x {font-size: 8em;}.ai-9x {font-size: 9em;}.ai-10x {font-size: 10em;}.ai-fw {text-align: center;width: 1.25em;}.ai-ul {list-style-type: none;margin-left: 2.5em;padding-left: 0;}.ai-ul > li {position: relative;}.ai-li {left: -2em;position: absolute;text-align: center;width: 2em;line-height: inherit;}.ai-border {border: solid 0.08em #eee;border-radius: .1em;padding: .2em .25em .15em;}.ai-pull-left {float: left;}.ai-pull-right {float: right;}.ai.ai-pull-left {margin-right: .3em;}.ai.ai-pull-right {margin-right: .3em;}.ai-stack {display: inline-block;height: 2em;line-height: 2em;position: relative;vertical-align: middle;width: 2.5em;}.ai-stack-1x, .ai-stack-2x {left: 0;position: absolute;text-align: center;width: 100%;}.ai-stack-1x {line-height: inherit;}.ai-stack-2x {font-size: 2em;}.ai-inverse {color: #fff;} diff --git a/src/css/bulma-carousel.min.css b/src/css/bulma-carousel.min.css deleted file mode 100644 index 4d4b7d1..0000000 --- a/src/css/bulma-carousel.min.css +++ /dev/null @@ -1 +0,0 @@ -@-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.slider{position:relative;width:100%}.slider-container{display:flex;flex-wrap:nowrap;flex-direction:row;overflow:hidden;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);min-height:100%}.slider-container.is-vertical{flex-direction:column}.slider-container .slider-item{flex:none}.slider-container .slider-item .image.is-covered img{-o-object-fit:cover;object-fit:cover;-o-object-position:center center;object-position:center center;height:100%;width:100%}.slider-container .slider-item .video-container{height:0;padding-bottom:0;padding-top:56.25%;margin:0;position:relative}.slider-container .slider-item .video-container.is-1by1,.slider-container .slider-item .video-container.is-square{padding-top:100%}.slider-container .slider-item .video-container.is-4by3{padding-top:75%}.slider-container .slider-item .video-container.is-21by9{padding-top:42.857143%}.slider-container .slider-item .video-container embed,.slider-container .slider-item .video-container iframe,.slider-container .slider-item .video-container object{position:absolute;top:0;left:0;width:100%!important;height:100%!important}.slider-navigation-next,.slider-navigation-previous{display:flex;justify-content:center;align-items:center;position:absolute;width:42px;height:42px;background:#fff center center no-repeat;background-size:20px 20px;border:1px solid #fff;border-radius:25091983px;box-shadow:0 2px 5px #3232321a;top:50%;margin-top:-20px;left:0;cursor:pointer;transition:opacity .3s,-webkit-transform .3s;transition:transform .3s,opacity .3s;transition:transform .3s,opacity .3s,-webkit-transform .3s}.slider-navigation-next:hover,.slider-navigation-previous:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.slider-navigation-next.is-hidden,.slider-navigation-previous.is-hidden{display:none;opacity:0}.slider-navigation-next svg,.slider-navigation-previous svg{width:25%}.slider-navigation-next{left:auto;right:0;background:#fff center center no-repeat;background-size:20px 20px}.slider-pagination{display:none;justify-content:center;align-items:center;position:absolute;bottom:0;left:0;right:0;padding:.5rem 1rem;text-align:center}.slider-pagination .slider-page{background:#fff;width:10px;height:10px;border-radius:25091983px;display:inline-block;margin:0 3px;box-shadow:0 2px 5px #3232321a;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;cursor:pointer}.slider-pagination .slider-page.is-active,.slider-pagination .slider-page:hover{-webkit-transform:scale(1.4);transform:scale(1.4)}@media screen and (min-width:800px){.slider-pagination{display:flex}}.hero.has-carousel{position:relative}.hero.has-carousel+.hero-body,.hero.has-carousel+.hero-footer,.hero.has-carousel+.hero-head{z-index:10;overflow:hidden}.hero.has-carousel .hero-carousel{position:absolute;top:0;left:0;bottom:0;right:0;height:auto;border:none;margin:auto;padding:0;z-index:0}.hero.has-carousel .hero-carousel .slider{width:100%;max-width:100%;overflow:hidden;height:100%!important;max-height:100%;z-index:0}.hero.has-carousel .hero-carousel .slider .has-background{max-height:100%}.hero.has-carousel .hero-carousel .slider .has-background .is-background{-o-object-fit:cover;object-fit:cover;-o-object-position:center center;object-position:center center;height:100%;width:100%}.hero.has-carousel .hero-body{margin:0 3rem;z-index:10} \ No newline at end of file diff --git a/src/css/bulma-slider.min.css b/src/css/bulma-slider.min.css deleted file mode 100644 index 09b4aeb..0000000 --- a/src/css/bulma-slider.min.css +++ /dev/null @@ -1 +0,0 @@ -@-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}input[type=range].slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin:1rem 0;background:0 0;touch-action:none}input[type=range].slider.is-fullwidth{display:block;width:100%}input[type=range].slider:focus{outline:0}input[type=range].slider:not([orient=vertical])::-webkit-slider-runnable-track{width:100%}input[type=range].slider:not([orient=vertical])::-moz-range-track{width:100%}input[type=range].slider:not([orient=vertical])::-ms-track{width:100%}input[type=range].slider:not([orient=vertical]).has-output+output,input[type=range].slider:not([orient=vertical]).has-output-tooltip+output{width:3rem;background:#4a4a4a;border-radius:4px;padding:.4rem .8rem;font-size:.75rem;line-height:.75rem;text-align:center;text-overflow:ellipsis;white-space:nowrap;color:#fff;overflow:hidden;pointer-events:none;z-index:200}input[type=range].slider:not([orient=vertical]).has-output-tooltip:disabled+output,input[type=range].slider:not([orient=vertical]).has-output:disabled+output{opacity:.5}input[type=range].slider:not([orient=vertical]).has-output{display:inline-block;vertical-align:middle;width:calc(100% - (4.2rem))}input[type=range].slider:not([orient=vertical]).has-output+output{display:inline-block;margin-left:.75rem;vertical-align:middle}input[type=range].slider:not([orient=vertical]).has-output-tooltip{display:block}input[type=range].slider:not([orient=vertical]).has-output-tooltip+output{position:absolute;left:0;top:-.1rem}input[type=range].slider[orient=vertical]{-webkit-appearance:slider-vertical;-moz-appearance:slider-vertical;appearance:slider-vertical;-webkit-writing-mode:bt-lr;-ms-writing-mode:bt-lr;writing-mode:bt-lr}input[type=range].slider[orient=vertical]::-webkit-slider-runnable-track{height:100%}input[type=range].slider[orient=vertical]::-moz-range-track{height:100%}input[type=range].slider[orient=vertical]::-ms-track{height:100%}input[type=range].slider::-webkit-slider-runnable-track{cursor:pointer;animate:.2s;box-shadow:0 0 0 #7a7a7a;background:#dbdbdb;border-radius:4px;border:0 solid #7a7a7a}input[type=range].slider::-moz-range-track{cursor:pointer;animate:.2s;box-shadow:0 0 0 #7a7a7a;background:#dbdbdb;border-radius:4px;border:0 solid #7a7a7a}input[type=range].slider::-ms-track{cursor:pointer;animate:.2s;box-shadow:0 0 0 #7a7a7a;background:#dbdbdb;border-radius:4px;border:0 solid #7a7a7a}input[type=range].slider::-ms-fill-lower{background:#dbdbdb;border-radius:4px}input[type=range].slider::-ms-fill-upper{background:#dbdbdb;border-radius:4px}input[type=range].slider::-webkit-slider-thumb{box-shadow:none;border:1px solid #b5b5b5;border-radius:4px;background:#fff;cursor:pointer}input[type=range].slider::-moz-range-thumb{box-shadow:none;border:1px solid #b5b5b5;border-radius:4px;background:#fff;cursor:pointer}input[type=range].slider::-ms-thumb{box-shadow:none;border:1px solid #b5b5b5;border-radius:4px;background:#fff;cursor:pointer}input[type=range].slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none}input[type=range].slider.is-circle::-webkit-slider-thumb{border-radius:290486px}input[type=range].slider.is-circle::-moz-range-thumb{border-radius:290486px}input[type=range].slider.is-circle::-ms-thumb{border-radius:290486px}input[type=range].slider:active::-webkit-slider-thumb{-webkit-transform:scale(1.25);transform:scale(1.25)}input[type=range].slider:active::-moz-range-thumb{transform:scale(1.25)}input[type=range].slider:active::-ms-thumb{transform:scale(1.25)}input[type=range].slider:disabled{opacity:.5;cursor:not-allowed}input[type=range].slider:disabled::-webkit-slider-thumb{cursor:not-allowed;-webkit-transform:scale(1);transform:scale(1)}input[type=range].slider:disabled::-moz-range-thumb{cursor:not-allowed;transform:scale(1)}input[type=range].slider:disabled::-ms-thumb{cursor:not-allowed;transform:scale(1)}input[type=range].slider:not([orient=vertical]){min-height:calc((1rem + 2px) * 1.25)}input[type=range].slider:not([orient=vertical])::-webkit-slider-runnable-track{height:.5rem}input[type=range].slider:not([orient=vertical])::-moz-range-track{height:.5rem}input[type=range].slider:not([orient=vertical])::-ms-track{height:.5rem}input[type=range].slider[orient=vertical]::-webkit-slider-runnable-track{width:.5rem}input[type=range].slider[orient=vertical]::-moz-range-track{width:.5rem}input[type=range].slider[orient=vertical]::-ms-track{width:.5rem}input[type=range].slider::-webkit-slider-thumb{height:1rem;width:1rem}input[type=range].slider::-moz-range-thumb{height:1rem;width:1rem}input[type=range].slider::-ms-thumb{height:1rem;width:1rem}input[type=range].slider::-ms-thumb{margin-top:0}input[type=range].slider::-webkit-slider-thumb{margin-top:-.25rem}input[type=range].slider[orient=vertical]::-webkit-slider-thumb{margin-top:auto;margin-left:-.25rem}input[type=range].slider.is-small:not([orient=vertical]){min-height:calc((.75rem + 2px) * 1.25)}input[type=range].slider.is-small:not([orient=vertical])::-webkit-slider-runnable-track{height:.375rem}input[type=range].slider.is-small:not([orient=vertical])::-moz-range-track{height:.375rem}input[type=range].slider.is-small:not([orient=vertical])::-ms-track{height:.375rem}input[type=range].slider.is-small[orient=vertical]::-webkit-slider-runnable-track{width:.375rem}input[type=range].slider.is-small[orient=vertical]::-moz-range-track{width:.375rem}input[type=range].slider.is-small[orient=vertical]::-ms-track{width:.375rem}input[type=range].slider.is-small::-webkit-slider-thumb{height:.75rem;width:.75rem}input[type=range].slider.is-small::-moz-range-thumb{height:.75rem;width:.75rem}input[type=range].slider.is-small::-ms-thumb{height:.75rem;width:.75rem}input[type=range].slider.is-small::-ms-thumb{margin-top:0}input[type=range].slider.is-small::-webkit-slider-thumb{margin-top:-.1875rem}input[type=range].slider.is-small[orient=vertical]::-webkit-slider-thumb{margin-top:auto;margin-left:-.1875rem}input[type=range].slider.is-medium:not([orient=vertical]){min-height:calc((1.25rem + 2px) * 1.25)}input[type=range].slider.is-medium:not([orient=vertical])::-webkit-slider-runnable-track{height:.625rem}input[type=range].slider.is-medium:not([orient=vertical])::-moz-range-track{height:.625rem}input[type=range].slider.is-medium:not([orient=vertical])::-ms-track{height:.625rem}input[type=range].slider.is-medium[orient=vertical]::-webkit-slider-runnable-track{width:.625rem}input[type=range].slider.is-medium[orient=vertical]::-moz-range-track{width:.625rem}input[type=range].slider.is-medium[orient=vertical]::-ms-track{width:.625rem}input[type=range].slider.is-medium::-webkit-slider-thumb{height:1.25rem;width:1.25rem}input[type=range].slider.is-medium::-moz-range-thumb{height:1.25rem;width:1.25rem}input[type=range].slider.is-medium::-ms-thumb{height:1.25rem;width:1.25rem}input[type=range].slider.is-medium::-ms-thumb{margin-top:0}input[type=range].slider.is-medium::-webkit-slider-thumb{margin-top:-.3125rem}input[type=range].slider.is-medium[orient=vertical]::-webkit-slider-thumb{margin-top:auto;margin-left:-.3125rem}input[type=range].slider.is-large:not([orient=vertical]){min-height:calc((1.5rem + 2px) * 1.25)}input[type=range].slider.is-large:not([orient=vertical])::-webkit-slider-runnable-track{height:.75rem}input[type=range].slider.is-large:not([orient=vertical])::-moz-range-track{height:.75rem}input[type=range].slider.is-large:not([orient=vertical])::-ms-track{height:.75rem}input[type=range].slider.is-large[orient=vertical]::-webkit-slider-runnable-track{width:.75rem}input[type=range].slider.is-large[orient=vertical]::-moz-range-track{width:.75rem}input[type=range].slider.is-large[orient=vertical]::-ms-track{width:.75rem}input[type=range].slider.is-large::-webkit-slider-thumb{height:1.5rem;width:1.5rem}input[type=range].slider.is-large::-moz-range-thumb{height:1.5rem;width:1.5rem}input[type=range].slider.is-large::-ms-thumb{height:1.5rem;width:1.5rem}input[type=range].slider.is-large::-ms-thumb{margin-top:0}input[type=range].slider.is-large::-webkit-slider-thumb{margin-top:-.375rem}input[type=range].slider.is-large[orient=vertical]::-webkit-slider-thumb{margin-top:auto;margin-left:-.375rem}input[type=range].slider.is-white::-moz-range-track{background:#fff!important}input[type=range].slider.is-white::-webkit-slider-runnable-track{background:#fff!important}input[type=range].slider.is-white::-ms-track{background:#fff!important}input[type=range].slider.is-white::-ms-fill-lower{background:#fff}input[type=range].slider.is-white::-ms-fill-upper{background:#fff}input[type=range].slider.is-white .has-output-tooltip+output,input[type=range].slider.is-white.has-output+output{background-color:#fff;color:#0a0a0a}input[type=range].slider.is-black::-moz-range-track{background:#0a0a0a!important}input[type=range].slider.is-black::-webkit-slider-runnable-track{background:#0a0a0a!important}input[type=range].slider.is-black::-ms-track{background:#0a0a0a!important}input[type=range].slider.is-black::-ms-fill-lower{background:#0a0a0a}input[type=range].slider.is-black::-ms-fill-upper{background:#0a0a0a}input[type=range].slider.is-black .has-output-tooltip+output,input[type=range].slider.is-black.has-output+output{background-color:#0a0a0a;color:#fff}input[type=range].slider.is-light::-moz-range-track{background:#f5f5f5!important}input[type=range].slider.is-light::-webkit-slider-runnable-track{background:#f5f5f5!important}input[type=range].slider.is-light::-ms-track{background:#f5f5f5!important}input[type=range].slider.is-light::-ms-fill-lower{background:#f5f5f5}input[type=range].slider.is-light::-ms-fill-upper{background:#f5f5f5}input[type=range].slider.is-light .has-output-tooltip+output,input[type=range].slider.is-light.has-output+output{background-color:#f5f5f5;color:#363636}input[type=range].slider.is-dark::-moz-range-track{background:#363636!important}input[type=range].slider.is-dark::-webkit-slider-runnable-track{background:#363636!important}input[type=range].slider.is-dark::-ms-track{background:#363636!important}input[type=range].slider.is-dark::-ms-fill-lower{background:#363636}input[type=range].slider.is-dark::-ms-fill-upper{background:#363636}input[type=range].slider.is-dark .has-output-tooltip+output,input[type=range].slider.is-dark.has-output+output{background-color:#363636;color:#f5f5f5}input[type=range].slider.is-primary::-moz-range-track{background:#00d1b2!important}input[type=range].slider.is-primary::-webkit-slider-runnable-track{background:#00d1b2!important}input[type=range].slider.is-primary::-ms-track{background:#00d1b2!important}input[type=range].slider.is-primary::-ms-fill-lower{background:#00d1b2}input[type=range].slider.is-primary::-ms-fill-upper{background:#00d1b2}input[type=range].slider.is-primary .has-output-tooltip+output,input[type=range].slider.is-primary.has-output+output{background-color:#00d1b2;color:#fff}input[type=range].slider.is-link::-moz-range-track{background:#3273dc!important}input[type=range].slider.is-link::-webkit-slider-runnable-track{background:#3273dc!important}input[type=range].slider.is-link::-ms-track{background:#3273dc!important}input[type=range].slider.is-link::-ms-fill-lower{background:#3273dc}input[type=range].slider.is-link::-ms-fill-upper{background:#3273dc}input[type=range].slider.is-link .has-output-tooltip+output,input[type=range].slider.is-link.has-output+output{background-color:#3273dc;color:#fff}input[type=range].slider.is-info::-moz-range-track{background:#209cee!important}input[type=range].slider.is-info::-webkit-slider-runnable-track{background:#209cee!important}input[type=range].slider.is-info::-ms-track{background:#209cee!important}input[type=range].slider.is-info::-ms-fill-lower{background:#209cee}input[type=range].slider.is-info::-ms-fill-upper{background:#209cee}input[type=range].slider.is-info .has-output-tooltip+output,input[type=range].slider.is-info.has-output+output{background-color:#209cee;color:#fff}input[type=range].slider.is-success::-moz-range-track{background:#23d160!important}input[type=range].slider.is-success::-webkit-slider-runnable-track{background:#23d160!important}input[type=range].slider.is-success::-ms-track{background:#23d160!important}input[type=range].slider.is-success::-ms-fill-lower{background:#23d160}input[type=range].slider.is-success::-ms-fill-upper{background:#23d160}input[type=range].slider.is-success .has-output-tooltip+output,input[type=range].slider.is-success.has-output+output{background-color:#23d160;color:#fff}input[type=range].slider.is-warning::-moz-range-track{background:#ffdd57!important}input[type=range].slider.is-warning::-webkit-slider-runnable-track{background:#ffdd57!important}input[type=range].slider.is-warning::-ms-track{background:#ffdd57!important}input[type=range].slider.is-warning::-ms-fill-lower{background:#ffdd57}input[type=range].slider.is-warning::-ms-fill-upper{background:#ffdd57}input[type=range].slider.is-warning .has-output-tooltip+output,input[type=range].slider.is-warning.has-output+output{background-color:#ffdd57;color:rgba(0,0,0,.7)}input[type=range].slider.is-danger::-moz-range-track{background:#ff3860!important}input[type=range].slider.is-danger::-webkit-slider-runnable-track{background:#ff3860!important}input[type=range].slider.is-danger::-ms-track{background:#ff3860!important}input[type=range].slider.is-danger::-ms-fill-lower{background:#ff3860}input[type=range].slider.is-danger::-ms-fill-upper{background:#ff3860}input[type=range].slider.is-danger .has-output-tooltip+output,input[type=range].slider.is-danger.has-output+output{background-color:#ff3860;color:#fff} \ No newline at end of file diff --git a/src/css/bulma.min.css b/src/css/bulma.min.css deleted file mode 100644 index a807a31..0000000 --- a/src/css/bulma.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! bulma.io v0.9.1 | MIT License | github.com/jgthms/bulma */@-webkit-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}.breadcrumb,.button,.delete,.file,.is-unselectable,.modal-close,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.tabs{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:.625em;margin-top:-.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.block:not(:last-child),.box:not(:last-child),.breadcrumb:not(:last-child),.content:not(:last-child),.highlight:not(:last-child),.level:not(:last-child),.message:not(:last-child),.notification:not(:last-child),.pagination:not(:last-child),.progress:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.tabs:not(:last-child),.title:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:none;border-radius:290486px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px}.delete::after,.delete::before,.modal-close::after,.modal-close::before{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:focus,.delete:hover,.modal-close:focus,.modal-close:hover{background-color:rgba(10,10,10,.3)}.delete:active,.modal-close:active{background-color:rgba(10,10,10,.4)}.is-small.delete,.is-small.modal-close{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.delete,.is-medium.modal-close{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.delete,.is-large.modal-close{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.button.is-loading::after,.control.is-loading::after,.loader,.select.is-loading::after{-webkit-animation:spinAround .5s infinite linear;animation:spinAround .5s infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img,.is-overlay,.modal,.modal-background{bottom:0;left:0;position:absolute;right:0;top:0}.button,.file-cta,.file-name,.input,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.select select,.textarea{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.5em - 1px);padding-left:calc(.75em - 1px);padding-right:calc(.75em - 1px);padding-top:calc(.5em - 1px);position:relative;vertical-align:top}.button:active,.button:focus,.file-cta:active,.file-cta:focus,.file-name:active,.file-name:focus,.input:active,.input:focus,.is-active.button,.is-active.file-cta,.is-active.file-name,.is-active.input,.is-active.pagination-ellipsis,.is-active.pagination-link,.is-active.pagination-next,.is-active.pagination-previous,.is-active.textarea,.is-focused.button,.is-focused.file-cta,.is-focused.file-name,.is-focused.input,.is-focused.pagination-ellipsis,.is-focused.pagination-link,.is-focused.pagination-next,.is-focused.pagination-previous,.is-focused.textarea,.pagination-ellipsis:active,.pagination-ellipsis:focus,.pagination-link:active,.pagination-link:focus,.pagination-next:active,.pagination-next:focus,.pagination-previous:active,.pagination-previous:focus,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{outline:0}.button[disabled],.file-cta[disabled],.file-name[disabled],.input[disabled],.pagination-ellipsis[disabled],.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .button,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .input,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-previous,fieldset[disabled] .select select,fieldset[disabled] .textarea{cursor:not-allowed}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */blockquote,body,dd,dl,dt,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,html,iframe,legend,li,ol,p,pre,textarea,ul{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,::after,::before{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}a{color:#3273dc;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#da1039;font-size:.875em;font-weight:400;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#363636}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;display:block;padding:1.25rem}a.box:focus,a.box:hover{box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px #3273dc}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #3273dc}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-large,.button .icon.is-medium,.button .icon.is-small{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-.5em - 1px);margin-right:calc(-.5em - 1px)}.button.is-hovered,.button:hover{border-color:#b5b5b5;color:#363636}.button.is-focused,.button:focus{border-color:#3273dc;color:#363636}.button.is-focused:not(:active),.button:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-active,.button:active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-text.is-focused,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text:hover{background-color:#f5f5f5;color:#363636}.button.is-text.is-active,.button.is-text:active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white.is-hovered,.button.is-white:hover{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white.is-focused,.button.is-white:focus{border-color:transparent;color:#0a0a0a}.button.is-white.is-focused:not(:active),.button.is-white:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white.is-active,.button.is-white:active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:transparent;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-hovered,.button.is-white.is-inverted:hover{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined.is-focused,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-outlined.is-loading.is-focused::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined.is-focused,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined:hover{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black.is-hovered,.button.is-black:hover{background-color:#040404;border-color:transparent;color:#fff}.button.is-black.is-focused,.button.is-black:focus{border-color:transparent;color:#fff}.button.is-black.is-focused:not(:active),.button.is-black:focus:not(:active){box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-black.is-active,.button.is-black:active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:transparent;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-hovered,.button.is-black.is-inverted:hover{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined.is-focused,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-outlined.is-loading.is-focused::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined.is-focused,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined:hover{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-hovered,.button.is-light:hover{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused,.button.is-light:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused:not(:active),.button.is-light:focus:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light.is-active,.button.is-light:active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:transparent;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-hovered,.button.is-light.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined.is-focused,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-outlined.is-loading.is-focused::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined.is-focused,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark.is-hovered,.button.is-dark:hover{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark.is-focused,.button.is-dark:focus{border-color:transparent;color:#fff}.button.is-dark.is-focused:not(:active),.button.is-dark:focus:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark.is-active,.button.is-dark:active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:transparent;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-hovered,.button.is-dark.is-inverted:hover{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined.is-focused,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined:hover{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-outlined.is-loading.is-focused::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined.is-focused,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined:hover{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#00d1b2;border-color:transparent;color:#fff}.button.is-primary.is-hovered,.button.is-primary:hover{background-color:#00c4a7;border-color:transparent;color:#fff}.button.is-primary.is-focused,.button.is-primary:focus{border-color:transparent;color:#fff}.button.is-primary.is-focused:not(:active),.button.is-primary:focus:not(:active){box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.button.is-primary.is-active,.button.is-primary:active{background-color:#00b89c;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#00d1b2;border-color:transparent;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-hovered,.button.is-primary.is-inverted:hover{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#00d1b2}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;color:#00d1b2}.button.is-primary.is-outlined.is-focused,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined:hover{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-outlined.is-loading.is-focused::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;box-shadow:none;color:#00d1b2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined.is-focused,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined:hover{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#ebfffc;color:#00947e}.button.is-primary.is-light.is-hovered,.button.is-primary.is-light:hover{background-color:#defffa;border-color:transparent;color:#00947e}.button.is-primary.is-light.is-active,.button.is-primary.is-light:active{background-color:#d1fff8;border-color:transparent;color:#00947e}.button.is-link{background-color:#3273dc;border-color:transparent;color:#fff}.button.is-link.is-hovered,.button.is-link:hover{background-color:#276cda;border-color:transparent;color:#fff}.button.is-link.is-focused,.button.is-link:focus{border-color:transparent;color:#fff}.button.is-link.is-focused:not(:active),.button.is-link:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-link.is-active,.button.is-link:active{background-color:#2366d1;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#3273dc;border-color:transparent;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#3273dc}.button.is-link.is-inverted.is-hovered,.button.is-link.is-inverted:hover{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3273dc}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;color:#3273dc}.button.is-link.is-outlined.is-focused,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined:hover{background-color:#3273dc;border-color:#3273dc;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #3273dc #3273dc!important}.button.is-link.is-outlined.is-loading.is-focused::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;box-shadow:none;color:#3273dc}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined.is-focused,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined:hover{background-color:#fff;color:#3273dc}.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3273dc #3273dc!important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eef3fc;color:#2160c4}.button.is-link.is-light.is-hovered,.button.is-link.is-light:hover{background-color:#e3ecfa;border-color:transparent;color:#2160c4}.button.is-link.is-light.is-active,.button.is-link.is-light:active{background-color:#d8e4f8;border-color:transparent;color:#2160c4}.button.is-info{background-color:#3298dc;border-color:transparent;color:#fff}.button.is-info.is-hovered,.button.is-info:hover{background-color:#2793da;border-color:transparent;color:#fff}.button.is-info.is-focused,.button.is-info:focus{border-color:transparent;color:#fff}.button.is-info.is-focused:not(:active),.button.is-info:focus:not(:active){box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.button.is-info.is-active,.button.is-info:active{background-color:#238cd1;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3298dc;border-color:transparent;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3298dc}.button.is-info.is-inverted.is-hovered,.button.is-info.is-inverted:hover{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3298dc}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined{background-color:transparent;border-color:#3298dc;color:#3298dc}.button.is-info.is-outlined.is-focused,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined:hover{background-color:#3298dc;border-color:#3298dc;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3298dc #3298dc!important}.button.is-info.is-outlined.is-loading.is-focused::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3298dc;box-shadow:none;color:#3298dc}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined.is-focused,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined:hover{background-color:#fff;color:#3298dc}.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3298dc #3298dc!important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eef6fc;color:#1d72aa}.button.is-info.is-light.is-hovered,.button.is-info.is-light:hover{background-color:#e3f1fa;border-color:transparent;color:#1d72aa}.button.is-info.is-light.is-active,.button.is-info.is-light:active{background-color:#d8ebf8;border-color:transparent;color:#1d72aa}.button.is-success{background-color:#48c774;border-color:transparent;color:#fff}.button.is-success.is-hovered,.button.is-success:hover{background-color:#3ec46d;border-color:transparent;color:#fff}.button.is-success.is-focused,.button.is-success:focus{border-color:transparent;color:#fff}.button.is-success.is-focused:not(:active),.button.is-success:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.button.is-success.is-active,.button.is-success:active{background-color:#3abb67;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#48c774;border-color:transparent;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#48c774}.button.is-success.is-inverted.is-hovered,.button.is-success.is-inverted:hover{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#48c774}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined{background-color:transparent;border-color:#48c774;color:#48c774}.button.is-success.is-outlined.is-focused,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined:hover{background-color:#48c774;border-color:#48c774;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #48c774 #48c774!important}.button.is-success.is-outlined.is-loading.is-focused::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#48c774;box-shadow:none;color:#48c774}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined.is-focused,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined:hover{background-color:#fff;color:#48c774}.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #48c774 #48c774!important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effaf3;color:#257942}.button.is-success.is-light.is-hovered,.button.is-success.is-light:hover{background-color:#e6f7ec;border-color:transparent;color:#257942}.button.is-success.is-light.is-active,.button.is-success.is-light:active{background-color:#dcf4e4;border-color:transparent;color:#257942}.button.is-warning{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-hovered,.button.is-warning:hover{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused,.button.is-warning:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused:not(:active),.button.is-warning:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.button.is-warning.is-active,.button.is-warning:active{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffdd57;border-color:transparent;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted.is-hovered,.button.is-warning.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffdd57}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;color:#ffdd57}.button.is-warning.is-outlined.is-focused,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined:hover{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffdd57 #ffdd57!important}.button.is-warning.is-outlined.is-loading.is-focused::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;box-shadow:none;color:#ffdd57}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined.is-focused,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #ffdd57 #ffdd57!important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffbeb;color:#947600}.button.is-warning.is-light.is-hovered,.button.is-warning.is-light:hover{background-color:#fff8de;border-color:transparent;color:#947600}.button.is-warning.is-light.is-active,.button.is-warning.is-light:active{background-color:#fff6d1;border-color:transparent;color:#947600}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger.is-hovered,.button.is-danger:hover{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger.is-focused,.button.is-danger:focus{border-color:transparent;color:#fff}.button.is-danger.is-focused:not(:active),.button.is-danger:focus:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger.is-active,.button.is-danger:active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:transparent;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-hovered,.button.is-danger.is-inverted:hover{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined.is-focused,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined:hover{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-outlined.is-loading.is-focused::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined.is-focused,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined:hover{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light.is-hovered,.button.is-danger.is-light:hover{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light.is-active,.button.is-danger.is-light:active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-small{border-radius:2px;font-size:.75rem}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent!important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em / 2));top:calc(50% - (1em / 2));position:absolute!important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:290486px;padding-left:calc(1em + .25em);padding-right:calc(1em + .25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){border-radius:2px;font-size:.75rem}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button.is-hovered,.buttons.has-addons .button:hover{z-index:2}.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-focused,.buttons.has-addons .button.is-selected,.buttons.has-addons .button:active,.buttons.has-addons .button:focus{z-index:3}.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button.is-selected:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button:focus:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none!important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width:1024px){.container{max-width:960px}}@media screen and (max-width:1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width:1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width:1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width:1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:.25em}.content blockquote:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content p:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child),.content ul:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sub,.content sup{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:290486px}.image.is-fullwidth{width:100%}.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img{height:100%;width:100%}.image.is-1by1,.image.is-square{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:0 0}.notification>.delete{right:.5rem;position:absolute;top:.5rem}.notification .content,.notification .subtitle,.notification .title{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#00d1b2;color:#fff}.notification.is-primary.is-light{background-color:#ebfffc;color:#00947e}.notification.is-link{background-color:#3273dc;color:#fff}.notification.is-link.is-light{background-color:#eef3fc;color:#2160c4}.notification.is-info{background-color:#3298dc;color:#fff}.notification.is-info.is-light{background-color:#eef6fc;color:#1d72aa}.notification.is-success{background-color:#48c774;color:#fff}.notification.is-success.is-light{background-color:#effaf3;color:#257942}.notification.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.notification.is-warning.is-light{background-color:#fffbeb;color:#947600}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:290486px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress::-ms-fill{background-color:#4a4a4a;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right,#fff 30%,#ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right,#0a0a0a 30%,#ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right,#f5f5f5 30%,#ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right,#363636 30%,#ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#00d1b2}.progress.is-primary::-moz-progress-bar{background-color:#00d1b2}.progress.is-primary::-ms-fill{background-color:#00d1b2}.progress.is-primary:indeterminate{background-image:linear-gradient(to right,#00d1b2 30%,#ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#3273dc}.progress.is-link::-moz-progress-bar{background-color:#3273dc}.progress.is-link::-ms-fill{background-color:#3273dc}.progress.is-link:indeterminate{background-image:linear-gradient(to right,#3273dc 30%,#ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3298dc}.progress.is-info::-moz-progress-bar{background-color:#3298dc}.progress.is-info::-ms-fill{background-color:#3298dc}.progress.is-info:indeterminate{background-image:linear-gradient(to right,#3298dc 30%,#ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#48c774}.progress.is-success::-moz-progress-bar{background-color:#48c774}.progress.is-success::-ms-fill{background-color:#48c774}.progress.is-success:indeterminate{background-image:linear-gradient(to right,#48c774 30%,#ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffdd57}.progress.is-warning::-moz-progress-bar{background-color:#ffdd57}.progress.is-warning::-ms-fill{background-color:#ffdd57}.progress.is-warning:indeterminate{background-image:linear-gradient(to right,#ffdd57 30%,#ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right,#f14668 30%,#ededed 30%)}.progress:indeterminate{-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:moveIndeterminate;animation-name:moveIndeterminate;-webkit-animation-timing-function:linear;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right,#4a4a4a 30%,#ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@-webkit-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.table td.is-link,.table th.is-link{background-color:#3273dc;border-color:#3273dc;color:#fff}.table td.is-info,.table th.is-info{background-color:#3298dc;border-color:#3298dc;color:#fff}.table td.is-success,.table th.is-success{background-color:#48c774;border-color:#48c774;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#00d1b2;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#363636}.table th:not([align]){text-align:inherit}.table tr.is-selected{background-color:#00d1b2;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#00d1b2;color:#fff}.tag:not(body).is-primary.is-light{background-color:#ebfffc;color:#00947e}.tag:not(body).is-link{background-color:#3273dc;color:#fff}.tag:not(body).is-link.is-light{background-color:#eef3fc;color:#2160c4}.tag:not(body).is-info{background-color:#3298dc;color:#fff}.tag:not(body).is-info.is-light{background-color:#eef6fc;color:#1d72aa}.tag:not(body).is-success{background-color:#48c774;color:#fff}.tag:not(body).is-success.is-light{background-color:#effaf3;color:#257942}.tag:not(body).is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffbeb;color:#947600}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::after,.tag:not(body).is-delete::before{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:focus,.tag:not(body).is-delete:hover{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:290486px}a.tag:hover{text-decoration:underline}.subtitle,.title{word-break:break-word}.subtitle em,.subtitle span,.title em,.title span{font-weight:inherit}.subtitle sub,.title sub{font-size:.75em}.subtitle sup,.title sup{font-size:.75em}.subtitle .tag,.title .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title+.highlight{margin-top:-.75rem}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.highlight{font-weight:400;max-width:100%;overflow:hidden;padding:0}.highlight pre{overflow:auto;max-width:100%}.number{align-items:center;background-color:#f5f5f5;border-radius:290486px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.input,.select select,.textarea{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder,.select select::-moz-placeholder,.textarea::-moz-placeholder{color:rgba(54,54,54,.3)}.input::-webkit-input-placeholder,.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.input:-moz-placeholder,.select select:-moz-placeholder,.textarea:-moz-placeholder{color:rgba(54,54,54,.3)}.input:-ms-input-placeholder,.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:rgba(54,54,54,.3)}.input:hover,.is-hovered.input,.is-hovered.textarea,.select select.is-hovered,.select select:hover,.textarea:hover{border-color:#b5b5b5}.input:active,.input:focus,.is-active.input,.is-active.textarea,.is-focused.input,.is-focused.textarea,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{border-color:#3273dc;box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder{color:rgba(122,122,122,.3)}.input,.textarea{box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}.input[readonly],.textarea[readonly]{box-shadow:none}.is-white.input,.is-white.textarea{border-color:#fff}.is-white.input:active,.is-white.input:focus,.is-white.is-active.input,.is-white.is-active.textarea,.is-white.is-focused.input,.is-white.is-focused.textarea,.is-white.textarea:active,.is-white.textarea:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.is-black.input,.is-black.textarea{border-color:#0a0a0a}.is-black.input:active,.is-black.input:focus,.is-black.is-active.input,.is-black.is-active.textarea,.is-black.is-focused.input,.is-black.is-focused.textarea,.is-black.textarea:active,.is-black.textarea:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.is-light.input,.is-light.textarea{border-color:#f5f5f5}.is-light.input:active,.is-light.input:focus,.is-light.is-active.input,.is-light.is-active.textarea,.is-light.is-focused.input,.is-light.is-focused.textarea,.is-light.textarea:active,.is-light.textarea:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.is-dark.input,.is-dark.textarea{border-color:#363636}.is-dark.input:active,.is-dark.input:focus,.is-dark.is-active.input,.is-dark.is-active.textarea,.is-dark.is-focused.input,.is-dark.is-focused.textarea,.is-dark.textarea:active,.is-dark.textarea:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.is-primary.input,.is-primary.textarea{border-color:#00d1b2}.is-primary.input:active,.is-primary.input:focus,.is-primary.is-active.input,.is-primary.is-active.textarea,.is-primary.is-focused.input,.is-primary.is-focused.textarea,.is-primary.textarea:active,.is-primary.textarea:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.is-link.input,.is-link.textarea{border-color:#3273dc}.is-link.input:active,.is-link.input:focus,.is-link.is-active.input,.is-link.is-active.textarea,.is-link.is-focused.input,.is-link.is-focused.textarea,.is-link.textarea:active,.is-link.textarea:focus{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.is-info.input,.is-info.textarea{border-color:#3298dc}.is-info.input:active,.is-info.input:focus,.is-info.is-active.input,.is-info.is-active.textarea,.is-info.is-focused.input,.is-info.is-focused.textarea,.is-info.textarea:active,.is-info.textarea:focus{box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.is-success.input,.is-success.textarea{border-color:#48c774}.is-success.input:active,.is-success.input:focus,.is-success.is-active.input,.is-success.is-active.textarea,.is-success.is-focused.input,.is-success.is-focused.textarea,.is-success.textarea:active,.is-success.textarea:focus{box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.is-warning.input,.is-warning.textarea{border-color:#ffdd57}.is-warning.input:active,.is-warning.input:focus,.is-warning.is-active.input,.is-warning.is-active.textarea,.is-warning.is-focused.input,.is-warning.is-focused.textarea,.is-warning.textarea:active,.is-warning.textarea:focus{box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.is-danger.input,.is-danger.textarea{border-color:#f14668}.is-danger.input:active,.is-danger.input:focus,.is-danger.is-active.input,.is-danger.is-active.textarea,.is-danger.is-focused.input,.is-danger.is-focused.textarea,.is-danger.textarea:active,.is-danger.textarea:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.is-small.input,.is-small.textarea{border-radius:2px;font-size:.75rem}.is-medium.input,.is-medium.textarea{font-size:1.25rem}.is-large.input,.is-large.textarea{font-size:1.5rem}.is-fullwidth.input,.is-fullwidth.textarea{display:block;width:100%}.is-inline.input,.is-inline.textarea{display:inline;width:auto}.input.is-rounded{border-radius:290486px;padding-left:calc(calc(.75em - 1px) + .375em);padding-right:calc(calc(.75em - 1px) + .375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox input[disabled],.checkbox[disabled],.radio input[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .radio{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#3273dc;right:1.125em;z-index:4}.select.is-rounded select{border-radius:290486px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:0}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select.is-hovered,.select.is-white select:hover{border-color:#f2f2f2}.select.is-white select.is-active,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select.is-hovered,.select.is-black select:hover{border-color:#000}.select.is-black select.is-active,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select.is-hovered,.select.is-light select:hover{border-color:#e8e8e8}.select.is-light select.is-active,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select.is-hovered,.select.is-dark select:hover{border-color:#292929}.select.is-dark select.is-active,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-primary select{border-color:#00d1b2}.select.is-primary select.is-hovered,.select.is-primary select:hover{border-color:#00b89c}.select.is-primary select.is-active,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.select.is-link:not(:hover)::after{border-color:#3273dc}.select.is-link select{border-color:#3273dc}.select.is-link select.is-hovered,.select.is-link select:hover{border-color:#2366d1}.select.is-link select.is-active,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select:focus{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.select.is-info:not(:hover)::after{border-color:#3298dc}.select.is-info select{border-color:#3298dc}.select.is-info select.is-hovered,.select.is-info select:hover{border-color:#238cd1}.select.is-info select.is-active,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select:focus{box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.select.is-success:not(:hover)::after{border-color:#48c774}.select.is-success select{border-color:#48c774}.select.is-success select.is-hovered,.select.is-success select:hover{border-color:#3abb67}.select.is-success select.is-active,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select:focus{box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.select.is-warning:not(:hover)::after{border-color:#ffdd57}.select.is-warning select{border-color:#ffdd57}.select.is-warning select.is-hovered,.select.is-warning select:hover{border-color:#ffd83d}.select.is-warning select.is-active,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select:focus{box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select.is-hovered,.select.is-danger select:hover{border-color:#ef2e55}.select.is-danger select.is-active,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white.is-hovered .file-cta,.file.is-white:hover .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white.is-focused .file-cta,.file.is-white:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white.is-active .file-cta,.file.is-white:active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black.is-hovered .file-cta,.file.is-black:hover .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black.is-focused .file-cta,.file.is-black:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black.is-active .file-cta,.file.is-black:active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-hovered .file-cta,.file.is-light:hover .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-focused .file-cta,.file.is-light:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light.is-active .file-cta,.file.is-light:active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark.is-hovered .file-cta,.file.is-dark:hover .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark.is-focused .file-cta,.file.is-dark:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark.is-active .file-cta,.file.is-dark:active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#00d1b2;border-color:transparent;color:#fff}.file.is-primary.is-hovered .file-cta,.file.is-primary:hover .file-cta{background-color:#00c4a7;border-color:transparent;color:#fff}.file.is-primary.is-focused .file-cta,.file.is-primary:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(0,209,178,.25);color:#fff}.file.is-primary.is-active .file-cta,.file.is-primary:active .file-cta{background-color:#00b89c;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#3273dc;border-color:transparent;color:#fff}.file.is-link.is-hovered .file-cta,.file.is-link:hover .file-cta{background-color:#276cda;border-color:transparent;color:#fff}.file.is-link.is-focused .file-cta,.file.is-link:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,115,220,.25);color:#fff}.file.is-link.is-active .file-cta,.file.is-link:active .file-cta{background-color:#2366d1;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3298dc;border-color:transparent;color:#fff}.file.is-info.is-hovered .file-cta,.file.is-info:hover .file-cta{background-color:#2793da;border-color:transparent;color:#fff}.file.is-info.is-focused .file-cta,.file.is-info:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,152,220,.25);color:#fff}.file.is-info.is-active .file-cta,.file.is-info:active .file-cta{background-color:#238cd1;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#48c774;border-color:transparent;color:#fff}.file.is-success.is-hovered .file-cta,.file.is-success:hover .file-cta{background-color:#3ec46d;border-color:transparent;color:#fff}.file.is-success.is-focused .file-cta,.file.is-success:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,199,116,.25);color:#fff}.file.is-success.is-active .file-cta,.file.is-success:active .file-cta{background-color:#3abb67;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-hovered .file-cta,.file.is-warning:hover .file-cta{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-focused .file-cta,.file.is-warning:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,221,87,.25);color:rgba(0,0,0,.7)}.file.is-warning.is-active .file-cta,.file.is-warning:active .file-cta{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger.is-hovered .file-cta,.file.is-danger:hover .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger.is-focused .file-cta,.file.is-danger:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger.is-active .file-cta,.file.is-danger:active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:0;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#00d1b2}.help.is-link{color:#3273dc}.help.is-info{color:#3298dc}.help.is-success{color:#48c774}.help.is-warning{color:#ffdd57}.help.is-danger{color:#f14668}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover{z-index:2}.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]):focus{z-index:3}.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width:769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width:768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width:769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width:769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#4a4a4a}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute!important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#3273dc;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ol,.breadcrumb ul{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;max-width:100%;overflow:hidden;position:relative}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#3273dc;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width:769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .subtitle,.level-item .title{margin-bottom:0}@media screen and (max-width:768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width:769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width:768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width:769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width:769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width:768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#3273dc;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#ebfffc}.message.is-primary .message-header{background-color:#00d1b2;color:#fff}.message.is-primary .message-body{border-color:#00d1b2;color:#00947e}.message.is-link{background-color:#eef3fc}.message.is-link .message-header{background-color:#3273dc;color:#fff}.message.is-link .message-body{border-color:#3273dc;color:#2160c4}.message.is-info{background-color:#eef6fc}.message.is-info .message-header{background-color:#3298dc;color:#fff}.message.is-info .message-body{border-color:#3298dc;color:#1d72aa}.message.is-success{background-color:#effaf3}.message.is-success .message-header{background-color:#48c774;color:#fff}.message.is-success .message-body{border-color:#48c774;color:#257942}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#ffdd57;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffdd57;color:#947600}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message-header{align-items:center;background-color:#4a4a4a;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#4a4a4a;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,.86)}.modal-card,.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width:769px){.modal-card,.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:0 0;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-foot,.modal-card-head{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link,.navbar.is-white .navbar-brand>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width:1024px){.navbar.is-white .navbar-end .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-start>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link::after,.navbar.is-white .navbar-start .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand .navbar-link,.navbar.is-black .navbar-brand>.navbar-item{color:#fff}.navbar.is-black .navbar-brand .navbar-link.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-black .navbar-end .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-start>.navbar-item{color:#fff}.navbar.is-black .navbar-end .navbar-link.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-end .navbar-link::after,.navbar.is-black .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link,.navbar.is-light .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-light .navbar-end .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link::after,.navbar.is-light .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand .navbar-link,.navbar.is-dark .navbar-brand>.navbar-item{color:#fff}.navbar.is-dark .navbar-brand .navbar-link.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-dark .navbar-end .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-start>.navbar-item{color:#fff}.navbar.is-dark .navbar-end .navbar-link.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-end .navbar-link::after,.navbar.is-dark .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#00d1b2;color:#fff}.navbar.is-primary .navbar-brand .navbar-link,.navbar.is-primary .navbar-brand>.navbar-item{color:#fff}.navbar.is-primary .navbar-brand .navbar-link.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-primary .navbar-end .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-start>.navbar-item{color:#fff}.navbar.is-primary .navbar-end .navbar-link.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-end .navbar-link::after,.navbar.is-primary .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#00d1b2;color:#fff}}.navbar.is-link{background-color:#3273dc;color:#fff}.navbar.is-link .navbar-brand .navbar-link,.navbar.is-link .navbar-brand>.navbar-item{color:#fff}.navbar.is-link .navbar-brand .navbar-link.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-link .navbar-end .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-start>.navbar-item{color:#fff}.navbar.is-link .navbar-end .navbar-link.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-end .navbar-link::after,.navbar.is-link .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#3273dc;color:#fff}}.navbar.is-info{background-color:#3298dc;color:#fff}.navbar.is-info .navbar-brand .navbar-link,.navbar.is-info .navbar-brand>.navbar-item{color:#fff}.navbar.is-info .navbar-brand .navbar-link.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-info .navbar-end .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-start>.navbar-item{color:#fff}.navbar.is-info .navbar-end .navbar-link.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-end .navbar-link::after,.navbar.is-info .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3298dc;color:#fff}}.navbar.is-success{background-color:#48c774;color:#fff}.navbar.is-success .navbar-brand .navbar-link,.navbar.is-success .navbar-brand>.navbar-item{color:#fff}.navbar.is-success .navbar-brand .navbar-link.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-success .navbar-end .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-start>.navbar-item{color:#fff}.navbar.is-success .navbar-end .navbar-link.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-end .navbar-link::after,.navbar.is-success .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#48c774;color:#fff}}.navbar.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link,.navbar.is-warning .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-warning .navbar-end .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link::after,.navbar.is-warning .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffdd57;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand .navbar-link,.navbar.is-danger .navbar-brand>.navbar-item{color:#fff}.navbar.is-danger .navbar-brand .navbar-link.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-danger .navbar-end .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-start>.navbar-item{color:#fff}.navbar.is-danger .navbar-end .navbar-link.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-end .navbar-link::after,.navbar.is-danger .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}body.has-navbar-fixed-top,html.has-navbar-fixed-top{padding-top:3.25rem}body.has-navbar-fixed-bottom,html.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#4a4a4a;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-.25rem;margin-right:-.25rem}.navbar-link,a.navbar-item{cursor:pointer}.navbar-link.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,a.navbar-item.is-active,a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover{background-color:#fafafa;color:#3273dc}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#3273dc}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#3273dc;border-bottom-style:solid;border-bottom-width:3px;color:#3273dc;padding-bottom:calc(.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#3273dc;margin-top:-.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width:1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}body.has-navbar-fixed-top-touch,html.has-navbar-fixed-top-touch{padding-top:3.25rem}body.has-navbar-fixed-bottom-touch,html.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width:1024px){.navbar,.navbar-end,.navbar-menu,.navbar-start{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-end,.navbar.is-spaced .navbar-start{align-items:center}.navbar.is-spaced .navbar-link,.navbar.is-spaced a.navbar-item{border-radius:4px}.navbar.is-transparent .navbar-link.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover{background-color:transparent!important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent!important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(.25em,-.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-dropdown{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.container>.navbar .navbar-brand,.navbar>.container .navbar-brand{margin-left:-.75rem}.container>.navbar .navbar-menu,.navbar>.container .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-desktop{top:0}body.has-navbar-fixed-top-desktop,html.has-navbar-fixed-top-desktop{padding-top:3.25rem}body.has-navbar-fixed-bottom-desktop,html.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}body.has-spaced-navbar-fixed-top,html.has-spaced-navbar-fixed-top{padding-top:5.25rem}body.has-spaced-navbar-fixed-bottom,html.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}.navbar-link.is-active,a.navbar-item.is-active{color:#0a0a0a}.navbar-link.is-active:not(:focus):not(:hover),a.navbar-item.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown.is-active .navbar-link,.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-next,.pagination.is-rounded .pagination-previous{padding-left:1em;padding-right:1em;border-radius:290486px}.pagination.is-rounded .pagination-link{border-radius:290486px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-link,.pagination-next,.pagination-previous{border-color:#dbdbdb;color:#363636;min-width:2.5em}.pagination-link:hover,.pagination-next:hover,.pagination-previous:hover{border-color:#b5b5b5;color:#363636}.pagination-link:focus,.pagination-next:focus,.pagination-previous:focus{border-color:#3273dc}.pagination-link:active,.pagination-next:active,.pagination-previous:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-next,.pagination-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#3273dc;border-color:#3273dc;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}@media screen and (max-width:768px){.pagination{flex-wrap:wrap}.pagination-next,.pagination-previous{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width:769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#00d1b2;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#00d1b2}.panel.is-primary .panel-block.is-active .panel-icon{color:#00d1b2}.panel.is-link .panel-heading{background-color:#3273dc;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#3273dc}.panel.is-link .panel-block.is-active .panel-icon{color:#3273dc}.panel.is-info .panel-heading{background-color:#3298dc;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3298dc}.panel.is-info .panel-block.is-active .panel-icon{color:#3298dc}.panel.is-success .panel-heading{background-color:#48c774;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#48c774}.panel.is-success .panel-block.is-active .panel-icon{color:#48c774}.panel.is-warning .panel-heading{background-color:#ffdd57;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffdd57}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffdd57}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel-block:not(:last-child),.panel-tabs:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#3273dc}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#3273dc;color:#363636}.panel-block.is-active .panel-icon{color:#3273dc}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#3273dc;color:#3273dc}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent!important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#3273dc;border-color:#3273dc;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:290486px;border-top-left-radius:290486px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:290486px;border-top-right-radius:290486px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width:768px){.column.is-narrow-mobile{flex:none}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width:769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width:1023px){.column.is-narrow-touch{flex:none}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width:1024px){.column.is-narrow-desktop{flex:none}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width:1216px){.column.is-narrow-widescreen{flex:none}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width:1408px){.column.is-narrow-fullhd{flex:none}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0!important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width:769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width:1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap:0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable .column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap:0rem}@media screen and (max-width:768px){.columns.is-variable.is-0-mobile{--columnGap:0rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-0-tablet{--columnGap:0rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-0-tablet-only{--columnGap:0rem}}@media screen and (max-width:1023px){.columns.is-variable.is-0-touch{--columnGap:0rem}}@media screen and (min-width:1024px){.columns.is-variable.is-0-desktop{--columnGap:0rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-0-desktop-only{--columnGap:0rem}}@media screen and (min-width:1216px){.columns.is-variable.is-0-widescreen{--columnGap:0rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-0-widescreen-only{--columnGap:0rem}}@media screen and (min-width:1408px){.columns.is-variable.is-0-fullhd{--columnGap:0rem}}.columns.is-variable.is-1{--columnGap:0.25rem}@media screen and (max-width:768px){.columns.is-variable.is-1-mobile{--columnGap:0.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-1-tablet{--columnGap:0.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-1-tablet-only{--columnGap:0.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-1-touch{--columnGap:0.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-1-desktop{--columnGap:0.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-1-desktop-only{--columnGap:0.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-1-widescreen{--columnGap:0.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-1-widescreen-only{--columnGap:0.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-1-fullhd{--columnGap:0.25rem}}.columns.is-variable.is-2{--columnGap:0.5rem}@media screen and (max-width:768px){.columns.is-variable.is-2-mobile{--columnGap:0.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-2-tablet{--columnGap:0.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-2-tablet-only{--columnGap:0.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-2-touch{--columnGap:0.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-2-desktop{--columnGap:0.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-2-desktop-only{--columnGap:0.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-2-widescreen{--columnGap:0.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-2-widescreen-only{--columnGap:0.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-2-fullhd{--columnGap:0.5rem}}.columns.is-variable.is-3{--columnGap:0.75rem}@media screen and (max-width:768px){.columns.is-variable.is-3-mobile{--columnGap:0.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-3-tablet{--columnGap:0.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-3-tablet-only{--columnGap:0.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-3-touch{--columnGap:0.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-3-desktop{--columnGap:0.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-3-desktop-only{--columnGap:0.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-3-widescreen{--columnGap:0.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-3-widescreen-only{--columnGap:0.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-3-fullhd{--columnGap:0.75rem}}.columns.is-variable.is-4{--columnGap:1rem}@media screen and (max-width:768px){.columns.is-variable.is-4-mobile{--columnGap:1rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-4-tablet{--columnGap:1rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-4-tablet-only{--columnGap:1rem}}@media screen and (max-width:1023px){.columns.is-variable.is-4-touch{--columnGap:1rem}}@media screen and (min-width:1024px){.columns.is-variable.is-4-desktop{--columnGap:1rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-4-desktop-only{--columnGap:1rem}}@media screen and (min-width:1216px){.columns.is-variable.is-4-widescreen{--columnGap:1rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-4-widescreen-only{--columnGap:1rem}}@media screen and (min-width:1408px){.columns.is-variable.is-4-fullhd{--columnGap:1rem}}.columns.is-variable.is-5{--columnGap:1.25rem}@media screen and (max-width:768px){.columns.is-variable.is-5-mobile{--columnGap:1.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-5-tablet{--columnGap:1.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-5-tablet-only{--columnGap:1.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-5-touch{--columnGap:1.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-5-desktop{--columnGap:1.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-5-desktop-only{--columnGap:1.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-5-widescreen{--columnGap:1.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-5-widescreen-only{--columnGap:1.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-5-fullhd{--columnGap:1.25rem}}.columns.is-variable.is-6{--columnGap:1.5rem}@media screen and (max-width:768px){.columns.is-variable.is-6-mobile{--columnGap:1.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-6-tablet{--columnGap:1.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-6-tablet-only{--columnGap:1.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-6-touch{--columnGap:1.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-6-desktop{--columnGap:1.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-6-desktop-only{--columnGap:1.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-6-widescreen{--columnGap:1.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-6-widescreen-only{--columnGap:1.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-6-fullhd{--columnGap:1.5rem}}.columns.is-variable.is-7{--columnGap:1.75rem}@media screen and (max-width:768px){.columns.is-variable.is-7-mobile{--columnGap:1.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-7-tablet{--columnGap:1.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-7-tablet-only{--columnGap:1.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-7-touch{--columnGap:1.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-7-desktop{--columnGap:1.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-7-desktop-only{--columnGap:1.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-7-widescreen{--columnGap:1.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-7-widescreen-only{--columnGap:1.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-7-fullhd{--columnGap:1.75rem}}.columns.is-variable.is-8{--columnGap:2rem}@media screen and (max-width:768px){.columns.is-variable.is-8-mobile{--columnGap:2rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-8-tablet{--columnGap:2rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-8-tablet-only{--columnGap:2rem}}@media screen and (max-width:1023px){.columns.is-variable.is-8-touch{--columnGap:2rem}}@media screen and (min-width:1024px){.columns.is-variable.is-8-desktop{--columnGap:2rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-8-desktop-only{--columnGap:2rem}}@media screen and (min-width:1216px){.columns.is-variable.is-8-widescreen{--columnGap:2rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-8-widescreen-only{--columnGap:2rem}}@media screen and (min-width:1408px){.columns.is-variable.is-8-fullhd{--columnGap:2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:-webkit-min-content;min-height:-moz-min-content;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0!important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem!important}@media screen and (min-width:769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.has-text-white{color:#fff!important}a.has-text-white:focus,a.has-text-white:hover{color:#e6e6e6!important}.has-background-white{background-color:#fff!important}.has-text-black{color:#0a0a0a!important}a.has-text-black:focus,a.has-text-black:hover{color:#000!important}.has-background-black{background-color:#0a0a0a!important}.has-text-light{color:#f5f5f5!important}a.has-text-light:focus,a.has-text-light:hover{color:#dbdbdb!important}.has-background-light{background-color:#f5f5f5!important}.has-text-dark{color:#363636!important}a.has-text-dark:focus,a.has-text-dark:hover{color:#1c1c1c!important}.has-background-dark{background-color:#363636!important}.has-text-primary{color:#00d1b2!important}a.has-text-primary:focus,a.has-text-primary:hover{color:#009e86!important}.has-background-primary{background-color:#00d1b2!important}.has-text-primary-light{color:#ebfffc!important}a.has-text-primary-light:focus,a.has-text-primary-light:hover{color:#b8fff4!important}.has-background-primary-light{background-color:#ebfffc!important}.has-text-primary-dark{color:#00947e!important}a.has-text-primary-dark:focus,a.has-text-primary-dark:hover{color:#00c7a9!important}.has-background-primary-dark{background-color:#00947e!important}.has-text-link{color:#3273dc!important}a.has-text-link:focus,a.has-text-link:hover{color:#205bbc!important}.has-background-link{background-color:#3273dc!important}.has-text-link-light{color:#eef3fc!important}a.has-text-link-light:focus,a.has-text-link-light:hover{color:#c2d5f5!important}.has-background-link-light{background-color:#eef3fc!important}.has-text-link-dark{color:#2160c4!important}a.has-text-link-dark:focus,a.has-text-link-dark:hover{color:#3b79de!important}.has-background-link-dark{background-color:#2160c4!important}.has-text-info{color:#3298dc!important}a.has-text-info:focus,a.has-text-info:hover{color:#207dbc!important}.has-background-info{background-color:#3298dc!important}.has-text-info-light{color:#eef6fc!important}a.has-text-info-light:focus,a.has-text-info-light:hover{color:#c2e0f5!important}.has-background-info-light{background-color:#eef6fc!important}.has-text-info-dark{color:#1d72aa!important}a.has-text-info-dark:focus,a.has-text-info-dark:hover{color:#248fd6!important}.has-background-info-dark{background-color:#1d72aa!important}.has-text-success{color:#48c774!important}a.has-text-success:focus,a.has-text-success:hover{color:#34a85c!important}.has-background-success{background-color:#48c774!important}.has-text-success-light{color:#effaf3!important}a.has-text-success-light:focus,a.has-text-success-light:hover{color:#c8eed6!important}.has-background-success-light{background-color:#effaf3!important}.has-text-success-dark{color:#257942!important}a.has-text-success-dark:focus,a.has-text-success-dark:hover{color:#31a058!important}.has-background-success-dark{background-color:#257942!important}.has-text-warning{color:#ffdd57!important}a.has-text-warning:focus,a.has-text-warning:hover{color:#ffd324!important}.has-background-warning{background-color:#ffdd57!important}.has-text-warning-light{color:#fffbeb!important}a.has-text-warning-light:focus,a.has-text-warning-light:hover{color:#fff1b8!important}.has-background-warning-light{background-color:#fffbeb!important}.has-text-warning-dark{color:#947600!important}a.has-text-warning-dark:focus,a.has-text-warning-dark:hover{color:#c79f00!important}.has-background-warning-dark{background-color:#947600!important}.has-text-danger{color:#f14668!important}a.has-text-danger:focus,a.has-text-danger:hover{color:#ee1742!important}.has-background-danger{background-color:#f14668!important}.has-text-danger-light{color:#feecf0!important}a.has-text-danger-light:focus,a.has-text-danger-light:hover{color:#fabdc9!important}.has-background-danger-light{background-color:#feecf0!important}.has-text-danger-dark{color:#cc0f35!important}a.has-text-danger-dark:focus,a.has-text-danger-dark:hover{color:#ee2049!important}.has-background-danger-dark{background-color:#cc0f35!important}.has-text-black-bis{color:#121212!important}.has-background-black-bis{background-color:#121212!important}.has-text-black-ter{color:#242424!important}.has-background-black-ter{background-color:#242424!important}.has-text-grey-darker{color:#363636!important}.has-background-grey-darker{background-color:#363636!important}.has-text-grey-dark{color:#4a4a4a!important}.has-background-grey-dark{background-color:#4a4a4a!important}.has-text-grey{color:#7a7a7a!important}.has-background-grey{background-color:#7a7a7a!important}.has-text-grey-light{color:#b5b5b5!important}.has-background-grey-light{background-color:#b5b5b5!important}.has-text-grey-lighter{color:#dbdbdb!important}.has-background-grey-lighter{background-color:#dbdbdb!important}.has-text-white-ter{color:#f5f5f5!important}.has-background-white-ter{background-color:#f5f5f5!important}.has-text-white-bis{color:#fafafa!important}.has-background-white-bis{background-color:#fafafa!important}.is-flex-direction-row{flex-direction:row!important}.is-flex-direction-row-reverse{flex-direction:row-reverse!important}.is-flex-direction-column{flex-direction:column!important}.is-flex-direction-column-reverse{flex-direction:column-reverse!important}.is-flex-wrap-nowrap{flex-wrap:nowrap!important}.is-flex-wrap-wrap{flex-wrap:wrap!important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse!important}.is-justify-content-flex-start{justify-content:flex-start!important}.is-justify-content-flex-end{justify-content:flex-end!important}.is-justify-content-center{justify-content:center!important}.is-justify-content-space-between{justify-content:space-between!important}.is-justify-content-space-around{justify-content:space-around!important}.is-justify-content-space-evenly{justify-content:space-evenly!important}.is-justify-content-start{justify-content:start!important}.is-justify-content-end{justify-content:end!important}.is-justify-content-left{justify-content:left!important}.is-justify-content-right{justify-content:right!important}.is-align-content-flex-start{align-content:flex-start!important}.is-align-content-flex-end{align-content:flex-end!important}.is-align-content-center{align-content:center!important}.is-align-content-space-between{align-content:space-between!important}.is-align-content-space-around{align-content:space-around!important}.is-align-content-space-evenly{align-content:space-evenly!important}.is-align-content-stretch{align-content:stretch!important}.is-align-content-start{align-content:start!important}.is-align-content-end{align-content:end!important}.is-align-content-baseline{align-content:baseline!important}.is-align-items-stretch{align-items:stretch!important}.is-align-items-flex-start{align-items:flex-start!important}.is-align-items-flex-end{align-items:flex-end!important}.is-align-items-center{align-items:center!important}.is-align-items-baseline{align-items:baseline!important}.is-align-items-start{align-items:start!important}.is-align-items-end{align-items:end!important}.is-align-items-self-start{align-items:self-start!important}.is-align-items-self-end{align-items:self-end!important}.is-align-self-auto{align-self:auto!important}.is-align-self-flex-start{align-self:flex-start!important}.is-align-self-flex-end{align-self:flex-end!important}.is-align-self-center{align-self:center!important}.is-align-self-baseline{align-self:baseline!important}.is-align-self-stretch{align-self:stretch!important}.is-flex-grow-0{flex-grow:0!important}.is-flex-grow-1{flex-grow:1!important}.is-flex-grow-2{flex-grow:2!important}.is-flex-grow-3{flex-grow:3!important}.is-flex-grow-4{flex-grow:4!important}.is-flex-grow-5{flex-grow:5!important}.is-flex-shrink-0{flex-shrink:0!important}.is-flex-shrink-1{flex-shrink:1!important}.is-flex-shrink-2{flex-shrink:2!important}.is-flex-shrink-3{flex-shrink:3!important}.is-flex-shrink-4{flex-shrink:4!important}.is-flex-shrink-5{flex-shrink:5!important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left!important}.is-pulled-right{float:right!important}.is-radiusless{border-radius:0!important}.is-shadowless{box-shadow:none!important}.is-clickable{cursor:pointer!important}.is-clipped{overflow:hidden!important}.is-relative{position:relative!important}.is-marginless{margin:0!important}.is-paddingless{padding:0!important}.m-0{margin:0!important}.mt-0{margin-top:0!important}.mr-0{margin-right:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-top:0!important;margin-bottom:0!important}.m-1{margin:.25rem!important}.mt-1{margin-top:.25rem!important}.mr-1{margin-right:.25rem!important}.mb-1{margin-bottom:.25rem!important}.ml-1{margin-left:.25rem!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.m-2{margin:.5rem!important}.mt-2{margin-top:.5rem!important}.mr-2{margin-right:.5rem!important}.mb-2{margin-bottom:.5rem!important}.ml-2{margin-left:.5rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.m-3{margin:.75rem!important}.mt-3{margin-top:.75rem!important}.mr-3{margin-right:.75rem!important}.mb-3{margin-bottom:.75rem!important}.ml-3{margin-left:.75rem!important}.mx-3{margin-left:.75rem!important;margin-right:.75rem!important}.my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.m-4{margin:1rem!important}.mt-4{margin-top:1rem!important}.mr-4{margin-right:1rem!important}.mb-4{margin-bottom:1rem!important}.ml-4{margin-left:1rem!important}.mx-4{margin-left:1rem!important;margin-right:1rem!important}.my-4{margin-top:1rem!important;margin-bottom:1rem!important}.m-5{margin:1.5rem!important}.mt-5{margin-top:1.5rem!important}.mr-5{margin-right:1.5rem!important}.mb-5{margin-bottom:1.5rem!important}.ml-5{margin-left:1.5rem!important}.mx-5{margin-left:1.5rem!important;margin-right:1.5rem!important}.my-5{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.m-6{margin:3rem!important}.mt-6{margin-top:3rem!important}.mr-6{margin-right:3rem!important}.mb-6{margin-bottom:3rem!important}.ml-6{margin-left:3rem!important}.mx-6{margin-left:3rem!important;margin-right:3rem!important}.my-6{margin-top:3rem!important;margin-bottom:3rem!important}.p-0{padding:0!important}.pt-0{padding-top:0!important}.pr-0{padding-right:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-top:0!important;padding-bottom:0!important}.p-1{padding:.25rem!important}.pt-1{padding-top:.25rem!important}.pr-1{padding-right:.25rem!important}.pb-1{padding-bottom:.25rem!important}.pl-1{padding-left:.25rem!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.p-2{padding:.5rem!important}.pt-2{padding-top:.5rem!important}.pr-2{padding-right:.5rem!important}.pb-2{padding-bottom:.5rem!important}.pl-2{padding-left:.5rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.p-3{padding:.75rem!important}.pt-3{padding-top:.75rem!important}.pr-3{padding-right:.75rem!important}.pb-3{padding-bottom:.75rem!important}.pl-3{padding-left:.75rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.p-4{padding:1rem!important}.pt-4{padding-top:1rem!important}.pr-4{padding-right:1rem!important}.pb-4{padding-bottom:1rem!important}.pl-4{padding-left:1rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.p-5{padding:1.5rem!important}.pt-5{padding-top:1.5rem!important}.pr-5{padding-right:1.5rem!important}.pb-5{padding-bottom:1.5rem!important}.pl-5{padding-left:1.5rem!important}.px-5{padding-left:1.5rem!important;padding-right:1.5rem!important}.py-5{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.p-6{padding:3rem!important}.pt-6{padding-top:3rem!important}.pr-6{padding-right:3rem!important}.pb-6{padding-bottom:3rem!important}.pl-6{padding-left:3rem!important}.px-6{padding-left:3rem!important;padding-right:3rem!important}.py-6{padding-top:3rem!important;padding-bottom:3rem!important}.is-size-1{font-size:3rem!important}.is-size-2{font-size:2.5rem!important}.is-size-3{font-size:2rem!important}.is-size-4{font-size:1.5rem!important}.is-size-5{font-size:1.25rem!important}.is-size-6{font-size:1rem!important}.is-size-7{font-size:.75rem!important}@media screen and (max-width:768px){.is-size-1-mobile{font-size:3rem!important}.is-size-2-mobile{font-size:2.5rem!important}.is-size-3-mobile{font-size:2rem!important}.is-size-4-mobile{font-size:1.5rem!important}.is-size-5-mobile{font-size:1.25rem!important}.is-size-6-mobile{font-size:1rem!important}.is-size-7-mobile{font-size:.75rem!important}}@media screen and (min-width:769px),print{.is-size-1-tablet{font-size:3rem!important}.is-size-2-tablet{font-size:2.5rem!important}.is-size-3-tablet{font-size:2rem!important}.is-size-4-tablet{font-size:1.5rem!important}.is-size-5-tablet{font-size:1.25rem!important}.is-size-6-tablet{font-size:1rem!important}.is-size-7-tablet{font-size:.75rem!important}}@media screen and (max-width:1023px){.is-size-1-touch{font-size:3rem!important}.is-size-2-touch{font-size:2.5rem!important}.is-size-3-touch{font-size:2rem!important}.is-size-4-touch{font-size:1.5rem!important}.is-size-5-touch{font-size:1.25rem!important}.is-size-6-touch{font-size:1rem!important}.is-size-7-touch{font-size:.75rem!important}}@media screen and (min-width:1024px){.is-size-1-desktop{font-size:3rem!important}.is-size-2-desktop{font-size:2.5rem!important}.is-size-3-desktop{font-size:2rem!important}.is-size-4-desktop{font-size:1.5rem!important}.is-size-5-desktop{font-size:1.25rem!important}.is-size-6-desktop{font-size:1rem!important}.is-size-7-desktop{font-size:.75rem!important}}@media screen and (min-width:1216px){.is-size-1-widescreen{font-size:3rem!important}.is-size-2-widescreen{font-size:2.5rem!important}.is-size-3-widescreen{font-size:2rem!important}.is-size-4-widescreen{font-size:1.5rem!important}.is-size-5-widescreen{font-size:1.25rem!important}.is-size-6-widescreen{font-size:1rem!important}.is-size-7-widescreen{font-size:.75rem!important}}@media screen and (min-width:1408px){.is-size-1-fullhd{font-size:3rem!important}.is-size-2-fullhd{font-size:2.5rem!important}.is-size-3-fullhd{font-size:2rem!important}.is-size-4-fullhd{font-size:1.5rem!important}.is-size-5-fullhd{font-size:1.25rem!important}.is-size-6-fullhd{font-size:1rem!important}.is-size-7-fullhd{font-size:.75rem!important}}.has-text-centered{text-align:center!important}.has-text-justified{text-align:justify!important}.has-text-left{text-align:left!important}.has-text-right{text-align:right!important}@media screen and (max-width:768px){.has-text-centered-mobile{text-align:center!important}}@media screen and (min-width:769px),print{.has-text-centered-tablet{text-align:center!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-centered-tablet-only{text-align:center!important}}@media screen and (max-width:1023px){.has-text-centered-touch{text-align:center!important}}@media screen and (min-width:1024px){.has-text-centered-desktop{text-align:center!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-centered-desktop-only{text-align:center!important}}@media screen and (min-width:1216px){.has-text-centered-widescreen{text-align:center!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-centered-widescreen-only{text-align:center!important}}@media screen and (min-width:1408px){.has-text-centered-fullhd{text-align:center!important}}@media screen and (max-width:768px){.has-text-justified-mobile{text-align:justify!important}}@media screen and (min-width:769px),print{.has-text-justified-tablet{text-align:justify!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-justified-tablet-only{text-align:justify!important}}@media screen and (max-width:1023px){.has-text-justified-touch{text-align:justify!important}}@media screen and (min-width:1024px){.has-text-justified-desktop{text-align:justify!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-justified-desktop-only{text-align:justify!important}}@media screen and (min-width:1216px){.has-text-justified-widescreen{text-align:justify!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-justified-widescreen-only{text-align:justify!important}}@media screen and (min-width:1408px){.has-text-justified-fullhd{text-align:justify!important}}@media screen and (max-width:768px){.has-text-left-mobile{text-align:left!important}}@media screen and (min-width:769px),print{.has-text-left-tablet{text-align:left!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-left-tablet-only{text-align:left!important}}@media screen and (max-width:1023px){.has-text-left-touch{text-align:left!important}}@media screen and (min-width:1024px){.has-text-left-desktop{text-align:left!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-left-desktop-only{text-align:left!important}}@media screen and (min-width:1216px){.has-text-left-widescreen{text-align:left!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-left-widescreen-only{text-align:left!important}}@media screen and (min-width:1408px){.has-text-left-fullhd{text-align:left!important}}@media screen and (max-width:768px){.has-text-right-mobile{text-align:right!important}}@media screen and (min-width:769px),print{.has-text-right-tablet{text-align:right!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-right-tablet-only{text-align:right!important}}@media screen and (max-width:1023px){.has-text-right-touch{text-align:right!important}}@media screen and (min-width:1024px){.has-text-right-desktop{text-align:right!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-right-desktop-only{text-align:right!important}}@media screen and (min-width:1216px){.has-text-right-widescreen{text-align:right!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-right-widescreen-only{text-align:right!important}}@media screen and (min-width:1408px){.has-text-right-fullhd{text-align:right!important}}.is-capitalized{text-transform:capitalize!important}.is-lowercase{text-transform:lowercase!important}.is-uppercase{text-transform:uppercase!important}.is-italic{font-style:italic!important}.has-text-weight-light{font-weight:300!important}.has-text-weight-normal{font-weight:400!important}.has-text-weight-medium{font-weight:500!important}.has-text-weight-semibold{font-weight:600!important}.has-text-weight-bold{font-weight:700!important}.is-family-primary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-secondary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-sans-serif{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-monospace{font-family:monospace!important}.is-family-code{font-family:monospace!important}.is-block{display:block!important}@media screen and (max-width:768px){.is-block-mobile{display:block!important}}@media screen and (min-width:769px),print{.is-block-tablet{display:block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-block-tablet-only{display:block!important}}@media screen and (max-width:1023px){.is-block-touch{display:block!important}}@media screen and (min-width:1024px){.is-block-desktop{display:block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-block-desktop-only{display:block!important}}@media screen and (min-width:1216px){.is-block-widescreen{display:block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-block-widescreen-only{display:block!important}}@media screen and (min-width:1408px){.is-block-fullhd{display:block!important}}.is-flex{display:flex!important}@media screen and (max-width:768px){.is-flex-mobile{display:flex!important}}@media screen and (min-width:769px),print{.is-flex-tablet{display:flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-flex-tablet-only{display:flex!important}}@media screen and (max-width:1023px){.is-flex-touch{display:flex!important}}@media screen and (min-width:1024px){.is-flex-desktop{display:flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-flex-desktop-only{display:flex!important}}@media screen and (min-width:1216px){.is-flex-widescreen{display:flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-flex-widescreen-only{display:flex!important}}@media screen and (min-width:1408px){.is-flex-fullhd{display:flex!important}}.is-inline{display:inline!important}@media screen and (max-width:768px){.is-inline-mobile{display:inline!important}}@media screen and (min-width:769px),print{.is-inline-tablet{display:inline!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-tablet-only{display:inline!important}}@media screen and (max-width:1023px){.is-inline-touch{display:inline!important}}@media screen and (min-width:1024px){.is-inline-desktop{display:inline!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-desktop-only{display:inline!important}}@media screen and (min-width:1216px){.is-inline-widescreen{display:inline!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-widescreen-only{display:inline!important}}@media screen and (min-width:1408px){.is-inline-fullhd{display:inline!important}}.is-inline-block{display:inline-block!important}@media screen and (max-width:768px){.is-inline-block-mobile{display:inline-block!important}}@media screen and (min-width:769px),print{.is-inline-block-tablet{display:inline-block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-block-tablet-only{display:inline-block!important}}@media screen and (max-width:1023px){.is-inline-block-touch{display:inline-block!important}}@media screen and (min-width:1024px){.is-inline-block-desktop{display:inline-block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-block-desktop-only{display:inline-block!important}}@media screen and (min-width:1216px){.is-inline-block-widescreen{display:inline-block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-block-widescreen-only{display:inline-block!important}}@media screen and (min-width:1408px){.is-inline-block-fullhd{display:inline-block!important}}.is-inline-flex{display:inline-flex!important}@media screen and (max-width:768px){.is-inline-flex-mobile{display:inline-flex!important}}@media screen and (min-width:769px),print{.is-inline-flex-tablet{display:inline-flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-flex-tablet-only{display:inline-flex!important}}@media screen and (max-width:1023px){.is-inline-flex-touch{display:inline-flex!important}}@media screen and (min-width:1024px){.is-inline-flex-desktop{display:inline-flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-flex-desktop-only{display:inline-flex!important}}@media screen and (min-width:1216px){.is-inline-flex-widescreen{display:inline-flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-flex-widescreen-only{display:inline-flex!important}}@media screen and (min-width:1408px){.is-inline-flex-fullhd{display:inline-flex!important}}.is-hidden{display:none!important}.is-sr-only{border:none!important;clip:rect(0,0,0,0)!important;height:.01em!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:.01em!important}@media screen and (max-width:768px){.is-hidden-mobile{display:none!important}}@media screen and (min-width:769px),print{.is-hidden-tablet{display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-hidden-tablet-only{display:none!important}}@media screen and (max-width:1023px){.is-hidden-touch{display:none!important}}@media screen and (min-width:1024px){.is-hidden-desktop{display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-hidden-desktop-only{display:none!important}}@media screen and (min-width:1216px){.is-hidden-widescreen{display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-hidden-widescreen-only{display:none!important}}@media screen and (min-width:1408px){.is-hidden-fullhd{display:none!important}}.is-invisible{visibility:hidden!important}@media screen and (max-width:768px){.is-invisible-mobile{visibility:hidden!important}}@media screen and (min-width:769px),print{.is-invisible-tablet{visibility:hidden!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-invisible-tablet-only{visibility:hidden!important}}@media screen and (max-width:1023px){.is-invisible-touch{visibility:hidden!important}}@media screen and (min-width:1024px){.is-invisible-desktop{visibility:hidden!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-invisible-desktop-only{visibility:hidden!important}}@media screen and (min-width:1216px){.is-invisible-widescreen{visibility:hidden!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-invisible-widescreen-only{visibility:hidden!important}}@media screen and (min-width:1408px){.is-invisible-fullhd{visibility:hidden!important}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:0 0}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width:1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,.7)}.hero.is-white .navbar-link.is-active,.hero.is-white .navbar-link:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black .navbar-link.is-active,.hero.is-black .navbar-link:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black a.navbar-item:hover{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}@media screen and (max-width:768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light .navbar-link.is-active,.hero.is-light .navbar-link:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark .navbar-link.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark a.navbar-item:hover{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}@media screen and (max-width:768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}}.hero.is-primary{background-color:#00d1b2;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-primary .navbar-menu{background-color:#00d1b2}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary .navbar-link.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary a.navbar-item:hover{background-color:#00b89c;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#00d1b2}.hero.is-primary.is-bold{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}@media screen and (max-width:768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}}.hero.is-link{background-color:#3273dc;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-link .navbar-menu{background-color:#3273dc}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link .navbar-link.is-active,.hero.is-link .navbar-link:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link a.navbar-item:hover{background-color:#2366d1;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3273dc}.hero.is-link.is-bold{background-image:linear-gradient(141deg,#1577c6 0,#3273dc 71%,#4366e5 100%)}@media screen and (max-width:768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1577c6 0,#3273dc 71%,#4366e5 100%)}}.hero.is-info{background-color:#3298dc;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-info .navbar-menu{background-color:#3298dc}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info .navbar-link.is-active,.hero.is-info .navbar-link:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info a.navbar-item:hover{background-color:#238cd1;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3298dc}.hero.is-info.is-bold{background-image:linear-gradient(141deg,#159dc6 0,#3298dc 71%,#4389e5 100%)}@media screen and (max-width:768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg,#159dc6 0,#3298dc 71%,#4389e5 100%)}}.hero.is-success{background-color:#48c774;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-success .navbar-menu{background-color:#48c774}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,.7)}.hero.is-success .navbar-link.is-active,.hero.is-success .navbar-link:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success a.navbar-item:hover{background-color:#3abb67;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#48c774}.hero.is-success.is-bold{background-image:linear-gradient(141deg,#29b342 0,#48c774 71%,#56d296 100%)}@media screen and (max-width:768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg,#29b342 0,#48c774 71%,#56d296 100%)}}.hero.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-warning .navbar-menu{background-color:#ffdd57}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning .navbar-link.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning a.navbar-item:hover{background-color:#ffd83d;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffdd57}.hero.is-warning.is-bold{background-image:linear-gradient(141deg,#ffaf24 0,#ffdd57 71%,#fffa70 100%)}@media screen and (max-width:768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg,#ffaf24 0,#ffdd57 71%,#fffa70 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger .navbar-link.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger a.navbar-item:hover{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}@media screen and (max-width:768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}}.hero.is-small .hero-body{padding:1.5rem}@media screen and (min-width:769px),print{.hero.is-medium .hero-body{padding:9rem 1.5rem}}@media screen and (min-width:769px),print{.hero.is-large .hero-body{padding:18rem 1.5rem}}.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body,.hero.is-halfheight .hero-body{align-items:center;display:flex}.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container,.hero.is-halfheight .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%,-50%,0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width:768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width:768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width:769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-foot,.hero-head{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}.section{padding:3rem 1.5rem}@media screen and (min-width:1024px){.section.is-medium{padding:9rem 1.5rem}.section.is-large{padding:18rem 1.5rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem} \ No newline at end of file diff --git a/src/css/css.css b/src/css/css.css deleted file mode 100644 index 8b56c25..0000000 --- a/src/css/css.css +++ /dev/null @@ -1,259 +0,0 @@ -/* - * See: https://fonts.google.com/license/googlerestricted - */ -/* latin-ext */ -@font-face { - font-family: 'Castoro'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/castoro/v19/1q2GY5yMCld3-O4cLYFOzdYe.woff2) format('woff2'); - unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Castoro'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/castoro/v19/1q2GY5yMCld3-O4cLY9OzQ.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* armenian */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJl1pynSEg.woff2) format('woff2'); - unicode-range: U+0308, U+0530-058F, U+2010, U+2024, U+25CC, U+FB13-FB17; -} -/* bengali */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJl3pynSEg.woff2) format('woff2'); - unicode-range: U+0951-0952, U+0964-0965, U+0980-09FE, U+1CD0, U+1CD2, U+1CD5-1CD6, U+1CD8, U+1CE1, U+1CEA, U+1CED, U+1CF2, U+1CF5-1CF7, U+200C-200D, U+20B9, U+25CC, U+A8F1; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlopynSEg.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} -/* cyrillic */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlhpynSEg.woff2) format('woff2'); - unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* devanagari */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlkpynSEg.woff2) format('woff2'); - unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09; -} -/* ethiopic */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJl0pynSEg.woff2) format('woff2'); - unicode-range: U+1200-1399, U+2D80-2DDE, U+AB01-AB2E, U+1E7E0-1E7E6, U+1E7E8-1E7EB, U+1E7ED-1E7EE, U+1E7F0-1E7FE; -} -/* georgian */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJl6pynSEg.woff2) format('woff2'); - unicode-range: U+0589, U+10A0-10FF, U+1C90-1CBA, U+1CBD-1CBF, U+2D00-2D2F; -} -/* greek */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlmpynSEg.woff2) format('woff2'); - unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; -} -/* gujarati */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJl-pynSEg.woff2) format('woff2'); - unicode-range: U+0951-0952, U+0964-0965, U+0A80-0AFF, U+200C-200D, U+20B9, U+25CC, U+A830-A839; -} -/* gurmukhi */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlGpynSEg.woff2) format('woff2'); - unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A76, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839; -} -/* hebrew */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlnpynSEg.woff2) format('woff2'); - unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F; -} -/* khmer */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlupynSEg.woff2) format('woff2'); - unicode-range: U+1780-17FF, U+19E0-19FF, U+200C-200D, U+25CC; -} -/* lao */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlspynSEg.woff2) format('woff2'); - unicode-range: U+0E81-0EDF, U+200C-200D, U+25CC; -} -/* oriya */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJl8pynSEg.woff2) format('woff2'); - unicode-range: U+0951-0952, U+0964-0965, U+0B01-0B77, U+1CDA, U+1CF2, U+200C-200D, U+20B9, U+25CC; -} -/* sinhala */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJl4pynSEg.woff2) format('woff2'); - unicode-range: U+0964-0965, U+0D81-0DF4, U+1CF2, U+200C-200D, U+25CC, U+111E1-111F4; -} -/* tamil */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlzpynSEg.woff2) format('woff2'); - unicode-range: U+0964-0965, U+0B82-0BFA, U+200C-200D, U+20B9, U+25CC; -} -/* telugu */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJl5pynSEg.woff2) format('woff2'); - unicode-range: U+0951-0952, U+0964-0965, U+0C00-0C7F, U+1CDA, U+1CF2, U+200C-200D, U+25CC; -} -/* thai */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlxpynSEg.woff2) format('woff2'); - unicode-range: U+0E01-0E5B, U+200C-200D, U+25CC; -} -/* vietnamese */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlqpynSEg.woff2) format('woff2'); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJlrpynSEg.woff2) format('woff2'); - unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Google Sans'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/googlesans/v58/4Ua_rENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RFD48TE63OOYKtrwEIJllpyk.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* cyrillic-ext */ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - src: url(https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9X6VLKzA.woff2) format('woff2'); - unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; -} -/* cyrillic */ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - src: url(https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9e6VLKzA.woff2) format('woff2'); - unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; -} -/* devanagari */ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - src: url(https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9b6VLKzA.woff2) format('woff2'); - unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09; -} -/* greek-ext */ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - src: url(https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9W6VLKzA.woff2) format('woff2'); - unicode-range: U+1F00-1FFF; -} -/* greek */ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - src: url(https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9Z6VLKzA.woff2) format('woff2'); - unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; -} -/* vietnamese */ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - src: url(https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9V6VLKzA.woff2) format('woff2'); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; -} -/* latin-ext */ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - src: url(https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9U6VLKzA.woff2) format('woff2'); - unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Noto Sans'; - font-style: normal; - font-weight: 400; - font-stretch: 100%; - src: url(https://fonts.gstatic.com/s/notosans/v36/o-0mIpQlx3QUlC5A4PNB6Ryti20_6n1iPHjcz6L1SoM-jCpoiyD9A-9a6VI.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} diff --git a/src/css/fontawesome.all.min.css b/src/css/fontawesome.all.min.css deleted file mode 100644 index cbeaa76..0000000 --- a/src/css/fontawesome.all.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - */ -.fa,.fab,.fad,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bacteria:before{content:"\e059"}.fa-bacterium:before{content:"\e05a"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\e05b"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudflare:before{content:"\e07d"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\e052"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-deezer:before{content:"\e077"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edge-legacy:before{content:"\e078"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-faucet:before{content:"\e005"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\e007"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-pay:before{content:"\e079"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guilded:before{content:"\e07e"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\e05d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-hands-wash:before{content:"\e05e"}.fa-handshake:before{content:"\f2b5"}.fa-handshake-alt-slash:before{content:"\e05f"}.fa-handshake-slash:before{content:"\e060"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-head-side-cough:before{content:"\e061"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-head-side-mask:before{content:"\e063"}.fa-head-side-virus:before{content:"\e064"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hive:before{content:"\e07f"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-house-user:before{content:"\e065"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\e013"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-innosoft:before{content:"\e080"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\e055"}.fa-instalod:before{content:"\e081"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-house:before{content:"\e066"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\e067"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\e01a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\e056"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-octopus-deploy:before{content:"\e082"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-arrows:before{content:"\e068"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-perbyte:before{content:"\e083"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\e01e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\e069"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pump-medical:before{content:"\e06a"}.fa-pump-soap:before{content:"\e06b"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-rust:before{content:"\e07a"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-shield-virus:before{content:"\e06c"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\e057"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sink:before{content:"\e06d"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\e06e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\e06f"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-store-alt-slash:before{content:"\e070"}.fa-store-slash:before{content:"\e071"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-tiktok:before{content:"\e07b"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\e041"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-uncharted:before{content:"\e084"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\e049"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-unsplash:before{content:"\e07c"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-users-slash:before{content:"\e073"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-vest:before{content:"\e085"}.fa-vest-patches:before{content:"\e086"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-virus:before{content:"\e074"}.fa-virus-slash:before{content:"\e075"}.fa-viruses:before{content:"\e076"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-watchman-monitoring:before{content:"\e087"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wodu:before{content:"\e088"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.fab,.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} diff --git a/src/css/index.css b/src/css/index.css deleted file mode 100644 index c455901..0000000 --- a/src/css/index.css +++ /dev/null @@ -1,137 +0,0 @@ -body { - font-family: 'Noto Sans', sans-serif; -} - - -.footer .icon-link { - font-size: 25px; - color: #000; -} - -.link-block a { - margin-top: 5px; - margin-bottom: 5px; -} - -.dnerf { - font-variant: small-caps; -} - - -.teaser .hero-body { - padding-top: 0; - padding-bottom: 3rem; -} - -.teaser { - font-family: 'Google Sans', sans-serif; -} - - -.publication-title { -} - -.publication-banner { - max-height: parent; - -} - -.publication-banner video { - position: relative; - left: auto; - top: auto; - transform: none; - object-fit: fit; -} - -.publication-header .hero-body { -} - -.publication-title { - font-family: 'Google Sans', sans-serif; -} - -.publication-authors { - font-family: 'Google Sans', sans-serif; -} - -.publication-venue { - color: #555; - width: fit-content; - font-weight: bold; -} - -.publication-awards { - color: #ff3860; - width: fit-content; - font-weight: bolder; -} - -.publication-authors { -} - -.publication-authors a { - color: hsl(204, 86%, 53%) !important; -} - -.publication-authors a:hover { - text-decoration: underline; -} - -.author-block { - display: inline-block; -} - -.publication-banner img { -} - -.publication-authors { - /*color: #4286f4;*/ -} - -.publication-video { - position: relative; - width: 100%; - height: 0; - padding-bottom: 56.25%; - - overflow: hidden; - border-radius: 10px !important; -} - -.publication-video iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -.publication-body img { -} - -.results-carousel { - overflow: hidden; -} - -.results-carousel .item { - margin: 5px; - overflow: hidden; - padding: 20px; - font-size: 0; -} - -.results-carousel video { - margin: 0; -} - -.slider-pagination .slider-page { - background: #000000; -} - -.eql-cntrb { - font-size: smaller; -} - - - diff --git a/src/customRouteLayout.tsx b/src/customRouteLayout.tsx deleted file mode 100644 index 45a42ca..0000000 --- a/src/customRouteLayout.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from 'react'; -import { - useGetList, - useAuthenticated, - Datagrid, - TextField, - Title, -} from 'react-admin'; - -const sort = { field: 'published_at', order: 'DESC' }; - -const CustomRouteLayout = ({ title = 'Posts' }) => { - useAuthenticated(); - - const { data, total, isLoading } = useGetList('posts', { - pagination: { page: 1, perPage: 10 }, - sort, - }); - - return !isLoading ? ( -
- - <h1>{title}</h1> - <p> - Found <span className="total">{total}</span> posts ! - </p> - <Datagrid - sort={sort} - data={data} - isLoading={isLoading} - total={total} - rowClick="edit" - > - <TextField source="id" sortable={false} /> - <TextField source="title" sortable={false} /> - </Datagrid> - </div> - ) : null; -}; - -export default CustomRouteLayout; diff --git a/src/customRouteNoLayout.tsx b/src/customRouteNoLayout.tsx deleted file mode 100644 index 9ac77f1..0000000 --- a/src/customRouteNoLayout.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; -import { useGetList } from 'react-admin'; - -const CustomRouteNoLayout = ({ title = 'Posts' }) => { - const { isLoading, total } = useGetList('posts', { - pagination: { page: 0, perPage: 10 }, - sort: { field: 'id', order: 'ASC' }, - }); - - return ( - <div> - <h1>{title}</h1> - {isLoading ? ( - <p className="app-loader">Loading...</p> - ) : ( - <p> - Found <span className="total">{total}</span> posts ! - </p> - )} - </div> - ); -}; - -export default CustomRouteNoLayout; diff --git a/src/dataProvider/index.ts b/src/dataProvider/index.ts index 68af0ef..0136adc 100644 --- a/src/dataProvider/index.ts +++ b/src/dataProvider/index.ts @@ -1,219 +1,346 @@ -import { stringify } from 'query-string'; -import { fetchUtils, DataProvider } from 'ra-core'; -import { useAuthProvider, AuthProvider } from 'react-admin'; -/** - * Maps react-admin queries to a simple REST API - * - * This REST dialect is similar to the one of FakeRest - * - * @see https://github.com/marmelab/FakeRest - * - * @example - * - * getList => GET http://my.api.url/posts?sort=['title','ASC']&range=[0, 24] - * getOne => GET http://my.api.url/posts/123 - * getMany => GET http://my.api.url/posts?filter={id:[123,456,789]} - * update => PUT http://my.api.url/posts/123 - * create => POST http://my.api.url/posts - * delete => DELETE http://my.api.url/posts/123 - * - * @example - * - * import * as React from "react"; - * import { Admin, Resource } from 'react-admin'; - * import simpleRestProvider from 'ra-data-simple-rest'; - * - * import { PostList } from './posts'; - * - * const App = () => ( - * <Admin dataProvider={simpleRestProvider('http://path.to.my.api/')}> - * <Resource name="posts" list={PostList} /> - * </Admin> - * ); - * - * export default App; - */ -const dataProvider = ( - apiUrl: string, - httpClient = fetchUtils.fetchJson, - countHeader: string = 'Content-Range' -): DataProvider => ({ - getList: (resource, params) => { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; +import { + fetchUtils, + DataProvider, + GetListParams, + GetManyReferenceParams, + HttpError, + PaginationPayload, + SortPayload, +} from 'react-admin'; + +import type { + ArchiveComplete, + ArchiveDownload, + ArchiveInitiate, + ArchiveInitiateRequest, + ArchivePartReceipt, + ArchiveProbe, + AssignAllResponse, + BatchProbe, + ClassGroup, + CompletedPart, + ConnectCode, + CoverSeries, + DeviceRename, + DeviceRevocation, + PerformanceSummary, + PresetAssignment, + PooledCover, + RunRecord, + RunsProbe, +} from '../contract'; + +type HttpClient = typeof fetchUtils.fetchJson; + +/** The vendored simple-rest verbs plus the registry's non-CRUD calls. */ +export interface DrmDataProvider extends DataProvider { + transectCover: ( + transectId: string, + level: string, + campaignId?: string, + ) => Promise<PooledCover>; + transectCoverSeries: (transectId: string, level: string) => Promise<CoverSeries>; + videoRuns: (videoId: string) => Promise<RunRecord[]>; + classGroups: () => Promise<ClassGroup[]>; + mintConnectCode: (deviceName: string) => Promise<ConnectCode>; + revokeDevice: (deviceId: string) => Promise<DeviceRevocation>; + renameDevice: (deviceId: string, name: string) => Promise<DeviceRename>; + assignPreset: (deviceId: string, presetId: string | null) => Promise<PresetAssignment>; + archiveInitiate: (body: ArchiveInitiateRequest) => Promise<ArchiveInitiate>; + archiveUploadPart: ( + objectId: string, + partNumber: number, + body: Blob, + ) => Promise<ArchivePartReceipt>; + archiveComplete: (objectId: string, parts: CompletedPart[]) => Promise<ArchiveComplete>; + archiveByHash: (contentHash: string) => Promise<ArchiveProbe | null>; + performanceSummary: () => Promise<PerformanceSummary>; + archiveProbe: (hashes: string[]) => Promise<BatchProbe>; + archiveRunsProbe: (runIds: string[]) => Promise<RunsProbe>; + archiveDownload: (objectId: string) => Promise<ArchiveDownload>; + assignPresetToAll: (presetId: string) => Promise<AssignAllResponse>; +} + +const DEFAULT_PAGINATION: PaginationPayload = { page: 1, perPage: 25 }; +const DEFAULT_SORT: SortPayload = { field: 'id', order: 'ASC' }; - const rangeStart = (page - 1) * perPage; - const rangeEnd = page * perPage - 1; +// The registry hides tombstones from its list routes already. Sent anyway so a console +// pointed at an older server does not start showing deleted rows. +const withoutTombstones = (filter: Record<string, unknown> | undefined) => ({ + deleted_at: null, + ...filter, +}); + +// react-admin round-trips whole records into save, but the registry owns these +// columns and refuses payloads that carry them. +const SERVER_OWNED_KEYS = [ + 'id', + 'created_at', + 'updated_at', + 'deleted_at', + 'device_id', + 'server_seq', +]; + +const stripServerOwned = (data: Record<string, unknown>) => + Object.fromEntries( + Object.entries(data).filter(([key]) => !SERVER_OWNED_KEYS.includes(key)), + ); - const query = { +const listQuery = ( + params: GetListParams | GetManyReferenceParams, + filter: Record<string, unknown>, +) => { + const { page, perPage } = params.pagination ?? DEFAULT_PAGINATION; + const { field, order } = params.sort ?? DEFAULT_SORT; + const rangeStart = (page - 1) * perPage; + const rangeEnd = page * perPage - 1; + return { + rangeStart, + rangeEnd, + query: { sort: JSON.stringify([field, order]), range: JSON.stringify([rangeStart, rangeEnd]), - filter: JSON.stringify(params.filter), - }; - const url = `${apiUrl}/${resource}?${stringify(query)}`; - const options = - countHeader === 'Content-Range' - ? { - // Chrome doesn't return `Content-Range` header if no `Range` is provided in the request. - headers: new Headers({ - Range: `${resource}=${rangeStart}-${rangeEnd}`, - }), - } - : {}; - - return httpClient(url, options).then(({ headers, json }) => { - if (!headers.has(countHeader)) { - throw new Error( - `The ${countHeader} header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare ${countHeader} in the Access-Control-Expose-Headers header?` - ); - } - return { + filter: JSON.stringify(filter), + }, + }; +}; + +const parseTotal = (headers: Headers, countHeader: string): number => { + const raw = headers.get(countHeader); + if (raw === null) { + throw new Error( + `The ${countHeader} header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare ${countHeader} in the Access-Control-Expose-Headers header?`, + ); + } + const value = countHeader === 'Content-Range' ? raw.split('/').pop() : raw; + return parseInt(value ?? '', 10); +}; + +const dataProvider = ( + apiUrl: string, + httpClient: HttpClient = fetchUtils.fetchJson, + countHeader = 'Content-Range', +): DrmDataProvider => { + const rangeOptions = (resource: string, rangeStart: number, rangeEnd: number) => + // Chrome omits `Content-Range` on a response unless the request carried a `Range`. + countHeader === 'Content-Range' + ? { headers: new Headers({ Range: `${resource}=${rangeStart}-${rangeEnd}` }) } + : {}; + + return { + getList: (resource, params) => { + const { rangeStart, rangeEnd, query } = listQuery( + params, + withoutTombstones(params.filter), + ); + const url = `${apiUrl}/${resource}?${new URLSearchParams(query)}`; + return httpClient(url, rangeOptions(resource, rangeStart, rangeEnd)).then( + ({ headers, json }) => ({ + data: json, + total: parseTotal(headers, countHeader), + }), + ); + }, + + getOne: (resource, params) => + httpClient(`${apiUrl}/${resource}/${params.id}`).then(({ json }) => ({ data: json, - total: - countHeader === 'Content-Range' - ? parseInt( - headers.get('content-range').split('/').pop(), - 10 - ) - : parseInt(headers.get(countHeader.toLowerCase())), + })), + + // Every ReferenceField on a page batches into one getMany. Without a range the + // registry answers with its default page of ten, and references past the tenth + // distinct record silently render empty. + getMany: (resource, params) => { + const last = Math.max(params.ids.length - 1, 0); + const query = { + filter: JSON.stringify({ id: params.ids }), + range: JSON.stringify([0, last]), }; - }); - }, - - getOne: (resource, params) => - httpClient(`${apiUrl}/${resource}/${params.id}`).then(({ json }) => ({ - data: json, - })), - - getMany: (resource, params) => { - const query = { - filter: JSON.stringify({ id: params.ids }), - }; - const url = `${apiUrl}/${resource}?${stringify(query)}`; - return httpClient(url).then(({ json }) => ({ data: json })); - }, - - getManyReference: (resource, params) => { - const { page, perPage } = params.pagination; - const { field, order } = params.sort; - - const rangeStart = (page - 1) * perPage; - const rangeEnd = page * perPage - 1; - - const query = { - sort: JSON.stringify([field, order]), - range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]), - filter: JSON.stringify({ - ...params.filter, + const url = `${apiUrl}/${resource}?${new URLSearchParams(query)}`; + return httpClient(url, rangeOptions(resource, 0, last)).then(({ json }) => ({ + data: json, + })); + }, + + getManyReference: (resource, params) => { + const { rangeStart, rangeEnd, query } = listQuery(params, { + ...withoutTombstones(params.filter), [params.target]: params.id, - }), - }; - const url = `${apiUrl}/${resource}?${stringify(query)}`; - const options = - countHeader === 'Content-Range' - ? { - // Chrome doesn't return `Content-Range` header if no `Range` is provided in the request. - headers: new Headers({ - Range: `${resource}=${rangeStart}-${rangeEnd}`, + }); + const url = `${apiUrl}/${resource}?${new URLSearchParams(query)}`; + return httpClient(url, rangeOptions(resource, rangeStart, rangeEnd)).then( + ({ headers, json }) => ({ + data: json, + total: parseTotal(headers, countHeader), + }), + ); + }, + + update: (resource, params) => + httpClient(`${apiUrl}/${resource}/${params.id}`, { + method: 'PUT', + body: JSON.stringify(stripServerOwned(params.data)), + }).then(({ json }) => ({ data: json })), + + // simple-rest has no updateMany route, so fall back to n updates. + updateMany: (resource, params) => + Promise.all( + params.ids.map(id => + httpClient(`${apiUrl}/${resource}/${id}`, { + method: 'PUT', + body: JSON.stringify(stripServerOwned(params.data)), }), - } - : {}; - - return httpClient(url, options).then(({ headers, json }) => { - if (!headers.has(countHeader)) { - throw new Error( - `The ${countHeader} header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare ${countHeader} in the Access-Control-Expose-Headers header?` - ); - } - return { - data: json, - total: - countHeader === 'Content-Range' - ? parseInt( - headers.get('content-range').split('/').pop(), - 10 - ) - : parseInt(headers.get(countHeader.toLowerCase())), - }; - }); - }, - - update: (resource, params) => - httpClient(`${apiUrl}/${resource}/${params.id}`, { - method: 'PUT', - body: JSON.stringify(params.data), - }).then(({ json }) => ({ data: json })), - - // simple-rest doesn't handle provide an updateMany route, so we fallback to calling update n times instead - updateMany: (resource, params) => - Promise.all( - params.ids.map(id => - httpClient(`${apiUrl}/${resource}/${id}`, { - method: 'PUT', - body: JSON.stringify(params.data), - }) - ) - ).then(responses => ({ data: responses.map(({ json }) => json.id) })), - - create: (resource, params) => { - return httpClient(`${apiUrl}/${resource}`, { - method: 'POST', - body: JSON.stringify(params.data), - }).then(({ json }) => ({ data: json })) - }, - - delete: (resource, params) => - httpClient(`${apiUrl}/${resource}/${params.id}`, { - method: 'DELETE', - headers: new Headers({ - 'Content-Type': 'text/plain', - }), - }).then(({ json }) => ({ data: json })), - - // simple-rest doesn't handle filters on DELETE route, so we fallback to calling DELETE n times instead - deleteMany: (resource, params) => - Promise.all( - params.ids.map(id => - httpClient(`${apiUrl}/${resource}/${id}`, { - method: 'DELETE', - headers: new Headers({ - 'Content-Type': 'text/plain', + ), + ).then(responses => ({ data: responses.map(({ json }) => json.id) })), + + create: (resource, params) => + httpClient(`${apiUrl}/${resource}`, { + method: 'POST', + body: JSON.stringify(stripServerOwned(params.data)), + }).then(({ json }) => ({ data: json })), + + // The registry tombstones rather than removes, so the row keeps coming back + // through sync with `deleted_at` set until every laptop has seen it. + delete: (resource, params) => + httpClient(`${apiUrl}/${resource}/${params.id}`, { + method: 'DELETE', + headers: new Headers({ 'Content-Type': 'text/plain' }), + }).then(({ json }) => ({ data: json })), + + // simple-rest has no filtered DELETE route, so fall back to n deletes. + deleteMany: (resource, params) => + Promise.all( + params.ids.map(id => + httpClient(`${apiUrl}/${resource}/${id}`, { + method: 'DELETE', + headers: new Headers({ 'Content-Type': 'text/plain' }), }), - }) - ) - ).then(responses => ({ - data: responses.map(({ json }) => json.id), - })), - - - getStatus: (resource, params) => { - const url = `${apiUrl}/status`; - // Return the promise with the JSON array - return httpClient(url).then(({ json }) => ({ data: json })); - }, - executeKubernetesJob: (id) => { - const url = `${apiUrl}/submissions/${id}/execute`; - // Return the promise with the JSON array - return httpClient(url, { method: "POST" }).then(({ json }) => ({ data: json })); - }, - downloadFile: (url) => { - // Get the auth token from url, then forward new URL back to browser - return httpClient(url) - .then(({ json }) => ({ data: json })) - .then(function (signed) { - window.location = `${apiUrl}/submissions/download/${signed.data.token}`; - }); - }, - regenerateVideoStatistics: (id) => { - const url = `${apiUrl}/objects/${id}`; - return httpClient(url, { method: "POST" }).then(({ json }) => ({ data: json })); - }, - deleteKubernetesJob: (id) => { - const url = `${apiUrl}/submissions/jobs/${id}`; - return httpClient(url, { method: "DELETE" }).then(({ json }) => ({ data: json })); - } -}); + ), + ).then(responses => ({ data: responses.map(({ json }) => json.id) })), + + // The registry pools the figure, so the console and the desktop application cannot + // report different numbers from the same rows. + transectCover: (transectId, level, campaignId) => { + const query = new URLSearchParams({ level }); + if (campaignId) query.set('campaign_id', campaignId); + return httpClient(`${apiUrl}/transects/${transectId}/cover?${query}`).then( + ({ json }) => json as PooledCover, + ); + }, + + // One response carries every survey event's figure, so the statistics tab does + // not fan out a request per campaign. + transectCoverSeries: (transectId, level) => { + const query = new URLSearchParams({ level }); + return httpClient(`${apiUrl}/transects/${transectId}/cover-series?${query}`).then( + ({ json }) => json as CoverSeries, + ); + }, + + // Runs hang off a pass, not a clip, so the join lives in the registry rather + // than a filter the console would have to reconstruct. + videoRuns: videoId => + httpClient(`${apiUrl}/videos/${videoId}/runs`).then( + ({ json }) => json as RunRecord[], + ), + + // Colours come from the registry, so a class reads the same here as in the + // desktop viewer. + classGroups: () => + httpClient(`${apiUrl}/config/class-groups`).then( + ({ json }) => json as ClassGroup[], + ), + + // Minting names the device: the code carries the name enrolment adopts. + mintConnectCode: deviceName => + httpClient(`${apiUrl}/devices/connect-codes`, { + method: 'POST', + body: JSON.stringify({ device_name: deviceName }), + }).then(({ json }) => json as ConnectCode), + + revokeDevice: deviceId => + httpClient(`${apiUrl}/devices/${deviceId}/revoke`, { + method: 'POST', + }).then(({ json }) => json as DeviceRevocation), + + // Devices carry no CRUD update route, so the name is changed through its own call. + renameDevice: (deviceId, name) => + httpClient(`${apiUrl}/devices/${deviceId}/rename`, { + method: 'POST', + body: JSON.stringify({ name }), + }).then(({ json }) => json as DeviceRename), + + // Null clears the assignment. Acknowledgement arrives via the device's + // next heartbeat, not this response. + assignPreset: (deviceId, presetId) => + httpClient(`${apiUrl}/devices/${deviceId}/assign-preset`, { + method: 'POST', + body: JSON.stringify({ preset_id: presetId }), + }).then(({ json }) => json as PresetAssignment), + + archiveInitiate: body => + httpClient(`${apiUrl}/archive/initiate`, { + method: 'POST', + body: JSON.stringify(body), + }).then(({ json }) => json as ArchiveInitiate), + + // The part's raw bytes, streamed by the registry into the store. The browser + // sets Content-Length from the blob itself. + archiveUploadPart: (objectId, partNumber, body) => + httpClient(`${apiUrl}/archive/${objectId}/parts/${partNumber}`, { + method: 'PUT', + headers: new Headers({ 'Content-Type': 'application/octet-stream' }), + body, + }).then(({ json }) => json as ArchivePartReceipt), + + archiveComplete: (objectId, parts) => + httpClient(`${apiUrl}/archive/${objectId}/complete`, { + method: 'POST', + body: JSON.stringify({ parts }), + }).then(({ json }) => json as ArchiveComplete), + + // Null rather than a thrown 404: nothing archived is an answer, not an error. + archiveByHash: contentHash => + httpClient(`${apiUrl}/archive/by-hash/${contentHash}`).then( + ({ json }) => json as ArchiveProbe, + (error: unknown) => { + if (error instanceof HttpError && error.status === 404) return null; + throw error; + }, + ), + + // One POST answers a whole page of rows, where by-hash would fire one per row. + archiveProbe: hashes => + httpClient(`${apiUrl}/archive/probe`, { + method: 'POST', + body: JSON.stringify({ hashes }), + }).then(({ json }) => json as BatchProbe), + + archiveRunsProbe: runIds => + httpClient(`${apiUrl}/archive/runs-probe`, { + method: 'POST', + body: JSON.stringify({ run_ids: runIds }), + }).then(({ json }) => json as RunsProbe), + + archiveDownload: objectId => + httpClient(`${apiUrl}/archive/${objectId}/download`).then( + ({ json }) => json as ArchiveDownload, + ), + // The registry aggregates per device × preset × models, so peaks compare + // across the fleet without the console pulling every run. + performanceSummary: () => + httpClient(`${apiUrl}/performance/summary`).then( + ({ json }) => json as PerformanceSummary, + ), + // Admin-only on the registry. Devices adopt it at their next heartbeat. + assignPresetToAll: presetId => + httpClient(`${apiUrl}/presets/${presetId}/assign-all`, { + method: 'POST', + }).then(({ json }) => json as AssignAllResponse), + }; +}; -export default dataProvider; \ No newline at end of file +export default dataProvider; diff --git a/src/devices/AssignedPresetPanel.tsx b/src/devices/AssignedPresetPanel.tsx new file mode 100644 index 0000000..fc2b06c --- /dev/null +++ b/src/devices/AssignedPresetPanel.tsx @@ -0,0 +1,137 @@ +import { useState } from 'react'; +import { + useDataProvider, + useGetList, + useGetOne, + useNotify, + useRecordContext, + useRefresh, +} from 'react-admin'; +import { + Box, + Button, + MenuItem, + Stack, + TextField as MuiTextField, + Typography, +} from '@mui/material'; + +import type { DrmDataProvider } from '../dataProvider/index'; +import type { Device, Preset } from '../contract'; +import { relativeTime } from './RelativeDateField'; + +/** How the device answered the assignment, from its heartbeat. */ +const acknowledgement = (device: Device, assigned?: Preset): string => { + if (!device.assigned_preset_id) { + return 'No preset assigned. The device follows its own default.'; + } + if (!device.active_preset_reported_at) return 'Not yet acknowledged.'; + const matches = + assigned && + device.active_preset_name === assigned.name && + device.active_preset_version === assigned.version; + if (matches) { + return `Acknowledged ${relativeTime(device.active_preset_reported_at)}.`; + } + return `Device reports ${device.active_preset_name ?? 'nothing'} v${ + device.active_preset_version ?? '?' + }.`; +}; + +/** The server-chosen default preset for this device, with its acknowledgement state. + * Sits beside the hardware panel: see a struggling laptop, hand it lighter settings. */ +const AssignedPresetPanel = () => { + const record = useRecordContext<Device>(); + const dataProvider = useDataProvider<DrmDataProvider>(); + const notify = useNotify(); + const refresh = useRefresh(); + // Only the pending override; empty means the select shows the assignment itself. + const [choice, setChoice] = useState(''); + const [saving, setSaving] = useState(false); + + const { data: presets } = useGetList<Preset>('presets', { + pagination: { page: 1, perPage: 100 }, + sort: { field: 'name', order: 'ASC' }, + }); + const { data: assigned } = useGetOne<Preset>( + 'presets', + { id: record?.assigned_preset_id ?? '' }, + { enabled: Boolean(record?.assigned_preset_id) }, + ); + + if (!record) return null; + + const current = record.assigned_preset_id ?? ''; + const value = choice || current; + const known = (presets ?? []).some(preset => preset.id === current); + + const assign = async (presetId: string | null) => { + setSaving(true); + try { + await dataProvider.assignPreset(record.id, presetId); + notify(presetId ? 'Preset assigned.' : 'Assignment cleared.', { type: 'info' }); + setChoice(''); + refresh(); + } catch (failure) { + notify(failure instanceof Error ? failure.message : 'Assignment failed.', { + type: 'warning', + }); + } finally { + setSaving(false); + } + }; + + return ( + <Stack spacing={1}> + <Stack direction="row" spacing={1} useFlexGap sx={{ alignItems: 'flex-start' }}> + <MuiTextField + select + size="small" + label="Assigned preset" + value={value} + onChange={event => setChoice(event.target.value)} + helperText={acknowledgement(record, assigned ?? undefined)} + sx={{ minWidth: 260 }} + > + {/* Keeps the select valid while presets load, or when the assigned + preset has been deleted since. */} + {current && !known && ( + <MenuItem value={current} disabled> + {assigned + ? `${assigned.name} v${assigned.version}` + : presets + ? 'Assigned preset no longer exists' + : '…'} + </MenuItem> + )} + {(presets ?? []).map(preset => ( + <MenuItem key={preset.id} value={preset.id}> + {preset.name} v{preset.version} + </MenuItem> + ))} + </MuiTextField> + <Button + variant="outlined" + size="small" + disabled={saving || !choice || choice === current} + onClick={() => assign(choice)} + > + Assign + </Button> + {record.assigned_preset_id && ( + <Button size="small" disabled={saving} onClick={() => assign(null)}> + Clear + </Button> + )} + </Stack> + <Box> + <Typography variant="caption" sx={{ color: 'text.secondary' }}> + The device learns the assignment when it next checks in, then reports the + preset it actually runs under. + </Typography> + </Box> + </Stack> + ); +}; + +export default AssignedPresetPanel; diff --git a/src/devices/ConnectDevice.tsx b/src/devices/ConnectDevice.tsx new file mode 100644 index 0000000..abf0af8 --- /dev/null +++ b/src/devices/ConnectDevice.tsx @@ -0,0 +1,193 @@ +import { useState } from 'react'; +import { Button, HttpError, Title, useDataProvider, useNotify } from 'react-admin'; +import { Link } from 'react-router-dom'; +import { + Alert, + Box, + Card, + CardContent, + Divider, + Stack, + TextField, + Typography, +} from '@mui/material'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import VpnKeyIcon from '@mui/icons-material/VpnKey'; + +import type { DrmDataProvider } from '../dataProvider/index'; +import type { ConnectCode } from '../contract'; +import { connectCodeUrl, isLocalUrl } from './connectCode'; + +const mintMessage = (error: unknown): string => { + if (error instanceof HttpError && error.status === 403) { + return 'Minting needs an interactive login. Device tokens are refused.'; + } + if (error instanceof HttpError && error.status === 500) { + return 'The server has no PUBLIC_BASE_URL set, so a code would point nowhere.'; + } + return error instanceof Error ? error.message : 'Minting a connect code failed.'; +}; + +const ConnectDevice = () => { + const dataProvider = useDataProvider<DrmDataProvider>(); + const notify = useNotify(); + const [deviceName, setDeviceName] = useState(''); + const [code, setCode] = useState<ConnectCode | null>(null); + const [error, setError] = useState<string | null>(null); + const [minting, setMinting] = useState(false); + + // The address the code carries. An operator pastes this into a laptop, so they should + // see which host it will trust before they send it anywhere. + const serverUrl = code ? connectCodeUrl(code.code) : null; + + const mint = async () => { + setError(null); + setMinting(true); + try { + setCode(await dataProvider.mintConnectCode(deviceName.trim())); + } catch (failure) { + setError(mintMessage(failure)); + } finally { + setMinting(false); + } + }; + + const copy = async () => { + if (!code) return; + try { + await navigator.clipboard.writeText(code.code); + notify('Connect code copied.', { type: 'info' }); + } catch { + notify('The browser refused clipboard access. Select the code and copy it.', { + type: 'warning', + }); + } + }; + + return ( + <> + <Title title="Connect a device" /> + <Card sx={{ mt: 2, maxWidth: 760 }}> + <CardContent> + <Stack spacing={2}> + <Typography variant="h6">Connect a device</Typography> + + <TextField + label="Device name" + helperText="Names the device that redeems this code." + value={deviceName} + onChange={event => setDeviceName(event.target.value)} + disabled={minting || code !== null} + fullWidth + size="small" + /> + + <Box> + <Button + variant="contained" + label={code ? 'Code minted' : 'Mint a connect code'} + onClick={mint} + disabled={minting || code !== null || !deviceName.trim()} + > + <VpnKeyIcon /> + </Button> + </Box> + + {error && <Alert severity="error">{error}</Alert>} + + {code && ( + <> + <Divider /> + <Alert severity="warning"> + Single use and shown once. Anyone holding it can enrol a + device, so send it privately. + </Alert> + + <Box + component="code" + sx={{ + userSelect: 'all', + fontFamily: 'monospace', + fontSize: '1.1rem', + wordBreak: 'break-all', + p: 2, + borderRadius: 1, + border: '1px solid', + borderColor: 'divider', + bgcolor: 'action.hover', + }} + > + {code.code} + </Box> + + <Stack + direction="row" + spacing={4} + useFlexGap + sx={{ + alignItems: 'flex-end', + flexWrap: 'wrap', + }} + > + <Box> + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + display: 'block', + }} + > + Points at + </Typography> + <Typography + variant="body2" + color={ + serverUrl && isLocalUrl(serverUrl) + ? 'warning.main' + : 'text.primary' + } + sx={{ + fontFamily: 'monospace', + }} + > + {serverUrl ?? 'unreadable'} + </Typography> + </Box> + <Box> + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + display: 'block', + }} + > + Expires + </Typography> + <Typography variant="body2"> + {new Date(code.expires_at).toLocaleTimeString()} + </Typography> + </Box> + <Button label="Copy" onClick={copy}> + <ContentCopyIcon /> + </Button> + </Stack> + + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Paste it into the desktop application, then find the device + under <Link to="/devices">Devices</Link>. + </Typography> + </> + )} + </Stack> + </CardContent> + </Card> + </> + ); +}; + +export default ConnectDevice; diff --git a/src/devices/DeviceList.tsx b/src/devices/DeviceList.tsx new file mode 100644 index 0000000..38ed6e7 --- /dev/null +++ b/src/devices/DeviceList.tsx @@ -0,0 +1,137 @@ +import { Dispatch, SetStateAction, useState } from 'react'; +import { + Button, + Datagrid, + DateField, + ExportButton, + List, + SearchInput, + TextField, + TextInput, + TopToolbar, +} from 'react-admin'; +import { Link } from 'react-router-dom'; +import { Stack, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'; +import AddLinkIcon from '@mui/icons-material/AddLink'; + +import { AccountField } from '../components'; +import DeviceStatusField from './DeviceStatusField'; +import RelativeDateField, { STALE_AFTER_SECONDS } from './RelativeDateField'; + +type Visibility = 'all' | 'active' | 'revoked'; + +// `revoked_at_neq: null` is the registry's is-not-null form. These go through the List +// `filter` prop rather than the filter form, which strips null values. +const VISIBILITY_FILTERS: Record<Visibility, Record<string, null>> = { + all: {}, + active: { revoked_at: null }, + revoked: { revoked_at_neq: null }, +}; + +const deviceFilters = [ + <SearchInput source="q" alwaysOn placeholder="Name" key="q" />, + <TextInput source="platform" key="platform" />, + <TextInput source="gui_version" label="GUI version" key="gui_version" />, +]; + +const ConnectButton = () => ( + <Button component={Link} to="/devices/connect" label="Connect a device"> + <AddLinkIcon /> + </Button> +); + +const DeviceListEmpty = () => ( + <Stack + spacing={2} + sx={{ + alignItems: 'flex-start', + p: 3, + }} + > + <Typography variant="h6">No devices are enrolled yet</Typography> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Mint a connect code and paste it into the desktop app once. It names itself at + enrolment. + </Typography> + <ConnectButton /> + </Stack> +); + +const DeviceListActions = ({ + visibility, + onVisibilityChange, +}: { + visibility: Visibility; + onVisibilityChange: Dispatch<SetStateAction<Visibility>>; +}) => ( + <TopToolbar> + <ToggleButtonGroup + size="small" + exclusive + value={visibility} + onChange={(_, next: Visibility | null) => next && onVisibilityChange(next)} + > + <ToggleButton value="all">All</ToggleButton> + <ToggleButton value="active">Active</ToggleButton> + <ToggleButton value="revoked">Revoked</ToggleButton> + </ToggleButtonGroup> + <ConnectButton /> + <ExportButton /> + </TopToolbar> +); + +const DeviceList = () => { + const [visibility, setVisibility] = useState<Visibility>('all'); + + return ( + <List + filters={deviceFilters} + filter={VISIBILITY_FILTERS[visibility]} + actions={ + <DeviceListActions + visibility={visibility} + onVisibilityChange={setVisibility} + /> + } + empty={<DeviceListEmpty />} + sort={{ field: 'created_at', order: 'DESC' }} + perPage={25} + > + <Datagrid + rowClick="show" + bulkActionButtons={false} + rowSx={record => (record.revoked_at ? { opacity: 0.55 } : {})} + > + <TextField source="name" label="Device name" /> + <DeviceStatusField label="Status" /> + <TextField source="platform" emptyText="—" sortable={false} /> + <TextField + source="gui_version" + label="GUI version" + emptyText="—" + sortable={false} + /> + <TextField + source="library_version" + label="Library version" + emptyText="—" + sortable={false} + /> + <AccountField source="enrolled_by" label="Onboarded by" sortable={false} /> + <DateField source="created_at" label="Onboarded" showTime /> + <RelativeDateField + source="last_seen_at" + label="Last seen" + staleAfter={STALE_AFTER_SECONDS} + /> + </Datagrid> + </List> + ); +}; + +export default DeviceList; diff --git a/src/devices/DevicePerformance.tsx b/src/devices/DevicePerformance.tsx new file mode 100644 index 0000000..c760dc9 --- /dev/null +++ b/src/devices/DevicePerformance.tsx @@ -0,0 +1,79 @@ +import { useGetList, useRecordContext } from 'react-admin'; +import { Link } from 'react-router-dom'; +import { Box, Stack, Typography } from '@mui/material'; + +import type { Device, Preset } from '../contract'; +import GroupTable from '../performance/GroupTable'; +import { PresetCell } from '../performance/MetricCells'; +import { configLabel, modelsLabel } from '../performance/statistics'; +import { usePerformanceSummary } from '../performance/usePerformanceSummary'; +import { presetLabel } from '../runs/preset'; + +const CAPTION = + 'Mean and sample standard deviation across the runs this laptop reported, where ' + + 'every run contributes its peak across stages. A failed run counts once it ' + + 'recorded peaks, because an out-of-memory run is the one worth seeing.'; + +const EMPTY = + 'No runs from this device report performance data yet. Figures appear once it ' + + 'syncs a run that recorded per-stage peaks.'; + +const UNAVAILABLE = + 'The registry did not answer the performance summary. It may predate this console.'; + +const Note = ({ children }: { children: string }) => ( + <Typography variant="body2" sx={{ color: 'text.secondary' }}> + {children} + </Typography> +); + +const FleetLink = () => ( + <Typography variant="body2"> + <Link to="/performance">Compare this laptop with the rest of the fleet</Link> + </Typography> +); + +/** What this device's runs cost it, from the fleet summary the registry aggregates. */ +const DevicePerformance = () => { + const record = useRecordContext<Device>(); + const { groups, error } = usePerformanceSummary(); + // Same parameters as the assignment panel, so the page shares one fetch. + const { data: presets } = useGetList<Preset>('presets', { + pagination: { page: 1, perPage: 100 }, + sort: { field: 'name', order: 'ASC' }, + }); + if (!record) return null; + if (error) return <Note>{UNAVAILABLE}</Note>; + if (!groups) return <Note>Loading…</Note>; + const rows = groups.filter(group => group.device_id === record.id); + if (!rows.length) { + return ( + <Stack spacing={1}> + <Note>{EMPTY}</Note> + <FleetLink /> + </Stack> + ); + } + return ( + <Stack spacing={1}> + <Note>{CAPTION}</Note> + <Box sx={{ overflowX: 'auto' }}> + <GroupTable + groups={rows} + sortKey={group => + `${presetLabel(group)}|${modelsLabel(group)}|${configLabel(group)}` + } + lead={[ + { + header: 'Preset', + cell: group => <PresetCell group={group} presets={presets} />, + }, + ]} + /> + </Box> + <FleetLink /> + </Stack> + ); +}; + +export default DevicePerformance; diff --git a/src/devices/DeviceShow.tsx b/src/devices/DeviceShow.tsx new file mode 100644 index 0000000..7d5c595 --- /dev/null +++ b/src/devices/DeviceShow.tsx @@ -0,0 +1,381 @@ +import { + Datagrid, + DateField, + FunctionField, + Labeled, + Pagination, + ReferenceField, + ReferenceManyField, + Show, + SimpleShowLayout, + TextField, + TopToolbar, + useNotify, + useRecordContext, +} from 'react-admin'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import { + Alert, + Box, + Divider, + IconButton, + LinearProgress, + Stack, + Tooltip, + Typography, +} from '@mui/material'; + +import { AccountField, asColumn, DurationField } from '../components'; +import { RunStatusChip } from '../runs/StatusField'; +import { formatBytes } from '../videos/VideoFields'; +import DevicePerformance from './DevicePerformance'; +import DeviceStatusField from './DeviceStatusField'; +import RelativeDateField, { STALE_AFTER_SECONDS, relativeTime } from './RelativeDateField'; +import AssignedPresetPanel from './AssignedPresetPanel'; +import RenameDeviceButton from './RenameDeviceButton'; +import RevokeDeviceButton from './RevokeDeviceButton'; +import { asObject, numberOf, textOf } from './profile'; +import type { Device } from '../contract'; + +const DurationColumn = asColumn(DurationField); + +const DeviceShowActions = () => ( + <TopToolbar> + <RenameDeviceButton /> + <RevokeDeviceButton /> + </TopToolbar> +); + +const RevokedNotice = () => { + const record = useRecordContext<Device>(); + if (!record?.revoked_at) return null; + return ( + <Alert severity="error"> + Revoked on {new Date(record.revoked_at).toLocaleString()}. This installation can no + longer sync, and needs a new connect code to come back. + </Alert> + ); +}; + +const SectionHeading = ({ title }: { title: string }) => ( + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + {title} + </Typography> +); + +const HardwareLine = ({ label, value }: { label: string; value: string | null }) => ( + <Labeled label={label}> + <Typography variant="body2">{value ?? '—'}</Typography> + </Labeled> +); + +// Older heartbeats sent only the total, so the gauge degrades to a plain figure. +const DiskLine = ({ profile }: { profile: Record<string, unknown> }) => { + const total = numberOf(profile, 'disk_total_bytes'); + const free = numberOf(profile, 'disk_free_bytes'); + if (total == null || free == null) { + return <HardwareLine label="Disk" value={total == null ? null : formatBytes(total)} />; + } + const used = Math.max(total - free, 0); + const fraction = total > 0 ? used / total : 0; + return ( + <Labeled label="Disk"> + <Box sx={{ minWidth: 240 }}> + <LinearProgress + variant="determinate" + value={Math.min(fraction, 1) * 100} + color={fraction > 0.85 ? 'warning' : 'primary'} + sx={{ height: 6, borderRadius: 1, mb: 0.5 }} + /> + <Typography variant="body2"> + {formatBytes(used)} used of {formatBytes(total)} · {formatBytes(free)} free + </Typography> + </Box> + </Labeled> + ); +}; + +/** What the device says about itself. Static hardware plus survey-disk headroom. */ +const HardwarePanel = () => { + const record = useRecordContext<Device>(); + const profile = asObject(record?.system_profile); + if (!record) return null; + if (!profile) { + return ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + This device has not reported its hardware yet. A profile arrives the next time + it checks in. + </Typography> + ); + } + + const os = textOf(profile, 'os_name'); + const osRelease = textOf(profile, 'os_release'); + const logical = numberOf(profile, 'cpu_logical'); + const physical = numberOf(profile, 'cpu_physical'); + const ram = numberOf(profile, 'total_ram_bytes'); + const swap = numberOf(profile, 'total_swap_bytes'); + const gpu = asObject(profile.gpu); + const gpuName = gpu && textOf(gpu, 'name'); + const vram = gpu && numberOf(gpu, 'total_vram_bytes'); + + return ( + <Stack + direction="row" + spacing={3} + useFlexGap + sx={{ + flexWrap: 'wrap', + }} + > + <HardwareLine + label="Operating system" + value={os ? [os, osRelease].filter(Boolean).join(' ') : null} + /> + <HardwareLine + label="CPU" + value={ + logical == null + ? null + : `${logical} logical / ${physical ?? '?'} physical cores` + } + /> + <HardwareLine label="RAM" value={ram == null ? null : formatBytes(ram)} /> + <HardwareLine label="Swap" value={swap == null ? null : formatBytes(swap)} /> + <HardwareLine label="GPU" value={gpuName} /> + <HardwareLine label="VRAM" value={vram == null ? null : formatBytes(vram)} /> + <DiskLine profile={profile} /> + </Stack> + ); +}; + +const SoftwareLines = () => { + const record = useRecordContext<Device>(); + if (!record) return null; + return ( + <Stack spacing={0.5}> + <Stack + direction="row" + spacing={3} + useFlexGap + sx={{ + flexWrap: 'wrap', + }} + > + <Labeled label="GUI version"> + <TextField source="gui_version" emptyText="—" /> + </Labeled> + <Labeled label="Library version"> + <TextField source="library_version" emptyText="—" /> + </Labeled> + <Labeled label="Preset schema"> + <TextField source="preset_schema_version" emptyText="—" /> + </Labeled> + </Stack> + {(record.profile_reported_at || record.versions_changed_at) && ( + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + }} + > + {[ + record.profile_reported_at && + `reported ${relativeTime(record.profile_reported_at)}`, + record.versions_changed_at && + `changed ${relativeTime(record.versions_changed_at)}`, + ] + .filter(Boolean) + .join(' · ')} + </Typography> + )} + </Stack> + ); +}; + +const AuditPanel = () => { + const record = useRecordContext<Device>(); + const notify = useNotify(); + if (!record) return null; + return ( + <Stack spacing={0.5}> + <Stack direction="row" spacing={0.5} sx={{ alignItems: 'center' }}> + <Typography + variant="caption" + sx={{ fontFamily: 'monospace', color: 'text.secondary' }} + > + {record.id} + </Typography> + <Tooltip title="Copy device id"> + <IconButton + size="small" + onClick={() => { + navigator.clipboard.writeText(String(record.id)); + notify('Device id copied.', { type: 'info' }); + }} + > + <ContentCopyIcon sx={{ fontSize: 14 }} /> + </IconButton> + </Tooltip> + </Stack> + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + }} + > + Connect code redeemed by{' '} + <AccountField source="enrolled_by" variant="caption" /> + </Typography> + </Stack> + ); +}; + +const NoRuns = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + No runs reported from this device yet. + </Typography> +); + +const NoClips = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + No clips registered from this device yet. + </Typography> +); + +const DeviceRuns = () => ( + <ReferenceManyField + reference="runs" + target="device_id" + sort={{ field: 'created_at', order: 'DESC' }} + perPage={10} + pagination={<Pagination />} + > + <Datagrid rowClick="show" bulkActionButtons={false} empty={<NoRuns />}> + <FunctionField + label="Status" + render={record => <RunStatusChip status={record.status as string} />} + /> + <ReferenceField + source="pass_id" + reference="passes" + link="show" + label="Pass" + sortable={false} + > + <TextField source="label" emptyText="Unlabelled pass" /> + </ReferenceField> + <TextField source="run_dir_name" label="Run directory" sortable={false} /> + <DateField source="started_at" showTime emptyText="—" sortable={false} /> + </Datagrid> + </ReferenceManyField> +); + +const DeviceClips = () => ( + <ReferenceManyField + reference="videos" + target="device_id" + sort={{ field: 'created_at', order: 'DESC' }} + perPage={10} + pagination={<Pagination />} + > + <Datagrid rowClick="show" bulkActionButtons={false} empty={<NoClips />}> + <TextField source="file_name" label="File name" sortable={false} /> + <DurationColumn label="Duration" source="duration_s" sortable={false} /> + <DateField source="captured_at" label="Captured" showTime emptyText="—" /> + </Datagrid> + </ReferenceManyField> +); + +const DeviceShow = () => ( + <Show actions={<DeviceShowActions />}> + <SimpleShowLayout> + <RevokedNotice /> + <Stack + direction="row" + spacing={3} + useFlexGap + sx={{ + flexWrap: 'wrap', + }} + > + <Labeled label="Device name"> + <TextField source="name" /> + </Labeled> + <Labeled label="Status"> + <DeviceStatusField /> + </Labeled> + <Labeled label="Platform"> + <TextField source="platform" emptyText="—" /> + </Labeled> + </Stack> + <Divider /> + <SectionHeading title="Hardware" /> + <HardwarePanel /> + <SectionHeading title="Software" /> + <SoftwareLines /> + <SectionHeading title="Assigned preset" /> + <AssignedPresetPanel /> + <Divider /> + <SectionHeading title="Performance" /> + <DevicePerformance /> + <Divider /> + <SectionHeading title="Enrolment" /> + <Stack + direction="row" + spacing={3} + useFlexGap + sx={{ + flexWrap: 'wrap', + }} + > + <Labeled label="Onboarded"> + <DateField source="created_at" showTime /> + </Labeled> + <Labeled label="Last seen"> + <RelativeDateField + source="last_seen_at" + staleAfter={STALE_AFTER_SECONDS} + /> + </Labeled> + <Labeled label="Revoked"> + <DateField source="revoked_at" showTime emptyText="—" /> + </Labeled> + </Stack> + <Divider /> + <Box> + <SectionHeading title="Recent runs" /> + <DeviceRuns /> + </Box> + <Box> + <SectionHeading title="Recent clips" /> + <DeviceClips /> + </Box> + <Divider /> + <SectionHeading title="Audit" /> + <AuditPanel /> + </SimpleShowLayout> + </Show> +); + +export default DeviceShow; diff --git a/src/devices/DeviceStatusField.tsx b/src/devices/DeviceStatusField.tsx new file mode 100644 index 0000000..11addd5 --- /dev/null +++ b/src/devices/DeviceStatusField.tsx @@ -0,0 +1,17 @@ +import { FC } from 'react'; +import { useRecordContext } from 'react-admin'; +import { Chip, Tooltip } from '@mui/material'; + +/** Enrolment state as a chip. Revoked devices stay listed as an audit trail. */ +const DeviceStatusField: FC<{ label?: string }> = () => { + const record = useRecordContext(); + const revokedAt = record?.revoked_at as string | null | undefined; + if (!revokedAt) return <Chip size="small" label="Active" color="success" />; + return ( + <Tooltip title={`Revoked ${new Date(revokedAt).toLocaleString()}`}> + <Chip size="small" label="Revoked" color="error" variant="outlined" /> + </Tooltip> + ); +}; + +export default DeviceStatusField; diff --git a/src/devices/RelativeDateField.tsx b/src/devices/RelativeDateField.tsx new file mode 100644 index 0000000..000e412 --- /dev/null +++ b/src/devices/RelativeDateField.tsx @@ -0,0 +1,79 @@ +import { useRecordContext } from 'react-admin'; +import { Tooltip, Typography } from '@mui/material'; + +const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [ + ['year', 31_536_000], + ['month', 2_592_000], + ['week', 604_800], + ['day', 86_400], + ['hour', 3_600], + ['minute', 60], +]; + +const FORMATTER = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' }); + +export const STALE_AFTER_SECONDS = 7 * 86_400; + +/** Seconds since `iso`, negative for a timestamp in the future. */ +export const secondsSince = (iso: string): number => + (Date.now() - new Date(iso).getTime()) / 1000; + +export const relativeTime = (iso: string): string => { + const elapsed = secondsSince(iso); + const magnitude = Math.abs(elapsed); + for (const [unit, seconds] of UNITS) { + if (magnitude >= seconds) { + return FORMATTER.format(-Math.round(elapsed / seconds), unit); + } + } + return FORMATTER.format(-Math.round(elapsed), 'second'); +}; + +/** + * A timestamp as `3 days ago`, with the absolute value on hover. + * + * `staleAfter` turns the text a warning colour, so a laptop that stopped syncing + * stands out in a list of otherwise healthy ones. + */ +const RelativeDateField = ({ + source, + emptyText = 'never', + staleAfter, + variant = 'body2', +}: { + source: string; + label?: string; + emptyText?: string; + staleAfter?: number; + variant?: 'body2' | 'caption'; +}) => { + const record = useRecordContext(); + const value = record?.[source] as string | null | undefined; + if (!value) { + return ( + <Typography + variant={variant} + component="span" + sx={{ + color: 'text.disabled', + }} + > + {emptyText} + </Typography> + ); + } + const stale = staleAfter !== undefined && secondsSince(value) > staleAfter; + return ( + <Tooltip title={new Date(value).toLocaleString()}> + <Typography + variant={variant} + component="span" + color={stale ? 'warning.main' : 'text.primary'} + > + {relativeTime(value)} + </Typography> + </Tooltip> + ); +}; + +export default RelativeDateField; diff --git a/src/devices/RenameDeviceButton.tsx b/src/devices/RenameDeviceButton.tsx new file mode 100644 index 0000000..b9678be --- /dev/null +++ b/src/devices/RenameDeviceButton.tsx @@ -0,0 +1,111 @@ +import { useState } from 'react'; +import { + HttpError, + useDataProvider, + useGetIdentity, + useNotify, + useRecordContext, + useRefresh, +} from 'react-admin'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Typography, +} from '@mui/material'; +import DriveFileRenameOutlineIcon from '@mui/icons-material/DriveFileRenameOutline'; + +import type { DrmDataProvider } from '../dataProvider/index'; +import type { Device } from '../contract'; +import { useIsAdmin } from '../permissions'; + +const renameMessage = (error: unknown): string => { + if (error instanceof HttpError && error.status === 403) { + return 'You may only rename devices you enrolled.'; + } + if (error instanceof HttpError && error.status === 404) { + return 'That device no longer exists.'; + } + return error instanceof Error ? error.message : 'Renaming the device failed.'; +}; + +/** Renaming is a human action: a device may not relabel its own uploads. */ +const RenameDeviceButton = () => { + const record = useRecordContext<Device>(); + const dataProvider = useDataProvider<DrmDataProvider>(); + const { identity } = useGetIdentity(); + const admin = useIsAdmin(); + const notify = useNotify(); + const refresh = useRefresh(); + const [open, setOpen] = useState(false); + const [pending, setPending] = useState(false); + const [name, setName] = useState(''); + + if (!record) return null; + if (!admin && identity?.id !== record.enrolled_by) return null; + + const start = () => { + setName(record.name); + setOpen(true); + }; + + const submit = async () => { + const next = name.trim(); + if (!next || next === record.name) { + setOpen(false); + return; + } + setPending(true); + try { + const renamed = await dataProvider.renameDevice(String(record.id), next); + notify(`Renamed to ${renamed.name}.`, { type: 'info' }); + refresh(); + setOpen(false); + } catch (error) { + notify(renameMessage(error), { type: 'error' }); + } finally { + setPending(false); + } + }; + + return ( + <> + <Button startIcon={<DriveFileRenameOutlineIcon />} onClick={start}> + Rename + </Button> + <Dialog open={open} onClose={() => setOpen(false)} fullWidth maxWidth="xs"> + <DialogTitle>Rename device</DialogTitle> + <DialogContent> + <TextField + autoFocus + fullWidth + size="small" + margin="dense" + label="Device name" + value={name} + onChange={event => setName(event.target.value)} + /> + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + }} + > + Shown as the uploader on everything this device has pushed. + </Typography> + </DialogContent> + <DialogActions> + <Button onClick={() => setOpen(false)}>Cancel</Button> + <Button variant="contained" onClick={submit} disabled={pending}> + Rename + </Button> + </DialogActions> + </Dialog> + </> + ); +}; + +export default RenameDeviceButton; diff --git a/src/devices/RevokeDeviceButton.tsx b/src/devices/RevokeDeviceButton.tsx new file mode 100644 index 0000000..cb4b6bf --- /dev/null +++ b/src/devices/RevokeDeviceButton.tsx @@ -0,0 +1,83 @@ +import { useState } from 'react'; +import { + Confirm, + HttpError, + useDataProvider, + useGetIdentity, + useNotify, + useRecordContext, + useRefresh, +} from 'react-admin'; +import { Button } from '@mui/material'; +import BlockIcon from '@mui/icons-material/Block'; + +import type { DrmDataProvider } from '../dataProvider/index'; +import type { Device } from '../contract'; +import { useIsAdmin } from '../permissions'; + +const revokeMessage = (error: unknown): string => { + if (error instanceof HttpError && error.status === 403) { + return 'You may only revoke devices you enrolled.'; + } + if (error instanceof HttpError && error.status === 404) { + return 'That device no longer exists.'; + } + return error instanceof Error ? error.message : 'Revoking the device failed.'; +}; + +const RevokeDeviceButton = () => { + const record = useRecordContext<Device>(); + const dataProvider = useDataProvider<DrmDataProvider>(); + const { identity } = useGetIdentity(); + const admin = useIsAdmin(); + const notify = useNotify(); + const refresh = useRefresh(); + const [open, setOpen] = useState(false); + const [pending, setPending] = useState(false); + + if (!record || record.revoked_at) return null; + // The registry allows self-or-admin, so anyone else is offered nothing to press. + if (!admin && identity?.id !== record.enrolled_by) return null; + + const revoke = async () => { + setPending(true); + try { + await dataProvider.revokeDevice(String(record.id)); + notify(`${record.name} revoked.`, { type: 'info' }); + refresh(); + } catch (error) { + notify(revokeMessage(error), { type: 'error' }); + } finally { + setPending(false); + setOpen(false); + } + }; + + return ( + <> + <Button + color="error" + startIcon={<BlockIcon />} + onClick={() => setOpen(true)} + disabled={pending} + > + Revoke + </Button> + <Confirm + isOpen={open} + loading={pending} + title={`Revoke ${record.name}?`} + content={ + 'It stops syncing at its next attempt, and needs a new connect code ' + + 'to come back. Data it already sent is kept.' + } + confirm="Revoke" + confirmColor="warning" + onConfirm={revoke} + onClose={() => setOpen(false)} + /> + </> + ); +}; + +export default RevokeDeviceButton; diff --git a/src/devices/connectCode.ts b/src/devices/connectCode.ts new file mode 100644 index 0000000..826460a --- /dev/null +++ b/src/devices/connectCode.ts @@ -0,0 +1,45 @@ +const PREFIX = 'drm1.'; + +const fromBase64Url = (encoded: string): string => { + const padded = encoded.replace(/-/g, '+').replace(/_/g, '/'); + return atob(padded + '='.repeat((4 - (padded.length % 4)) % 4)); +}; + +// The same rules the desktop app's decoder applies (sync/connect_code.py): +// http or https only, and never an embedded username or password. +const acceptableUrl = (value: string): boolean => { + try { + const url = new URL(value); + if (url.protocol !== 'http:' && url.protocol !== 'https:') return false; + return url.username === '' && url.password === ''; + } catch { + return false; + } +}; + +/** The server address a `drm1.…` code points at, or null if it will not decode. */ +export const connectCodeUrl = (code: string): string | null => { + if (!code.startsWith(PREFIX)) return null; + try { + const payload = JSON.parse(fromBase64Url(code.slice(PREFIX.length))) as { + url?: unknown; + }; + return typeof payload.url === 'string' && acceptableUrl(payload.url) + ? payload.url + : null; + } catch { + return null; + } +}; + +// The desktop app's loopback set. URL.hostname keeps the brackets on IPv6. +const LOCAL_HOSTS = ['localhost', '127.0.0.1', '::1', '[::1]']; + +/** True for an address only reachable from the machine that minted the code. */ +export const isLocalUrl = (url: string): boolean => { + try { + return LOCAL_HOSTS.includes(new URL(url).hostname); + } catch { + return false; + } +}; diff --git a/src/devices/index.tsx b/src/devices/index.tsx new file mode 100644 index 0000000..7292a97 --- /dev/null +++ b/src/devices/index.tsx @@ -0,0 +1,18 @@ +import { Route } from 'react-router-dom'; +import DevicesIcon from '@mui/icons-material/Devices'; + +import ConnectDevice from './ConnectDevice'; +import DeviceList from './DeviceList'; +import DeviceShow from './DeviceShow'; + +// Enrolment happens at /api/enrol from the desktop application and revoking is a custom +// endpoint, so there is no create view. Renaming is a dialog on the show page. +export default { + list: DeviceList, + show: DeviceShow, + icon: DevicesIcon, + options: { + label: 'Devices', + }, + children: <Route path="connect" element={<ConnectDevice />} />, +}; diff --git a/src/devices/profile.ts b/src/devices/profile.ts new file mode 100644 index 0000000..a2ce95b --- /dev/null +++ b/src/devices/profile.ts @@ -0,0 +1,35 @@ +// The system profile is stored as sent, so every read here survives a device that +// reported a different shape. + +export const asObject = (value: unknown): Record<string, unknown> | null => + value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record<string, unknown>) + : null; + +export const textOf = (profile: Record<string, unknown>, key: string): string | null => { + const value = profile[key]; + return typeof value === 'string' && value ? value : null; +}; + +export const numberOf = (profile: Record<string, unknown>, key: string): number | null => { + const value = profile[key]; + return typeof value === 'number' && Number.isFinite(value) ? value : null; +}; + +export type ProfileTotals = { + ram: number | null; + swap: number | null; + vram: number | null; +}; + +/** Memory ceilings from a device's `system_profile`, for scaling observed peaks. */ +export const profileTotals = (systemProfile: unknown): ProfileTotals => { + const profile = asObject(systemProfile); + if (!profile) return { ram: null, swap: null, vram: null }; + const gpu = asObject(profile.gpu); + return { + ram: numberOf(profile, 'total_ram_bytes'), + swap: numberOf(profile, 'total_swap_bytes'), + vram: gpu && numberOf(gpu, 'total_vram_bytes'), + }; +}; diff --git a/src/index.tsx b/src/index.tsx index 40e54d8..2e902d7 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,8 +1,13 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + import App from './App'; -const container = document.getElementById('root'); -const root = ReactDOM.createRoot(container); +const container: HTMLElement | null = document.getElementById('root'); +if (!container) throw new Error('#root is missing from index.html'); -root.render(<App />); +createRoot(container).render( + <StrictMode> + <App /> + </StrictMode>, +); diff --git a/src/layout/Menu.tsx b/src/layout/Menu.tsx new file mode 100644 index 0000000..50da760 --- /dev/null +++ b/src/layout/Menu.tsx @@ -0,0 +1,52 @@ +import { Menu, useSidebarState } from 'react-admin'; +import { Typography } from '@mui/material'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import SpeedIcon from '@mui/icons-material/Speed'; + +// Hidden while the sidebar is collapsed, where only the icons remain legible. +const Section = ({ label }: { label: string }) => { + const [open] = useSidebarState(); + if (!open) return null; + return ( + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + display: 'block', + px: 2, + pt: 1.5, + lineHeight: 1.5, + }} + > + {label} + </Typography> + ); +}; + +/** The sidebar, grouped by what a row is: catalogue entries, synced assets, laptops. */ +const DrmMenu = () => ( + <Menu> + <Menu.DashboardItem /> + <Section label="Catalogue" /> + <Menu.ResourceItem name="sites" /> + <Menu.ResourceItem name="campaigns" /> + <Menu.ResourceItem name="transects" /> + <Menu.ResourceItem name="passes" /> + <Menu.ResourceItem name="pass_groups" /> + <Section label="Library" /> + <Menu.ResourceItem name="videos" /> + <Menu.ResourceItem name="runs" /> + <Section label="Operations" /> + <Menu.ResourceItem name="devices" /> + <Menu.ResourceItem name="presets" /> + <Menu.Item to="/performance" primaryText="Performance" leftIcon={<SpeedIcon />} /> + <Menu.ResourceItem name="stored_objects" /> + <Menu.Item + to="/stored_objects/upload" + primaryText="Upload" + leftIcon={<CloudUploadIcon />} + /> + </Menu> +); + +export default DrmMenu; diff --git a/src/maps/Layers.tsx b/src/maps/Layers.tsx index bad1601..7aa0844 100644 --- a/src/maps/Layers.tsx +++ b/src/maps/Layers.tsx @@ -2,34 +2,27 @@ import { LayersControl, TileLayer } from 'react-leaflet'; import { useTheme } from 'react-admin'; export const BaseLayers = () => { - const { BaseLayer, Overlay } = LayersControl; - const [theme, setTheme] = useTheme(); + const { BaseLayer } = LayersControl; + const [theme] = useTheme(); return ( <LayersControl> - <BaseLayer - name="CARTO Dark" - checked={theme === 'dark'} - > - {/* {theme === 'dark' ? ( - ) : ( - )} */} + <BaseLayer name="CARTO Dark" checked={theme === 'dark'}> <TileLayer - url='https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png' + url="https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png" attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>' - subdomains='abcd' + subdomains="abcd" maxZoom={20} zIndex={0} /> </BaseLayer> - <BaseLayer - name="OpenStreetMap" - checked={theme !== 'dark'} - > - <TileLayer attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' + <BaseLayer name="OpenStreetMap" checked={theme !== 'dark'}> + <TileLayer + attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" opacity={0.5} /> </BaseLayer> - </LayersControl>) -}; \ No newline at end of file + </LayersControl> + ); +}; diff --git a/src/maps/Legend.tsx b/src/maps/Legend.tsx deleted file mode 100644 index ec6235a..0000000 --- a/src/maps/Legend.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useEffect } from 'react'; -import { useMap } from 'react-leaflet'; -import L from 'leaflet'; -import 'leaflet/dist/leaflet.css'; -import 'leaflet.awesome-markers/dist/leaflet.awesome-markers.css'; -import 'leaflet.awesome-markers/dist/leaflet.awesome-markers.js'; - -const Legend = () => { - const map = useMap(); // Get the map instance - - useEffect(() => { - const legend = L.control({ position: 'bottomright' }); - - legend.onAdd = () => { - const div = L.DomUtil.create('div', 'info legend'); - div.innerHTML = ` - <div style=" - background: rgba(255, 255, 255, 0.5); - padding: 10px; - border: 2px solid black; - border-radius: 5px; - box-shadow: 0 0 15px rgba(0, 0, 0, 0.2); - "> - <h4 style="margin-top: 0;">Legend</h4> - <div style="display: flex; align-items: center; margin-bottom: 5px;"> - <i class="fa fa-temperature-low" style="color: yellow; background: blue; width: 18px; height: 18px; display: inline-block; margin-right: 5px; text-align: center; line-height: 18px;"></i> - Sensor - </div> - <div style="display: flex; align-items: center; margin-bottom: 5px;"> - <i class="fa fa-trowel" style="color: black; background: green; width: 18px; height: 18px; display: inline-block; margin-right: 5px; text-align: center; line-height: 18px;"></i> - Plot - </div> - <div style="display: flex; align-items: center;"> - <i class="fa fa-clipboard" style="color: yellow; background: red; width: 18px; height: 18px; display: inline-block; margin-right: 5px; text-align: center; line-height: 18px;"></i> - Soil Profile - </div> - </div> - `; - return div; - }; - - legend.addTo(map); - return () => { - legend.remove(); - }; - }, [map]); - - return null; -}; - -export default Legend; diff --git a/src/maps/Overview.tsx b/src/maps/Overview.tsx new file mode 100644 index 0000000..45a0406 --- /dev/null +++ b/src/maps/Overview.tsx @@ -0,0 +1,82 @@ +import { CSSProperties } from 'react'; +import { Loading, useRedirect } from 'react-admin'; +import { MapContainer, Marker, Polyline, Tooltip } from 'react-leaflet'; +import { latLngBounds, LatLngBoundsExpression, LatLngTuple } from 'leaflet'; +import 'leaflet/dist/leaflet.css'; +import { Alert, Typography } from '@mui/material'; + +import type { Site, Transect } from '../contract'; +import { BaseLayers } from './Layers'; +import { useSiteGeometry, useTransectGeometry } from './useGeometry'; + +const WORLD: LatLngBoundsExpression = [ + [-60, -180], + [60, 180], +]; + +const endPoints = (transect: Transect): [LatLngTuple, LatLngTuple] => [ + [transect.start_lat, transect.start_lon], + [transect.end_lat, transect.end_lon], +]; + +type LocatedSite = Site & { latitude: number; longitude: number }; + +const located = (sites: Site[]): LocatedSite[] => + sites.filter( + (site): site is LocatedSite => site.latitude != null && site.longitude != null, + ); + +/** Every transect and every located site on one map. */ +const Overview = ({ height = '460px' }: { height?: string }) => { + const redirect = useRedirect(); + const transects = useTransectGeometry(); + const sites = useSiteGeometry(); + + if (transects.isPending || sites.isPending) return <Loading />; + + const lines = transects.data ?? []; + const markers = located(sites.data ?? []); + if (!lines.length && !markers.length) { + return <Alert severity="info">Nothing mapped yet. Add a site or a transect.</Alert>; + } + + const points: LatLngTuple[] = [ + ...lines.flatMap(endPoints), + ...markers.map((site): LatLngTuple => [site.latitude, site.longitude]), + ]; + const style: CSSProperties = { width: '100%', height }; + + return ( + <MapContainer + style={style} + minZoom={2} + bounds={points.length ? latLngBounds(points).pad(0.5) : WORLD} + scrollWheelZoom + > + <BaseLayers /> + {markers.map(site => ( + <Marker + key={site.id} + position={[site.latitude, site.longitude]} + eventHandlers={{ click: () => redirect('show', 'sites', site.id) }} + > + <Tooltip>{site.name}</Tooltip> + </Marker> + ))} + {lines.map(transect => ( + <Polyline + key={transect.id} + positions={endPoints(transect)} + pathOptions={{ weight: 6 }} + eventHandlers={{ click: () => redirect('show', 'transects', transect.id) }} + > + <Tooltip> + <Typography variant="subtitle2">{transect.name}</Typography> + </Tooltip> + </Polyline> + ))} + </MapContainer> + ); +}; + +export default Overview; diff --git a/src/maps/Sites.tsx b/src/maps/Sites.tsx new file mode 100644 index 0000000..6642e7e --- /dev/null +++ b/src/maps/Sites.tsx @@ -0,0 +1,110 @@ +import { Link, Loading, useCreatePath, useRedirect } from 'react-admin'; +import { MapContainer, Marker, Popup, Tooltip } from 'react-leaflet'; +import L from 'leaflet'; +import 'leaflet/dist/leaflet.css'; +import markerIcon from 'leaflet/dist/images/marker-icon.png'; +import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png'; +import markerShadow from 'leaflet/dist/images/marker-shadow.png'; +import { Alert, Button, Typography } from '@mui/material'; +import { BaseLayers } from './Layers'; +import { useSiteGeometry } from './useGeometry'; +import type { Site } from '../contract'; + +// Leaflet derives its marker URLs by parsing the stylesheet, which the bundler rewrites. +// An empty `imagePath` stops it prefixing the bundled URLs below with the detected one. +L.Icon.Default.imagePath = ''; +L.Icon.Default.mergeOptions({ + iconUrl: markerIcon, + iconRetinaUrl: markerIcon2x, + shadowUrl: markerShadow, +}); + +/** A site whose representative point is set, so it can be drawn. */ +type LocatedSite = Site & { latitude: number; longitude: number }; + +const located = (sites: Site[]): LocatedSite[] => + sites.filter( + (site): site is LocatedSite => site.latitude != null && site.longitude != null, + ); + +const MAP_STYLE = { width: '100%', height: '400px' }; + +const SitePopup = ({ site }: { site: LocatedSite }) => { + const createPath = useCreatePath(); + return ( + <Popup> + <Typography variant="subtitle1">{site.name}</Typography> + {site.country || site.region ? ( + <> + {[site.region, site.country].filter(Boolean).join(', ')} + <br /> + </> + ) : null} + <b>Coordinates</b>: {`${site.latitude}°, ${site.longitude}°`} + <br /> + <Link to={createPath({ resource: 'sites', type: 'show', id: site.id })}> + <Button variant="contained" color="primary" size="small"> + View + </Button> + </Link> + </Popup> + ); +}; + +/** Every site with coordinates, as clickable markers. Fetched independently of the list page. */ +export const SiteMapAll = () => { + const redirect = useRedirect(); + const { data, isPending } = useSiteGeometry(); + + if (isPending) return <Loading />; + + const sites = located(data ?? []); + if (sites.length === 0) { + return ( + <Alert severity="info" sx={{ mb: 2 }}> + No site has coordinates yet, so there is nothing to map. Add a latitude and + longitude when you create or edit a site. + </Alert> + ); + } + + const bounds = L.latLngBounds( + sites.map(site => [site.latitude, site.longitude] as [number, number]), + ).pad(0.5); + + return ( + <MapContainer style={MAP_STYLE} minZoom={2} bounds={bounds} scrollWheelZoom> + <BaseLayers /> + {sites.map(site => ( + <Marker + key={site.id} + position={[site.latitude, site.longitude]} + eventHandlers={{ click: () => redirect('show', 'sites', site.id) }} + > + <Tooltip>{site.name}</Tooltip> + <SitePopup site={site} /> + </Marker> + ))} + </MapContainer> + ); +}; + +/** One site's representative point, for the Show page. */ +export const SiteMapOne = ({ record }: { record: Site }) => { + if (record.latitude == null || record.longitude == null) { + return ( + <Alert severity="info"> + No coordinates recorded. Edit the site to place it on the map. + </Alert> + ); + } + const position: [number, number] = [record.latitude, record.longitude]; + return ( + <MapContainer style={MAP_STYLE} center={position} zoom={11} scrollWheelZoom> + <BaseLayers /> + <Marker position={position}> + <Tooltip permanent>{record.name}</Tooltip> + </Marker> + </MapContainer> + ); +}; diff --git a/src/maps/Transects.tsx b/src/maps/Transects.tsx index e103b90..ff81e8f 100644 --- a/src/maps/Transects.tsx +++ b/src/maps/Transects.tsx @@ -1,138 +1,89 @@ -import { - useRedirect, - Button, - Link, - useCreatePath, - Loading, - useGetList, - useRecordContext, -} from 'react-admin'; -import { - MapContainer, - TileLayer, - Polygon, - Tooltip, - FeatureGroup, - Popup, - Marker, -} from 'react-leaflet'; -import { EditControl } from "react-leaflet-draw" -import { BaseLayers } from './Layers'; +import { CSSProperties } from 'react'; +import { Loading, useRedirect } from 'react-admin'; +import { MapContainer, Polyline, Tooltip } from 'react-leaflet'; +import { latLngBounds, LatLngBoundsExpression, LatLngTuple } from 'leaflet'; +import 'leaflet/dist/leaflet.css'; import { Typography } from '@mui/material'; -import { useEffect, useRef } from 'react'; -export const TransectMapAll = () => { - const redirect = useRedirect(); - const createPath = useCreatePath(); +import type { Transect } from '../contract'; +import { BaseLayers } from './Layers'; +import { useTransectGeometry } from './useGeometry'; - const { data, total, isLoading, error } = useGetList( - 'transects', {} - ); +const MAP_STYLE: CSSProperties = { width: '100%', height: '500px' }; +const WORLD: LatLngBoundsExpression = [ + [-60, -180], + [60, 180], +]; - if (isLoading) { - return <Loading />; - } +const endPoints = (transect: Transect): [LatLngTuple, LatLngTuple] => [ + [transect.start_lat, transect.start_lon], + [transect.end_lat, transect.end_lon], +]; - if (data.length === 0 || data === undefined) { - return; - } +const boundsOf = (transects: Transect[]): LatLngBoundsExpression => { + const points = transects.flatMap(endPoints); + return points.length ? latLngBounds(points).pad(0.5) : WORLD; +}; - const bounds = L.latLngBounds( - data.map( - (transect) => ( - [[transect.latitude_start, transect.longitude_start], - [transect.latitude_end, transect.longitude_end]] - ) - ) - ).pad(1); +const metres = (value: number | null | undefined) => (value == null ? '—' : `${value} m`); - return ( - <MapContainer - style={{ width: '100%', height: '500px' }} - minZoom={2} - zoom={2} - // Set bounds to a wider area than the calculated bounds to allow for - // the user to zoom out and see the whole area - bounds={bounds} - scrollWheelZoom={true} - > - <BaseLayers /> - {data.map( - (transect, index) => ( - <Polygon - key={index} - pathOptions={{ fillOpacity: 0.25, weight: 5 }} // Increased weight for thicker lines - eventHandlers={{ - click: () => { - redirect('show', 'transects', transect['id']); - } - }} - // Structure as transect.latitude_start, transect.longitude_start, transect.latitude_end, transect.longitude_end - positions={[[transect.latitude_start, transect.longitude_start], [transect.latitude_end, transect.longitude_end]]} - > - <Marker - key={index} - position={[transect.latitude_start, transect.longitude_start]} - > - <Tooltip permanent>{transect.name}</Tooltip> - <Popup - // permanent - // interactive={true} - > - <Typography variant="subtitle1" >{transect.name}</Typography> - <b>Length</b>: {transect.length ? transect.length : "N/A"} (m)<br /> - <b>Depth</b>: {transect.depth ? transect.depth : "N/A"} (m)<br /> - <b>Coordinates</b>: - <br />  <b>From</b>: {`${transect.latitude_start}°, ${transect.longitude_start}°`} - <br />  <b>To</b>: {`${transect.latitude_end}°, ${transect.longitude_end}°`}<br /> - <b>Files</b>: {transect.inputs?.length ? transect.inputs.length : 0}<br /> - <b>Submissions</b>: {transect.submissions?.length ? transect.submissions.length : 0}<br /> - <Link to={createPath({ resource: 'transects', type: 'show', id: transect.id })}> - <Button variant="contained" color="primary">View</Button> - </Link> - </Popup> - </Marker> - </Polygon> - ) - )} - </MapContainer > - ); -}; +const TransectSummary = ({ transect }: { transect: Transect }) => ( + <> + <Typography variant="subtitle2">{transect.name}</Typography> + <b>Length</b>: {metres(transect.length_m)} + <br /> + <b>Depth</b>: {metres(transect.depth_m)} + <br /> + <b>From</b>: {`${transect.start_lat}°, ${transect.start_lon}°`} + <br /> + <b>To</b>: {`${transect.end_lat}°, ${transect.end_lon}°`} + </> +); -export const TransectMapOne = ({ record }) => { - if (record === null) { - return - } - // Set bounds to the first transect (CHANGE THIS) - const bounds = L.latLngBounds( - [[record.latitude_start, record.longitude_start], - [record.latitude_end, record.longitude_end]] - ).pad(1); +/** Every transect matching `filter` as a clickable line. */ +export const TransectMapAll = ({ filter }: { filter?: Record<string, unknown> }) => { + const redirect = useRedirect(); + const { data, isPending } = useTransectGeometry(filter); + + if (isPending) return <Loading />; + const transects = data ?? []; + // The list's own empty state carries the "define a transect" message. + if (!transects.length) return null; return ( <MapContainer - style={{ width: '100%', height: '500px' }} - // Set bounds to a wider area than the calculated bounds to allow for - // the user to zoom out and see the whole area - bounds={bounds} - scrollWheelZoom={true} + style={MAP_STYLE} + minZoom={2} + bounds={boundsOf(transects)} + scrollWheelZoom > <BaseLayers /> - <Polygon - pathOptions={{ fillOpacity: 0.25, weight: 20 }} // Increased weight for thicker lines - positions={[[record.latitude_start, record.longitude_start], [record.latitude_end, record.longitude_end]]} - > - <Tooltip - permanent + {transects.map(transect => ( + <Polyline + key={transect.id} + positions={endPoints(transect)} + pathOptions={{ weight: 6 }} + eventHandlers={{ + click: () => redirect('show', 'transects', transect.id), + }} > - <Typography variant="subtitle1">{record.name}</Typography> - <b>Length</b>: {record.length ? record.length : "N/A"} (m)<br /> - <b>Depth</b>: {record.depth ? record.depth : "N/A"} (m)<br /> - <b>Coordinates</b>: - <br />  <b>From</b>: {`${record.latitude_start}°, ${record.longitude_start}°`} - <br />  <b>To</b>: {`<${record.latitude_end}°, ${record.longitude_end}°`}<br /> - </Tooltip> - </Polygon> + <Tooltip> + <TransectSummary transect={transect} /> + </Tooltip> + </Polyline> + ))} </MapContainer> ); }; + +/** A single survey line, with its details pinned open. */ +export const TransectMapOne = ({ record }: { record: Transect }) => ( + <MapContainer style={MAP_STYLE} bounds={boundsOf([record])} scrollWheelZoom> + <BaseLayers /> + <Polyline positions={endPoints(record)} pathOptions={{ weight: 8 }}> + <Tooltip permanent> + <TransectSummary transect={record} /> + </Tooltip> + </Polyline> + </MapContainer> +); diff --git a/src/maps/useGeometry.ts b/src/maps/useGeometry.ts new file mode 100644 index 0000000..5133c0d --- /dev/null +++ b/src/maps/useGeometry.ts @@ -0,0 +1,23 @@ +import { useGetList } from 'react-admin'; + +import type { Site, Transect } from '../contract'; + +// One request per resource rather than one per page of a list, so cap what a map +// will draw. Pagination and sort are fixed so every map shares one react-query +// cache entry instead of refetching the same 500 rows. +export const MAX_DRAWN = 500; + +const PARAMS = { + pagination: { page: 1, perPage: MAX_DRAWN }, + sort: { field: 'name', order: 'ASC' as const }, +}; + +export const useSiteGeometry = () => useGetList<Site>('sites', PARAMS); + +// An empty filter normalises to no filter, so a filterless list shares the +// overview map's cache entry. +export const useTransectGeometry = (filter?: Record<string, unknown>) => + useGetList<Transect>( + 'transects', + filter && Object.keys(filter).length ? { ...PARAMS, filter } : PARAMS, + ); diff --git a/src/objects/ObjectEdit.tsx b/src/objects/ObjectEdit.tsx deleted file mode 100644 index 36d573b..0000000 --- a/src/objects/ObjectEdit.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint react/jsx-key: off */ -import * as React from 'react'; -import { useParams } from 'react-router'; -import { - Edit, - SimpleForm, - TextField, - TextInput, - required, - List, - Datagrid, - ResourceContextProvider, - EditButton, - TranslatableInputs, - NumberInput, - FileInput, - FileField, - ReferenceInput, - SelectInput, -} from 'react-admin'; - -const SubmissionEdit = () => { - return ( - <Edit redirect="show" mutationMode="pessimistic"> - <SimpleForm> - <TextInput source="id" disabled /> - <TextInput source="notes" multiline /> - - <ReferenceInput - source="transect_id" - reference="transects" - > - <SelectInput - optionText={ - (record) => `${record.name} (${record.latitude_start}°, ${record.longitude_start}°) - (${record.latitude_end}°, ${record.longitude_end}°)` - } - /> - </ReferenceInput> - </SimpleForm> - </Edit> - ) -}; - -export default SubmissionEdit; diff --git a/src/objects/ObjectList.tsx b/src/objects/ObjectList.tsx deleted file mode 100644 index e6ff70c..0000000 --- a/src/objects/ObjectList.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { - List, - Datagrid, - TextField, - usePermissions, - TopToolbar, - ExportButton, - NumberField, - DateField, - BooleanField, - FunctionField, - useRedirect, - Button, - useRecordContext, - useListContext, - useCreate, - useCreatePath, - Link, - ReferenceField, -} from "react-admin"; -import 'react-dropzone-uploader/dist/styles.css' -import { FilePond } from 'react-filepond'; -import 'filepond/dist/filepond.min.css'; -import { useEffect } from "react"; - -import { Card, CardContent, Typography } from '@mui/material'; -import MailIcon from '@mui/icons-material/MailOutline'; -import CategoryIcon from '@mui/icons-material/LocalOffer'; -import { stopPropagation } from "ol/events/Event"; -import { FilePondUploaderList } from '../uploader/FilePond'; - -const CreateSubmissionButton = () => { - const listContext = useListContext(); - const redirect = useRedirect(); - const [create, { data, loading, loaded, error }] = useCreate(); - - useEffect(() => { - if (!data) return; - if (data.id) { - redirect('show', 'submissions', data.id); - } - }, [data]); - - // Create a list of input_association objects from the selected video ids - const input_associations = listContext.selectedIds.map((id, index) => { - return { - input_object_id: id, - processing_order: index + 1 - } - }); - - // Create a list of selected videos that have not completed uploading to - // disable the button if any of the selected videos are incomplete - const selectedIncompleteData = listContext.selectedIds.some(id => { - const record = listContext.data.find(data => data.id === id); - return record.all_parts_received === false; - }); - const handleClick = () => { - create('submissions', { data: { input_associations: input_associations } }) - } - if (listContext.selectedIds.length > 2) { - return <Button - variant="contained" - color="error" - disabled={true} - >Maximum 2 videos can be selected</Button> - } - if (selectedIncompleteData) { - return <Button - variant="contained" - color="error" - disabled={selectedIncompleteData} - >Deselect incomplete data</Button> - } - return <Button - variant="contained" - color="success" - disabled={selectedIncompleteData} - onClick={handleClick}>{ - listContext.selectedIds.length === 1 ? - 'Create submission from selected video' : 'Create submission from selected videos' - }</Button> -}; - - -const ObjectListActions = () => { - return ( - - <TopToolbar > - - <ExportButton /> - </TopToolbar> - ); -} -const TransectNameField = () => { - const record = useRecordContext(); - const createPath = useCreatePath(); - if (!record) return <Loading />; - let path = null; - - if (record.transect) { - path = createPath({ - resource: 'transects', - type: 'show', - id: record.transect.id, - }); - } - - return ( - <Link to={path} onClick={stopPropagation}> - <TextField source="transect.name" label="Area" emptyText='No associated transect' /> - </Link> - ); -} -const ObjectList = () => { - const FieldWrapper = ({ children, label }) => children; - const { permissions } = usePermissions(); - - return ( - <> - <List disableSyncWithLocation - actions={<ObjectListActions />} - perPage={10} - sort={{ field: 'time_added_utc', order: 'DESC' }} - queryOptions={{ refetchInterval: 10000 }} - empty={false} - > - <FilePondUploaderList /> - <Datagrid - bulkActionButtons={<CreateSubmissionButton />} - rowClick="show" - > - <DateField - label="Submitted at" - source="time_added_utc" - showTime - transform={value => new Date(value + 'Z')} // Fix UTC time - /> - <TextField source="filename" /> - <FunctionField label="Size (MB)" render={(record) => { return (record.size_bytes / 1000000).toFixed(2); }} /> - <NumberField source="time_seconds" label="Time (s)" /> - <TextField source="processing_message" /> - <BooleanField label="Upload complete" source="all_parts_received" /> - <BooleanField label="Processing started" source="processing_has_started" /> - <BooleanField label="Processing successful" source="processing_completed_successfully" /> - <FieldWrapper label="Transect"><TransectNameField /></FieldWrapper> - <FunctionField - label="Associated submissions" - render={record => record.input_associations.length} - /> - {permissions === 'admin' ? (<ReferenceField source="owner" reference="users" link="show"> - <FunctionField render={record => `${record.firstName} ${record.lastName}`} source="Owner" /> - </ReferenceField>) : null} - </Datagrid> - </List > - </> - ) -}; - -export default ObjectList; diff --git a/src/objects/ObjectShow.tsx b/src/objects/ObjectShow.tsx deleted file mode 100644 index 08ef779..0000000 --- a/src/objects/ObjectShow.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useEffect, useState } from 'react'; -import dataProvider from '../../../.history/deepreefmap-ui/src/dataProvider/index_20240424113643'; -import { Typography } from '@mui/material'; -import { - Show, - SimpleShowLayout, - TextField, - NumberField, - EditButton, - TopToolbar, - DeleteButton, - usePermissions, - DateField, - BooleanField, - Button, - useDataProvider, - useRecordContext, - useRefresh, - ArrayField, - Datagrid, - useRedirect, - useCreate, - FunctionField, - useCreatePath, - Link, - Loading, - ReferenceField, -} from 'react-admin'; // eslint-disable-line import/no-unresolved -import { stopPropagation } from 'ol/events/Event'; -import { TransectMapOne } from '../maps/Transects'; - -const ObjectShowActions = () => { - const dataProvider = useDataProvider(); - const record = useRecordContext(); - const refresh = useRefresh(); - const timeout = ms => new Promise(res => setTimeout(res, ms)); - if (!record) return <Loading />; - const RegenerateStatisticsButton = () => { - return <Button - type="button" - variant="contained" - color="primary" - label="Regenerate Video Statistics" - disabled={record.all_parts_received === false} - onClick={() => dataProvider.regenerateVideoStatistics(record.id).then(() => timeout(3000)).then(() => refresh())} - />; - }; - - const CreateSubmissionButton = () => { - const record = useRecordContext(); - const redirect = useRedirect(); - const [create, { data, loading, loaded, error }] = useCreate(); - useEffect(() => { - if (!data) return; - if (data.id) { - redirect('show', 'submissions', data.id); - } - }, [data]); - const handleClick = () => { - create('submissions', - { - data: { - input_associations: [ - { - input_object_id: record.id, - processing_order: 1 - }], - transect_id: record.transect.id, - } - }) - } - if (!record) return <Loading />; - return <Button - type="button" - variant="contained" - color="success" - label="Create Submission from this video" - disabled={record.all_parts_received === false} - onClick={handleClick} - />; - }; - return ( - <TopToolbar> - <><CreateSubmissionButton /><RegenerateStatisticsButton /><EditButton /><DeleteButton /></> - </TopToolbar > - ); -} - -const TransectNameField = () => { - const record = useRecordContext(); - const createPath = useCreatePath(); - if (!record) return <Loading />; - let path = null; - - if (record.transect) { - path = createPath({ - resource: 'transects', - type: 'show', - id: record.transect.id, - }); - } - - return ( - <><Link to={path} onClick={stopPropagation}> - <TextField source="transect.name" label="Area" emptyText='No associated transect' /> - </Link> - <TransectMapOne record={record.transect} /> - </> - ); -} - - -const ObjectShow = (props) => { - const FieldWrapper = ({ children, label }) => children; - const redirect = useRedirect(); - const redirectToSubmission = (id, basePath, record) => { - redirect('show', 'submissions', record.submission_id); - }; - const { permissions } = usePermissions(); - - return ( - - <Show - actions={<ObjectShowActions />} - {...props} - queryOptions={{ refetchInterval: 5000 }} - > - <SimpleShowLayout> - <FunctionField render={(record) => { - if (record.all_parts_received === false) { - return <Typography variant="h5" color='error' gutterBottom >Video upload incomplete</Typography> - } - }} /> - <TextField source="id" /> - {permissions === 'admin' ? (<ReferenceField source="owner" reference="users" link="show"> - <FunctionField render={record => `${record.firstName} ${record.lastName}`} source="Owner" /> - </ReferenceField>) : null} - <TextField source="filename" /> - <NumberField source="size_bytes" /> - <DateField - label="Submitted at" - source="time_added_utc" - showTime - transform={value => new Date(value + 'Z')} // Fix UTC time - /> - <TextField source="hash_md5sum" label="MD5" /> - <TextField source="notes" /> - <NumberField source="time_seconds" /> - <NumberField source="fps" /> - <TextField source="processing_message" /> - <BooleanField source="processing_completed_successfully" /> - <BooleanField source="processing_has_started" /> - <ArrayField source="input_associations" label="Associated submissions"> - <Datagrid bulkActionButtons={false} rowClick={redirectToSubmission}> - <TextField source="submission_id" label="Submission ID" /> - <ReferenceField source="submission_id" reference="submissions" label="Name" link={false}> - <TextField source="name" /> - </ReferenceField> - <ReferenceField source="submission_id" reference="submissions" label="Run status" link={false}> - <TextField source="run_status[0].status" emptyText='No jobs submitted' /> - </ReferenceField> - </Datagrid> - </ArrayField> - <FieldWrapper label="Associated transect"> - <TransectNameField /> - </FieldWrapper> - </SimpleShowLayout> - </Show > - ) -}; - -export default ObjectShow; \ No newline at end of file diff --git a/src/objects/index.tsx b/src/objects/index.tsx deleted file mode 100644 index a5f14cb..0000000 --- a/src/objects/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import ObjectEdit from './ObjectEdit'; -import ObjectList from './ObjectList'; -import ObjectShow from './ObjectShow'; -import VideocamIcon from '@mui/icons-material/Videocam'; - -export default { - edit: ObjectEdit, - list: ObjectList, - show: ObjectShow, - icon: VideocamIcon, - options: { - label: 'Videos', - }, -}; diff --git a/src/passGroups/PassGroupCreate.tsx b/src/passGroups/PassGroupCreate.tsx new file mode 100644 index 0000000..1240c7f --- /dev/null +++ b/src/passGroups/PassGroupCreate.tsx @@ -0,0 +1,13 @@ +import { Create, SimpleForm } from 'react-admin'; + +import PassGroupInputs from './PassGroupInputs'; + +const PassGroupCreate = () => ( + <Create redirect="list"> + <SimpleForm defaultValues={{ description: '' }}> + <PassGroupInputs /> + </SimpleForm> + </Create> +); + +export default PassGroupCreate; diff --git a/src/passGroups/PassGroupEdit.tsx b/src/passGroups/PassGroupEdit.tsx new file mode 100644 index 0000000..974961e --- /dev/null +++ b/src/passGroups/PassGroupEdit.tsx @@ -0,0 +1,27 @@ +import { Edit, SaveButton, SimpleForm, Toolbar, TopToolbar } from 'react-admin'; + +import { TombstoneButton } from '../components'; +import PassGroupInputs from './PassGroupInputs'; + +// Rows are tombstoned by the sync contract, so the default toolbar's delete is wrong here. +const PassGroupEditToolbar = () => ( + <Toolbar> + <SaveButton /> + </Toolbar> +); + +const PassGroupEditActions = () => ( + <TopToolbar> + <TombstoneButton noun="group" /> + </TopToolbar> +); + +const PassGroupEdit = () => ( + <Edit redirect="list" mutationMode="pessimistic" actions={<PassGroupEditActions />}> + <SimpleForm toolbar={<PassGroupEditToolbar />}> + <PassGroupInputs /> + </SimpleForm> + </Edit> +); + +export default PassGroupEdit; diff --git a/src/passGroups/PassGroupInputs.tsx b/src/passGroups/PassGroupInputs.tsx new file mode 100644 index 0000000..15a0f3d --- /dev/null +++ b/src/passGroups/PassGroupInputs.tsx @@ -0,0 +1,38 @@ +import { required, TextInput } from 'react-admin'; +import { Grid } from '@mui/material'; + +const PassGroupInputs = () => ( + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <TextInput + source="name" + validate={required()} + helperText="The survey event, for example Autumn resurvey." + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <TextInput + source="period_label" + label="Period label" + helperText="Where the event sits on a timeline, for example 2024 spring. Orders the statistics series." + fullWidth + /> + </Grid> + <Grid size={12}> + <TextInput source="description" multiline rows={3} fullWidth /> + </Grid> + </Grid> +); + +export default PassGroupInputs; diff --git a/src/passGroups/PassGroupList.tsx b/src/passGroups/PassGroupList.tsx new file mode 100644 index 0000000..ec04300 --- /dev/null +++ b/src/passGroups/PassGroupList.tsx @@ -0,0 +1,61 @@ +import { + CreateButton, + Datagrid, + DateField, + ExportButton, + List, + TextField, + TopToolbar, +} from 'react-admin'; +import { Box, Typography } from '@mui/material'; + +import { useCanAuthor } from '../permissions'; + +const PassGroupListActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + {canAuthor && <CreateButton />} + <ExportButton /> + </TopToolbar> + ); +}; + +const PassGroupEmpty = () => { + const canAuthor = useCanAuthor(); + return ( + <Box + sx={{ + textAlign: 'center', + m: 4, + }} + > + <Typography variant="h6" gutterBottom> + No groups yet + </Typography> + <Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}> + A group names one survey event, finer than a campaign: one expedition can + resurvey the same transect twice. Assign passes to a group from the passes + list, and the statistics series orders itself by period label. + </Typography> + {canAuthor && <CreateButton label="Create the first group" />} + </Box> + ); +}; + +const PassGroupList = () => ( + <List + actions={<PassGroupListActions />} + sort={{ field: 'period_label', order: 'ASC' }} + perPage={25} + empty={<PassGroupEmpty />} + > + <Datagrid rowClick="edit" bulkActionButtons={false}> + <TextField source="name" /> + <TextField source="period_label" label="Period label" emptyText="—" /> + <DateField source="updated_at" label="Updated" showTime /> + </Datagrid> + </List> +); + +export default PassGroupList; diff --git a/src/passGroups/index.tsx b/src/passGroups/index.tsx new file mode 100644 index 0000000..9305476 --- /dev/null +++ b/src/passGroups/index.tsx @@ -0,0 +1,17 @@ +import WorkspacesIcon from '@mui/icons-material/Workspaces'; + +import PassGroupCreate from './PassGroupCreate'; +import PassGroupEdit from './PassGroupEdit'; +import PassGroupList from './PassGroupList'; + +// No show: a group is three fields, the edit form is the whole story. +export default { + list: PassGroupList, + create: PassGroupCreate, + edit: PassGroupEdit, + icon: WorkspacesIcon, + recordRepresentation: 'name', + options: { + label: 'Groups', + }, +}; diff --git a/src/passes/DirectionField.tsx b/src/passes/DirectionField.tsx new file mode 100644 index 0000000..bc7a092 --- /dev/null +++ b/src/passes/DirectionField.tsx @@ -0,0 +1,46 @@ +import { useRecordContext } from 'react-admin'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { Stack } from '@mui/material'; + +import { DIRECTION_VALUES, Direction } from '../contract'; + +const DIRECTION_LABELS: Record<Direction, string> = { + forward: 'Forward', + reverse: 'Reverse', +}; + +const DIRECTION_ICONS: Record<Direction, typeof ArrowForwardIcon> = { + forward: ArrowForwardIcon, + reverse: ArrowBackIcon, +}; + +export const directionChoices = DIRECTION_VALUES.map(id => ({ + id, + name: DIRECTION_LABELS[id], +})); + +/** Which way the diver swam the tape, which is a property of the swim, not the clip. */ +export const DirectionField = ({ + emptyText = '—', +}: { + label?: string; + emptyText?: string; +}) => { + const record = useRecordContext(); + const value = record?.direction as Direction | null | undefined; + if (!value || !(value in DIRECTION_LABELS)) return <span>{emptyText}</span>; + const Icon = DIRECTION_ICONS[value]; + return ( + <Stack + direction="row" + spacing={0.5} + sx={{ + alignItems: 'center', + }} + > + <Icon fontSize="small" color="action" /> + <span>{DIRECTION_LABELS[value]}</span> + </Stack> + ); +}; diff --git a/src/passes/GroupPassesButton.tsx b/src/passes/GroupPassesButton.tsx new file mode 100644 index 0000000..fc35f2b --- /dev/null +++ b/src/passes/GroupPassesButton.tsx @@ -0,0 +1,168 @@ +import { useState } from 'react'; +import { + AutocompleteInput, + Button, + Form, + FormDataConsumer, + RadioButtonGroupInput, + ReferenceInput, + SaveButton, + TextInput, + required, + useCreate, + useListContext, + useNotify, + useRefresh, + useUnselectAll, + useUpdateMany, +} from 'react-admin'; +import { Dialog, DialogActions, DialogContent, DialogTitle, Typography } from '@mui/material'; +import WorkspacesIcon from '@mui/icons-material/Workspaces'; + +import type { PassGroup } from '../contract'; + +type GroupFormValues = { + mode: 'existing' | 'new' | 'ungroup'; + survey_group_id?: string; + name?: string; + period_label?: string; +}; + +const modeChoices = [ + { id: 'existing', name: 'Existing group' }, + { id: 'new', name: 'New group' }, + { id: 'ungroup', name: 'Ungroup' }, +]; + +// pass_groups has no `q` search, so the autocomplete matches on `name` instead. +const groupFilter = (searchText: string) => ({ name: searchText }); + +// A pass arrives labelled only with its campaign, which is too coarse for a series: one +// expedition can survey the same transect twice. The grouping is the curator's judgement +// and sits outside the sync contract, so a device re-pushing the pass cannot clobber it. +const GroupPassesButton = () => { + const [open, setOpen] = useState(false); + const { selectedIds } = useListContext(); + const [create] = useCreate<PassGroup>(); + const [updateMany, { isPending }] = useUpdateMany(); + const unselectAll = useUnselectAll('passes'); + const notify = useNotify(); + const refresh = useRefresh(); + + const apply = async ({ mode, survey_group_id, name, period_label }: GroupFormValues) => { + let groupId: string | null = mode === 'existing' ? (survey_group_id ?? null) : null; + if (mode === 'new') { + try { + const created = await create( + 'pass_groups', + { data: { name, period_label: period_label || null, description: '' } }, + { returnPromise: true }, + ); + if (!created) return; + groupId = created.id; + } catch (error) { + notify(error instanceof Error ? error.message : 'Could not create the group', { + type: 'error', + }); + return; + } + } + await updateMany( + 'passes', + { ids: selectedIds, data: { survey_group_id: groupId } }, + { + onSuccess: () => { + notify( + groupId + ? `Grouped ${selectedIds.length} passes` + : `Ungrouped ${selectedIds.length} passes`, + { type: 'info' }, + ); + unselectAll(); + refresh(); + setOpen(false); + }, + onError: error => + notify(error instanceof Error ? error.message : 'Grouping failed', { + type: 'error', + }), + }, + ); + }; + + return ( + <> + <Button + label="Group passes" + onClick={() => setOpen(true)} + startIcon={<WorkspacesIcon />} + /> + <Dialog open={open} onClose={() => setOpen(false)} fullWidth maxWidth="xs"> + <DialogTitle>Group passes</DialogTitle> + <Form onSubmit={apply as never} defaultValues={{ mode: 'existing' }}> + <DialogContent> + <RadioButtonGroupInput + source="mode" + label={false} + choices={modeChoices} + helperText={false} + /> + <FormDataConsumer<GroupFormValues>> + {({ formData }) => { + if (formData.mode === 'new') { + return ( + <> + <TextInput + source="name" + validate={required()} + fullWidth + /> + <TextInput + source="period_label" + label="Period label" + helperText="For example 2024 spring. Orders the statistics series." + fullWidth + /> + </> + ); + } + if (formData.mode === 'ungroup') { + return ( + <Typography variant="body2"> + Clears the group on the selected passes. + </Typography> + ); + } + return ( + <ReferenceInput + source="survey_group_id" + reference="pass_groups" + sort={{ field: 'name', order: 'ASC' }} + > + <AutocompleteInput + label="Group" + optionText="name" + filterToQuery={groupFilter} + validate={required()} + fullWidth + /> + </ReferenceInput> + ); + }} + </FormDataConsumer> + </DialogContent> + <DialogActions> + <Button label="ra.action.cancel" onClick={() => setOpen(false)} /> + <SaveButton + label="Apply" + disabled={isPending} + icon={<WorkspacesIcon />} + /> + </DialogActions> + </Form> + </Dialog> + </> + ); +}; + +export default GroupPassesButton; diff --git a/src/passes/PassEdit.tsx b/src/passes/PassEdit.tsx new file mode 100644 index 0000000..3271878 --- /dev/null +++ b/src/passes/PassEdit.tsx @@ -0,0 +1,35 @@ +import { Edit, SaveButton, SimpleForm, TextInput, Toolbar } from 'react-admin'; +import { Grid } from '@mui/material'; + +import { QualityInput } from '../components'; + +// A CRUD delete removes the row outright, while syncing clients expect a tombstone. +const SaveOnlyToolbar = () => ( + <Toolbar> + <SaveButton /> + </Toolbar> +); + +// The window, the direction and the camera orientation are what the desktop application +// recorded. Quality and notes are the two judgements a person adds afterwards. +const PassEdit = () => ( + <Edit redirect="show"> + <SimpleForm toolbar={<SaveOnlyToolbar />}> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <QualityInput fullWidth /> + </Grid> + <Grid size={12}> + <TextInput source="notes" multiline fullWidth /> + </Grid> + </Grid> + </SimpleForm> + </Edit> +); + +export default PassEdit; diff --git a/src/passes/PassList.tsx b/src/passes/PassList.tsx new file mode 100644 index 0000000..2819372 --- /dev/null +++ b/src/passes/PassList.tsx @@ -0,0 +1,136 @@ +import { + Datagrid, + ExportButton, + List, + ReferenceField, + ReferenceInput, + SelectInput, + TextField, + TextInput, + TopToolbar, +} from 'react-admin'; +import { Box, Typography } from '@mui/material'; + +import { asColumn, DurationField, QualityField, qualityChoices } from '../components'; +import { useCanAuthor } from '../permissions'; +import { DirectionField, directionChoices } from './DirectionField'; +import GroupPassesButton from './GroupPassesButton'; + +const WindowColumn = asColumn(DurationField); +const QualityColumn = asColumn(QualityField); + +const REFERENCE_SORT = { field: 'name', order: 'ASC' } as const; + +const passFilters = [ + <TextInput key="q" source="q" label="Search label or notes" alwaysOn />, + <ReferenceInput + key="transect_id" + source="transect_id" + reference="transects" + sort={REFERENCE_SORT} + alwaysOn + />, + <ReferenceInput + key="campaign_id" + source="campaign_id" + reference="campaigns" + sort={REFERENCE_SORT} + alwaysOn + />, + <ReferenceInput + key="survey_group_id" + source="survey_group_id" + reference="pass_groups" + sort={REFERENCE_SORT} + > + <SelectInput optionText="name" label="Group" /> + </ReferenceInput>, + <SelectInput key="quality" source="quality" choices={qualityChoices} />, + <SelectInput key="direction" source="direction" choices={directionChoices} />, +]; + +// No CreateButton: passes have no create view, the desktop application records them. +const PassListActions = () => ( + <TopToolbar> + <ExportButton /> + </TopToolbar> +); + +const PassEmpty = () => ( + <Box + sx={{ + textAlign: 'center', + m: 4, + }} + > + <Typography variant="h6" gutterBottom> + No passes recorded yet + </Typography> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Passes are created by the desktop application and arrive when an enrolled laptop + syncs. + </Typography> + </Box> +); + +const PassList = () => { + const canAuthor = useCanAuthor(); + return ( + <List + actions={<PassListActions />} + filters={passFilters} + sort={{ field: 'created_at', order: 'DESC' }} + perPage={25} + empty={<PassEmpty />} + > + <Datagrid + rowClick="show" + bulkActionButtons={canAuthor ? <GroupPassesButton /> : false} + > + <ReferenceField + source="transect_id" + reference="transects" + link="show" + sortable={false} + > + <TextField source="name" /> + </ReferenceField> + <ReferenceField + source="campaign_id" + reference="campaigns" + link="show" + sortable={false} + > + <TextField source="name" /> + </ReferenceField> + <ReferenceField + source="survey_group_id" + reference="pass_groups" + link="edit" + label="Group" + sortable={false} + emptyText="—" + > + <TextField source="name" /> + </ReferenceField> + <TextField source="label" emptyText="Unnamed" sortable={false} /> + <DirectionField label="Direction" /> + <QualityColumn label="Quality" source="quality" /> + {/* The API sorts on `quality` but not on the window bounds. */} + <WindowColumn + label="Window" + source="begin_s" + endSource="end_s" + sortable={false} + /> + </Datagrid> + </List> + ); +}; + +export default PassList; diff --git a/src/passes/PassShow.tsx b/src/passes/PassShow.tsx new file mode 100644 index 0000000..89f1835 --- /dev/null +++ b/src/passes/PassShow.tsx @@ -0,0 +1,292 @@ +import { + BooleanField, + Datagrid, + DateField, + EditButton, + FunctionField, + Labeled, + NumberField, + ReferenceField, + ReferenceManyField, + Show, + TextField, + TopToolbar, +} from 'react-admin'; +import { Box, Divider, Grid, Stack, Typography } from '@mui/material'; + +import { + asColumn, + DurationField, + QualityField, + SyncFields, + TombstoneButton, +} from '../components'; +import { useCanAuthor } from '../permissions'; +import { RunStatusChip } from '../runs/StatusField'; +import { DirectionField } from './DirectionField'; + +const DurationColumn = asColumn(DurationField); + +const PassShowActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + {canAuthor && <EditButton />} + <TombstoneButton noun="pass" /> + </TopToolbar> + ); +}; + +const SectionHeading = ({ title, hint }: { title: string; hint?: string }) => ( + <Box> + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + {title} + </Typography> + {hint && ( + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + display: 'block', + }} + > + {hint} + </Typography> + )} + </Box> +); + +const NoClips = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + p: 1, + }} + > + No clips are linked to this pass, so it cannot be reconstructed. + </Typography> +); + +const NoRuns = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + p: 1, + }} + > + This pass has not been processed yet. Runs appear once a desktop client reconstructs it + and syncs. + </Typography> +); + +const PassClips = () => ( + <ReferenceManyField + reference="pass_videos" + target="pass_id" + sort={{ field: 'ordinal', order: 'ASC' }} + perPage={25} + > + <Datagrid bulkActionButtons={false} empty={<NoClips />}> + <NumberField source="ordinal" label="Order" /> + <ReferenceField + source="video_id" + reference="videos" + link="show" + label="File" + sortable={false} + > + <TextField source="file_name" /> + </ReferenceField> + <ReferenceField + source="video_id" + reference="videos" + link={false} + label="Duration" + sortable={false} + > + <DurationColumn source="duration_s" /> + </ReferenceField> + <ReferenceField + source="video_id" + reference="videos" + link={false} + label="Captured" + sortable={false} + > + <DateField source="captured_at" showTime emptyText="—" /> + </ReferenceField> + </Datagrid> + </ReferenceManyField> +); + +const PassRuns = () => ( + <ReferenceManyField + reference="runs" + target="pass_id" + sort={{ field: 'created_at', order: 'DESC' }} + perPage={10} + > + <Datagrid bulkActionButtons={false} empty={<NoRuns />}> + <FunctionField + label="Status" + render={record => <RunStatusChip status={record.status as string} />} + /> + <TextField source="run_dir_name" label="Run directory" sortable={false} /> + <TextField source="mapping_backend" emptyText="—" sortable={false} /> + <TextField source="segmentation_model" emptyText="—" sortable={false} /> + <DateField source="started_at" showTime emptyText="—" /> + <DateField source="finished_at" showTime emptyText="—" /> + </Datagrid> + </ReferenceManyField> +); + +const PassShow = () => ( + <Show actions={<PassShowActions />}> + <Stack + spacing={2} + sx={{ + p: 2, + }} + > + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Label"> + <TextField source="label" emptyText="Unnamed" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Transect"> + <ReferenceField + source="transect_id" + reference="transects" + link="show" + emptyText="No transect: unscaled" + > + <TextField source="name" /> + </ReferenceField> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Campaign"> + <ReferenceField + source="campaign_id" + reference="campaigns" + link="show" + emptyText="—" + > + <TextField source="name" /> + </ReferenceField> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Group"> + <ReferenceField + source="survey_group_id" + reference="pass_groups" + link="edit" + emptyText="—" + > + <TextField source="name" /> + </ReferenceField> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Quality"> + <QualityField /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Window"> + <DurationField source="begin_s" endSource="end_s" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Direction"> + <DirectionField /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Upside down"> + <BooleanField source="upside_down" /> + </Labeled> + </Grid> + <Grid size={12}> + <Labeled label="Notes"> + <TextField source="notes" emptyText="—" /> + </Labeled> + </Grid> + </Grid> + + <Divider /> + <SectionHeading + title="Clips, in playing order" + hint="The window above is an offset into these clips played end to end." + /> + <PassClips /> + + <Divider /> + <SectionHeading title="Runs" /> + <PassRuns /> + + <Divider /> + <SyncFields /> + </Stack> + </Show> +); + +export default PassShow; diff --git a/src/passes/index.tsx b/src/passes/index.tsx new file mode 100644 index 0000000..060b82b --- /dev/null +++ b/src/passes/index.tsx @@ -0,0 +1,17 @@ +import ScubaDivingIcon from '@mui/icons-material/ScubaDiving'; + +import PassEdit from './PassEdit'; +import PassList from './PassList'; +import PassShow from './PassShow'; + +// No create: the desktop application is what records a pass. Edit is the correction +// path for one already synced. +export default { + list: PassList, + show: PassShow, + edit: PassEdit, + icon: ScubaDivingIcon, + options: { + label: 'Passes', + }, +}; diff --git a/src/performance/GroupTable.tsx b/src/performance/GroupTable.tsx new file mode 100644 index 0000000..d505aaa --- /dev/null +++ b/src/performance/GroupTable.tsx @@ -0,0 +1,92 @@ +import type { ReactNode } from 'react'; +import { Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material'; + +import type { PerformanceGroup } from '../contract'; +import { presetLabel } from '../runs/preset'; +import { relativeTime } from '../devices/RelativeDateField'; +import { columnMaxima, MetricCells, MetricHeaders, RunsCell } from './MetricCells'; +import { configLabel, groupTotals, metricStats, modelsLabel } from './statistics'; + +/** A column the caller puts in front of the shared models, config and metric ones. */ +type LeadColumn = { + header: string; + cell: (group: PerformanceGroup) => ReactNode; +}; + +// The registry groups by preset hash as well as name and version, and the console only +// warns against editing settings without a version bump. Two such edits are two rows +// that agree on everything else, so the hash has to be in the key. +const rowKey = (group: PerformanceGroup): string => + `${group.device_id ?? ''}|${presetLabel(group)}|${group.preset_hash ?? ''}|${modelsLabel(group)}|${configLabel(group)}`; + +const HeadCell = ({ children }: { children: ReactNode }) => ( + <TableCell sx={{ whiteSpace: 'nowrap' }}>{children}</TableCell> +); + +const Nowrap = ({ children }: { children: string }) => ( + <Typography variant="body2" sx={{ whiteSpace: 'nowrap' }}> + {children} + </Typography> +); + +/** Device-grain rows of the fleet summary, one shape wherever they are shown. */ +const GroupTable = ({ + groups, + lead, + sortKey, +}: { + groups: PerformanceGroup[]; + lead: LeadColumn[]; + sortKey: (group: PerformanceGroup) => string; +}) => { + const rows = groups + .map(group => ({ + group, + stats: metricStats(group), + totals: groupTotals(group), + })) + .sort((a, b) => sortKey(a.group).localeCompare(sortKey(b.group))); + const maxima = columnMaxima(rows); + return ( + <Table size="small"> + <TableHead> + <TableRow> + {lead.map(column => ( + <HeadCell key={column.header}>{column.header}</HeadCell> + ))} + <HeadCell>Models</HeadCell> + <HeadCell>Config</HeadCell> + <HeadCell>Runs</HeadCell> + <MetricHeaders /> + <HeadCell>Last run</HeadCell> + </TableRow> + </TableHead> + <TableBody> + {rows.map(({ group, stats, totals }) => ( + <TableRow key={rowKey(group)}> + {lead.map(column => ( + <TableCell key={column.header}>{column.cell(group)}</TableCell> + ))} + <TableCell> + <Typography variant="body2">{modelsLabel(group)}</Typography> + </TableCell> + <TableCell> + <Nowrap>{configLabel(group)}</Nowrap> + </TableCell> + <TableCell> + <RunsCell count={group.run_count} failed={group.failed_count} /> + </TableCell> + <MetricCells stats={stats} totals={totals} maxima={maxima} /> + <TableCell> + <Nowrap> + {group.last_run_at ? relativeTime(group.last_run_at) : '—'} + </Nowrap> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + ); +}; + +export default GroupTable; diff --git a/src/performance/MetricCells.tsx b/src/performance/MetricCells.tsx new file mode 100644 index 0000000..60fc311 --- /dev/null +++ b/src/performance/MetricCells.tsx @@ -0,0 +1,291 @@ +import { Box, TableCell, Tooltip, Typography } from '@mui/material'; +import { Link } from 'react-router-dom'; + +import type { Preset } from '../contract'; +import { formatDuration } from '../runs/duration'; +import { presetLabel } from '../runs/preset'; +import { HOT_FRACTION, MeterBar } from '../runs/StageBreakdown'; +import { byteScale, formatBytes } from '../videos/VideoFields'; +import { METRIC_KEYS } from './statistics'; +import type { MetricKey, MetricStats, MetricTotals, MetricUtilisation } from './statistics'; + +const METRIC_HEADERS: Record<MetricKey, string> = { + ram: 'Mean peak RAM', + swap: 'Mean peak swap', + vram: 'Mean peak VRAM', + duration: 'Mean duration', +}; + +// The sampler reads RAM from the run's own process tree and VRAM from the card, so +// the two are not on the same footing and the column that says so has to be visible. +const METRIC_NOTES: Partial<Record<MetricKey, string>> = { + vram: 'Sampled across the whole card, so the desktop and anything else using the GPU counts towards it. RAM covers the processes of the run alone.', +}; + +export const Dash = () => ( + <Typography variant="body2" component="span" sx={{ color: 'text.disabled' }}> + — + </Typography> +); + +export const RunsCell = ({ count, failed }: { count: number; failed: number }) => ( + <Typography variant="body2" component="span"> + {count} + {failed > 0 && ( + <Typography variant="body2" component="span" color="warning.main"> + {' '} + ({failed} failed) + </Typography> + )} + </Typography> +); + +/** "name vN" as a link when the preset still exists, plain text when it is gone. */ +export const PresetCell = ({ + group, + presets, +}: { + group: { preset_name?: string | null; preset_version?: number | null }; + presets: Preset[] | undefined; +}) => { + if (!group.preset_name) return <Dash />; + const match = (presets ?? []).find( + preset => preset.name === group.preset_name && preset.version === group.preset_version, + ); + const label = presetLabel(group); + if (!match) return <Typography variant="body2">{label}</Typography>; + return ( + <Typography variant="body2"> + <Link to={`/presets/${match.id}/show`}>{label}</Link> + </Typography> + ); +}; + +/** The device the runs came from, linked while the registry still knows it. */ +export const DeviceCell = ({ + id, + name, +}: { + id: string | null | undefined; + name: string | null | undefined; +}) => { + if (!name) return <Dash />; + return ( + <Typography variant="body2"> + {id ? <Link to={`/devices/${id}/show`}>{name}</Link> : name} + </Typography> + ); +}; + +// A mean and its deviation only compare if they read in one unit, so the byte +// formatters take both figures and scale them against a single divisor. +const scaledBytes = (value: number, divisor: number): string => + divisor === 1 ? String(Math.round(value)) : (value / divisor).toFixed(1); + +const bytesPair = (mean: number, std: number | null): string => { + const { divisor, unit } = byteScale(mean); + const figure = scaledBytes(mean, divisor); + return std == null + ? `${figure} ${unit}` + : `${figure} ± ${scaledBytes(std, divisor)} ${unit}`; +}; + +const bytesRange = (low: number, high: number): string => { + const { divisor, unit } = byteScale(high); + return `${scaledBytes(low, divisor)} to ${scaledBytes(high, divisor)} ${unit}`; +}; + +// formatDuration rounds to whole seconds, so a deviation under half a second reads as +// no spread at all. Small figures keep a decimal to stay honest about that. +const deviationSeconds = (seconds: number): string => + seconds < 10 ? `${seconds.toFixed(1)}s` : formatDuration(seconds); + +const headline = (metric: MetricKey, mean: number, std: number | null): string => { + if (metric !== 'duration') return bytesPair(mean, std); + return std == null + ? formatDuration(mean) + : `${formatDuration(mean)} ± ${deviationSeconds(std)}`; +}; + +const range = (metric: MetricKey, low: number, high: number): string => + metric === 'duration' + ? `${formatDuration(low)} to ${formatDuration(high)}` + : bytesRange(low, high); + +/** Only memory carries these. Swap and duration report neither figure. */ +const memoryFigure = ( + metric: MetricKey, + figures: MetricTotals | MetricUtilisation | undefined, +): number | null => { + if (!figures) return null; + if (metric === 'ram') return figures.ram; + if (metric === 'vram') return figures.vram; + return null; +}; + +// Duration reports no ceiling, so this line is memory only. A row of one device +// states the peak against that device's memory. A pooled row cannot, because its +// devices are different sizes, so it states the fullest device's own share instead. +// Either way the ceiling is the memory reported now, not what the machine carried +// when the runs happened, so a since-downsized laptop is named rather than given a +// share above 100%. +const ceilingNote = ( + total: number | null, + max: number | null, + utilisation: number | null, +): string | null => { + if (total != null && total > 0 && max != null) { + return max > total + ? `highest run above the ${formatBytes(total)} this device reports now` + : `highest run ${Math.round((max / total) * 100)}% of ${formatBytes(total)}`; + } + if (utilisation == null) return null; + return utilisation > 1 + ? 'fullest device ran above the memory it reports now' + : `fullest device reached ${Math.round(utilisation * 100)}% of its own memory`; +}; + +/** + * One metric as `mean ± sample deviation`, over the observed range and sample size. + * + * The bar carries the mean and the tick the highest run observed. The mean is the + * figure to plan a fleet around, the maximum is the one that fills a laptop, so a + * maximum near the device ceiling turns the whole cell to the warning colour even + * when the mean sits comfortably below it. + */ +const MetricCell = ({ + metric, + stats, + total, + utilisation, + columnMax, +}: { + metric: MetricKey; + stats: MetricStats; + total: number | null; + utilisation: number | null; + columnMax: number; +}) => { + const { mean, std, min, max, n } = stats; + if (!n || mean == null) return <Dash />; + // Duration, a pooled row and a device that never reported its memory all lack a + // ceiling, so their bars rank the column instead and are striped to say so. + const ceiling = total != null && total > 0 ? total : null; + const scale = ceiling ?? columnMax; + const share = (value: number) => (scale > 0 ? Math.min(value / scale, 1) : 0); + const filled = ceiling != null && max != null ? max / ceiling : utilisation; + const hot = filled != null && filled > HOT_FRACTION; + const figure = headline(metric, mean, std); + // A single run, or several that landed on the same figure, has no range to show. + const observed = + min != null && max != null && min !== max ? range(metric, min, max) : null; + const tip = [ + n === 1 ? `${figure} from a single run` : `mean ${figure} across ${n} runs`, + observed && `range ${observed}`, + ceilingNote(total, max, utilisation), + ceiling == null && 'the bar ranks this row against the widest figure in the column', + ] + .filter(Boolean) + .join(' · '); + return ( + <Tooltip title={tip}> + <Box sx={{ minWidth: 128 }}> + <Typography + variant="body2" + sx={{ whiteSpace: 'nowrap' }} + color={hot ? 'warning.main' : 'text.primary'} + > + {figure} + </Typography> + <MeterBar fraction={share(mean)} relative={ceiling == null} hot={hot}> + {max != null && ( + <Box + sx={{ + position: 'absolute', + top: -2, + bottom: -2, + width: 2, + // Clamped so the tick stays inside its own track at + // either end of the scale. + left: `clamp(0px, ${share(max) * 100}%, calc(100% - 2px))`, + bgcolor: hot ? 'warning.main' : 'text.secondary', + }} + /> + )} + </MeterBar> + <Typography + variant="caption" + sx={{ color: 'text.secondary', whiteSpace: 'nowrap' }} + > + {observed ? `${observed} · ` : ''}n={n} + </Typography> + </Box> + </Tooltip> + ); +}; + +const MetricHeader = ({ metric }: { metric: MetricKey }) => { + const note = METRIC_NOTES[metric]; + if (!note) return <>{METRIC_HEADERS[metric]}</>; + return ( + <Tooltip title={note}> + <Box component="span" sx={{ textDecoration: 'underline dotted', cursor: 'help' }}> + {METRIC_HEADERS[metric]} + </Box> + </Tooltip> + ); +}; + +export const MetricHeaders = () => ( + <> + {METRIC_KEYS.map(metric => ( + <TableCell key={metric} sx={{ whiteSpace: 'nowrap' }}> + <MetricHeader metric={metric} /> + </TableCell> + ))} + </> +); + +/** The four metric cells of a row, in the order `MetricHeaders` names them. */ +export const MetricCells = ({ + stats, + maxima, + totals, + utilisation, +}: { + stats: Record<MetricKey, MetricStats>; + maxima: Record<MetricKey, number>; + // A row of one device carries that device's ceilings. A pooled row carries the + // fullest share its devices reached instead, never both: comparing one machine's + // peak against another machine's total is what produces a figure over 100%. + totals?: MetricTotals; + utilisation?: MetricUtilisation; +}) => ( + <> + {METRIC_KEYS.map(metric => ( + <TableCell key={metric}> + <MetricCell + metric={metric} + stats={stats[metric]} + total={memoryFigure(metric, totals)} + utilisation={memoryFigure(metric, utilisation)} + columnMax={maxima[metric]} + /> + </TableCell> + ))} + </> +); + +/** The widest figure per metric, for the columns that have no device ceiling. */ +export const columnMaxima = ( + rows: { stats: Record<MetricKey, MetricStats> }[], +): Record<MetricKey, number> => { + const widest = (metric: MetricKey) => + rows.reduce((high, row) => Math.max(high, row.stats[metric].max ?? 0), 0); + return { + ram: widest('ram'), + swap: widest('swap'), + vram: widest('vram'), + duration: widest('duration'), + }; +}; diff --git a/src/performance/PerformancePage.tsx b/src/performance/PerformancePage.tsx new file mode 100644 index 0000000..a2bfec4 --- /dev/null +++ b/src/performance/PerformancePage.tsx @@ -0,0 +1,244 @@ +import { useState } from 'react'; +import { Title, useGetList } from 'react-admin'; +import { + Box, + Card, + CardContent, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + ToggleButton, + ToggleButtonGroup, + Typography, +} from '@mui/material'; + +import type { PerformanceGroup, Preset } from '../contract'; +import { relativeTime } from '../devices/RelativeDateField'; +import { presetLabel } from '../runs/preset'; +import { formatBytes } from '../videos/VideoFields'; +import GroupTable from './GroupTable'; +import { + columnMaxima, + DeviceCell, + MetricCells, + MetricHeaders, + PresetCell, + RunsCell, +} from './MetricCells'; +import { configLabel, modelsLabel, rollUpByPreset } from './statistics'; +import type { PresetRollup } from './statistics'; +import { usePerformanceSummary } from './usePerformanceSummary'; + +type Grouping = 'device' | 'preset'; + +const CAPTION = + 'Each run contributes one figure per metric, its peak across every stage of that ' + + 'run. The columns are the mean and sample standard deviation of those per-run ' + + 'peaks, taken across the runs in each group, with the observed range and the ' + + 'sample size beneath. n is counted per metric, so a laptop with no discrete GPU ' + + 'contributes runs but no VRAM. The figures cover runs that recorded per-stage ' + + 'peaks, a failed run among them once it recorded some. A run that recorded none, ' + + 'after an early crash or from an older build, does not appear at all, not even ' + + 'in the Runs count.'; + +const BARS = + 'A solid bar fills a memory total the device reported, so it says how full that ' + + 'machine was. A striped bar has none to fill and only ranks its row against the ' + + 'widest figure in the column, which is what duration, swap and any device that ' + + 'never reported its memory get. The tick on a bar marks the highest single run.'; + +const HeadCell = ({ children }: { children: string }) => ( + <TableCell sx={{ whiteSpace: 'nowrap' }}>{children}</TableCell> +); + +const Note = ({ children }: { children: string }) => ( + <Typography variant="body2" sx={{ color: 'text.secondary' }}> + {children} + </Typography> +); + +const ConfigCell = ({ children }: { children: string }) => ( + <TableCell> + <Typography variant="body2" sx={{ whiteSpace: 'nowrap' }}> + {children} + </Typography> + </TableCell> +); + +const LastRunCell = ({ at }: { at: string | null | undefined }) => ( + <TableCell> + <Typography variant="body2" sx={{ whiteSpace: 'nowrap' }}> + {at ? relativeTime(at) : '—'} + </Typography> + </TableCell> +); + +const systemLabel = (group: PerformanceGroup): string | null => { + const parts = [ + group.gpu_name, + group.total_vram_bytes == null ? null : `${formatBytes(group.total_vram_bytes)} VRAM`, + group.total_ram_bytes == null ? null : `${formatBytes(group.total_ram_bytes)} RAM`, + ].filter(Boolean); + return parts.length ? parts.join(' · ') : null; +}; + +const DeviceGrainTable = ({ + groups, + presets, +}: { + groups: PerformanceGroup[]; + presets: Preset[] | undefined; +}) => ( + <GroupTable + groups={groups} + sortKey={group => + `${group.device_name ?? ''}|${presetLabel(group)}|${modelsLabel(group)}|${configLabel(group)}` + } + lead={[ + { + header: 'Device', + cell: group => <DeviceCell id={group.device_id} name={group.device_name} />, + }, + { + header: 'System', + cell: group => ( + <Typography variant="body2" sx={{ color: 'text.secondary' }}> + {systemLabel(group) ?? '—'} + </Typography> + ), + }, + { + header: 'Preset', + cell: group => <PresetCell group={group} presets={presets} />, + }, + ]} + /> +); + +const PresetRollupTable = ({ + rollups, + presets, +}: { + rollups: PresetRollup[]; + presets: Preset[] | undefined; +}) => { + const maxima = columnMaxima(rollups); + return ( + <Table size="small"> + <TableHead> + <TableRow> + <HeadCell>Preset</HeadCell> + <HeadCell>Models</HeadCell> + <HeadCell>Config</HeadCell> + <HeadCell>Devices</HeadCell> + <HeadCell>Runs</HeadCell> + <MetricHeaders /> + <HeadCell>Last run</HeadCell> + </TableRow> + </TableHead> + <TableBody> + {rollups.map(row => ( + <TableRow key={row.key}> + <TableCell> + <PresetCell group={row} presets={presets} /> + </TableCell> + <TableCell> + <Typography variant="body2">{row.models}</Typography> + </TableCell> + <ConfigCell>{row.config}</ConfigCell> + <TableCell> + <Typography variant="body2">{row.device_count}</Typography> + </TableCell> + <TableCell> + <RunsCell count={row.run_count} failed={row.failed_count} /> + </TableCell> + <MetricCells + stats={row.stats} + utilisation={row.utilisation} + maxima={maxima} + /> + <LastRunCell at={row.last_run_at} /> + </TableRow> + ))} + </TableBody> + </Table> + ); +}; + +/** Fleet resource use per device × preset × models × config, from synced runs. */ +const PerformancePage = () => { + const [grouping, setGrouping] = useState<Grouping>('device'); + const { groups, error } = usePerformanceSummary(); + // Same parameters as the assignment panel, so the whole console shares one fetch. + const { data: presets } = useGetList<Preset>('presets', { + pagination: { page: 1, perPage: 100 }, + sort: { field: 'name', order: 'ASC' }, + }); + + return ( + <Card sx={{ mt: 1 }}> + <Title title="Performance" /> + <CardContent> + <Stack spacing={2}> + <Stack + direction="row" + spacing={2} + useFlexGap + sx={{ alignItems: 'center', flexWrap: 'wrap' }} + > + <Typography variant="h6">Performance</Typography> + <ToggleButtonGroup + size="small" + exclusive + value={grouping} + onChange={(_, next: Grouping | null) => next && setGrouping(next)} + > + <ToggleButton value="device">By device</ToggleButton> + <ToggleButton value="preset">By preset</ToggleButton> + </ToggleButtonGroup> + </Stack> + <Note>{CAPTION}</Note> + <Note>{BARS}</Note> + {grouping === 'preset' && ( + <Note> + One row per preset, models and config, pooled across the devices + that ran it. The pooled deviation carries the spread between those + devices as well as the spread within each of them. These rows have + no single memory ceiling, so every bar is a striped one, and a row + is flagged when one of its devices came close to filling its own + memory. + </Note> + )} + {error ? ( + <Note> + {`The registry did not answer the performance summary. It may predate this console. (${error})`} + </Note> + ) : !groups ? ( + <Note>Loading…</Note> + ) : !groups.length ? ( + <Note> + No runs report performance data yet. Figures appear once enrolled + laptops sync finished runs. + </Note> + ) : ( + <Box sx={{ overflowX: 'auto' }}> + {grouping === 'device' ? ( + <DeviceGrainTable groups={groups} presets={presets} /> + ) : ( + <PresetRollupTable + rollups={rollUpByPreset(groups)} + presets={presets} + /> + )} + </Box> + )} + </Stack> + </CardContent> + </Card> + ); +}; + +export default PerformancePage; diff --git a/src/performance/PresetPerformance.tsx b/src/performance/PresetPerformance.tsx new file mode 100644 index 0000000..dc580ee --- /dev/null +++ b/src/performance/PresetPerformance.tsx @@ -0,0 +1,63 @@ +import { useRecordContext } from 'react-admin'; +import { Box, Typography } from '@mui/material'; + +import type { Preset } from '../contract'; +import GroupTable from './GroupTable'; +import { DeviceCell } from './MetricCells'; +import { configLabel, modelsLabel } from './statistics'; +import { usePerformanceSummary } from './usePerformanceSummary'; + +const Note = ({ children }: { children: string }) => ( + <Typography variant="body2" sx={{ color: 'text.secondary' }}> + {children} + </Typography> +); + +/** How this preset has performed per device, models and config, from the shared fleet + * summary. Runs name their preset, so the match is name plus version. */ +const PresetPerformance = () => { + const record = useRecordContext<Preset>(); + const { groups, error } = usePerformanceSummary(); + if (!record) return null; + if (error) { + return ( + <Note> + The registry did not answer the performance summary. It may predate this + console. + </Note> + ); + } + if (!groups) return <Note>Loading…</Note>; + const rows = groups.filter( + group => group.preset_name === record.name && group.preset_version === record.version, + ); + if (!rows.length) { + return <Note>No runs report performance data against this preset yet.</Note>; + } + return ( + <> + <Note> + Mean and sample standard deviation across runs, where every run contributes its + peak across stages. + </Note> + <Box sx={{ overflowX: 'auto' }}> + <GroupTable + groups={rows} + sortKey={group => + `${group.device_name ?? ''}|${modelsLabel(group)}|${configLabel(group)}` + } + lead={[ + { + header: 'Device', + cell: group => ( + <DeviceCell id={group.device_id} name={group.device_name} /> + ), + }, + ]} + /> + </Box> + </> + ); +}; + +export default PresetPerformance; diff --git a/src/performance/statistics.ts b/src/performance/statistics.ts new file mode 100644 index 0000000..9982ce7 --- /dev/null +++ b/src/performance/statistics.ts @@ -0,0 +1,226 @@ +import type { PerformanceGroup } from '../contract'; +import { presetLabel } from '../runs/preset'; + +export const METRIC_KEYS = ['ram', 'swap', 'vram', 'duration'] as const; +export type MetricKey = (typeof METRIC_KEYS)[number]; + +/** + * One metric across the runs in a group. Every run contributes its own peak, the + * largest value across that run's stages, and these are the figures over those + * per-run peaks: bytes for memory, seconds for duration. + * + * `std` is the sample standard deviation, null below two observations. `n` counts + * the runs that observed this metric, so it sits below the group's run count when + * a machine reports no VRAM or a run reported nothing usable. + */ +export type MetricStats = { + mean: number | null; + std: number | null; + min: number | null; + max: number | null; + n: number; +}; + +/** The memory ceilings a group's bars scale against. Swap has no reported total. */ +export type MetricTotals = { ram: number | null; vram: number | null }; + +/** + * The largest share of its own memory that any device in a pooled row reached. + * + * A row spanning several machines has no single ceiling, and one machine's peak + * against another's total says nothing: a 40 GB peak on a 64 GB laptop is not 250% + * of the 16 GB laptop beside it. Each machine's own peak over its own total is a + * real figure, and the largest of those is the one that came closest to filling a + * device, so that is what a pooled row warns against. + */ +export type MetricUtilisation = { ram: number | null; vram: number | null }; + +const NOTHING_OBSERVED: MetricStats = { mean: null, std: null, min: null, max: null, n: 0 }; + +export const metricStats = (group: PerformanceGroup): Record<MetricKey, MetricStats> => ({ + ram: { + mean: group.ram_mean_bytes ?? null, + std: group.ram_std_bytes ?? null, + min: group.ram_min_bytes ?? null, + max: group.ram_max_bytes ?? null, + n: group.ram_n, + }, + swap: { + mean: group.swap_mean_bytes ?? null, + std: group.swap_std_bytes ?? null, + min: group.swap_min_bytes ?? null, + max: group.swap_max_bytes ?? null, + n: group.swap_n, + }, + vram: { + mean: group.vram_mean_bytes ?? null, + std: group.vram_std_bytes ?? null, + min: group.vram_min_bytes ?? null, + max: group.vram_max_bytes ?? null, + n: group.vram_n, + }, + duration: { + mean: group.duration_mean_s ?? null, + std: group.duration_std_s ?? null, + min: group.duration_min_s ?? null, + max: group.duration_max_s ?? null, + n: group.duration_n, + }, +}); + +/** The device's current hardware, not what it carried when the runs happened. */ +export const groupTotals = (group: PerformanceGroup): MetricTotals => ({ + ram: group.total_ram_bytes ?? null, + vram: group.total_vram_bytes ?? null, +}); + +const minOf = (values: (number | null)[]): number | null => + values.reduce<number | null>( + (low, value) => (value == null ? low : Math.min(low ?? value, value)), + null, + ); + +const maxOf = (values: (number | null)[]): number | null => + values.reduce<number | null>( + (high, value) => (value == null ? high : Math.max(high ?? value, value)), + null, + ); + +/** + * Several groups' figures for one metric as a single figure. + * + * The mean weights each group by its own sample size, so a device with forty runs + * does not count the same as one with two. The variance adds the spread *between* + * the group means to the spread within them: two laptops each perfectly steady but + * sitting 10 GB apart are not a fleet with zero deviation, and averaging their + * deviations would claim exactly that. + * + * Min and max are the true observed extremes, so they pool by taking the extreme. + */ +export const poolStats = (parts: MetricStats[]): MetricStats => { + const observed = parts.flatMap(part => + part.n > 0 && part.mean != null ? [{ ...part, mean: part.mean }] : [], + ); + const n = observed.reduce((total, part) => total + part.n, 0); + if (!n) return NOTHING_OBSERVED; + const mean = observed.reduce((sum, part) => sum + part.n * part.mean, 0) / n; + const sumOfSquares = observed.reduce((sum, part) => { + const within = part.std == null ? 0 : (part.n - 1) * part.std ** 2; + const between = part.n * (part.mean - mean) ** 2; + return sum + within + between; + }, 0); + return { + mean, + std: n < 2 ? null : Math.sqrt(sumOfSquares / (n - 1)), + min: minOf(observed.map(part => part.min)), + max: maxOf(observed.map(part => part.max)), + n, + }; +}; + +export const modelsLabel = (group: PerformanceGroup): string => + `${group.segmentation_model ?? 'no segmentation'} · ${group.mapping_backend ?? 'no mapping'}`; + +/** Legacy runs carry no config, so a group of only those reads as one dash. */ +export const configLabel = (group: PerformanceGroup): string => { + const resolution = + group.processing_width != null && group.processing_height != null + ? `${group.processing_width}×${group.processing_height}` + : '—'; + const fps = group.fps != null ? `${group.fps}fps` : '—'; + const batch = + group.preprocess_batch_size != null ? `batch ${group.preprocess_batch_size}` : '—'; + if (resolution === '—' && fps === '—' && batch === '—') return '—'; + return `${resolution} · ${fps} · ${batch}`; +}; + +/** One row per preset × models × config, pooled across the devices that ran it. */ +export type PresetRollup = { + key: string; + preset_name: string | null; + preset_version: number | null; + models: string; + config: string; + device_count: number; + run_count: number; + failed_count: number; + stats: Record<MetricKey, MetricStats>; + utilisation: MetricUtilisation; + last_run_at: string | null; +}; + +const latestRun = (members: PerformanceGroup[]): string | null => + members.reduce<string | null>( + (latest, member) => + member.last_run_at && (!latest || member.last_run_at > latest) + ? member.last_run_at + : latest, + null, + ); + +const peakUtilisation = ( + members: PerformanceGroup[], + peak: (member: PerformanceGroup) => number | null | undefined, + ceiling: (member: PerformanceGroup) => number | null | undefined, +): number | null => + maxOf( + members.map(member => { + const total = ceiling(member); + const highest = peak(member); + return total != null && total > 0 && highest != null ? highest / total : null; + }), + ); + +const rollUp = (key: string, members: PerformanceGroup[]): PresetRollup => { + const perMember = members.map(metricStats); + return { + key, + preset_name: members[0].preset_name ?? null, + preset_version: members[0].preset_version ?? null, + models: modelsLabel(members[0]), + config: configLabel(members[0]), + // By device rather than by row: two rows whose labels collide are one laptop, + // and the runs that carry no device at all count as one between them. + device_count: new Set(members.map(member => member.device_id ?? '')).size, + run_count: members.reduce((total, member) => total + member.run_count, 0), + failed_count: members.reduce((total, member) => total + member.failed_count, 0), + stats: { + ram: poolStats(perMember.map(entry => entry.ram)), + swap: poolStats(perMember.map(entry => entry.swap)), + vram: poolStats(perMember.map(entry => entry.vram)), + duration: poolStats(perMember.map(entry => entry.duration)), + }, + utilisation: { + ram: peakUtilisation( + members, + member => member.ram_max_bytes, + member => member.total_ram_bytes, + ), + vram: peakUtilisation( + members, + member => member.vram_max_bytes, + member => member.total_vram_bytes, + ), + }, + last_run_at: latestRun(members), + }; +}; + +// The hash is in the bucket key but not here, so two settings published under one +// name and version still sort beside each other rather than by their hashes. +const sortKey = (row: PresetRollup): string => + `${presetLabel(row)}|${row.models}|${row.config}`; + +export const rollUpByPreset = (groups: PerformanceGroup[]): PresetRollup[] => { + const buckets = new Map<string, PerformanceGroup[]>(); + for (const group of groups) { + // Config is part of the key, so runs at 4K never blend into quarter-size ones, + // and so is the settings hash, because the console only warns against editing + // settings without a version bump. Both device-grain views key on it too. + const key = `${presetLabel(group)}|${group.preset_hash ?? ''}|${modelsLabel(group)}|${configLabel(group)}`; + buckets.set(key, [...(buckets.get(key) ?? []), group]); + } + return Array.from(buckets.entries(), ([key, members]) => rollUp(key, members)).sort( + (a, b) => sortKey(a).localeCompare(sortKey(b)), + ); +}; diff --git a/src/performance/usePerformanceSummary.ts b/src/performance/usePerformanceSummary.ts new file mode 100644 index 0000000..255a185 --- /dev/null +++ b/src/performance/usePerformanceSummary.ts @@ -0,0 +1,32 @@ +import { useQuery } from '@tanstack/react-query'; +import { useDataProvider } from 'react-admin'; + +import type { PerformanceGroup } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; + +/** + * The fleet-wide performance summary. + * + * One query key, so the Performance page, the preset panel and the device panel share + * a single fetch of a summary that changes only as runs finish, and react-admin's + * Refresh invalidates it like any other query. + * + * `groups` is `undefined` while loading or after a failure, and `error` says which. + */ +export const usePerformanceSummary = (): { + groups: PerformanceGroup[] | undefined; + error: string | undefined; +} => { + const dataProvider = useDataProvider<DrmDataProvider>(); + const { data, error } = useQuery({ + queryKey: ['performance', 'summary'], + queryFn: () => dataProvider.performanceSummary(), + staleTime: Infinity, + // A registry that predates the endpoint refuses every attempt alike. + retry: false, + }); + return { + groups: data?.groups, + error: error ? error.message || 'Could not read the performance summary' : undefined, + }; +}; diff --git a/src/permissions.ts b/src/permissions.ts new file mode 100644 index 0000000..78c3d04 --- /dev/null +++ b/src/permissions.ts @@ -0,0 +1,24 @@ +import { usePermissions } from 'react-admin'; + +/** + * True for a member or an administrator. + * + * Members set up the work the field team will do, so they create and edit sites, + * campaigns, transects, passes and pass videos. + */ +export const useCanAuthor = (): boolean => { + const { permissions } = usePermissions(); + return permissions === 'admin' || permissions === 'user'; +}; + +/** + * True only for `deepreefmap-admin`. + * + * The registry needs the role for two things: deleting anything, since a tombstone + * reaches every laptop that already pulled the row, and writing the rows devices + * report rather than humans author (videos, runs, cover rows). + */ +export const useIsAdmin = (): boolean => { + const { permissions } = usePermissions(); + return permissions === 'admin'; +}; diff --git a/src/polyfills.ts b/src/polyfills.ts deleted file mode 100644 index 81164f9..0000000 --- a/src/polyfills.ts +++ /dev/null @@ -1,3 +0,0 @@ -import 'react-app-polyfill/ie11'; -import 'react-app-polyfill/stable'; -import 'proxy-polyfill/proxy.min.js'; diff --git a/src/presets/AssignToAllButton.tsx b/src/presets/AssignToAllButton.tsx new file mode 100644 index 0000000..a19b0e1 --- /dev/null +++ b/src/presets/AssignToAllButton.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; +import { + Confirm, + useDataProvider, + useNotify, + useRecordContext, + useRefresh, +} from 'react-admin'; +import { Button } from '@mui/material'; +import SendToMobileIcon from '@mui/icons-material/SendToMobile'; + +import type { DrmDataProvider } from '../dataProvider/index'; +import type { Preset } from '../contract'; +import { useIsAdmin } from '../permissions'; + +/** Assigns this preset to every active device in one call. Admin-only, like the route. */ +const AssignToAllButton = () => { + const record = useRecordContext<Preset>(); + const dataProvider = useDataProvider<DrmDataProvider>(); + const admin = useIsAdmin(); + const notify = useNotify(); + const refresh = useRefresh(); + const [open, setOpen] = useState(false); + const [pending, setPending] = useState(false); + + if (!record || !admin || record.deleted_at) return null; + + const assignAll = async () => { + setPending(true); + try { + const result = await dataProvider.assignPresetToAll(String(record.id)); + notify(`Assigned to ${result.assigned_count} device(s).`, { type: 'info' }); + refresh(); + } catch (error) { + notify( + error instanceof Error ? error.message : 'Assigning to all devices failed.', + { type: 'error' }, + ); + } finally { + setPending(false); + setOpen(false); + } + }; + + return ( + <> + <Button + startIcon={<SendToMobileIcon />} + onClick={() => setOpen(true)} + disabled={pending} + > + Assign to all devices + </Button> + <Confirm + isOpen={open} + loading={pending} + title={`Assign ${record.name} v${record.version} to every device?`} + content={ + 'Every active device adopts it at its next check-in. Revoked devices ' + + 'are skipped, and an explicit choice on a laptop still outranks it.' + } + confirm="Assign" + onConfirm={assignAll} + onClose={() => setOpen(false)} + /> + </> + ); +}; + +export default AssignToAllButton; diff --git a/src/presets/PresetCreate.tsx b/src/presets/PresetCreate.tsx new file mode 100644 index 0000000..96faf0c --- /dev/null +++ b/src/presets/PresetCreate.tsx @@ -0,0 +1,16 @@ +import { Create, SimpleForm } from 'react-admin'; + +import PresetInputs from './PresetInputs'; +import { defaultSettings } from './schema'; + +const PresetCreate = () => ( + <Create redirect="show"> + <SimpleForm + defaultValues={{ description: '', version: 1, settings: defaultSettings() }} + > + <PresetInputs /> + </SimpleForm> + </Create> +); + +export default PresetCreate; diff --git a/src/presets/PresetEdit.tsx b/src/presets/PresetEdit.tsx new file mode 100644 index 0000000..56a51de --- /dev/null +++ b/src/presets/PresetEdit.tsx @@ -0,0 +1,26 @@ +import { Edit, SaveButton, SimpleForm, Toolbar } from 'react-admin'; +import { Typography } from '@mui/material'; + +import PresetInputs from './PresetInputs'; + +// Rows are tombstoned by the sync contract, so the default toolbar's delete is wrong here. +const PresetEditToolbar = () => ( + <Toolbar> + <SaveButton /> + </Toolbar> +); + +const PresetEdit = () => ( + <Edit redirect="show" mutationMode="pessimistic"> + <SimpleForm toolbar={<PresetEditToolbar />}> + <Typography variant="caption" sx={{ color: 'text.secondary', mb: 1 }}> + Changing the settings warrants a version bump: devices label their runs with + the preset's name and version, and a silent change would leave two + different runs labelled identically. + </Typography> + <PresetInputs /> + </SimpleForm> + </Edit> +); + +export default PresetEdit; diff --git a/src/presets/PresetInputs.tsx b/src/presets/PresetInputs.tsx new file mode 100644 index 0000000..4bfa3df --- /dev/null +++ b/src/presets/PresetInputs.tsx @@ -0,0 +1,199 @@ +import { useState } from 'react'; +import { + BooleanInput, + maxValue, + minValue, + NumberInput, + required, + SelectInput, + TextInput, +} from 'react-admin'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Alert, + Box, + Button, + Grid, + TextField as MuiTextField, + Typography, +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; + +import { choicesFor, PRESET_FIELDS, PRESET_SCHEMA_VERSION } from './schema'; +import type { PresetFieldDef } from './schema'; + +const titled = (label: string) => label.charAt(0).toUpperCase() + label.slice(1); + +// The one relationship the flat field table cannot express: an explicit processing +// size applies only under the `Custom` resolution preset. Native, Half and Quarter +// divide the segmentation model's native size on the device +// (`form/panel.py::_apply_resolution_preset`), so the console has no number to +// publish. Disabled rather than read-only on purpose: react-hook-form leaves a +// disabled field out of the submitted values, so a number typed under Custom stops +// being published the moment the resolution moves off it. +const CUSTOM_SIZE_KEYS = ['processing_width', 'processing_height']; + +const DERIVED_SIZE_HELP = + 'Native, Half and Quarter derive this from the segmentation model. Choose Custom to set it.'; + +const SettingInput = ({ + field, + customSize, +}: { + field: PresetFieldDef; + customSize: boolean; +}) => { + const source = `settings.${field.key}`; + const label = field.unit ? `${titled(field.label)} (${field.unit})` : titled(field.label); + if (field.kind === 'bool') { + return <BooleanInput source={source} label={label} helperText={false} />; + } + if (field.kind === 'enum') { + return ( + <SelectInput + source={source} + label={label} + choices={choicesFor(field.choices)} + validate={required()} + fullWidth + helperText={false} + /> + ); + } + const derived = !customSize && CUSTOM_SIZE_KEYS.includes(field.key); + const validate = [ + ...(field.minimum === null ? [] : [minValue(field.minimum)]), + ...(field.maximum === null ? [] : [maxValue(field.maximum)]), + ]; + return ( + <NumberInput + source={source} + label={label} + min={field.minimum ?? undefined} + max={field.maximum ?? undefined} + step={field.step ?? undefined} + validate={validate} + fullWidth + disabled={derived} + helperText={ + derived + ? DERIVED_SIZE_HELP + : field.nullable + ? 'Empty follows the model.' + : false + } + /> + ); +}; + +/** The generated settings form: every field the schema publishes, scoped to the + * chosen processing method. */ +const SettingsFields = () => { + const mapping = useWatch({ name: 'settings.mapping_name' }) as string | undefined; + const resolution = useWatch({ name: 'settings.resolution_preset' }) as string | undefined; + const visible = PRESET_FIELDS.filter( + field => + field.applies_when.length === 0 || + (mapping !== undefined && field.applies_when.includes(mapping)), + ); + return ( + <Grid container spacing={2}> + {visible.map(field => ( + <Grid key={field.key} size={{ xs: 12, sm: field.kind === 'enum' ? 6 : 4 }}> + <SettingInput field={field} customSize={resolution === 'Custom'} /> + </Grid> + ))} + </Grid> + ); +}; + +// The escape hatch the raw textarea used to be: paste a whole document, apply, and +// the form fields take it. Keys the schema does not know survive a round trip. +const AdvancedJsonEditor = () => { + const { getValues, setValue } = useFormContext(); + const [text, setText] = useState(''); + const [error, setError] = useState<string | null>(null); + + const snapshot = (_: unknown, expanded: boolean) => { + if (!expanded) return; + setText(JSON.stringify(getValues('settings') ?? {}, null, 2)); + setError(null); + }; + + const apply = () => { + try { + const parsed: unknown = JSON.parse(text); + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { + throw new Error('the document must be a JSON object'); + } + setValue('settings', parsed, { shouldDirty: true }); + setError(null); + } catch (failure) { + setError( + `Not applied: ${failure instanceof Error ? failure.message : 'invalid JSON'}`, + ); + } + }; + + return ( + <Accordion disableGutters elevation={0} onChange={snapshot} sx={{ mt: 1 }}> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Typography variant="body2" sx={{ color: 'text.secondary' }}> + Advanced: edit as JSON + </Typography> + </AccordionSummary> + <AccordionDetails> + <MuiTextField + value={text} + onChange={event => setText(event.target.value)} + multiline + minRows={8} + fullWidth + slotProps={{ input: { sx: { fontFamily: 'monospace', fontSize: 13 } } }} + /> + {error && ( + <Alert severity="error" sx={{ mt: 1 }}> + {error} + </Alert> + )} + <Box sx={{ mt: 1 }}> + <Button variant="outlined" size="small" onClick={apply}> + Apply to the form + </Button> + </Box> + </AccordionDetails> + </Accordion> + ); +}; + +const PresetInputs = () => ( + <Grid container spacing={2}> + <Grid size={{ xs: 12, sm: 8 }}> + <TextInput + source="name" + validate={required()} + helperText="Devices label a run with name and version." + fullWidth + /> + </Grid> + <Grid size={{ xs: 12, sm: 4 }}> + <NumberInput source="version" validate={required()} fullWidth /> + </Grid> + <Grid size={12}> + <TextInput source="description" multiline rows={2} fullWidth /> + </Grid> + <Grid size={12}> + <SettingsFields /> + <Typography variant="caption" sx={{ color: 'text.secondary', display: 'block' }}> + Fields follow preset schema v{PRESET_SCHEMA_VERSION}. Devices ignore keys they + do not recognise. + </Typography> + <AdvancedJsonEditor /> + </Grid> + </Grid> +); + +export default PresetInputs; diff --git a/src/presets/PresetList.tsx b/src/presets/PresetList.tsx new file mode 100644 index 0000000..d1c0d24 --- /dev/null +++ b/src/presets/PresetList.tsx @@ -0,0 +1,84 @@ +import { + CreateButton, + Datagrid, + DateField, + ExportButton, + List, + NumberField, + TextField, + TopToolbar, + useGetList, + useRecordContext, +} from 'react-admin'; +import { Box, Typography } from '@mui/material'; + +import { asColumn } from '../components'; +import type { Device, Preset } from '../contract'; +import { useCanAuthor } from '../permissions'; + +// Every row runs the identical query, so react-admin's cache answers the whole +// column from one request. +const AssignedCountField = () => { + const record = useRecordContext<Preset>(); + const { data } = useGetList<Device>('devices', { + pagination: { page: 1, perPage: 1000 }, + sort: { field: 'name', order: 'ASC' }, + filter: { revoked_at: null }, + }); + if (!record || !data) return <span>—</span>; + return ( + <span>{data.filter(device => device.assigned_preset_id === record.id).length}</span> + ); +}; + +const AssignedCountColumn = asColumn(AssignedCountField); + +const PresetListActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + {canAuthor && <CreateButton />} + <ExportButton /> + </TopToolbar> + ); +}; + +const PresetEmpty = () => { + const canAuthor = useCanAuthor(); + return ( + <Box + sx={{ + textAlign: 'center', + m: 4, + }} + > + <Typography variant="h6" gutterBottom> + No presets yet + </Typography> + <Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}> + A preset is a named, versioned settings document the desktop application pulls, + so every laptop reconstructs with the same parameters. + </Typography> + {canAuthor && <CreateButton label="Create the first preset" />} + </Box> + ); +}; + +const PresetList = () => ( + <List + actions={<PresetListActions />} + sort={{ field: 'updated_at', order: 'DESC' }} + perPage={25} + empty={<PresetEmpty />} + > + <Datagrid rowClick="show" bulkActionButtons={false}> + <TextField source="name" /> + <NumberField source="version" /> + <TextField source="description" emptyText="—" sortable={false} /> + <AssignedCountColumn label="Assigned devices" sortable={false} /> + <DateField source="updated_at" label="Updated" showTime /> + </Datagrid> + </List> +); + +export default PresetList; diff --git a/src/presets/PresetShow.tsx b/src/presets/PresetShow.tsx new file mode 100644 index 0000000..832f9bc --- /dev/null +++ b/src/presets/PresetShow.tsx @@ -0,0 +1,247 @@ +import { + Datagrid, + EditButton, + Labeled, + NumberField, + Pagination, + ReferenceManyField, + Show, + TextField, + TopToolbar, + useRecordContext, +} from 'react-admin'; +import { + Box, + Divider, + Grid, + Stack, + Table, + TableBody, + TableCell, + TableRow, + Tooltip, + Typography, +} from '@mui/material'; + +import { asColumn, SyncFields, TombstoneButton } from '../components'; +import DeviceStatusField from '../devices/DeviceStatusField'; +import PresetPerformance from '../performance/PresetPerformance'; +import { useCanAuthor } from '../permissions'; +import type { Device, Preset } from '../contract'; +import AssignToAllButton from './AssignToAllButton'; +import { PRESET_FIELDS, PRESET_SCHEMA_VERSION, unknownKeys } from './schema'; + +const PresetShowActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + <AssignToAllButton /> + {canAuthor && <EditButton />} + <TombstoneButton noun="preset" /> + </TopToolbar> + ); +}; + +const titled = (label: string) => label.charAt(0).toUpperCase() + label.slice(1); + +const shown = (value: unknown, unit: string): string => { + if (value === undefined) return '—'; + if (value === null) return 'follows the model'; + if (typeof value === 'boolean') return value ? 'yes' : 'no'; + const plain = typeof value === 'string' ? value : JSON.stringify(value); + return unit ? `${plain} ${unit}` : plain; +}; + +// The stored document read through the published schema: a labelled row per known +// key, and whatever else the document carries listed as unrecognised. +const SettingsBlock = () => { + const record = useRecordContext<Preset>(); + if (!record) return null; + const settings = (record.settings ?? {}) as Record<string, unknown>; + const unrecognised = unknownKeys(settings); + return ( + <Box> + <Table size="small" sx={{ maxWidth: 560 }}> + <TableBody> + {PRESET_FIELDS.map(field => ( + <TableRow key={field.key}> + <TableCell sx={{ color: 'text.secondary', border: 0, pl: 0 }}> + {titled(field.label)} + </TableCell> + <TableCell sx={{ border: 0, fontFamily: 'monospace' }}> + {shown(settings[field.key], field.unit)} + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + {unrecognised.length > 0 && ( + <Box sx={{ mt: 1 }}> + <Typography variant="caption" sx={{ color: 'text.secondary' }}> + Not in preset schema v{PRESET_SCHEMA_VERSION}: + </Typography> + <Box + component="pre" + sx={{ + m: 0, + p: 1.5, + borderRadius: 1, + bgcolor: 'action.hover', + fontSize: 13, + overflowX: 'auto', + }} + > + {JSON.stringify( + Object.fromEntries(unrecognised.map(key => [key, settings[key]])), + null, + 2, + )} + </Box> + </Box> + )} + </Box> + ); +}; + +// Null or behind means the laptop would refuse or misread this document, which is +// exactly what an assignment (or the fallback after clearing one) needs visible. +const SchemaVersionField = () => { + const device = useRecordContext<Device>(); + if (!device) return null; + const version = device.preset_schema_version; + if (version != null && version >= PRESET_SCHEMA_VERSION) return <span>{version}</span>; + return ( + <Tooltip + title={ + version == null + ? 'This device has never reported a preset schema version.' + : `This device understands schema v${version}; the console writes v${PRESET_SCHEMA_VERSION}.` + } + > + <Typography variant="body2" component="span" color="warning.main"> + {version ?? '—'} + </Typography> + </Tooltip> + ); +}; + +const SchemaVersionColumn = asColumn(SchemaVersionField); + +const NoAssignees = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + p: 1, + }} + > + No device is assigned this preset. + </Typography> +); + +const AssignedDevices = () => ( + <ReferenceManyField + reference="devices" + target="assigned_preset_id" + sort={{ field: 'name', order: 'ASC' }} + perPage={10} + pagination={<Pagination />} + > + <Datagrid rowClick="show" bulkActionButtons={false} empty={<NoAssignees />}> + <TextField source="name" label="Device name" /> + <DeviceStatusField label="Status" /> + <TextField + source="gui_version" + label="GUI version" + emptyText="—" + sortable={false} + /> + <SchemaVersionColumn label="Preset schema" sortable={false} /> + </Datagrid> + </ReferenceManyField> +); + +const PresetShow = () => ( + <Show actions={<PresetShowActions />}> + <Stack + spacing={2} + sx={{ + p: 2, + }} + > + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + md: 4, + }} + > + <Labeled label="Name"> + <TextField source="name" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 2, + }} + > + <Labeled label="Version"> + <NumberField source="version" /> + </Labeled> + </Grid> + <Grid size={12}> + <Labeled label="Description"> + <TextField source="description" emptyText="—" /> + </Labeled> + </Grid> + <Grid size={12}> + <Labeled label="Settings" sx={{ width: '100%' }}> + <SettingsBlock /> + </Labeled> + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + }} + > + Console preset schema v{PRESET_SCHEMA_VERSION} + </Typography> + </Grid> + </Grid> + + <Divider /> + <Box> + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + Devices using this preset + </Typography> + <AssignedDevices /> + </Box> + + <Divider /> + <Box> + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + Performance + </Typography> + <PresetPerformance /> + </Box> + + <Divider /> + <SyncFields /> + </Stack> + </Show> +); + +export default PresetShow; diff --git a/src/presets/index.tsx b/src/presets/index.tsx new file mode 100644 index 0000000..8acff86 --- /dev/null +++ b/src/presets/index.tsx @@ -0,0 +1,18 @@ +import TuneIcon from '@mui/icons-material/Tune'; + +import PresetCreate from './PresetCreate'; +import PresetEdit from './PresetEdit'; +import PresetList from './PresetList'; +import PresetShow from './PresetShow'; + +export default { + list: PresetList, + show: PresetShow, + edit: PresetEdit, + create: PresetCreate, + icon: TuneIcon, + recordRepresentation: 'name', + options: { + label: 'Presets', + }, +}; diff --git a/src/presets/schema.ts b/src/presets/schema.ts new file mode 100644 index 0000000..4bd3e41 --- /dev/null +++ b/src/presets/schema.ts @@ -0,0 +1,84 @@ +import schema from '../contract/preset-schema.json'; + +// The field table and model catalogue published by the registry as +// contract/preset-schema.json, mirrored from the desktop application. The form is +// generated from it, so the console offers exactly what a laptop accepts. + +export type PresetFieldKind = 'int' | 'float' | 'bool' | 'enum'; + +export type PresetFieldDef = { + key: string; + label: string; + kind: PresetFieldKind; + minimum: number | null; + maximum: number | null; + step: number | null; + decimals: number | null; + unit: string; + choices: string; + applies_when: string[]; + nullable: boolean; + default: unknown; +}; + +export type ModelChoice = { + name: string; + description: string; + hf_repos: string[]; + gated: boolean; + gpu_only: boolean; + approx_size_mb: number | null; +}; + +export const PRESET_SCHEMA_VERSION: number = schema.preset_schema_version; +export const PRESET_FIELDS = schema.fields as PresetFieldDef[]; +export const UNPUBLISHABLE_KEYS: string[] = schema.unpublishable_keys; + +const MODEL_CHOICES: Record<string, ModelChoice[]> = { + segmentation: schema.choices.segmentation as ModelChoice[], + mapping: schema.choices.mapping as ModelChoice[], +}; + +const PLAIN_CHOICES: Record<string, string[]> = { + camera: schema.choices.camera, + resolution: schema.choices.resolution, +}; + +const sizeLabel = (mb: number | null): string => { + if (mb === null) return ''; + return mb >= 1024 ? `${(mb / 1024).toFixed(1)} GB` : `${mb} MB`; +}; + +/** react-admin choices for one enumeration, described well enough to pick from. */ +export const choicesFor = (name: string): { id: string; name: string }[] => { + const models = MODEL_CHOICES[name]; + if (models) { + return models.map(model => { + const notes = [ + sizeLabel(model.approx_size_mb), + model.gated ? 'gated' : '', + model.gpu_only ? 'GPU only' : '', + ] + .filter(Boolean) + .join(', '); + return { + id: model.name, + name: notes ? `${model.name} (${notes})` : model.name, + }; + }); + } + return (PLAIN_CHOICES[name] ?? []).map(value => ({ id: value, name: value })); +}; + +/** The bundled defaults, for a fresh create form. */ +export const defaultSettings = (): Record<string, unknown> => + Object.fromEntries(PRESET_FIELDS.map(field => [field.key, field.default])); + +/** Keys of a stored document the schema does not describe, shown but not edited. */ +export const unknownKeys = (settings: unknown): string[] => { + if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) { + return []; + } + const known = new Set(PRESET_FIELDS.map(field => field.key)); + return Object.keys(settings).filter(key => !known.has(key)); +}; diff --git a/src/runs/ProvenanceTable.tsx b/src/runs/ProvenanceTable.tsx new file mode 100644 index 0000000..84a5cb2 --- /dev/null +++ b/src/runs/ProvenanceTable.tsx @@ -0,0 +1,62 @@ +import { Table, TableBody, TableCell, TableRow, Typography } from '@mui/material'; + +/** A json column as key/value rows. Nested objects become indented key rows. */ +const asEntries = (value: unknown): [string, unknown][] => { + if (!value || typeof value !== 'object' || Array.isArray(value)) return []; + return Object.entries(value as Record<string, unknown>); +}; + +type Row = { key: string; depth: number; value?: string }; + +const renderValue = (value: unknown): string => { + if (value == null) return '—'; + if (typeof value === 'object') return JSON.stringify(value); + return String(value); +}; + +const flatten = (value: unknown, depth = 0): Row[] => + asEntries(value) + .sort((a, b) => a[0].localeCompare(b[0])) + .flatMap(([key, entry]) => + asEntries(entry).length + ? [{ key, depth }, ...flatten(entry, depth + 1)] + : [{ key, depth, value: renderValue(entry) }], + ); + +const ProvenanceTable = ({ value, emptyText }: { value: unknown; emptyText: string }) => { + const rows = flatten(value); + if (!rows.length) { + return ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + {emptyText} + </Typography> + ); + } + return ( + <Table size="small"> + <TableBody> + {rows.map((row, index) => ( + <TableRow key={`${row.depth}-${row.key}-${index}`}> + <TableCell + sx={{ width: '40%', verticalAlign: 'top', pl: 2 + row.depth * 2 }} + > + {row.key} + </TableCell> + <TableCell sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}> + {row.value ?? ''} + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + ); +}; + +export const hasEntries = (value: unknown): boolean => asEntries(value).length > 0; + +export default ProvenanceTable; diff --git a/src/runs/RunList.tsx b/src/runs/RunList.tsx new file mode 100644 index 0000000..a2abc5a --- /dev/null +++ b/src/runs/RunList.tsx @@ -0,0 +1,170 @@ +import { + Datagrid, + DateField, + FunctionField, + List, + ReferenceField, + SelectInput, + TextField, + useListContext, + useRecordContext, +} from 'react-admin'; +import { Chip, Stack, Tooltip, Typography } from '@mui/material'; + +import { useRunsProbeBatch } from '../archive/useBatchProbe'; +import { asColumn } from '../components'; +import type { RunRecord } from '../contract'; +import StatusField, { statusChoices } from './StatusField'; +import { hasEntries } from './ProvenanceTable'; +import { formatDuration, runDuration } from './duration'; +import { presetLabel } from './preset'; + +const runFilters = [ + <SelectInput + key="status" + source="status" + label="Status" + choices={statusChoices} + alwaysOn + />, +]; + +// Every row asks with the whole page's ids, so the batch hook collapses the +// column into one probe. +const OutputsField = () => { + const { data } = useListContext<RunRecord>(); + const record = useRecordContext<RunRecord>(); + const { states, error } = useRunsProbeBatch((data ?? []).map(run => run.id)); + if (!record) return null; + if (error) { + return ( + <Tooltip title={error}> + <Chip size="small" label="Archive unavailable" variant="outlined" /> + </Tooltip> + ); + } + const state = states.get(record.id); + if (state === undefined) { + return <Chip size="small" label="Checking…" variant="outlined" />; + } + if (state === null) { + return ( + <Typography + variant="body2" + component="span" + sx={{ + color: 'text.disabled', + }} + > + — + </Typography> + ); + } + if (state.failed > 0) { + return <Chip size="small" color="error" label="Archive failed" />; + } + if (state.complete === state.artifacts) { + return <Chip size="small" color="success" label={`Archived ${state.artifacts}`} />; + } + return ( + <Chip + size="small" + color="warning" + variant="outlined" + label={`${state.complete}/${state.artifacts} archived`} + /> + ); +}; + +const OutputsColumn = asColumn(OutputsField); + +const Empty = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + p: 3, + }} + > + Runs appear here after a desktop client processes a pass and syncs. + </Typography> +); + +/** Runs are reported by the desktop app, so this list is read-only. */ +const RunList = () => ( + <List + filters={runFilters} + sort={{ field: 'started_at', order: 'DESC' }} + perPage={25} + empty={<Empty />} + > + <Datagrid rowClick="show" bulkActionButtons={false}> + <ReferenceField + source="pass_id" + reference="passes" + link="show" + label="Pass" + sortable={false} + > + <TextField source="label" /> + </ReferenceField> + <ReferenceField + source="pass_id" + reference="passes" + link={false} + label="Transect" + sortable={false} + > + <ReferenceField source="transect_id" reference="transects" link="show"> + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + <StatusField label="Status" /> + <DateField source="started_at" label="Started" showTime /> + <FunctionField<RunRecord> + label="Duration" + render={record => { + const seconds = runDuration(record.started_at, record.finished_at); + return seconds == null ? '—' : formatDuration(seconds); + }} + /> + <TextField + source="gui_version" + label="GUI version" + emptyText="—" + sortable={false} + /> + <TextField + source="segmentation_model" + label="Segmentation model" + emptyText="—" + sortable={false} + /> + <FunctionField<RunRecord> + label="Preset" + render={record => ( + // Both facts: an overridden run is the one whose preset matters. + <Stack + direction="row" + spacing={1} + useFlexGap + sx={{ alignItems: 'center', flexWrap: 'wrap' }} + > + <span>{presetLabel(record)}</span> + {hasEntries(record.preset_deviations) && ( + <Chip + size="small" + color="warning" + label="Deviations" + variant="outlined" + /> + )} + </Stack> + )} + /> + <OutputsColumn label="Outputs" sortable={false} /> + </Datagrid> + </List> +); + +export default RunList; diff --git a/src/runs/RunShow.tsx b/src/runs/RunShow.tsx new file mode 100644 index 0000000..927f317 --- /dev/null +++ b/src/runs/RunShow.tsx @@ -0,0 +1,383 @@ +import { ReactNode, useState } from 'react'; +import { + DateField, + Labeled, + Loading, + NumberField, + ReferenceField, + Show, + TextField, + useGetOne, + useRecordContext, +} from 'react-admin'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Alert, + Box, + Card, + CardContent, + Chip, + Stack, + Typography, +} from '@mui/material'; + +import ArchivedOutputs from '../archive/ArchivedOutputs'; +import { HashField, SyncFields } from '../components'; +import type { Device, RunRecord } from '../contract'; +import RunCoverTable from '../cover/RunCoverTable'; +import { profileTotals, type ProfileTotals } from '../devices/profile'; +import RunCloudTab from '../viewer/RunCloudTab'; +import ProvenanceTable, { hasEntries } from './ProvenanceTable'; +import { RunPeakSummary, StageDurationsTable, StagePeaksTable } from './StageBreakdown'; +import StatusField from './StatusField'; +import { formatDuration, runDuration } from './duration'; +import { presetLabel } from './preset'; + +const Heading = ({ title }: { title: string }) => ( + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + {title} + </Typography> +); + +const Panel = ({ title, children }: { title: string; children: ReactNode }) => ( + <Card variant="outlined"> + <CardContent> + <Stack spacing={1.5}> + <Heading title={title} /> + {children} + </Stack> + </CardContent> + </Card> +); + +const Fields = ({ children }: { children: ReactNode }) => ( + <Box + sx={{ + display: 'grid', + gap: 2, + gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', + }} + > + {children} + </Box> +); + +const Monospace = ({ + value, + emptyText = '—', +}: { + value?: string | null; + emptyText?: string; +}) => ( + <Typography variant="body2" sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}> + {value || emptyText} + </Typography> +); + +const CloudSection = () => { + // Mounted on first expand, so the cloud never downloads for runs nobody opens. + const [opened, setOpened] = useState(false); + return ( + <Accordion + variant="outlined" + disableGutters + defaultExpanded={false} + onChange={(_, expanded) => expanded && setOpened(true)} + > + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Heading title="3D cloud" /> + </AccordionSummary> + <AccordionDetails>{opened && <RunCloudTab />}</AccordionDetails> + </Accordion> + ); +}; + +const Provenance = ({ record, totals }: { record: RunRecord; totals: ProfileTotals }) => { + const deviated = hasEntries(record.preset_deviations); + return ( + <Accordion variant="outlined" disableGutters> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Stack + direction="row" + spacing={1.5} + useFlexGap + sx={{ alignItems: 'center', flexWrap: 'wrap' }} + > + <Heading title="Provenance and resource use" /> + <Typography variant="caption" sx={{ color: 'text.secondary' }}> + Versions, models, preset settings, and memory and time per stage + </Typography> + {deviated && ( + <Chip + size="small" + color="warning" + label="Preset deviations" + variant="outlined" + /> + )} + </Stack> + </AccordionSummary> + <AccordionDetails> + <Stack spacing={1.5}> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Two runs are comparable only where every value here matches. + </Typography> + <Fields> + <Labeled label="GUI version"> + <TextField source="gui_version" emptyText="—" /> + </Labeled> + <Labeled label="Library version"> + <TextField source="library_version" emptyText="—" /> + </Labeled> + <Labeled label="Segmentation model"> + <TextField source="segmentation_model" emptyText="—" /> + </Labeled> + <Labeled label="Mapping backend"> + <TextField source="mapping_backend" emptyText="—" /> + </Labeled> + <Labeled label="Taxonomy version"> + <NumberField source="taxonomy_version" emptyText="—" /> + </Labeled> + <Labeled label="Taxonomy hash"> + <HashField source="taxonomy_hash" emptyText="—" /> + </Labeled> + <Labeled label="Preset"> + <TextField source="preset_name" emptyText="—" /> + </Labeled> + <Labeled label="Preset version"> + <NumberField source="preset_version" emptyText="—" /> + </Labeled> + <Labeled label="Preset hash"> + <HashField source="preset_hash" emptyText="—" /> + </Labeled> + <Labeled label="Duration"> + <Typography variant="body2"> + {record.run_duration_s == null + ? '—' + : formatDuration(record.run_duration_s)} + </Typography> + </Labeled> + <Labeled label="Run directory"> + <Monospace value={record.run_dir_name} /> + </Labeled> + </Fields> + + <Heading title="Model revisions" /> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Best-effort: the upstream revision present at launch, not proof it was + loaded. + </Typography> + <ProvenanceTable + value={record.model_revisions} + emptyText="No revisions recorded." + /> + + <Heading title="Preset deviations" /> + {deviated ? ( + <> + <Alert severity="warning"> + The operator departed from the {record.preset_name || 'preset'}{' '} + on the settings below. + </Alert> + <ProvenanceTable value={record.preset_deviations} emptyText="" /> + </> + ) : ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Nothing recorded. Either the run stayed on its preset, or the + desktop app did not report deviations. + </Typography> + )} + + {hasEntries(record.stage_durations) && ( + <> + <Heading title="Stage durations" /> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Wall clock per stage, so a slow run names its slow stage. + </Typography> + <StageDurationsTable value={record.stage_durations} emptyText="" /> + </> + )} + + {hasEntries(record.stage_peaks) && ( + <> + <Heading title="Stage peaks" /> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Peak resource use per stage, so an out-of-memory run stays + explicable. + </Typography> + <StagePeaksTable + value={record.stage_peaks} + totals={totals} + emptyText="" + /> + </> + )} + </Stack> + </AccordionDetails> + </Accordion> + ); +}; + +const RunLayout = () => { + const record = useRecordContext<RunRecord>(); + // The device row carries the memory ceilings the stage peaks are scaled against. + const { data: device } = useGetOne<Device>( + 'devices', + { id: record?.device_id ?? '' }, + { enabled: Boolean(record?.device_id) }, + ); + if (!record) return <Loading />; + const totals = profileTotals(device?.system_profile); + const seconds = runDuration(record.started_at, record.finished_at); + const versions = [ + record.gui_version ? `GUI ${record.gui_version}` : null, + record.library_version ? `lib ${record.library_version}` : null, + ] + .filter(Boolean) + .join(' · '); + return ( + <Stack spacing={2} sx={{ p: 2 }}> + <Panel title="Run"> + <Fields> + <Labeled label="Status"> + <StatusField /> + </Labeled> + <Labeled label="Pass"> + <ReferenceField source="pass_id" reference="passes" link="show"> + <TextField source="label" emptyText="Unlabelled pass" /> + </ReferenceField> + </Labeled> + <Labeled label="Transect"> + <ReferenceField source="pass_id" reference="passes" link={false}> + <ReferenceField + source="transect_id" + reference="transects" + link="show" + emptyText="—" + > + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + </Labeled> + <Labeled label="Started"> + <DateField source="started_at" showTime emptyText="—" /> + </Labeled> + <Labeled label="Finished"> + <DateField source="finished_at" showTime emptyText="—" /> + </Labeled> + <Labeled label="Duration"> + <Typography variant="body2"> + {seconds == null ? '—' : formatDuration(seconds)} + </Typography> + </Labeled> + <Labeled label="Device"> + <ReferenceField + source="device_id" + reference="devices" + link="show" + emptyText="—" + > + <TextField source="name" /> + </ReferenceField> + </Labeled> + <Labeled label="Preset"> + {/* Both facts: an overridden run is the one whose preset matters. */} + <Stack + direction="row" + spacing={1} + useFlexGap + sx={{ alignItems: 'center', flexWrap: 'wrap' }} + > + <Typography variant="body2">{presetLabel(record)}</Typography> + {hasEntries(record.preset_deviations) && ( + <Chip + size="small" + color="warning" + label="Deviations" + variant="outlined" + /> + )} + </Stack> + </Labeled> + <Labeled label="Versions"> + <Typography variant="body2">{versions || '—'}</Typography> + </Labeled> + </Fields> + <Heading title="Peak resource use" /> + <RunPeakSummary value={record.stage_peaks} totals={totals} /> + </Panel> + + {record.status === 'failed' && ( + <Alert severity="error"> + <Typography + variant="body2" + sx={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }} + > + {record.error || 'The run failed without reporting an error.'} + </Typography> + </Alert> + )} + + <Panel title="Cover"> + <RunCoverTable /> + </Panel> + + <Panel title="Archived outputs"> + <ArchivedOutputs /> + </Panel> + + <CloudSection /> + + <Provenance record={record} totals={totals} /> + + <SyncFields /> + </Stack> + ); +}; + +const RunTitle = () => { + const record = useRecordContext<RunRecord>(); + return <span>{record ? `Run ${record.run_dir_name || record.id}` : 'Run'}</span>; +}; + +/** Provenance for one pipeline execution. Runs are never authored here. */ +const RunShow = () => ( + <Show title={<RunTitle />} actions={false}> + <RunLayout /> + </Show> +); + +export default RunShow; diff --git a/src/runs/StageBreakdown.tsx b/src/runs/StageBreakdown.tsx new file mode 100644 index 0000000..9cdc8ef --- /dev/null +++ b/src/runs/StageBreakdown.tsx @@ -0,0 +1,380 @@ +import type { ReactNode } from 'react'; +import { + Box, + Stack, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Tooltip, + Typography, + useTheme, +} from '@mui/material'; + +import type { ProfileTotals } from '../devices/profile'; +import { formatBytes } from '../videos/VideoFields'; +import { formatDuration } from './duration'; + +// The coarse pipeline stages in execution order, matching the desktop +// application's instrumentation keys. Unknown stages render after these. +export const STAGE_ORDER = [ + 'startup', + 'preprocess', + 'mapping', + 'cloud', + 'ortho', + 'save_view', + 'scene_save', +]; + +export type StagePeak = { + ram_bytes: number | null; + swap_bytes: number | null; + vram_bytes: number | null; +}; + +/** The largest figure per metric across every stage of one run. */ +type RunPeaks = { + ram: number | null; + swap: number | null; + vram: number | null; +}; + +const asObject = (value: unknown): Record<string, unknown> | null => + value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record<string, unknown>) + : null; + +const asBytes = (value: unknown): number | null => + typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null; + +const inPipelineOrder = <T,>(entries: [string, T][]): [string, T][] => { + const rank = (stage: string) => { + const index = STAGE_ORDER.indexOf(stage); + return index === -1 ? STAGE_ORDER.length : index; + }; + return [...entries].sort((a, b) => rank(a[0]) - rank(b[0])); +}; + +/** A `stage_peaks` json column as ordered rows, dropping anything malformed. */ +const parseStagePeaks = (value: unknown): [string, StagePeak][] => { + const stages = asObject(value); + if (!stages) return []; + const entries: [string, StagePeak][] = []; + for (const [stage, raw] of Object.entries(stages)) { + const peak = asObject(raw); + if (!peak) continue; + entries.push([ + stage, + { + ram_bytes: asBytes(peak.ram_bytes), + swap_bytes: asBytes(peak.swap_bytes), + vram_bytes: asBytes(peak.vram_bytes), + }, + ]); + } + return inPipelineOrder(entries); +}; + +const parseStageDurations = (value: unknown): [string, number][] => { + const stages = asObject(value); + if (!stages) return []; + const entries: [string, number][] = []; + for (const [stage, raw] of Object.entries(stages)) { + const seconds = asBytes(raw); + if (seconds !== null) entries.push([stage, seconds]); + } + return inPipelineOrder(entries); +}; + +const peakOf = (values: (number | null)[]): number | null => + values.reduce<number | null>( + (peak, value) => (value == null ? peak : Math.max(peak ?? 0, value)), + null, + ); + +/** Above this share of a ceiling, a peak is close enough to warn about. */ +export const HOT_FRACTION = 0.85; + +/** + * The bar under a memory figure. + * + * A solid bar fills a ceiling the device reported. A striped one has no ceiling to + * fill and only ranks its row against the widest figure beside it, which is a + * weaker claim, so the two never look alike. + */ +export const MeterBar = ({ + fraction, + relative, + hot, + height = 4, + children, +}: { + fraction: number; + relative: boolean; + hot: boolean; + height?: number; + children?: ReactNode; +}) => { + const theme = useTheme(); + const colour = hot ? theme.palette.warning.main : theme.palette.primary.main; + return ( + <Box sx={{ position: 'relative', height, borderRadius: 2, bgcolor: 'action.hover' }}> + <Box + sx={{ + width: `${Math.min(Math.max(fraction, 0), 1) * 100}%`, + height: '100%', + borderRadius: 2, + ...(relative + ? { + backgroundImage: `repeating-linear-gradient(115deg, ${colour} 0 3px, transparent 3px 6px)`, + } + : { backgroundColor: colour }), + }} + /> + {children} + </Box> + ); +}; + +/** A peak with a bar under it: against the device total when known, else `max`. */ +const PeakCell = ({ + bytes, + total, + max, +}: { + bytes: number | null; + total: number | null; + max: number; +}) => { + if (bytes == null) { + return ( + <Typography + variant="body2" + component="span" + sx={{ + color: 'text.disabled', + }} + > + — + </Typography> + ); + } + const scale = total ?? max; + const hot = total != null && total > 0 && bytes / total > HOT_FRACTION; + const tip = + total == null + ? `${formatBytes(bytes)}, ranked against the largest stage of this run` + : `${formatBytes(bytes)} of ${formatBytes(total)}`; + return ( + <Tooltip title={tip}> + <Box sx={{ minWidth: 96 }}> + <Typography variant="body2" color={hot ? 'warning.main' : 'text.primary'}> + {formatBytes(bytes)} + </Typography> + <MeterBar + fraction={scale > 0 ? bytes / scale : 0} + relative={total == null} + hot={hot} + /> + </Box> + </Tooltip> + ); +}; + +/** The peak each metric reached anywhere in the run. */ +const runPeaks = (value: unknown): RunPeaks => { + const rows = parseStagePeaks(value); + return { + ram: peakOf(rows.map(([, peak]) => peak.ram_bytes)), + swap: peakOf(rows.map(([, peak]) => peak.swap_bytes)), + vram: peakOf(rows.map(([, peak]) => peak.vram_bytes)), + }; +}; + +type Gauge = { label: string; bytes: number; total: number | null }; + +const gaugeNote = (bytes: number, total: number | null): string => { + if (total == null || total <= 0) return 'this device reports no total'; + // The total is the memory the device reports now, so an older run can sit above it. + if (bytes > total) return `above the ${formatBytes(total)} it reports now`; + return `${Math.round((bytes / total) * 100)}% of the device total`; +}; + +const PeakGauge = ({ label, bytes, total }: Gauge) => { + const hot = total != null && total > 0 && bytes / total > HOT_FRACTION; + return ( + <Stack spacing={0.25} sx={{ flex: '1 1 200px', maxWidth: 300 }}> + <Typography variant="caption" sx={{ color: 'text.secondary' }}> + {label} + </Typography> + <Typography + variant="h6" + sx={{ lineHeight: 1.3 }} + color={hot ? 'warning.main' : 'text.primary'} + > + {total == null + ? formatBytes(bytes) + : `${formatBytes(bytes)} of ${formatBytes(total)}`} + </Typography> + {total != null && total > 0 && ( + <MeterBar fraction={bytes / total} relative={false} hot={hot} height={6} /> + )} + <Typography variant="caption" sx={{ color: 'text.secondary' }}> + {gaugeNote(bytes, total)} + </Typography> + </Stack> + ); +}; + +/** What the run peaked at, as gauges, so the figures read without opening anything. */ +export const RunPeakSummary = ({ + value, + totals, +}: { + value: unknown; + totals: ProfileTotals; +}) => { + const peaks = runPeaks(value); + const gauges: Gauge[] = []; + if (peaks.ram != null) + gauges.push({ label: 'Peak RAM', bytes: peaks.ram, total: totals.ram }); + // Zero swap or VRAM says the run never touched them, which earns no gauge. + if (peaks.swap) gauges.push({ label: 'Peak swap', bytes: peaks.swap, total: totals.swap }); + if (peaks.vram) gauges.push({ label: 'Peak VRAM', bytes: peaks.vram, total: totals.vram }); + if (!gauges.length) { + return ( + <Typography variant="body2" sx={{ color: 'text.secondary' }}> + This run reported no resource figures. A run that died early, or one from a + build that never sampled them, carries none. + </Typography> + ); + } + return ( + <Stack spacing={1}> + <Stack direction="row" spacing={3} useFlexGap sx={{ flexWrap: 'wrap' }}> + {gauges.map(gauge => ( + <PeakGauge key={gauge.label} {...gauge} /> + ))} + </Stack> + <Typography variant="caption" sx={{ color: 'text.secondary' }}> + Highest across every stage of the run. Stage by stage is under Provenance and + resource use. + </Typography> + </Stack> + ); +}; + +/** Per-stage memory peaks as bars, scaled to the device's ceilings when known. */ +export const StagePeaksTable = ({ + value, + totals, + emptyText, +}: { + value: unknown; + totals: ProfileTotals; + emptyText: string; +}) => { + const rows = parseStagePeaks(value); + if (!rows.length) { + return ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + {emptyText} + </Typography> + ); + } + const ramMax = peakOf(rows.map(([, peak]) => peak.ram_bytes)) ?? 0; + const swapMax = peakOf(rows.map(([, peak]) => peak.swap_bytes)) ?? 0; + const vramMax = peakOf(rows.map(([, peak]) => peak.vram_bytes)) ?? 0; + return ( + <Box> + <Table size="small" sx={{ maxWidth: 560 }}> + <TableHead> + <TableRow> + <TableCell sx={{ pl: 0 }}>Stage</TableCell> + <TableCell>RAM</TableCell> + <TableCell>Swap</TableCell> + <TableCell>VRAM</TableCell> + </TableRow> + </TableHead> + <TableBody> + {rows.map(([stage, peak]) => ( + <TableRow key={stage}> + <TableCell sx={{ border: 0, pl: 0, color: 'text.secondary' }}> + {stage} + </TableCell> + <TableCell sx={{ border: 0 }}> + <PeakCell + bytes={peak.ram_bytes} + total={totals.ram} + max={ramMax} + /> + </TableCell> + <TableCell sx={{ border: 0 }}> + <PeakCell + bytes={peak.swap_bytes} + total={totals.swap} + max={swapMax} + /> + </TableCell> + <TableCell sx={{ border: 0 }}> + <PeakCell + bytes={peak.vram_bytes} + total={totals.vram} + max={vramMax} + /> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + <Typography variant="caption" sx={{ color: 'text.secondary' }}> + A solid bar fills the total the device reported. A striped one ranks the stage + against the largest of this run, which is all there is without a total. + </Typography> + </Box> + ); +}; + +/** Per-stage wall-clock durations in pipeline order. */ +export const StageDurationsTable = ({ + value, + emptyText, +}: { + value: unknown; + emptyText: string; +}) => { + const rows = parseStageDurations(value); + if (!rows.length) { + return ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + {emptyText} + </Typography> + ); + } + return ( + <Table size="small" sx={{ maxWidth: 360 }}> + <TableBody> + {rows.map(([stage, seconds]) => ( + <TableRow key={stage}> + <TableCell sx={{ border: 0, pl: 0, color: 'text.secondary' }}> + {stage} + </TableCell> + <TableCell sx={{ border: 0 }}>{formatDuration(seconds)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + ); +}; diff --git a/src/runs/StatusField.tsx b/src/runs/StatusField.tsx new file mode 100644 index 0000000..80815da --- /dev/null +++ b/src/runs/StatusField.tsx @@ -0,0 +1,66 @@ +import { Chip } from '@mui/material'; +import { useRecordContext } from 'react-admin'; +import CancelIcon from '@mui/icons-material/Cancel'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import ErrorIcon from '@mui/icons-material/Error'; +import PauseCircleIcon from '@mui/icons-material/PauseCircle'; +import ScheduleIcon from '@mui/icons-material/Schedule'; +import SyncIcon from '@mui/icons-material/Sync'; + +import { RUN_STATUS_VALUES, RunStatus } from '../contract'; + +const STATUS_LABELS: Record<RunStatus, string> = { + pending: 'Pending', + running: 'Running', + succeeded: 'Succeeded', + failed: 'Failed', + cancelled: 'Cancelled', + interrupted: 'Interrupted', +}; + +const STATUS_COLOURS: Record<RunStatus, 'success' | 'info' | 'warning' | 'error' | 'default'> = + { + pending: 'default', + running: 'info', + succeeded: 'success', + failed: 'error', + cancelled: 'default', + interrupted: 'warning', + }; + +const STATUS_ICONS: Record<RunStatus, typeof CheckCircleIcon> = { + pending: ScheduleIcon, + running: SyncIcon, + succeeded: CheckCircleIcon, + failed: ErrorIcon, + cancelled: CancelIcon, + interrupted: PauseCircleIcon, +}; + +export const statusChoices = RUN_STATUS_VALUES.map(id => ({ id, name: STATUS_LABELS[id] })); + +/** How a run status reads everywhere it is shown, whatever record carries it. */ +export const RunStatusChip = ({ status }: { status: string }) => { + if (!(status in STATUS_LABELS)) return <span>{status}</span>; + const known = status as RunStatus; + const Icon = STATUS_ICONS[known]; + return ( + <Chip + size="small" + icon={<Icon fontSize="small" />} + label={STATUS_LABELS[known]} + color={STATUS_COLOURS[known]} + variant={known === 'succeeded' || known === 'failed' ? 'filled' : 'outlined'} + /> + ); +}; + +// `label` is read by the Datagrid header, not here. +const StatusField = ({ emptyText = '—' }: { label?: string; emptyText?: string }) => { + const record = useRecordContext(); + const status = record?.status as RunStatus | undefined; + if (!status) return <span>{emptyText}</span>; + return <RunStatusChip status={status} />; +}; + +export default StatusField; diff --git a/src/runs/duration.ts b/src/runs/duration.ts new file mode 100644 index 0000000..2a9fe07 --- /dev/null +++ b/src/runs/duration.ts @@ -0,0 +1,17 @@ +/** Elapsed wall clock of a run, or `null` while it is still going. */ +export const runDuration = ( + startedAt: string | null | undefined, + finishedAt: string | null | undefined, +): number | null => { + if (!startedAt || !finishedAt) return null; + const seconds = (Date.parse(finishedAt) - Date.parse(startedAt)) / 1000; + return Number.isFinite(seconds) && seconds >= 0 ? seconds : null; +}; + +export const formatDuration = (seconds: number): string => { + const total = Math.round(seconds); + if (total < 60) return `${total}s`; + const minutes = Math.floor(total / 60); + if (minutes < 60) return `${minutes}m ${total % 60}s`; + return `${Math.floor(minutes / 60)}h ${minutes % 60}m`; +}; diff --git a/src/runs/index.tsx b/src/runs/index.tsx new file mode 100644 index 0000000..c6f80b3 --- /dev/null +++ b/src/runs/index.tsx @@ -0,0 +1,14 @@ +import MemoryIcon from '@mui/icons-material/Memory'; + +import RunList from './RunList'; +import RunShow from './RunShow'; + +// No create or edit: the desktop app reports runs, the console only reads them. +export default { + list: RunList, + show: RunShow, + icon: MemoryIcon, + options: { + label: 'Runs', + }, +}; diff --git a/src/runs/preset.ts b/src/runs/preset.ts new file mode 100644 index 0000000..94b9854 --- /dev/null +++ b/src/runs/preset.ts @@ -0,0 +1,8 @@ +/** The preset a run names, as `name vN`. Runs carry the label, never the preset row. */ +export const presetLabel = (run: { + preset_name?: string | null; + preset_version?: number | null; +}): string => + run.preset_name + ? `${run.preset_name}${run.preset_version == null ? '' : ` v${run.preset_version}`}` + : '—'; diff --git a/src/sites/SiteCreate.tsx b/src/sites/SiteCreate.tsx new file mode 100644 index 0000000..d04c76d --- /dev/null +++ b/src/sites/SiteCreate.tsx @@ -0,0 +1,13 @@ +import { Create, SimpleForm } from 'react-admin'; + +import SiteInputs, { validateSite } from './SiteInputs'; + +const SiteCreate = () => ( + <Create redirect="show"> + <SimpleForm defaultValues={{ description: '' }} validate={validateSite}> + <SiteInputs /> + </SimpleForm> + </Create> +); + +export default SiteCreate; diff --git a/src/sites/SiteEdit.tsx b/src/sites/SiteEdit.tsx new file mode 100644 index 0000000..e17b04e --- /dev/null +++ b/src/sites/SiteEdit.tsx @@ -0,0 +1,20 @@ +import { Edit, SaveButton, SimpleForm, Toolbar } from 'react-admin'; + +import SiteInputs, { validateSite } from './SiteInputs'; + +// Rows are tombstoned by the sync contract, so the default toolbar's delete is wrong here. +const SiteEditToolbar = () => ( + <Toolbar> + <SaveButton /> + </Toolbar> +); + +const SiteEdit = () => ( + <Edit redirect="show" mutationMode="pessimistic"> + <SimpleForm toolbar={<SiteEditToolbar />} validate={validateSite}> + <SiteInputs /> + </SimpleForm> + </Edit> +); + +export default SiteEdit; diff --git a/src/sites/SiteInputs.tsx b/src/sites/SiteInputs.tsx new file mode 100644 index 0000000..9c3f643 --- /dev/null +++ b/src/sites/SiteInputs.tsx @@ -0,0 +1,92 @@ +import { maxValue, minValue, NumberInput, required, TextInput } from 'react-admin'; +import { Grid, Typography } from '@mui/material'; + +type SiteFormValues = { + latitude?: number | null; + longitude?: number | null; +}; + +/** A lone coordinate cannot be mapped, so take both or neither. */ +export const validateSite = (values: SiteFormValues) => { + const { latitude, longitude } = values; + if ((latitude == null) === (longitude == null)) return {}; + return latitude == null + ? { latitude: 'Latitude is required when longitude is set' } + : { longitude: 'Longitude is required when latitude is set' }; +}; + +const SiteInputs = () => ( + <> + <Typography variant="h6" gutterBottom> + Identity + </Typography> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <TextInput + source="name" + validate={required()} + helperText="Unique across the registry, ignoring case." + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 3, + }} + > + <TextInput source="country" fullWidth /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 3, + }} + > + <TextInput source="region" fullWidth /> + </Grid> + <Grid size={12}> + <TextInput source="description" multiline rows={3} fullWidth /> + </Grid> + </Grid> + + <Typography variant="h6" gutterBottom sx={{ mt: 2 }}> + Representative point + </Typography> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <NumberInput + source="latitude" + validate={[minValue(-90), maxValue(90)]} + helperText="Decimal degrees, -90 to 90." + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <NumberInput + source="longitude" + validate={[minValue(-180), maxValue(180)]} + helperText="Decimal degrees, -180 to 180." + fullWidth + /> + </Grid> + </Grid> + </> +); + +export default SiteInputs; diff --git a/src/sites/SiteList.tsx b/src/sites/SiteList.tsx new file mode 100644 index 0000000..50036cb --- /dev/null +++ b/src/sites/SiteList.tsx @@ -0,0 +1,70 @@ +import { + CreateButton, + Datagrid, + ExportButton, + List, + SearchInput, + TextField, + TextInput, + TopToolbar, +} from 'react-admin'; +import { Box, Typography } from '@mui/material'; + +import { SiteMapAll } from '../maps/Sites'; +import { useCanAuthor } from '../permissions'; + +const siteFilters = [ + <SearchInput source="q" alwaysOn key="q" />, + <TextInput source="country" key="country" />, + <TextInput source="region" key="region" />, +]; + +const SiteListActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + {canAuthor && <CreateButton />} + <ExportButton /> + </TopToolbar> + ); +}; + +const SiteEmpty = () => { + const canAuthor = useCanAuthor(); + return ( + <Box + sx={{ + textAlign: 'center', + m: 4, + }} + > + <Typography variant="h6" gutterBottom> + No sites yet + </Typography> + <Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}> + A site is a named reef location. Define one here, then add the transects the + divers will swim, and the desktop clients will pick both up on their next sync. + </Typography> + {canAuthor && <CreateButton label="Create the first site" />} + </Box> + ); +}; + +const SiteList = () => ( + <List + actions={<SiteListActions />} + filters={siteFilters} + sort={{ field: 'name', order: 'ASC' }} + perPage={25} + empty={<SiteEmpty />} + > + <SiteMapAll /> + <Datagrid rowClick="show" bulkActionButtons={false}> + <TextField source="name" /> + <TextField source="country" emptyText="—" /> + <TextField source="region" emptyText="—" sortable={false} /> + </Datagrid> + </List> +); + +export default SiteList; diff --git a/src/sites/SiteShow.tsx b/src/sites/SiteShow.tsx new file mode 100644 index 0000000..a86d6ad --- /dev/null +++ b/src/sites/SiteShow.tsx @@ -0,0 +1,145 @@ +import { + CreateButton, + Datagrid, + EditButton, + FunctionField, + Labeled, + Loading, + NumberField, + Pagination, + ReferenceManyField, + Show, + SimpleShowLayout, + TextField, + TopToolbar, + useRecordContext, +} from 'react-admin'; +import { Box, Divider, Grid, Typography } from '@mui/material'; + +import { CoordinateField, SyncFields, TombstoneButton } from '../components'; +import { useCanAuthor } from '../permissions'; +import { SiteMapOne } from '../maps/Sites'; +import type { Site, Transect } from '../contract'; + +const SiteShowActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + {canAuthor && <EditButton />} + <TombstoneButton noun="site" /> + </TopToolbar> + ); +}; + +const SiteMap = () => { + const record = useRecordContext<Site>(); + if (!record) return <Loading />; + return <SiteMapOne record={record} />; +}; + +const NoTransects = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + No transects yet. + </Typography> +); + +const SiteTransects = () => { + const canAuthor = useCanAuthor(); + const record = useRecordContext<Site>(); + return ( + <Box> + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + Transects + </Typography> + <ReferenceManyField + reference="transects" + target="site_id" + sort={{ field: 'name', order: 'ASC' }} + perPage={10} + pagination={<Pagination />} + > + <Datagrid rowClick="show" bulkActionButtons={false} empty={<NoTransects />}> + <TextField source="name" /> + <NumberField source="length_m" label="Length (m)" emptyText="—" /> + <NumberField source="depth_m" label="Depth (m)" emptyText="—" /> + <FunctionField<Transect> + label="Start" + render={record => `${record.start_lat}°, ${record.start_lon}°`} + /> + <FunctionField<Transect> + label="End" + render={record => `${record.end_lat}°, ${record.end_lon}°`} + /> + </Datagrid> + </ReferenceManyField> + {canAuthor && record && ( + <Box + sx={{ + mt: 1, + }} + > + <CreateButton + resource="transects" + label="Add transect" + state={{ record: { site_id: record.id } }} + /> + </Box> + )} + </Box> + ); +}; + +const SiteShow = () => ( + <Show actions={<SiteShowActions />}> + <SimpleShowLayout> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + md: 5, + }} + > + <Labeled label="Name"> + <TextField source="name" /> + </Labeled> + <Labeled label="Country"> + <TextField source="country" emptyText="—" /> + </Labeled> + <Labeled label="Region"> + <TextField source="region" emptyText="—" /> + </Labeled> + <Labeled label="Coordinates"> + <CoordinateField latSource="latitude" lonSource="longitude" /> + </Labeled> + <Labeled label="Description"> + <TextField source="description" emptyText="—" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + md: 7, + }} + > + <SiteMap /> + </Grid> + </Grid> + <Divider /> + <SiteTransects /> + <Divider /> + <SyncFields /> + </SimpleShowLayout> + </Show> +); + +export default SiteShow; diff --git a/src/sites/index.tsx b/src/sites/index.tsx new file mode 100644 index 0000000..1c849d5 --- /dev/null +++ b/src/sites/index.tsx @@ -0,0 +1,17 @@ +import PlaceIcon from '@mui/icons-material/Place'; + +import SiteCreate from './SiteCreate'; +import SiteEdit from './SiteEdit'; +import SiteList from './SiteList'; +import SiteShow from './SiteShow'; + +export default { + list: SiteList, + show: SiteShow, + edit: SiteEdit, + create: SiteCreate, + icon: PlaceIcon, + options: { + label: 'Sites', + }, +}; diff --git a/src/status/StatusList.tsx b/src/status/StatusList.tsx deleted file mode 100644 index eb5047b..0000000 --- a/src/status/StatusList.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { - useDataProvider, - Loading, -} from "react-admin"; -import { useEffect, useState } from "react"; -import { Card, Stack, Typography, Grid, Paper, Divider } from '@mui/material'; -import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; - -export const RunnerStatus = () => { - const dataProvider = useDataProvider(); - const [systemStatus, setSystemStatus] = useState("Offline"); - const [status, setStatus] = useState([]); - const [runningJobCount, setRunningJobCount] = useState(0); - const [jobCount, setJobCount] = useState(null); - - useEffect(() => { - const fetchData = async () => { - const statusData = await dataProvider.getStatus(); - setStatus(statusData.data); - setSystemStatus("Online"); - }; - - fetchData(); - }, [dataProvider]); - - useEffect(() => { - if (!status) { - return (<Loading />); - } - const getRunningJobs = () => { - let runningJobs = 0; - status?.kubernetes?.forEach((job) => { - if (job.status.phase === "Pending" - || job.status.phase === "Running" - || job.status.phase === "ContainerCreating") { - runningJobs++; - } - }); - return runningJobs; - }; - setRunningJobCount(getRunningJobs); - setJobCount(status?.kubernetes?.length ?? 0); - }, [status]); - - return ( - <Card sx={{ padding: 2, marginBottom: 2 }}> - <Typography variant="h5" gutterBottom> - System Status - </Typography> - - <Grid container spacing={3} sx={{ marginTop: 2 }}> - <Grid item xs={12} sm={6}> - <Paper variant="outlined" sx={{ padding: 2 }}> - <Typography variant="h6">Services</Typography> - <Typography variant="caption">If any of these are offline, contact an Administrator</Typography> - <Divider sx={{ marginY: 1 }} /> - <Typography - variant="body1" - color={status.kubernetes_status ? "green" : "error"} - > - RCP GPUs: {status.kubernetes_status ? "Online" : "Offline"} - </Typography> - <Typography - variant="body1" - color={status.s3_status ? "green" : "error"} - > - S3 storage: {status.s3_status ? "Online" : "Offline"} - </Typography> - </Paper> - </Grid> - - <Grid item xs={12} sm={6}> - <Paper variant="outlined" sx={{ padding: 2 }}> - <Typography variant="h6">Jobs</Typography> - <Divider sx={{ marginY: 1 }} /> - <Typography variant="body1">Running: {runningJobCount}</Typography> - <Typography variant="body1">Finished: {jobCount}</Typography> - </Paper> - </Grid> - - <Grid item xs={12}> - <Paper variant="outlined" sx={{ padding: 2 }}> - <Typography variant="h6">S3 Bucket</Typography> - <Divider sx={{ marginY: 1 }} /> - <TableContainer> - <Table size="small"> - <TableHead> - <TableRow> - <TableCell>Property</TableCell> - <TableCell align="right">Value</TableCell> - </TableRow> - </TableHead> - <TableBody> - {status?.s3_local && ( - <> - <TableRow> - <TableCell>Total Object Count</TableCell> - <TableCell align="right">{status.s3_local.total_object_count}</TableCell> - </TableRow> - <TableRow> - <TableCell>Input Object Size (GB)</TableCell> - <TableCell align="right">{(status.s3_local.input_size / 1024 / 1024 / 1024).toFixed(2)}</TableCell> - </TableRow> - <TableRow> - <TableCell>Input Object Count</TableCell> - <TableCell align="right">{status.s3_local.input_object_count}</TableCell> - </TableRow> - <TableRow> - <TableCell>Output Object Size (GB)</TableCell> - <TableCell align="right">{(status.s3_local.output_size / 1024 / 1024 / 1024).toFixed(2)}</TableCell> - </TableRow> - <TableRow> - <TableCell>Output Object Count</TableCell> - <TableCell align="right">{status.s3_local.output_object_count}</TableCell> - </TableRow> - <TableRow> - <TableCell>Total Size (GB)</TableCell> - <TableCell align="right">{(status.s3_local.total_size / 1024 / 1024 / 1024).toFixed(2)}</TableCell> - </TableRow> - </> - )} - </TableBody> - </Table> - </TableContainer> - </Paper> - </Grid> - </Grid> - </Card > - ); -}; - -const StatusList = () => { - return ( - <Stack spacing={2}> - <RunnerStatus /> - </Stack> - ); -}; - -export default StatusList; diff --git a/src/status/index.tsx b/src/status/index.tsx deleted file mode 100644 index 3a3d56a..0000000 --- a/src/status/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import StatusList from './StatusList'; -import MonitorHeartIcon from '@mui/icons-material/MonitorHeart'; -export default { - // create: SubmissionCreate, - // edit: SubmissionEdit, - list: StatusList, - // show: SubmissionShow, - icon: MonitorHeartIcon, - options: { - label: 'Status', - }, -}; diff --git a/src/submissions/SubmissionCreate.tsx b/src/submissions/SubmissionCreate.tsx deleted file mode 100644 index 54ab863..0000000 --- a/src/submissions/SubmissionCreate.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - Create, - SimpleForm, - NumberInput, - TextInput, - ReferenceInput, - SelectInput, - required, - ArrayInput, - SimpleFormIterator, - minValue, - useChoicesContext, -} from 'react-admin'; -import { useFormContext } from 'react-hook-form'; -import { Grid, Typography } from '@mui/material'; - - -export const videoArraySizeValidation = (value) => { - if (value.length < 1) { - return 'At least one video must be processed'; - } - if (value.length > 2) { - return 'Only two videos can be processed at a time'; - } - // In the two array items, there should be only unique processing_order values - if (value.length === 2) { - if (value[0].processing_order === value[1].processing_order) { - return 'The processing order must be unique'; - } - } - if (value.length === 2 && value[0].input_object_id === value[1].input_object_id) { - return 'The videos must be unique. The same video has been chosen twice'; - } - return undefined; -} - -export const endDurationValidation = (value, allValues) => { - if (value <= allValues.time_seconds_start && allValues.input_associations.length === 1) { - return 'The end time must be greater than the start time'; - } - return undefined; -}; - -export const TotalDuration = ({ videoChoices }) => { - // Overengineered component to calculate the total duration of the videos - - const { getValues, watch } = useFormContext(); - const [startTime, setStartTime] = useState(null); - const [endTime, setEndTime] = useState(null); - const [videos, setVideos] = useState(null); - const [totalDuration, setTotalDuration] = useState(0); - const [firstVideoDuration, setFirstVideoDuration] = useState(null); - const [secondVideoDuration, setSecondVideoDuration] = useState(null); - const [outputText, setOutputText] = useState("Choose a video, a start and an end time"); - const [overTotalDuration, setOverTotalDuration] = useState(false); - - useEffect(() => { - const startTime = getValues('time_seconds_start'); - const endTime = getValues('time_seconds_end'); - const videos = getValues('input_associations'); - - setStartTime(startTime); - setEndTime(endTime); - setVideos(videos); - }, [watch('time_seconds_start'), watch('time_seconds_end'), watch('input_associations'), videoChoices]); - - useEffect(() => { - // Get just the first video's duration, as the second video's total - // time is not needed as we can just use the end time to add to the - // first video's duration - - if (videos && videoChoices && videoChoices.length > 0 && videos.length > 0 && videos[0].input_object_id) { - const video_id = videos[0].input_object_id; - const video = videoChoices.find(video => video.id === video_id); - if (video) { - setFirstVideoDuration(video.time_seconds); - } - } - if (videos && videoChoices && videoChoices.length > 0 && videos.length > 1 && videos[1].input_object_id) { - const video_id = videos[1].input_object_id; - const video = videoChoices.find(video => video.id === video_id); - if (video) { - setSecondVideoDuration(video.time_seconds); - } - } - }, [videos, startTime, endTime, videoChoices]); - - useEffect(() => { - if (videos && startTime && endTime) { - if (videos.length === 1) { - setTotalDuration(endTime - startTime); - } else if (videos.length === 2 && firstVideoDuration) { - setTotalDuration((firstVideoDuration - startTime) + endTime); - } - } else { - setTotalDuration(0); - } - }, [videos, startTime, endTime, firstVideoDuration, secondVideoDuration]); - - useEffect(() => { - if (videos) { - if (firstVideoDuration && videos.length === 1 && (endTime > firstVideoDuration || startTime > firstVideoDuration)) { - setOverTotalDuration(true); - return - } - if (firstVideoDuration && secondVideoDuration && videos.length === 2 && (endTime > secondVideoDuration || startTime > firstVideoDuration)) { - setOverTotalDuration(true); - return - } - else { - setOverTotalDuration(false); - return - } - } - }, [firstVideoDuration, secondVideoDuration, startTime, endTime, videos]); - useEffect(() => { - if (totalDuration > 0) { - setOutputText(`Total duration: ${Math.round(totalDuration)} seconds`); - } else { - setOutputText("Choose a video, a start and an end time"); - } - }, [totalDuration]); - - if (!videos || videos.length === 0 || videos.length > 2) { - return null; - } - - return ( - <> - <Typography variant="body1">{outputText}</Typography> - <Typography variant="caption"> - {videos.length > 1 ? "Note: Time is calculated on two videos" : null} - </Typography> - <br /> - {overTotalDuration ? <Typography variant="caption" color="error" > - Warning: The given times exceed the video's duration - </Typography> : null} - - </> - ); -}; - - -export const VideoInput = ({ transectID, setChoices }) => { - const { allChoices } = useChoicesContext(); - - useEffect(() => { - if (allChoices) { - setChoices(allChoices); - } - }, [allChoices]); - - return ( - <> - <SelectInput - optionText={(record) => `${record.filename} (${record.time_seconds} seconds, ${(record.size_bytes / 1024 / 1024).toFixed(2)} MB)`} - fullWidth - disabled={!transectID} - /> - </> - ); -}; - - - -export const VideoChoice = ({ setQtyVideos, setChoices }) => { - const { setValue, getValues, watch } = useFormContext(); - const [transectID, setTransectID] = useState(null); - - useEffect(() => { - const transectID = getValues('transect_id'); - setTransectID(transectID); - }, [watch('transect_id')]); - // Effect to set processing_order based on index - useEffect(() => { - const setProcessingOrder = () => { - const values = getValues('input_associations'); - if (values) { - values.forEach((item, index) => { - setValue(`input_associations[${index}].processing_order`, index + 1); - }); - setQtyVideos(values.length); - } - }; - setProcessingOrder(); - }, [watch('input_associations')]); - - - return (<> - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Videos - </Typography> - <Typography variant="caption" gutterBottom> - Select a video in the order it is to be processed with the model. If you select two, the videos will be concatenated by the model. - </Typography> - <ArrayInput source="input_associations" label="Videos" validate={[videoArraySizeValidation]}> - <SimpleFormIterator - inline - getItemLabel={index => `#${index + 1}`} - disableAdd={getValues('input_associations')?.length === 2} - disableClear - > - <Grid container spacing={2}> - <Grid item xs={12}> - <ReferenceInput source="input_object_id" reference="objects" label="Select Video" filter={{ transect_id: getValues('transect_id') }} > - {transectID ? null : <Typography variant="caption" color="error" gutterBottom>Choose a transect first</Typography>} - <VideoInput transectID={transectID} setChoices={setChoices} /> - </ReferenceInput> - </Grid> - </Grid> - </SimpleFormIterator> - </ArrayInput > - </> - ); - -} - -const SubmissionCreate = () => { - const [qtyVideos, setQtyVideos] = useState(undefined); - const [videoChoices, setVideoChoices] = useState([]); - - return ( - <Create redirect="show"> - <SimpleForm> - <Typography variant="h6" gutterBottom> - Basic Information - </Typography> - <Grid container spacing={2}> - <Grid item xs={12}> - <TextInput source="name" fullWidth validate={[required()]} /> - </Grid> - <Grid item xs={12}> - <TextInput source="description" multiline fullWidth /> - </Grid> - </Grid> - - {/* Transect Reference */} - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Transect Information - </Typography> - <Grid container spacing={2}> - <Grid item xs={12}> - <ReferenceInput source="transect_id" reference="transects"> - <SelectInput - optionText={(record) => - `${record.name} (${record.latitude_start}°, ${record.longitude_start}°) - (${record.latitude_end}°, ${record.longitude_end}°)` - } - fullWidth - /> - </ReferenceInput> - </Grid> - </Grid> - - <VideoChoice setQtyVideos={setQtyVideos} setChoices={setVideoChoices} /> - - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Processing Settings - </Typography> - <Grid container spacing={2}> - <Grid item xs={12} sm={3}> - <NumberInput - source="fps" - label="FPS" - step={1} - validate={[required(), minValue(1)]} - helperText="The FPS used to process the model" /> - </Grid> - <Grid item xs={12} sm={3}> - <NumberInput - source="time_seconds_start" - step={1} - validate={[required(), minValue(0)]} - fullWidth - helperText={qtyVideos && qtyVideos === 2 ? "The start position in seconds in the first video" : "The start position in seconds in the video"} - /> - </Grid> - <Grid item xs={12} sm={3}> - <NumberInput - source="time_seconds_end" - step={1} - validate={[required(), minValue(0), endDurationValidation]} - helperText={qtyVideos && qtyVideos === 2 ? "The end position in seconds relative to the beginning of the second video" : "The end position in seconds in the video"} - fullWidth - /> - </Grid> - <Grid item xs={12} sm={3}> - <TotalDuration videoChoices={videoChoices} /> - </Grid> - </Grid> - </SimpleForm> - </Create> - ); -}; - -export default SubmissionCreate; diff --git a/src/submissions/SubmissionEdit.tsx b/src/submissions/SubmissionEdit.tsx deleted file mode 100644 index 1b6a3f1..0000000 --- a/src/submissions/SubmissionEdit.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { - Edit, - SimpleForm, - TextInput, - NumberInput, - minValue, - required, - SelectInput, - ReferenceInput, -} from 'react-admin'; -import { TotalDuration, endDurationValidation, VideoChoice } from './SubmissionCreate'; -import { Grid, Typography } from '@mui/material'; -import { useState } from 'react'; - - -const SubmissionEdit = () => { - const [qtyVideos, setQtyVideos] = useState(undefined); - const [videoChoices, setVideoChoices] = useState([]); - - return ( - <Edit resource="submissions" redirect="show" mutationMode="pessimistic"> - <SimpleForm> - <Typography variant="h6" gutterBottom> - Basic Information - </Typography> - <Grid container spacing={2}> - <Grid item xs={12}> - <TextInput source="name" fullWidth validate={[required()]} /> - </Grid> - <Grid item xs={12}> - <TextInput source="description" multiline fullWidth /> - </Grid> - </Grid> - - {/* Transect Reference */} - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Transect Information - </Typography> - <Grid container spacing={2}> - <Grid item xs={12}> - <ReferenceInput source="transect_id" reference="transects"> - <SelectInput - optionText={(record) => - `${record.name} (${record.latitude_start}°, ${record.longitude_start}°) - (${record.latitude_end}°, ${record.longitude_end}°)` - } - fullWidth - /> - </ReferenceInput> - </Grid> - </Grid> - - <VideoChoice setQtyVideos={setQtyVideos} setChoices={setVideoChoices} /> - - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Processing Settings - </Typography> - <Grid container spacing={2}> - <Grid item xs={12} sm={3}> - <NumberInput - source="fps" - label="FPS" - step={1} - validate={[required(), minValue(1)]} - helperText="The FPS used to process the model" /> - </Grid> - <Grid item xs={12} sm={3}> - <NumberInput - source="time_seconds_start" - step={1} - validate={[required(), minValue(0)]} - fullWidth - helperText={qtyVideos && qtyVideos === 2 ? "The start position in seconds in the first video" : "The start position in seconds in the video"} - /> - </Grid> - <Grid item xs={12} sm={3}> - <NumberInput - source="time_seconds_end" - step={1} - validate={[required(), minValue(0), endDurationValidation]} - helperText={qtyVideos && qtyVideos === 2 ? "The end position in seconds relative to the beginning of the second video" : "The end position in seconds in the video"} - fullWidth - /> - </Grid> - <Grid item xs={12} sm={3}> - <TotalDuration videoChoices={videoChoices} /> - </Grid> - </Grid> - </SimpleForm> - </Edit> - ) -}; - -export default SubmissionEdit; diff --git a/src/submissions/SubmissionJobLogsShow.tsx b/src/submissions/SubmissionJobLogsShow.tsx deleted file mode 100644 index 22067fe..0000000 --- a/src/submissions/SubmissionJobLogsShow.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { - Show, - SimpleShowLayout, - TextField, - useRecordContext, - useRedirect, -} from 'react-admin'; -import { Button } from '@mui/material'; -import ArrowBackIcon from '@mui/icons-material/ArrowBack'; - -const extractSubmissionId = (id) => { - // Form the UUID of the submission from the job log ID - // The UUID should be suffixed after the 1st hyphen - // eg. deepreef-7694163c-48f9-414c-a8a6-32d0c94b1147-60980-0-0 - - const parts = id.split('-'); - return parts.slice(1, 6).join('-'); // Extract the middle part -}; - -const BackToSubmissionButton = () => { - - const record = useRecordContext(); - - if (!record) return null; - - const redirect = useRedirect(); - return ( - - <Button - variant="contained" - startIcon={<ArrowBackIcon />} - onClick={() => { - const submissionId = extractSubmissionId(record.id); - redirect('show', 'submissions', submissionId); - }} - > - Back to Submission - </Button> - ); -} - -const SubmissionJobLogsShow = () => { - - return ( - <Show> - <SimpleShowLayout> - <BackToSubmissionButton /> - <TextField source="message" component="pre" /> - </SimpleShowLayout> - </Show> - ); -}; - -export default SubmissionJobLogsShow; diff --git a/src/submissions/SubmissionList.tsx b/src/submissions/SubmissionList.tsx deleted file mode 100644 index 1fbfb39..0000000 --- a/src/submissions/SubmissionList.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { - List, - Datagrid, - TextField, - usePermissions, - TopToolbar, - CreateButton, - ExportButton, - DateField, - FunctionField, - ReferenceField, - useRecordContext, - useCreatePath, - Link, -} from "react-admin"; -import { stopPropagation } from "ol/events/Event"; - -const SubmissionListActions = () => { - const { permissions } = usePermissions(); - - return ( - - <TopToolbar > - <> - {permissions === 'admin' ? <CreateButton /> : null} - <ExportButton /> - </> - </TopToolbar> - ); -} -const TransectNameField = () => { - const record = useRecordContext(); - const createPath = useCreatePath(); - if (!record) return <Loading />; - - let path = null; - if (record.transect) { - path = createPath({ - resource: 'transects', - type: 'show', - id: record.transect.id, - }); - } - - return path ? ( - <Link to={path} onClick={stopPropagation}> - <TextField source="transect.name" label="Area" emptyText='N/A' /> - </Link> - ) : ( - <TextField source="transect.name" label="Area" emptyText='N/A' /> - ); -}; - -const SubmissionList = () => { - const FieldWrapper = ({ children, label }) => children; - const { permissions } = usePermissions(); - return ( - <List disableSyncWithLocation - actions={<SubmissionListActions />} - perPage={25} - sort={{ field: 'time_added_utc', order: 'DESC' }} - > - <> - <Datagrid - bulkActionButtons={permissions === 'admin' ? true : false} - rowClick="show" - size="small" - > - <DateField - label="Submitted at" - source="time_added_utc" - showTime - transform={value => new Date(value + 'Z')} // Fix UTC time - /> - <FunctionField render={(record) => { - if (record.name === null) { - return record.id - } - return `${record.name}` - }} label="Name" /> - <FunctionField render={record => `${record?.input_associations?.length ?? ""}`} label="Files" /> - - <FunctionField - label="Last job status" - render={record => `${record?.run_status[0]?.status ?? 'No jobs submitted'}`} - /> - <FieldWrapper label="Transect"><TransectNameField /></FieldWrapper> - {permissions === 'admin' ? ( - <ReferenceField source="owner" reference="users" link="show"> - <FunctionField render={record => `${record.firstName} ${record.lastName}`} source="Owner" /> - </ReferenceField> - ) : null} - - </Datagrid> - </> - </List > - - ) -}; - -export default SubmissionList; diff --git a/src/submissions/SubmissionShow.tsx b/src/submissions/SubmissionShow.tsx deleted file mode 100644 index 9ed62b5..0000000 --- a/src/submissions/SubmissionShow.tsx +++ /dev/null @@ -1,468 +0,0 @@ -import { - Show, - SimpleShowLayout, - TextField, - NumberField, - EditButton, - TopToolbar, - DeleteButton, - usePermissions, - DateField, - Labeled, - ArrayField, - Datagrid, - Button, - useRecordContext, - useDataProvider, - TabbedShowLayout, - FunctionField, - useRefresh, - useCreatePath, - useTheme, - Link, - ReferenceField, - useNotify, - BooleanField, -} from 'react-admin'; // eslint-disable-line import/no-unresolved -import { useState } from 'react'; -import { Box, Typography, Grid } from '@mui/material'; -import Plot from 'react-plotly.js'; -import { stopPropagation } from 'ol/events/Event'; -import { TransectMapOne } from '../maps/Transects'; -import Brightness1TwoToneIcon from '@mui/icons-material/Brightness1TwoTone'; -import { FilePond } from 'react-filepond'; - -const TransectNameField = () => { - const createPath = useCreatePath(); - const record = useRecordContext(); - let path = null; - if (!record) return (<Typography>No associated transect" </Typography>); - - if (record.transect) { - path = createPath({ - resource: 'transects', - type: 'show', - id: record.transect.id, - }); - } - - return ( - <> - <Typography variant="caption"> - This is the map of the transect that the submission is associated with. - </Typography> - <br /> - <Link to={path} onClick={stopPropagation} style={{ textDecoration: 'none' }} > - Transect: <TextField source="transect.name" label="Area" emptyText='No transect defined' /> - </Link> - <TransectMapOne record={record.transect} /> - </> - ); -}; - -const calculateDuration = (record) => { - // Get the total duration of the submission. We need to know the file inputs - const fileInputs = record.input_associations.sort((a, b) => a.processing_order - b.processing_order); - - if (fileInputs.length === 0) { - return 0 - } else if (fileInputs.length === 1) { - return Math.round(record.time_seconds_end - record.time_seconds_start) - } else { - return Math.round(fileInputs[0].input_object.time_seconds - record.time_seconds_start + record.time_seconds_end) - - } - return 0 -} - - -const SubmissionShow = (props) => { - const [disableExecuteButton, setDisableExecuteButton] = useState(false); - const [listOfDisabledDeletionButtons, setListOfDisabledDeletionButtons] = useState([]); - const readinessStatusMessageGenerator = (record) => { - // Add a list of possible statuses here. Append each if statement to the list - var statusList = []; - - if (record.fps === null) { - statusList.push('FPS not set'); - } - - if (record.time_seconds_start === null) { - statusList.push('Start time not set'); - } - - if (record.time_seconds_end === null) { - statusList.push('End time not set'); - } - if (record.input_associations.length === 0) { - statusList.push('No input files'); - } - - return statusList; - } - - const readinessStatusMessage = (record) => { - const statusList = readinessStatusMessageGenerator(record); - - // Return the text in green if ready, red if not - if (jobStatus(record) === 'Pending' || jobStatus(record) === 'Running') { - return 'Job is running...'; - } - if (statusList.length === 0) { - return 'Ready. Click "Execute Job" to run.'; - } - - return statusList.join(', '); - } - - const SubmissionShowActions = () => { - function timeout(delay: number) { - return new Promise(res => setTimeout(res, delay)); - } - - const dataProvider = useDataProvider(); - const record = useRecordContext(); - const refresh = useRefresh(); - if (!record) return null; - - // Create a function callback for onClick that calls a PUT request to the API - const executeJob = () => { - // Wait for return of the promise before refreshing the page - dataProvider.executeKubernetesJob(record.id).then(() => { - notify('Job submitted. It may take some time for it to appear...'); - setDisableExecuteButton(true); - timeout(10000).then(() => { - setDisableExecuteButton(false); - }); - }); - } - const readyToSubmit = readinessStatusMessageGenerator(record).length !== 0; - - return ( - <TopToolbar> - <> - <Typography variant="caption" align='right'> - To make modifications to the FPS, start and end times, click the 'Edit' button. <br />Once ready, click 'Execute Job' to run the submission. - </Typography> - <Button - variant="contained" - color="success" - disabled={readyToSubmit || disableExecuteButton} - onClick={executeJob}>Execute Job</Button> - <EditButton /> - <DeleteButton /></> - </TopToolbar> - ); - } - - const createPath = useCreatePath(); - const { permissions } = usePermissions(); - const dataProvider = useDataProvider(); - const notify = useNotify(); - - const redirectToJobLogs = (id, basePath, record, event) => { - if (record.status == 'Pending') { - notify('Job is pending, logs are not available yet'); - return false; - } - if (record.logs.length === 0) { - notify('Logs are not available for this job'); - return false; - } - return createPath({ - resource: 'submission_job_logs', - type: 'show', - id: record.kubernetes_pod_name, - }); - }; - const redirectToObject = (id, basePath, record) => { - return createPath({ - resource: 'objects', - type: 'show', - id: record.input_object.id, - }); - }; - - const downloadFile = (id, basePath, record, event) => { - dataProvider.downloadFile(record.url); - event.stopPropagation(); - }; - - - const DeleteKubernetesJobButton = () => { - const record = useRecordContext(); - return <Button - type="button" - variant="outlined" - color="error" - label="Request Deletion" - disabled={record.time_started === null || !record.is_still_kubernetes_resource || listOfDisabledDeletionButtons.includes(record.kubernetes_pod_name) - } - onClick={(event) => { - dataProvider.deleteKubernetesJob(record.kubernetes_pod_name).then( - () => { - notify('Deletion request sent. It may take some time for the job to be deleted.') - // Add record.kubernetes_pod_name from the record to the list of disabled buttons - setListOfDisabledDeletionButtons([...listOfDisabledDeletionButtons, record.kubernetes_pod_name]); - } - ).catch( - () => notify('Deletion request failed. It may have already been deleted. Please try again later.') - ); - event.stopPropagation(); - } - } - />; - }; - const jobStatus = (record) => { - return record.run_status[0]?.status ?? 'No status'; - } - const ClassPieChart = () => { - const record = useRecordContext(); - const [theme, setTheme] = useTheme(); - - const data = record.percentage_covers; - const rgbToString = (rgbArray) => `rgb(${rgbArray.join(', ')})`; - // If no data is available, return a message - if (data.length === 0) { - return <> - <Grid container justifyContent="center" alignItems="center"> - <Typography variant="body1" align='center'> - Execute a job to obtain class information - </Typography> - </Grid> - </>; - } - const labels = data.map((item) => `${item.class} (${(item.percentage_cover * 100).toFixed(2)}%)`); - const colors = data.map((item) => rgbToString(item.color)); // Convert RGB array to CSS rgb string - // Set labels to capitalise the first letter - - labels.forEach((label, index) => { - labels[index] = label.charAt(0).toUpperCase() + label.slice(1); - }); - - const values = data.map((item) => item.percentage_cover); - const pieData = [{ - values: values, - labels: labels, - type: 'pie', - hoverinfo: 'label+percent', - textinfo: 'label+percent', - textposition: 'inside', - insidetextorientation: 'radial', - marker: { - colors: colors, // Assign the custom colors to the pie chart - }, - }]; - return (<> - <Typography variant="h6" align='center'>Classes</Typography> - <Plot - data={pieData} - layout={{ - width: 800, - height: 400, - paper_bgcolor: theme === 'dark' ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', - autosize: true, - margin: { - l: 0, // Left margin - r: 0, // Right margin - t: 0, // Top margin - b: 0, // Bottom margin - }, - font: { - color: theme === 'dark' ? 'white' : 'black', - size: 14, - } - }} - /></> - ); - }; - const StatusIndicator = () => { - const record = useRecordContext(); - if (!record) return null; - - const noUserInputErrors: boolean = (readinessStatusMessageGenerator(record).length === 0); - const jobStatusMessage = jobStatus(record); - - return (<><Brightness1TwoToneIcon color={ - noUserInputErrors ? ( - (jobStatusMessage == "Pending" || jobStatusMessage == "Running") ? "warning" : "success") - : "error"} /> - <FunctionField paddingLeft={1} - label="Readiness status" - render={readinessStatusMessage} - /></> - ); - } - const RunStatusIndicator = ( - - ) => { - const record = useRecordContext(); - if (!record) return null; - - console.log(record); - const isRunning = record.status === 'Running' || record.status === 'Pending'; - const isError = record.status === 'Error'; - const isComplete = record.status === 'Succeeded'; - - return (<> - <Brightness1TwoToneIcon - color={isRunning ? "warning" : isError ? "error" : isComplete ? "success" : "disabled"} - /></> - - ); - - - - }; - - return ( - <Show actions={<SubmissionShowActions />} {...props} queryOptions={{ refetchInterval: 5000 }}> - <SimpleShowLayout> - <Grid container alignItems="center" justifyContent="space-between"> - <Grid item> - <Box sx={{ display: 'flex', alignItems: 'center' }}> - <StatusIndicator /> - </Box> - </Grid> - <Grid item> - <DateField - label="Submitted at" - source="time_added_utc" - sortable={false} - showTime - transform={value => new Date(value + 'Z')} // Fix UTC time - /> - </Grid> - </Grid> - <Box - sx={{ - height: 2, - width: '100%', - bgcolor: 'gray', - marginY: 1, - }} - /> - <Grid container> - <Grid item xs={2}> - <Box sx={{ display: 'flex', flexDirection: 'column' }}> - {permissions === 'admin' ? ( - <> - <Labeled> - <ReferenceField source="owner" reference="users" link="show"> - <FunctionField render={record => `${record.firstName} ${record.lastName} `} source="Owner" /> - </ReferenceField> - </Labeled> - </> - ) : null} - <Labeled> - <FunctionField - label="FPS" - render={record => record.fps === null ? <Typography variant="body" color='red' >Required</Typography> : record.fps} - /> - </Labeled> - <Labeled> - <FunctionField - label="Start time (s)" - render={record => record.time_seconds_start === null ? <Typography variant="body" color='red' >Required</Typography> : record.time_seconds_start} - /> - </Labeled> - <Labeled> - <FunctionField - label="End time (s)" - render={record => record.time_seconds_end === null ? <Typography variant="body" color='red' >Required</Typography> : record.time_seconds_end} - /> - </Labeled> - <Labeled> - <FunctionField label="Duration (s)" render={(record) => calculateDuration(record)} /> - </Labeled> - <Labeled> - <FunctionField - label="Last job status" - render={jobStatus} - /> - </Labeled> - <Labeled> - <TextField source="description" /> - </Labeled> - </Box> - </Grid> - <Grid item xs={10}> - <ClassPieChart /> - </Grid> - </Grid> - <TabbedShowLayout> - <TabbedShowLayout.Tab label="Run status"> - <Typography variant="caption"> - This is a list of the jobs that have been submitted for this submission. Click on them to view their logs.<br /> - Logs become available after job enters the 'Pending' status. A request for deletion may not be possible depending on the stage of execution. - </Typography> - <ArrayField - source="run_status" - > - <Datagrid - bulkActionButtons={false} - rowClick={redirectToJobLogs} - > - <RunStatusIndicator /> - <DateField - source="time_started" - sortable={false} - showTime - /> - <TextField source="status" sortable={false} /> - <DeleteKubernetesJobButton /> - - </Datagrid> - </ArrayField> - </TabbedShowLayout.Tab> - <TabbedShowLayout.Tab label="File inputs"> - <Typography variant="caption"> - These are the input files which have been assigned to this submission. If these are incorrect, please delete the submission and resubmit one with the correct files. - </Typography> - <ArrayField source="input_associations" label="File Inputs"> - <Datagrid bulkActionButtons={false} rowClick={redirectToObject}> - <TextField source="input_object.filename" label="Filename" /> - <FunctionField label="Size (MB)" render={(record) => { return (record.input_object.size_bytes / 1000000).toFixed(2); }} /> - <DateField source="input_object.time_added_utc" - showTime - transform={value => new Date(value + 'Z')} // Fix UTC time - label="Time Added" /> - <TextField source="input_object.hash_md5sum" label="MD5 Hash" /> - <NumberField source="processing_order" /> - <NumberField source="input_object.fps" label="FPS" /> - <NumberField source="input_object.time_seconds" label="Duration (s)" /> - <NumberField source="input_object.frame_count" label="Frames" /> - </Datagrid> - </ArrayField> - </TabbedShowLayout.Tab> - - <TabbedShowLayout.Tab label="File outputs"> - <Typography variant="caption"> - These are the files that have been produced by the submission. Click on each of them to download. - </Typography> - <ArrayField source="file_outputs" label="File Outputs" > - <Datagrid bulkActionButtons={false} rowClick={downloadFile} - > - <TextField source="filename" label="Filename" /> - <FunctionField label="Size (MB)" render={(record) => { return (record.size_bytes / 1000000).toFixed(5); }} /> - <DateField - source="last_modified" - sortable={false} - showTime - /> - </Datagrid> - </ArrayField> - </TabbedShowLayout.Tab> - <TabbedShowLayout.Tab label="Transect"> - - <TransectNameField /> - </TabbedShowLayout.Tab> - </TabbedShowLayout> - - </SimpleShowLayout > - </Show > - ) -}; - - -export default SubmissionShow; \ No newline at end of file diff --git a/src/submissions/index.tsx b/src/submissions/index.tsx deleted file mode 100644 index 0638d0f..0000000 --- a/src/submissions/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import SubmissionCreate from './SubmissionCreate'; -import SubmissionEdit from './SubmissionEdit'; -import SubmissionList from './SubmissionList'; -import SubmissionShow from './SubmissionShow'; -import ModelTrainingIcon from '@mui/icons-material/ModelTraining'; - -export default { - create: SubmissionCreate, - edit: SubmissionEdit, - list: SubmissionList, - show: SubmissionShow, - icon: ModelTrainingIcon, - options: { - label: 'Submissions', - }, - recordRepresentation: (record) => { - return (record && record.name) ? `${record.name}` : `${record.id}`; - } -}; diff --git a/src/transects/AssignSiteButton.tsx b/src/transects/AssignSiteButton.tsx new file mode 100644 index 0000000..1dcad9e --- /dev/null +++ b/src/transects/AssignSiteButton.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react'; +import { + Button, + Form, + ReferenceInput, + SaveButton, + SelectInput, + required, + useListContext, + useNotify, + useRefresh, + useUnselectAll, + useUpdateMany, +} from 'react-admin'; +import { Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; +import PlaceIcon from '@mui/icons-material/Place'; + +// A transect a device pushed carries no site, because the desktop app has no site picker. +// Without this the uploaded lines cannot be grouped under the reef they belong to. +const AssignSiteButton = () => { + const [open, setOpen] = useState(false); + const { selectedIds } = useListContext(); + const [updateMany, { isPending }] = useUpdateMany(); + const unselectAll = useUnselectAll('transects'); + const notify = useNotify(); + const refresh = useRefresh(); + + const assign = async ({ site_id }: { site_id?: string }) => { + await updateMany( + 'transects', + { ids: selectedIds, data: { site_id } }, + { + onSuccess: () => { + notify(`Assigned ${selectedIds.length} transects`, { type: 'info' }); + unselectAll(); + refresh(); + setOpen(false); + }, + onError: error => + notify(error instanceof Error ? error.message : 'Assignment failed', { + type: 'error', + }), + }, + ); + }; + + return ( + <> + <Button + label="Assign to site" + onClick={() => setOpen(true)} + startIcon={<PlaceIcon />} + /> + <Dialog open={open} onClose={() => setOpen(false)} fullWidth maxWidth="xs"> + <DialogTitle>Assign to site</DialogTitle> + <Form onSubmit={assign as never}> + <DialogContent> + <ReferenceInput source="site_id" reference="sites"> + <SelectInput + optionText="name" + label="Site" + validate={required()} + fullWidth + /> + </ReferenceInput> + </DialogContent> + <DialogActions> + <Button label="ra.action.cancel" onClick={() => setOpen(false)} /> + <SaveButton label="Assign" disabled={isPending} icon={<PlaceIcon />} /> + </DialogActions> + </Form> + </Dialog> + </> + ); +}; + +export default AssignSiteButton; diff --git a/src/transects/TransectCreate.tsx b/src/transects/TransectCreate.tsx index 24f8c85..3d17a48 100644 --- a/src/transects/TransectCreate.tsx +++ b/src/transects/TransectCreate.tsx @@ -1,67 +1,20 @@ -import { - Create, - SimpleForm, - TextInput, - required, - minValue, - maxValue, - NumberInput, -} from 'react-admin'; -import { Grid, Typography } from '@mui/material'; -import 'react-dropzone-uploader/dist/styles.css'; -import 'filepond/dist/filepond.min.css'; +import { Create, SimpleForm, SaveButton, Toolbar } from 'react-admin'; -const TransectCreate = () => { - return ( - <Create redirect="show"> - <SimpleForm> - {/* Basic Information */} - <Typography variant="h6" gutterBottom> - Basic Information - </Typography> - <Grid container spacing={2}> - <Grid item xs={12} sm={6}> - <TextInput source="name" validate={[required()]} fullWidth /> - </Grid> - <Grid item xs={12}> - <TextInput source="description" multiline fullWidth /> - </Grid> - </Grid> +import TransectFormFields from './TransectFormFields'; - {/* Location */} - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Location - </Typography> - <Grid container spacing={2}> - <Grid item xs={12} sm={6}> - <NumberInput source="latitude_start" validate={[required(), minValue(-90), maxValue(90)]} fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <NumberInput source="longitude_start" validate={[required(), minValue(-180), maxValue(180)]} fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <NumberInput source="latitude_end" validate={[required(), minValue(-90), maxValue(90)]} fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <NumberInput source="longitude_end" validate={[required(), minValue(-180), maxValue(180)]} fullWidth /> - </Grid> - </Grid> +// Rows are tombstoned by the sync protocol, never removed, so no delete is offered. +const TransectFormToolbar = () => ( + <Toolbar> + <SaveButton /> + </Toolbar> +); - {/* Measurements */} - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Measurements - </Typography> - <Grid container spacing={2}> - <Grid item xs={12} sm={6}> - <NumberInput source="length" label="length (m)" validate={[minValue(0)]} fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <NumberInput source="depth" label="depth (m)" validate={[minValue(0)]} fullWidth /> - </Grid> - </Grid> - </SimpleForm> - </Create> - ); -}; +const TransectCreate = () => ( + <Create redirect="show"> + <SimpleForm toolbar={<TransectFormToolbar />}> + <TransectFormFields /> + </SimpleForm> + </Create> +); export default TransectCreate; diff --git a/src/transects/TransectEdit.tsx b/src/transects/TransectEdit.tsx index 879b4d6..ec30b91 100644 --- a/src/transects/TransectEdit.tsx +++ b/src/transects/TransectEdit.tsx @@ -1,68 +1,20 @@ -import { - Edit, - SimpleForm, - TextInput, - NumberInput, - minValue, - maxValue, - required, -} from 'react-admin'; -import { Grid, Typography } from '@mui/material'; +import { Edit, SaveButton, SimpleForm, Toolbar } from 'react-admin'; -const TransectEdit = () => { - return ( - <Edit redirect="show"> - <SimpleForm> - {/* Basic Information */} - <Typography variant="h6" gutterBottom> - Basic Information - </Typography> - <Grid container spacing={2}> - <Grid item xs={12} sm={6}> - <TextInput source="id" disabled fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <TextInput source="name" validate={[required()]} fullWidth /> - </Grid> - <Grid item xs={12}> - <TextInput source="description" multiline /> - </Grid> - </Grid> +import TransectFormFields from './TransectFormFields'; - {/* Location */} - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Location - </Typography> - <Grid container spacing={2}> - <Grid item xs={12} sm={6}> - <NumberInput source="latitude_start" validate={[required(), minValue(-90), maxValue(90)]} fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <NumberInput source="longitude_start" validate={[required(), minValue(-180), maxValue(180)]} fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <NumberInput source="latitude_end" validate={[required(), minValue(-90), maxValue(90)]} fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <NumberInput source="longitude_end" validate={[required(), minValue(-180), maxValue(180)]} fullWidth /> - </Grid> - </Grid> +// Rows are tombstoned by the sync protocol, never removed, so no delete is offered. +const TransectEditToolbar = () => ( + <Toolbar> + <SaveButton /> + </Toolbar> +); - {/* Measurements */} - <Typography variant="h6" gutterBottom style={{ marginTop: '16px' }}> - Measurements - </Typography> - <Grid container spacing={2}> - <Grid item xs={12} sm={6}> - <NumberInput source="length" label="length (m)" validate={[minValue(0)]} fullWidth /> - </Grid> - <Grid item xs={12} sm={6}> - <NumberInput source="depth" label="depth (m)" validate={[minValue(0)]} fullWidth /> - </Grid> - </Grid> - </SimpleForm> - </Edit> - ); -}; +const TransectEdit = () => ( + <Edit redirect="show" mutationMode="pessimistic"> + <SimpleForm toolbar={<TransectEditToolbar />}> + <TransectFormFields /> + </SimpleForm> + </Edit> +); export default TransectEdit; diff --git a/src/transects/TransectFormFields.tsx b/src/transects/TransectFormFields.tsx new file mode 100644 index 0000000..fbb65cd --- /dev/null +++ b/src/transects/TransectFormFields.tsx @@ -0,0 +1,170 @@ +import { + NumberInput, + ReferenceInput, + SelectInput, + TextInput, + maxValue, + minValue, + required, +} from 'react-admin'; +import { Grid, Typography } from '@mui/material'; + +const latitude = [required(), minValue(-90), maxValue(90)]; +const longitude = [required(), minValue(-180), maxValue(180)]; +const nonNegative = [minValue(0)]; + +const TransectFormFields = () => ( + <> + <Typography variant="h6" gutterBottom> + Identity + </Typography> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <ReferenceInput source="site_id" reference="sites"> + <SelectInput + optionText="name" + label="Site" + helperText="A transect name only has to be unique within its site." + fullWidth + /> + </ReferenceInput> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <TextInput source="name" validate={required()} fullWidth /> + </Grid> + <Grid size={12}> + <TextInput source="description" multiline defaultValue="" fullWidth /> + </Grid> + </Grid> + + <Typography variant="h6" gutterBottom sx={{ mt: 2 }}> + End points + </Typography> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <NumberInput + source="start_lat" + label="Start latitude" + validate={latitude} + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <NumberInput + source="start_lon" + label="Start longitude" + validate={longitude} + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <NumberInput + source="start_accuracy_m" + label="Start accuracy (m)" + validate={nonNegative} + helperText="GPS accuracy at the time of the fix." + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <NumberInput + source="end_lat" + label="End latitude" + validate={latitude} + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <NumberInput + source="end_lon" + label="End longitude" + validate={longitude} + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 4, + }} + > + <NumberInput + source="end_accuracy_m" + label="End accuracy (m)" + validate={nonNegative} + fullWidth + /> + </Grid> + </Grid> + + <Typography variant="h6" gutterBottom sx={{ mt: 2 }}> + Measurements + </Typography> + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <NumberInput + source="length_m" + label="Length (m)" + validate={nonNegative} + helperText="The tape reading used to scale the reconstruction." + fullWidth + /> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <NumberInput + source="depth_m" + label="Depth (m)" + validate={nonNegative} + fullWidth + /> + </Grid> + </Grid> + </> +); + +export default TransectFormFields; diff --git a/src/transects/TransectList.tsx b/src/transects/TransectList.tsx index 2112c26..bc18afa 100644 --- a/src/transects/TransectList.tsx +++ b/src/transects/TransectList.tsx @@ -1,65 +1,96 @@ import { - List, - Datagrid, - TextField, - usePermissions, - TopToolbar, CreateButton, + Datagrid, ExportButton, - FunctionField, + List, + NumberField, ReferenceField, - DateField, -} from "react-admin"; -import { useEffect, useState } from "react"; -import { TransectMapAll } from "../maps/Transects"; -import { Typography } from "@mui/material"; + ReferenceInput, + SearchInput, + SelectInput, + TextField, + TopToolbar, + useListContext, +} from 'react-admin'; +import { Stack, Typography } from '@mui/material'; + +import type { Transect } from '../contract'; +import { TransectMapAll } from '../maps/Transects'; +import { useCanAuthor } from '../permissions'; +import AssignSiteButton from './AssignSiteButton'; +const transectFilters = [ + <SearchInput source="q" alwaysOn key="q" />, + <ReferenceInput source="site_id" reference="sites" key="site_id"> + <SelectInput optionText="name" label="Site" /> + </ReferenceInput>, +]; const TransectListActions = () => { + const canAuthor = useCanAuthor(); return ( - - <TopToolbar > - <CreateButton /> + <TopToolbar> + {canAuthor && <CreateButton />} <ExportButton /> </TopToolbar> ); -} +}; -const TransectList = () => { - const { permissions } = usePermissions(); +const TransectListEmpty = () => { + const canAuthor = useCanAuthor(); + return ( + <Stack + spacing={2} + sx={{ + alignItems: 'center', + p: 6, + textAlign: 'center', + }} + > + <Typography variant="h6">No transects yet</Typography> + {canAuthor && <CreateButton label="Create transect" />} + </Stack> + ); +}; + +const TransectListBody = () => { + const { filterValues } = useListContext<Transect>(); + const canAuthor = useCanAuthor(); return ( <> - <List disableSyncWithLocation - actions={<TransectListActions />} - perPage={25} > - <> - <Typography variant="caption"> - This is a list of all transects that have been created. Click on a transect to view more details or to create a new one select '+ Create'. - </Typography> - <TransectMapAll /> - <Datagrid - bulkActionButtons={permissions === 'admin' ? true : false} - rowClick="show" - > - <TextField source="name" /> - <DateField source="created_on" /> - <TextField source="length" label="Length (m)" /> - <TextField source="depth" label="Depth (m)" /> - <FunctionField label="Associated submissions" render={(record) => { - return record.submissions?.length ? record.submissions.length : 0; - }} /> - <FunctionField label="Associated files" render={(record) => { - return record.inputs?.length ? record.inputs.length : 0; - }} /> - {permissions === 'admin' ? (<ReferenceField source="owner" reference="users" link="show"> - <FunctionField render={record => `${record.firstName} ${record.lastName}`} source="Owner" /> - </ReferenceField>) : null} - </Datagrid> - </> - </List ></> - - ) + <TransectMapAll filter={filterValues} /> + <Datagrid + rowClick="show" + bulkActionButtons={canAuthor ? <AssignSiteButton /> : false} + > + <TextField source="name" /> + <ReferenceField + source="site_id" + reference="sites" + link="show" + sortable={false} + emptyText="unassigned" + > + <TextField source="name" /> + </ReferenceField> + <NumberField source="length_m" label="Length (m)" /> + <NumberField source="depth_m" label="Depth (m)" /> + </Datagrid> + </> + ); }; +const TransectList = () => ( + <List + filters={transectFilters} + actions={<TransectListActions />} + empty={<TransectListEmpty />} + sort={{ field: 'name', order: 'ASC' }} + perPage={25} + > + <TransectListBody /> + </List> +); + export default TransectList; diff --git a/src/transects/TransectPasses.tsx b/src/transects/TransectPasses.tsx new file mode 100644 index 0000000..6bc1ccd --- /dev/null +++ b/src/transects/TransectPasses.tsx @@ -0,0 +1,58 @@ +import { + Datagrid, + Pagination, + ReferenceField, + ReferenceManyField, + TextField, +} from 'react-admin'; +import { Stack, Typography } from '@mui/material'; + +import { asColumn, DurationField, QualityField } from '../components'; +import { DirectionField } from '../passes/DirectionField'; + +const QualityColumn = asColumn(QualityField); +const WindowColumn = asColumn(DurationField); + +const NoPasses = () => ( + <Stack spacing={1} sx={{ py: 4 }}> + <Typography variant="subtitle2">No passes on this line yet</Typography> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Passes arrive from the desktop application when a diver syncs. + </Typography> + </Stack> +); + +/** Every pass swum on this line, across all campaigns. The revisit history. */ +const TransectPasses = () => ( + <ReferenceManyField + reference="passes" + target="transect_id" + sort={{ field: 'created_at', order: 'DESC' }} + perPage={25} + pagination={<Pagination />} + > + <Datagrid rowClick="show" bulkActionButtons={false} empty={<NoPasses />}> + <ReferenceField + source="campaign_id" + reference="campaigns" + link="show" + label="Campaign" + sortable={false} + emptyText="—" + > + <TextField source="name" /> + </ReferenceField> + <TextField source="label" sortable={false} emptyText="Unnamed" /> + <WindowColumn label="Window" sortable={false} source="begin_s" endSource="end_s" /> + <DirectionField label="Direction" /> + <QualityColumn label="Quality" /> + </Datagrid> + </ReferenceManyField> +); + +export default TransectPasses; diff --git a/src/transects/TransectShow.tsx b/src/transects/TransectShow.tsx index 7944351..80a7ae9 100644 --- a/src/transects/TransectShow.tsx +++ b/src/transects/TransectShow.tsx @@ -1,319 +1,141 @@ import { - Show, - SimpleShowLayout, - TextField, EditButton, - TopToolbar, - DeleteButton, - usePermissions, - Link, FunctionField, - ArrayField, Labeled, - Datagrid, - useRedirect, - TabbedShowLayout, - useRecordContext, Loading, - DateField, NumberField, - BooleanField, - useCreate, - useListContext, - Button, - useUnselectAll, - useNotify, - useCreatePath, + ReferenceField, + Show, + TabbedShowLayout, + TextField, + TopToolbar, + useRecordContext, } from 'react-admin'; -import { Typography, Grid } from '@mui/material'; -import { TransectMapOne } from '../maps/Transects'; -import { FilePondUploaderTransect } from '../uploader/FilePond'; -import { useEffect } from "react"; -import Brightness1TwoToneIcon from '@mui/icons-material/Brightness1TwoTone'; -import { IconButton } from '@mui/material'; -import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; - -const CreateSubmissionButton = () => { - const record = useRecordContext(); - if (!record) return null; - - const listContext = useListContext(); - const redirect = useRedirect(); - const [create, { data, loading, loaded, error }] = useCreate(); - - useEffect(() => { - if (!data) return; - if (data.id) { - redirect('show', 'submissions', data.id); - } - }, [data]); - - // Create a list of input_association objects from the selected video ids - const input_associations = listContext.selectedIds.map((id, index) => { - return { - input_object_id: id, - processing_order: index + 1 - } - }); - - // Create a list of selected videos that have not completed uploading to - // disable the button if any of the selected videos are incomplete - const selectedIncompleteData = listContext.selectedIds.some(id => { - const record = listContext?.data?.find(data => data.id === id); - if (!record) return false; - - return record.all_parts_received === false; - }); - const handleClick = () => { - redirect('create', 'submissions', null, {}, { - record: { - transect_id: record.id, - input_associations: input_associations, - } - }) - } - if (listContext.selectedIds.length > 2) { - return <Button - variant="contained" - color="error" - disabled={true} - >Maximum 2 videos can be selected</Button> - } - if (selectedIncompleteData) { - return <Button - variant="contained" - color="error" - disabled={selectedIncompleteData} - >Deselect incomplete data</Button> - } - return <Button - variant="contained" - color="success" - disabled={selectedIncompleteData} - onClick={handleClick}>{ - listContext.selectedIds.length === 1 ? - 'Create submission from selected video' : 'Create submission from selected videos' - }</Button> -}; - - -export const CreateSingleSubmissionButton = () => { - const record = useRecordContext(); - if (!record) return null; - const notify = useNotify(); - const redirect = useRedirect(); - let color = 'error'; - - const getColor = (record) => { - if (record.all_parts_received && record.processing_has_started && record.processing_completed_successfully) { - return "success"; - } else if (record.all_parts_received && record.processing_has_started) { - return "warning"; - } else { - return "error"; - } - } - - const getProcessingMessage = (record) => { - if (record.all_parts_received && record.processing_has_started && record.processing_completed_successfully) { - return "Click to create submission from video"; - } else if (record.all_parts_received && record.processing_has_started) { - return "Upload has finished, processing video"; - } else { - return "Upload in progress"; - } - } - - return <IconButton - color={getColor(record)} - title={getProcessingMessage(record)} - onClick={(event) => { - if (record.all_parts_received && record.processing_has_started && record.processing_completed_successfully) { - redirect('create', 'submissions', null, {}, { - record: { - transect_id: record.transect_id, - input_associations: [ - { - input_object_id: record.id, - processing_order: 1 - } - ], - } - }) - event.stopPropagation(); - } else if (record.all_parts_received && record.processing_has_started) { - notify("Please wait, processing video"); - event.stopPropagation(); - } else { - notify('Upload in progress, please wait for it to finish'); - event.stopPropagation(); - } - - }} - > - <AddCircleOutlineIcon /> - </IconButton>; -}; +import { Box, Grid } from '@mui/material'; +import { CoordinateField, SyncFields, TombstoneButton } from '../components'; +import { useCanAuthor } from '../permissions'; +import type { Transect } from '../contract'; +import Statistics from '../cover/Statistics'; +import { TransectMapOne } from '../maps/Transects'; +import TransectPasses from './TransectPasses'; -const TransectTabs = () => { - const record = useRecordContext(); - const createPath = useCreatePath(); - const unselectAll = useUnselectAll('transects'); - useEffect(() => { - return () => - unselectAll(); - }, [] +const TransectShowActions = () => { + const canAuthor = useCanAuthor(); + return ( + <TopToolbar> + {canAuthor && <EditButton />} + <TombstoneButton noun="transect" /> + </TopToolbar> ); +}; - - const objectClick = (id, resource, record) => (createPath({ resource: 'objects', type: 'show', id: record.id })); - const submissionClick = (id, resource, record) => (createPath({ resource: 'submissions', type: 'show', id: record.id })); +const TransectMap = () => { + const record = useRecordContext<Transect>(); if (!record) return <Loading />; - return ( - <><Typography variant="h6" gutterBottom>Associations</Typography> - - <TabbedShowLayout> - <TabbedShowLayout.Tab label={`Files (${record.inputs?.length ? record.inputs.length : 0})`}> - <ArrayField source="inputs"> - <Typography variant="caption"> - Upload videos associated with this transect. To create a submission, select one or two files and click "Create submission from selected video". - </Typography> - <FilePondUploaderTransect /> - <Datagrid - bulkActionButtons={<CreateSubmissionButton />} - rowClick={objectClick} - isRowSelectable={(record) => (record.all_parts_received && record.processing_has_started && record.processing_completed_successfully)} - > - <DateField - label="Uploaded at" - source="time_added_utc" - transform={value => new Date(value + 'Z')} // Fix UTC time - showTime - /> - <TextField source="filename" /> - <FunctionField label="Size (MB)" render={(record) => { return (record.size_bytes / 1000000).toFixed(2); }} /> - <NumberField source="time_seconds" label="Duration (s)" /> - <NumberField source="fps" label="FPS" /> - <CreateSingleSubmissionButton label="Ready" /> + return <TransectMapOne record={record} />; +}; - </Datagrid> - </ArrayField> - </TabbedShowLayout.Tab> - <TabbedShowLayout.Tab label={`Submissions (${record.submissions?.length ? record.submissions.length : 0})`}> - <Typography variant="caption"> - These are the related submissions to this transect. Click on them to view their details. - </Typography> - <ArrayField source="submissions" sort={{ field: "time_added_utc", order: "DESC" }}> - <Datagrid rowClick={submissionClick} bulkActionButtons={false} +const accuracy = (value: number | null | undefined) => (value == null ? '' : ` ±${value} m`); + +const TransectHeader = () => ( + <Grid container spacing={2} sx={{ p: 2 }}> + <Grid + size={{ + xs: 12, + md: 4, + }} + > + <Grid container spacing={2}> + <Grid size={6}> + <Labeled label="Name"> + <TextField source="name" /> + </Labeled> + </Grid> + <Grid size={6}> + <Labeled label="Site"> + <ReferenceField + source="site_id" + reference="sites" + link="show" + emptyText="—" > - <DateField - source="time_added_utc" - label="Added" - showTime - transform={value => new Date(value + 'Z')} // Fix UTC time - /> <TextField source="name" /> - <FunctionField - label="Last job status" - render={record => `${record?.run_status[0]?.status ?? 'No jobs submitted'}`} - /> - - </Datagrid> - </ArrayField> + </ReferenceField> + </Labeled> + </Grid> + <Grid size={6}> + <Labeled label="Length (m)"> + <NumberField source="length_m" emptyText="—" /> + </Labeled> + </Grid> + <Grid size={6}> + <Labeled label="Depth (m)"> + <NumberField source="depth_m" emptyText="—" /> + </Labeled> + </Grid> + <Grid size={12}> + <Labeled label="Description"> + <TextField source="description" emptyText="—" /> + </Labeled> + </Grid> + <Grid size={12}> + <Labeled label="Start"> + <FunctionField<Transect> + render={record => ( + <> + <CoordinateField + latSource="start_lat" + lonSource="start_lon" + /> + {accuracy(record.start_accuracy_m)} + </> + )} + /> + </Labeled> + </Grid> + <Grid size={12}> + <Labeled label="End"> + <FunctionField<Transect> + render={record => ( + <> + <CoordinateField latSource="end_lat" lonSource="end_lon" /> + {accuracy(record.end_accuracy_m)} + </> + )} + /> + </Labeled> + </Grid> + </Grid> + </Grid> + <Grid + size={{ + xs: 12, + md: 8, + }} + > + <TransectMap /> + </Grid> + </Grid> +); + +const TransectShow = () => ( + <Show actions={<TransectShowActions />}> + <> + <TransectHeader /> + <TabbedShowLayout> + <TabbedShowLayout.Tab label="Passes"> + <TransectPasses /> + </TabbedShowLayout.Tab> + <TabbedShowLayout.Tab label="Statistics"> + <Statistics /> </TabbedShowLayout.Tab> </TabbedShowLayout> + <Box sx={{ p: 2 }}> + <SyncFields /> + </Box> </> - ) -} - -const TransectMap = () => { - const record = useRecordContext(); - if (!record) return <Loading />; - return ( - <TransectMapOne record={record} /> - ) -} - - -const TransectShow = (props) => { - const { permissions } = usePermissions(); - - const TransectShowActions = () => { - const { permissions } = usePermissions(); - return ( - <TopToolbar> - {permissions === 'admin' && <> - <EditButton /> - <DeleteButton /></>} - </TopToolbar> - ); - } - - return ( - <Show actions={<TransectShowActions />} {...props} queryOptions={{ refetchInterval: 5000 }}> - <SimpleShowLayout> - <Grid container spacing={2}> - <Grid item xs={3}> - <Grid item xs={6}> - <Labeled label="Name"> - <TextField source="name" /> - </Labeled> - </Grid> - <Grid /> - - <Grid item xs={6}> - <Labeled label="Length (m)"> - <TextField source="length" label="Length (m)" /> - </Labeled> - </Grid> - <Grid item xs={6}> - <Labeled label="Depth (m)"> - <TextField source="depth" label="Depth (m)" /> - </Labeled> - </Grid> - <Grid item xs={6}> - <Labeled label="Description"> - <TextField source="description" /> - </Labeled> - </Grid> - <Grid item xs={6}> - <Labeled label="Start coordiantes"> - <FunctionField label="Coordinates" render={(record) => { - return ( - <Link - to={`https://www.google.com/maps?q=${record.latitude_start},${record.longitude_start}`} - target="_blank" - >{`${record.latitude_start}°, ${record.longitude_start}°`}</Link> - ) - } - } /> - </Labeled> - </Grid> - <Grid item xs={6} /> - <Grid item xs={6}> - <Labeled label="End coordinates"> - <FunctionField label="Coordinates" render={(record) => { - return ( - <Link - to={`https://www.google.com/maps?q=${record.latitude_end},${record.longitude_end}`} - target="_blank" - >{`${record.latitude_end}°, ${record.longitude_end}°`}</Link> - ) - } - } /> - </Labeled> - </Grid> - </Grid> - <Grid item xs={9}> - <TransectMap /> - </Grid> - </Grid> - <TransectTabs /> - </SimpleShowLayout> - </Show > - ) -}; - + </Show> +); -export default TransectShow; \ No newline at end of file +export default TransectShow; diff --git a/src/transects/index.tsx b/src/transects/index.tsx index 34fcf04..a75b07b 100644 --- a/src/transects/index.tsx +++ b/src/transects/index.tsx @@ -1,14 +1,15 @@ +import PolylineIcon from '@mui/icons-material/Polyline'; + import TransectCreate from './TransectCreate'; -import TransactEdit from './TransectEdit'; +import TransectEdit from './TransectEdit'; import TransectList from './TransectList'; import TransectShow from './TransectShow'; -import PolylineIcon from '@mui/icons-material/Polyline'; export default { - create: TransectCreate, - edit: TransactEdit, list: TransectList, show: TransectShow, + edit: TransectEdit, + create: TransectCreate, icon: PolylineIcon, options: { label: 'Transects', diff --git a/src/uploader/FilePond.tsx b/src/uploader/FilePond.tsx deleted file mode 100644 index 5ddab7e..0000000 --- a/src/uploader/FilePond.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { - useRecordContext, - Loading, - useAuthProvider, - useRefresh, -} from 'react-admin'; -import { FilePond, registerPlugin } from 'react-filepond'; -import 'filepond/dist/filepond.min.css'; -import * as tus from 'tus-js-client' -import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'; - -registerPlugin(FilePondPluginFileValidateType); - -export const FilePondUploaderList = () => { - const auth = useAuthProvider(); - const token = auth.getToken(); - const refresh = useRefresh(); - - return ( - <FilePond - acceptedFileTypes={['video/*']} - chunkUploads={true} - onprocessfiles={refresh} - allowMultiple={true} - credits={false} - timeout={200} - allowRevert={false} - allowRemove={false} - server={{ - url: '/api/objects', - process: (fieldName, file, metadata, load, error, progress, abort) => { - var upload = new tus.Upload(file, { - endpoint: "/files", - metadataForPartialUploads: { - filename: file.name, - filetype: file.type - }, - retryDelays: [0, 1000, 3000, 5000], - headers: { - 'Authorization': `Bearer ${token}`, - 'Transect-Id': "", - }, - metadata: { - filename: file.name, - filetype: file.type - }, - onError: function (err) { - console.log("Failed because: " + err) - // error(err) - }, - onProgress: function (bytesUploaded, bytesTotal) { - progress(true, bytesUploaded, bytesTotal) - }, - onSuccess: function () { - load(upload.url.split('/').pop()) - } - }) - // Start the upload - upload.start() - return { - abort: () => { - upload.abort() - abort() - } - } - } - }} - />) -} - - - -export const FilePondUploaderTransect = () => { - const auth = useAuthProvider(); - const token = auth.getToken(); - const refresh = useRefresh(); - const record = useRecordContext(); - - if (!record) return <Loading />; - - return ( - <FilePond - acceptedFileTypes={['video/*']} - chunkUploads={true} - onprocessfiles={refresh} - allowMultiple={true} - credits={false} - timeout={200} - allowRevert={false} - allowRemove={false} - server={{ - url: '/api/objects', - process: (fieldName, file, metadata, load, error, progress, abort) => { - var upload = new tus.Upload(file, { - endpoint: "/files", - metadataForPartialUploads: { - filename: file.name, - filetype: file.type - }, - retryDelays: [0, 1000, 3000, 5000], - headers: { - 'Authorization': `Bearer ${token}`, - 'Transect-Id': record.id.toString(), - }, - metadata: { - filename: file.name, - filetype: file.type - }, - onError: function (err) { - console.log("Failed because: " + err) - // error(err) - }, - onProgress: function (bytesUploaded, bytesTotal) { - progress(true, bytesUploaded, bytesTotal) - }, - onSuccess: function () { - load(upload.url.split('/').pop()) - } - }) - // Start the upload - upload.start() - return { - abort: () => { - upload.abort() - abort() - } - } - } - }} - />) -} diff --git a/src/users/Aside.tsx b/src/users/Aside.tsx deleted file mode 100644 index 418fcd3..0000000 --- a/src/users/Aside.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import * as React from 'react'; -import { styled } from '@mui/material/styles'; -import { Typography } from '@mui/material'; - -const PREFIX = 'Aside'; - -const classes = { - root: `${PREFIX}-root`, -}; - -const Root = styled('div')(({ theme }) => ({ - [`&.${classes.root}`]: { - [theme.breakpoints.up('sm')]: { - width: 200, - margin: '1em', - }, - [theme.breakpoints.down('md')]: { - width: 0, - overflowX: 'hidden', - margin: 0, - }, - }, -})); - -const Aside = () => { - return ( - /*<Box - sx={{ - width: { - sm: 200, - md: 0, - }, - margin: { - sm: '1em', - md: 0, - }, - overflowX: { - md: 'hidden', - }, - }} - >*/ - <Root className={classes.root}> - <Typography variant="h6">Admin Users</Typography> - <Typography variant="body2"> - These users have admin access to the portal, allowing them to - manage data and users. To remove admin access, delete the user - and to add other users as admins, add them by their EPFL - username. - </Typography> - </Root> - ); -}; - -export default Aside; diff --git a/src/users/UserCreate.tsx b/src/users/UserCreate.tsx deleted file mode 100644 index df38aba..0000000 --- a/src/users/UserCreate.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint react/jsx-key: off */ -import * as React from 'react'; -import { useFormContext } from 'react-hook-form'; -import { - Create, - FormTab, - SaveButton, - List, - Datagrid, - TextField, - AutocompleteInput, - BooleanField, - TabbedForm, - TextInput, - Toolbar, - SearchInput, - required, - SelectInput, - useNotify, - usePermissions, - useDataProvider, - useRefresh, -} from 'react-admin'; - -import Aside from './Aside'; -import { Typography } from '@mui/material'; - - -const UserCreate = () => { - const notify = useNotify(); - const refresh = useRefresh(); - const dataProvider = useDataProvider(); - - const { permissions } = usePermissions(); - const handleRowClick = (id, basePath, record) => { - // Custom logic for handling row click - - dataProvider - .update('users', { id, data: { role: "user" } }) - .then(() => { - notify('User updated successfully'); - refresh(); - }) - .catch((error) => { - console.error('Error updating user:', error); - notify('Error updating user', 'error'); - }); - }; - - - const postFilters = [ - <SearchInput source="username" placeholder="Username" alwaysOn /> - ]; - return ( - <Create aside={< Aside />} redirect="show" > - <Typography variant="h3">Approve user</Typography> - <Typography variant="caption"> - Click the row to provide permission to a user, they will be added as a standard user. Elevation to admin status if necessary, can be provided in the user list afterwards.<br /> - External users must login first (via Github, etc.) to show up in this list. - </Typography> - - <List - filters={postFilters} - actions={null} - pagination={null} - disableSyncWithLocation - > - <Typography variant="caption">The username is the EPFL Gaspar, or the external provider (Github) username</Typography> - <Datagrid bulkActionButtons={false} rowClick={handleRowClick}> - <TextField source="username" /> - <TextField source="firstName" /> - <TextField source="lastName" /> - <TextField source="email" /> - <TextField source="loginMethod" /> - <BooleanField source="approved_user" /> - <BooleanField source="admin" /> - </Datagrid> - </List> - </Create > - ); -}; - -export default UserCreate; diff --git a/src/users/UserList.tsx b/src/users/UserList.tsx deleted file mode 100644 index bd01bfa..0000000 --- a/src/users/UserList.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* eslint react/jsx-key: off */ -import PeopleIcon from '@mui/icons-material/People'; -import memoize from 'lodash/memoize'; -import { useMediaQuery, Theme, Typography } from '@mui/material'; -import * as React from 'react'; -import { - BulkDeleteWithConfirmButton, - Datagrid, - InfiniteList, - SearchInput, - SimpleList, - TextField, - TextInput, - BooleanField, - usePermissions, - useRefresh, - useDataProvider, - useNotify, - List, - EditButton, - Button, - useRecordContext, - TopToolbar, - CreateButton, - ExportButton, -} from 'react-admin'; -export const UserIcon = PeopleIcon; - -const UserListActions = () => { - return ( - - <TopToolbar> - <><CreateButton label="Approve user" /></> - <ExportButton /> - </TopToolbar> - ); -} - -const UserList = () => { - const refresh = useRefresh(); - const dataProvider = useDataProvider(); - - const { permissions } = usePermissions(); - const AdminButton = () => { - const record = useRecordContext(); - // A button that switches from normal user to admin depending on user status - - if (record.admin === true) { - return <Button - type="button" - variant="contained" - color="secondary" - label="Revoke Admin" - onClick={(event) => { - dataProvider.update( - 'users', - { id: record.id, data: { role: "user" } }).then(() => refresh()) - event.stopPropagation(); - }} - />; - } else { - return <Button - type="button" - variant="contained" - color="primary" - label="Make Admin" - // disabled={record.admin === true} - onClick={(event) => { - dataProvider.update( - 'users', - { id: record.id, data: { role: "admin" } }).then(() => refresh()) - event.stopPropagation(); - }} - />; - } - }; - - return ( - <> - - <List - actions={<UserListActions />} - disableSyncWithLocation - perPage={50} - filter={{ users_only: true }} - > - <Typography variant="h4">Approved users</Typography> - <> - <Datagrid - bulkActionButtons={permissions === 'admin' ? true : false} - rowClick="show" - > - <TextField source="firstName" /> - <TextField source="lastName" /> - <TextField source="username" /> - <TextField source="email" /> - <TextField source="loginMethod" /> - <BooleanField source="admin" /> - <AdminButton /> - </Datagrid> - </> - </List > - </> - ); -}; - -export default UserList; diff --git a/src/users/UserShow.tsx b/src/users/UserShow.tsx deleted file mode 100644 index c7c0d34..0000000 --- a/src/users/UserShow.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { - Show, - SimpleShowLayout, - TextField, - BooleanField, - DeleteButton, - TopToolbar, -} from 'react-admin'; - -const UserShowActions = () => { - - return ( - <TopToolbar> - <> - <DeleteButton /></> - </TopToolbar> - ); -} - -const UserShow = () => { - return ( - <Show - actions={<UserShowActions />} - redirect="list" - title="User Details" - > - <SimpleShowLayout> - <TextField source="id" /> - <TextField source="firstName" /> - <TextField source="lastName" /> - <TextField source="username" /> - <TextField source="email" /> - <TextField source="loginMethod" /> - <BooleanField source="approved_user" /> - <BooleanField source="admin" /> - </SimpleShowLayout> - </Show > - ) -}; - -export default UserShow; \ No newline at end of file diff --git a/src/users/index.tsx b/src/users/index.tsx deleted file mode 100644 index a4a92e9..0000000 --- a/src/users/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import PeopleIcon from '@mui/icons-material/People'; -import UserCreate from './UserCreate'; -import UserEdit from './UserEdit'; -import UserList from './UserList'; -import UserShow from './UserShow'; - -export default { - list: UserList, - create: UserCreate, - show: UserShow, - icon: PeopleIcon, -}; diff --git a/src/validators.tsx b/src/validators.tsx deleted file mode 100644 index 1dd70eb..0000000 --- a/src/validators.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { - required as createRequiredValidator, - number as createNumberValidator, -} from 'react-admin'; - -export const required = createRequiredValidator(); -export const number = createNumberValidator(); diff --git a/src/videos/VideoFields.tsx b/src/videos/VideoFields.tsx new file mode 100644 index 0000000..293521a --- /dev/null +++ b/src/videos/VideoFields.tsx @@ -0,0 +1,49 @@ +import { useRecordContext } from 'react-admin'; + +const UNITS = ['B', 'KB', 'MB', 'GB', 'TB']; + +/** The unit a byte count reads best in, so several figures can share one. */ +export const byteScale = (bytes: number): { divisor: number; unit: string } => { + let divisor = 1; + let index = 0; + while (bytes / divisor >= 1000 && index < UNITS.length - 1) { + divisor *= 1000; + index += 1; + } + return { divisor, unit: UNITS[index] }; +}; + +export const formatBytes = (bytes: number) => { + const { divisor, unit } = byteScale(bytes); + const value = bytes / divisor; + return `${divisor === 1 ? value : value.toFixed(1)} ${unit}`; +}; + +/** File size in the units a camera reports, so a 4 GB clip reads as one. */ +export const SizeField = ({ + source = 'size_bytes', + emptyText = '—', +}: { + label?: string; + sortable?: boolean; + source?: string; + emptyText?: string; +}) => { + const record = useRecordContext(); + const bytes = record?.[source] as number | null | undefined; + return <span>{bytes == null ? emptyText : formatBytes(bytes)}</span>; +}; + +/** Frame size as `1920 × 1080`. */ +export const ResolutionField = ({ + emptyText = '—', +}: { + label?: string; + emptyText?: string; +}) => { + const record = useRecordContext(); + const width = record?.width as number | null | undefined; + const height = record?.height as number | null | undefined; + if (width == null || height == null) return <span>{emptyText}</span>; + return <span>{`${width} × ${height}`}</span>; +}; diff --git a/src/videos/VideoList.tsx b/src/videos/VideoList.tsx new file mode 100644 index 0000000..14700a4 --- /dev/null +++ b/src/videos/VideoList.tsx @@ -0,0 +1,110 @@ +import { + Datagrid, + DateField, + ExportButton, + List, + SelectInput, + TextField, + TextInput, + TopToolbar, + useListContext, + useRecordContext, +} from 'react-admin'; +import { Box, Typography } from '@mui/material'; + +import { ArchiveStateChip } from '../archive/ArchiveChip'; +import { useArchiveProbeBatch } from '../archive/useBatchProbe'; +import { + asColumn, + DurationField, + HashField, + TriStateField, + triStateChoices, +} from '../components'; +import type { VideoAsset } from '../contract'; +import { SizeField } from './VideoFields'; + +const DurationColumn = asColumn(DurationField); +const TriStateColumn = asColumn(TriStateField); + +const videoFilters = [ + <TextInput key="q" source="q" label="Search file name" alwaysOn />, + <TextInput key="codec" source="codec" label="Codec" helperText="Exact match, eg. hvc1" />, + <SelectInput key="gravity" source="gravity" label="Gravity" choices={triStateChoices} />, + <SelectInput key="gps" source="gps" label="GPS" choices={triStateChoices} />, +]; + +// Every row asks with the whole page's hashes, so the batch hook collapses the +// column into one probe. +const ArchiveField = () => { + const { data } = useListContext<VideoAsset>(); + const record = useRecordContext<VideoAsset>(); + const { states, error } = useArchiveProbeBatch((data ?? []).map(video => video.hash)); + if (!record?.hash) { + return ( + <Typography + variant="body2" + component="span" + sx={{ + color: 'text.disabled', + }} + > + — + </Typography> + ); + } + return <ArchiveStateChip state={states.get(record.hash)} error={error} />; +}; + +const ArchiveColumn = asColumn(ArchiveField); + +const VideoListActions = () => ( + <TopToolbar> + <ExportButton /> + </TopToolbar> +); + +const VideoEmpty = () => ( + <Box + sx={{ + textAlign: 'center', + m: 4, + }} + > + <Typography variant="h6" gutterBottom> + No video assets registered + </Typography> + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + }} + > + Clips are metadata only and appear once an enrolled laptop syncs. + </Typography> + </Box> +); + +const VideoList = () => ( + <List + actions={<VideoListActions />} + filters={videoFilters} + sort={{ field: 'captured_at', order: 'DESC' }} + perPage={50} + empty={<VideoEmpty />} + > + <Datagrid rowClick="show" bulkActionButtons={false}> + <TextField source="file_name" label="File name" /> + <HashField label="Quick hash" /> + <DurationColumn label="Duration" source="duration_s" /> + <SizeField label="Size" source="size_bytes" /> + <DateField source="captured_at" label="Captured" showTime emptyText="—" /> + <TextField source="codec" emptyText="—" sortable={false} /> + <TriStateColumn label="Gravity" source="gravity" sortable={false} /> + <TriStateColumn label="GPS" source="gps" sortable={false} /> + <ArchiveColumn label="Archive" sortable={false} /> + </Datagrid> + </List> +); + +export default VideoList; diff --git a/src/videos/VideoShow.tsx b/src/videos/VideoShow.tsx new file mode 100644 index 0000000..a250b14 --- /dev/null +++ b/src/videos/VideoShow.tsx @@ -0,0 +1,341 @@ +import { + Datagrid, + DateField, + EditButton, + FunctionField, + Labeled, + ListContextProvider, + Loading, + NumberField, + ReferenceField, + ReferenceManyField, + Show, + TextField, + TopToolbar, + useList, + useRecordContext, +} from 'react-admin'; +import { Alert, Box, Divider, Grid, Stack, Typography } from '@mui/material'; + +import ArchiveChip from '../archive/ArchiveChip'; +import { + asColumn, + DurationField, + HashField, + SyncFields, + TombstoneButton, + TriStateField, +} from '../components'; +import type { VideoAsset } from '../contract'; +import { useIsAdmin } from '../permissions'; +import { RunStatusChip } from '../runs/StatusField'; +import { ResolutionField, SizeField } from './VideoFields'; +import { useVideoRuns } from './useVideoRuns'; + +const DurationColumn = asColumn(DurationField); + +const VideoShowActions = () => { + const admin = useIsAdmin(); + return ( + <TopToolbar> + {admin && <EditButton />} + <TombstoneButton noun="video" /> + </TopToolbar> + ); +}; + +const NoPasses = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + p: 1, + }} + > + No pass covers this clip yet. Divers trim their passes on the laptop, and the windows + arrive with the next sync. + </Typography> +); + +const NoRuns = () => ( + <Typography + variant="body2" + sx={{ + color: 'text.secondary', + p: 1, + }} + > + No run has consumed this clip yet. Runs appear once a desktop client reconstructs a + pass built on it and syncs. + </Typography> +); + +// The probe keys on the content hash, so an unhashed clip is never looked up. +const VideoArchive = () => { + const record = useRecordContext<VideoAsset>(); + return <ArchiveChip contentHash={record?.hash} />; +}; + +/** Runs reach a clip through its passes, so the registry resolves the join. */ +const VideoRuns = () => { + const record = useRecordContext<VideoAsset>(); + const { runs, error, pending } = useVideoRuns(record?.id); + const listContext = useList({ data: runs ?? [] }); + if (error) return <Alert severity="error">{error}</Alert>; + if (pending || !runs) return <Loading />; + return ( + <ListContextProvider value={listContext}> + <Datagrid resource="runs" bulkActionButtons={false} empty={<NoRuns />}> + <FunctionField + label="Status" + render={run => <RunStatusChip status={run.status as string} />} + /> + <ReferenceField + source="pass_id" + reference="passes" + link="show" + label="Pass" + sortable={false} + > + <TextField source="label" emptyText="Unlabelled pass" /> + </ReferenceField> + <TextField source="mapping_backend" emptyText="—" sortable={false} /> + <TextField source="segmentation_model" emptyText="—" sortable={false} /> + <DateField source="started_at" showTime emptyText="—" sortable={false} /> + </Datagrid> + </ListContextProvider> + ); +}; + +const VideoPasses = () => ( + <ReferenceManyField + reference="pass_videos" + target="video_id" + sort={{ field: 'ordinal', order: 'ASC' }} + perPage={25} + > + <Datagrid bulkActionButtons={false} empty={<NoPasses />}> + <NumberField source="ordinal" label="Ordinal in pass" /> + <ReferenceField + source="pass_id" + reference="passes" + link="show" + label="Pass" + sortable={false} + > + <TextField source="label" emptyText="Unnamed" /> + </ReferenceField> + <ReferenceField + source="pass_id" + reference="passes" + link={false} + label="Window" + sortable={false} + > + <DurationColumn source="begin_s" endSource="end_s" /> + </ReferenceField> + <ReferenceField + source="pass_id" + reference="passes" + link={false} + label="Transect" + sortable={false} + > + <ReferenceField + source="transect_id" + reference="transects" + link="show" + emptyText="—" + > + <TextField source="name" /> + </ReferenceField> + </ReferenceField> + </Datagrid> + </ReferenceManyField> +); + +const VideoShow = () => ( + <Show actions={<VideoShowActions />}> + <Stack + spacing={2} + sx={{ + p: 2, + }} + > + <Grid container spacing={2}> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <Labeled label="File name"> + <TextField source="file_name" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + }} + > + <Labeled label="Quick hash (imohash)"> + <HashField abbreviate={false} /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + md: 4, + }} + > + <Labeled label="Archive"> + <VideoArchive /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Duration"> + <DurationField source="duration_s" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Size"> + <SizeField /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Resolution"> + <ResolutionField /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Frame rate"> + <NumberField source="fps" emptyText="—" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Codec"> + <TextField source="codec" emptyText="—" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Captured"> + <DateField source="captured_at" showTime emptyText="—" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Labeled label="Timestamp source"> + <TextField source="captured_source" emptyText="Unknown" /> + </Labeled> + </Grid> + <Grid + size={{ + xs: 12, + sm: 6, + md: 3, + }} + > + <Stack direction="row" spacing={3}> + <Labeled label="Gravity"> + <TriStateField source="gravity" /> + </Labeled> + <Labeled label="GPS"> + <TriStateField source="gps" /> + </Labeled> + </Stack> + </Grid> + </Grid> + + <Divider /> + <Box> + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + Passes using this clip + </Typography> + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + display: 'block', + }} + > + One clip often holds several passes, sometimes swum in both directions. + </Typography> + </Box> + <VideoPasses /> + + <Divider /> + <Box> + <Typography + variant="overline" + sx={{ + color: 'text.secondary', + }} + > + Runs from this clip + </Typography> + <Typography + variant="caption" + sx={{ + color: 'text.secondary', + display: 'block', + }} + > + Every reconstruction whose pass drew frames from this clip. + </Typography> + </Box> + <VideoRuns /> + + <Divider /> + <SyncFields /> + </Stack> + </Show> +); + +export default VideoShow; diff --git a/src/videos/index.tsx b/src/videos/index.tsx new file mode 100644 index 0000000..7d166d4 --- /dev/null +++ b/src/videos/index.tsx @@ -0,0 +1,17 @@ +import VideocamIcon from '@mui/icons-material/Videocam'; + +import VideoList from './VideoList'; +import VideoShow from './VideoShow'; + +// No create: an asset exists because a client hashed a file, never because the console +// said so. Edit is the administrator's correction path, and App.tsx withholds it otherwise. +export default { + list: VideoList, + show: VideoShow, + // Without this react-admin titles the record `#<uuid>`: there is no `name` to infer from. + recordRepresentation: 'file_name', + icon: VideocamIcon, + options: { + label: 'Videos', + }, +}; diff --git a/src/videos/useVideoRuns.ts b/src/videos/useVideoRuns.ts new file mode 100644 index 0000000..3a87374 --- /dev/null +++ b/src/videos/useVideoRuns.ts @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react'; +import { useDataProvider } from 'react-admin'; + +import type { RunRecord } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; + +/** Every run that consumed one clip, newest first, straight from the registry. */ +export const useVideoRuns = (videoId: string | undefined) => { + const dataProvider = useDataProvider<DrmDataProvider>(); + const [runs, setRuns] = useState<RunRecord[]>(); + const [error, setError] = useState<string>(); + const [pending, setPending] = useState(false); + + useEffect(() => { + if (!videoId) { + setRuns(undefined); + return; + } + let current = true; + setPending(true); + setError(undefined); + dataProvider + .videoRuns(videoId) + .then(result => { + if (current) setRuns(result); + }) + .catch((e: unknown) => { + if (current) setError(e instanceof Error ? e.message : 'Could not read runs'); + }) + .finally(() => { + if (current) setPending(false); + }); + return () => { + current = false; + }; + }, [dataProvider, videoId]); + + return { runs, error, pending }; +}; diff --git a/src/viewer/CloudViewer.tsx b/src/viewer/CloudViewer.tsx new file mode 100644 index 0000000..1256671 --- /dev/null +++ b/src/viewer/CloudViewer.tsx @@ -0,0 +1,264 @@ +import { useEffect, useRef, useState } from 'react'; +import { + Alert, + Box, + Checkbox, + FormControlLabel, + Slider, + Stack, + Switch, + Typography, +} from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import * as THREE from 'three'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; + +import type { DrmwCloud } from './drmw'; + +// The cloud's RGB bytes are sRGB. Pass-through rendering keeps them exact rather +// than letting three's colour management brighten them on the way out. +THREE.ColorManagement.enabled = false; + +type ClassEntry = { + points: THREE.Points; + material: THREE.PointsMaterial; + colour: THREE.Color; + prefixEnd: number[]; + pointCount: number; +}; + +type SceneHandle = { + scene: THREE.Scene; + byClass: Map<number, ClassEntry>; + baseSize: number; + render: () => void; +}; + +const swatch = (colour: [number, number, number]) => `rgb(${colour.join(', ')})`; + +const CloudViewer = ({ cloud }: { cloud: DrmwCloud }) => { + const theme = useTheme(); + const mountRef = useRef<HTMLDivElement>(null); + const handleRef = useRef<SceneHandle | null>(null); + const [contextLost, setContextLost] = useState(false); + const [colourByClass, setColourByClass] = useState(false); + const [hidden, setHidden] = useState<Set<number>>(() => new Set()); + const [frame, setFrame] = useState(cloud.header.frame_count - 1); + const [sizeScale, setSizeScale] = useState(1); + + useEffect(() => { + const mount = mountRef.current; + if (!mount) return; + const renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.outputColorSpace = THREE.LinearSRGBColorSpace; + renderer.setPixelRatio(window.devicePixelRatio); + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(60, 1, 0.01, 1000); + const byClass = new Map<number, ClassEntry>(); + const bounds = new THREE.Box3(); + const classTable = new Map(cloud.header.classes.map(entry => [entry.id, entry])); + for (const perClass of cloud.header.per_class) { + const begin = perClass.point_offset * 3; + const end = begin + perClass.point_count * 3; + // Subarray views over the fetched buffer, so nothing is copied. + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( + 'position', + new THREE.BufferAttribute(cloud.xyz.subarray(begin, end), 3), + ); + geometry.setAttribute( + 'color', + new THREE.BufferAttribute(cloud.rgb.subarray(begin, end), 3, true), + ); + geometry.computeBoundingBox(); + geometry.computeBoundingSphere(); + if (perClass.point_count > 0 && geometry.boundingBox) { + bounds.union(geometry.boundingBox); + } + const material = new THREE.PointsMaterial({ + vertexColors: true, + sizeAttenuation: true, + }); + const points = new THREE.Points(geometry, material); + scene.add(points); + const declared = classTable.get(perClass.class_id)?.colour ?? [128, 128, 128]; + byClass.set(perClass.class_id, { + points, + material, + colour: new THREE.Color( + declared[0] / 255, + declared[1] / 255, + declared[2] / 255, + ), + prefixEnd: perClass.prefix_end, + pointCount: perClass.point_count, + }); + } + + const centre = bounds.getCenter(new THREE.Vector3()); + const sphere = bounds.getBoundingSphere(new THREE.Sphere()); + const radius = Math.max(sphere.radius, 0.1); + camera.near = radius / 100; + camera.far = radius * 100; + const distance = (radius / Math.tan((camera.fov * Math.PI) / 360)) * 1.2; + camera.position.set(centre.x, centre.y, centre.z + distance); + camera.updateProjectionMatrix(); + + const render = () => renderer.render(scene, camera); + const controls = new OrbitControls(camera, renderer.domElement); + controls.target.copy(centre); + controls.update(); + controls.addEventListener('change', render); + + const resize = () => { + renderer.setSize(mount.clientWidth, mount.clientHeight); + camera.aspect = mount.clientWidth / Math.max(mount.clientHeight, 1); + camera.updateProjectionMatrix(); + render(); + }; + const observer = new ResizeObserver(resize); + observer.observe(mount); + const onLost = (event: Event) => { + event.preventDefault(); + setContextLost(true); + }; + renderer.domElement.addEventListener('webglcontextlost', onLost); + mount.appendChild(renderer.domElement); + resize(); + handleRef.current = { scene, byClass, baseSize: radius / 200, render }; + + return () => { + handleRef.current = null; + observer.disconnect(); + controls.dispose(); + renderer.domElement.removeEventListener('webglcontextlost', onLost); + for (const entry of byClass.values()) { + entry.points.geometry.dispose(); + entry.material.dispose(); + } + renderer.dispose(); + mount.removeChild(renderer.domElement); + }; + }, [cloud]); + + useEffect(() => { + const handle = handleRef.current; + if (!handle) return; + handle.scene.background = new THREE.Color(theme.palette.background.default); + for (const [classId, entry] of handle.byClass) { + entry.points.visible = !hidden.has(classId); + // Swapping vertexColors recompiles the shader, hence needsUpdate. + entry.material.vertexColors = !colourByClass; + entry.material.color.set(colourByClass ? entry.colour : 0xffffff); + entry.material.size = handle.baseSize * sizeScale; + entry.material.needsUpdate = true; + entry.points.geometry.setDrawRange(0, entry.prefixEnd[frame] ?? entry.pointCount); + } + handle.render(); + }, [cloud, theme, colourByClass, hidden, frame, sizeScale]); + + const toggleClass = (classId: number) => + setHidden(current => { + const next = new Set(current); + if (next.has(classId)) next.delete(classId); + else next.add(classId); + return next; + }); + + if (contextLost) { + return ( + <Alert severity="error"> + The browser dropped the WebGL context, usually under GPU memory pressure. Close + other 3D tabs and reopen this section. + </Alert> + ); + } + return ( + <Stack spacing={1.5}> + <Box ref={mountRef} sx={{ height: 480, '& canvas': { display: 'block' } }} /> + <Stack + direction="row" + spacing={3} + sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }} + > + <FormControlLabel + control={ + <Switch + size="small" + checked={colourByClass} + onChange={(_, checked) => setColourByClass(checked)} + /> + } + label={<Typography variant="body2">Colour by class</Typography>} + /> + <Stack direction="row" spacing={1.5} sx={{ alignItems: 'center', width: 220 }}> + <Typography variant="body2" sx={{ whiteSpace: 'nowrap' }}> + Point size + </Typography> + <Slider + size="small" + min={0.25} + max={4} + step={0.05} + value={sizeScale} + onChange={(_, value) => setSizeScale(value as number)} + /> + </Stack> + <Typography variant="body2" sx={{ color: 'text.secondary' }}> + {cloud.header.point_count.toLocaleString()} points + </Typography> + </Stack> + {cloud.header.frame_count > 1 && ( + <Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}> + <Typography variant="body2" sx={{ whiteSpace: 'nowrap' }}> + Timeline + </Typography> + <Slider + size="small" + min={0} + max={cloud.header.frame_count - 1} + step={1} + value={frame} + onChange={(_, value) => setFrame(value as number)} + valueLabelDisplay="auto" + /> + </Stack> + )} + <Box + sx={{ + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', + columnGap: 2, + }} + > + {cloud.header.classes.map(entry => ( + <FormControlLabel + key={entry.id} + control={ + <Checkbox + size="small" + checked={!hidden.has(entry.id)} + onChange={() => toggleClass(entry.id)} + /> + } + label={ + <Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}> + <Box + sx={{ + width: 12, + height: 12, + borderRadius: '2px', + bgcolor: swatch(entry.colour), + }} + /> + <Typography variant="body2">{entry.name}</Typography> + </Stack> + } + /> + ))} + </Box> + </Stack> + ); +}; + +export default CloudViewer; diff --git a/src/viewer/RunCloudTab.tsx b/src/viewer/RunCloudTab.tsx new file mode 100644 index 0000000..715f062 --- /dev/null +++ b/src/viewer/RunCloudTab.tsx @@ -0,0 +1,140 @@ +import { Suspense, lazy, useEffect, useState } from 'react'; +import { useDataProvider, useGetList, useGetOne, useRecordContext } from 'react-admin'; +import { Alert, Box, LinearProgress, Typography } from '@mui/material'; + +import type { RunArtifact, RunRecord, StoredObject } from '../contract'; +import type { DrmDataProvider } from '../dataProvider'; +import { formatBytes } from '../videos/VideoFields'; +import { fetchWebCloud, type DrmwCloud } from './drmw'; + +// three.js is the app's largest optional dependency, so the viewer loads as its +// own chunk only when a cloud is actually opened. +const CloudViewer = lazy(() => import('./CloudViewer')); + +const CLOUD_RELPATH = 'cloud_web.drmw'; + +type Load = + | { name: 'loading'; received: number; total: number | null } + | { name: 'ready'; cloud: DrmwCloud } + | { name: 'error'; message: string }; + +const Muted = ({ children }: { children: string }) => ( + <Typography variant="body2" sx={{ color: 'text.secondary' }}> + {children} + </Typography> +); + +const Downloading = ({ received, total }: { received: number; total: number | null }) => ( + <Box> + <Typography variant="body2" gutterBottom> + Downloading the cloud: {formatBytes(received)} + {total ? ` of ${formatBytes(total)}` : ''} + </Typography> + <LinearProgress + variant={total ? 'determinate' : 'indeterminate'} + value={total ? (received / total) * 100 : undefined} + /> + </Box> +); + +const CloudLoader = ({ objectId }: { objectId: string }) => { + const dataProvider = useDataProvider<DrmDataProvider>(); + const [load, setLoad] = useState<Load>({ name: 'loading', received: 0, total: null }); + + useEffect(() => { + let alive = true; + const run = async () => { + try { + const { url } = await dataProvider.archiveDownload(objectId); + if (!alive) return; + const cloud = await fetchWebCloud(url, (received, total) => { + if (alive) setLoad({ name: 'loading', received, total }); + }); + if (alive) setLoad({ name: 'ready', cloud }); + } catch (error) { + if (alive) { + setLoad({ + name: 'error', + message: + error instanceof Error + ? error.message + : 'The cloud failed to load.', + }); + } + } + }; + run(); + return () => { + alive = false; + }; + }, [dataProvider, objectId]); + + if (load.name === 'error') return <Alert severity="error">{load.message}</Alert>; + if (load.name === 'loading') { + return <Downloading received={load.received} total={load.total} />; + } + return ( + <Suspense fallback={<LinearProgress />}> + <CloudViewer cloud={load.cloud} /> + </Suspense> + ); +}; + +/** Finds the run's archived web cloud and mounts the viewer over it. */ +const RunCloudTab = () => { + const record = useRecordContext<RunRecord>(); + const { + data: artifacts, + isPending, + error, + } = useGetList<RunArtifact>( + 'run_artifacts', + { + filter: { run_id: record?.id }, + pagination: { page: 1, perPage: 1000 }, + sort: { field: 'relpath', order: 'ASC' }, + }, + { enabled: !!record }, + ); + const artifact = artifacts?.find(candidate => candidate.relpath === CLOUD_RELPATH); + const { data: object } = useGetOne<StoredObject>( + 'stored_objects', + { id: artifact?.stored_object_id ?? '' }, + { enabled: !!artifact?.stored_object_id }, + ); + + if (!record || isPending) return <LinearProgress />; + if (error) { + return <Alert severity="error">The run's artefacts could not be listed.</Alert>; + } + if (!artifact) { + return ( + <Muted> + This run has not archived a web cloud yet. Archive the run from the desktop + app. + </Muted> + ); + } + if (!artifact.stored_object_id) { + return ( + <Muted> + The web cloud is registered but its bytes have not been archived yet. Archive + the run from the desktop app. + </Muted> + ); + } + if (!object) return <LinearProgress />; + if (object.status === 'failed') { + return ( + <Alert severity="error"> + The archived cloud did not upload completely, so it cannot be viewed. + </Alert> + ); + } + if (object.status !== 'complete') { + return <Muted>The web cloud is still uploading.</Muted>; + } + return <CloudLoader objectId={object.id} />; +}; + +export default RunCloudTab; diff --git a/src/viewer/drmw.ts b/src/viewer/drmw.ts new file mode 100644 index 0000000..9756069 --- /dev/null +++ b/src/viewer/drmw.ts @@ -0,0 +1,157 @@ +// Reader for the pipeline's 'drmw' web cloud export. The layout is authored by +// deepreefmap/io/web_cloud.py: 8-byte magic, uint32 header length, JSON header, +// then 4-byte-aligned buffers whose offsets are relative to the end of the header. + +export type DrmwClass = { + id: number; + name: string; + colour: [number, number, number]; +}; + +export type DrmwPerClass = { + class_id: number; + point_offset: number; + point_count: number; + prefix_end: number[]; +}; + +export type DrmwBuffer = { + name: string; + dtype: string; + byte_offset: number; + byte_length: number; +}; + +export type DrmwHeader = { + format: string; + version: number; + point_count: number; + frame_count: number; + frame_order: number[]; + has_confidence: boolean; + classes: DrmwClass[]; + per_class: DrmwPerClass[]; + buffers: DrmwBuffer[]; +}; + +export type DrmwCloud = { + header: DrmwHeader; + xyz: Float32Array; + rgb: Uint8Array; + conf?: Float32Array; +}; + +const MAGIC = 'DRMWEB01'; +const VERSION = 1; + +const namedBuffer = ( + header: DrmwHeader, + raw: ArrayBuffer, + dataStart: number, + name: string, +) => { + const entry = header.buffers.find(candidate => candidate.name === name); + if (!entry) throw new Error(`The drmw header declares no '${name}' buffer.`); + if (dataStart + entry.byte_offset + entry.byte_length > raw.byteLength) { + throw new Error(`The '${name}' buffer runs past the end of the file.`); + } + return entry; +}; + +const float32View = ( + header: DrmwHeader, + raw: ArrayBuffer, + dataStart: number, + name: string, +) => { + const entry = namedBuffer(header, raw, dataStart, name); + if (entry.byte_length % 4 !== 0) { + throw new Error(`The '${name}' buffer length is not a whole number of float32s.`); + } + return new Float32Array(raw, dataStart + entry.byte_offset, entry.byte_length / 4); +}; + +export const parseWebCloud = (raw: ArrayBuffer): DrmwCloud => { + if (raw.byteLength < MAGIC.length + 4) { + throw new Error('The file is too short to be a drmw cloud.'); + } + const magic = new TextDecoder().decode(new Uint8Array(raw, 0, MAGIC.length)); + if (magic !== MAGIC) { + throw new Error('This file is not a drmw cloud (bad magic).'); + } + const headerLength = new DataView(raw).getUint32(MAGIC.length, true); + const dataStart = MAGIC.length + 4 + headerLength; + if (dataStart > raw.byteLength) { + throw new Error('The drmw header runs past the end of the file.'); + } + let header: DrmwHeader; + try { + header = JSON.parse( + new TextDecoder().decode(new Uint8Array(raw, MAGIC.length + 4, headerLength)), + ) as DrmwHeader; + } catch { + throw new Error('The drmw header is not valid JSON.'); + } + if (header.format !== 'drmw' || header.version !== VERSION) { + throw new Error( + `Unsupported drmw header (${header.format} v${header.version}). ` + + `This console reads drmw v${VERSION}.`, + ); + } + const xyz = float32View(header, raw, dataStart, 'xyz'); + if (xyz.length !== header.point_count * 3) { + throw new Error( + `The 'xyz' buffer holds ${xyz.length / 3} points, ` + + `but the header declares ${header.point_count}.`, + ); + } + const rgbEntry = namedBuffer(header, raw, dataStart, 'rgb'); + const rgb = new Uint8Array(raw, dataStart + rgbEntry.byte_offset, rgbEntry.byte_length); + if (rgb.length !== header.point_count * 3) { + throw new Error( + `The 'rgb' buffer holds ${rgb.length / 3} points, ` + + `but the header declares ${header.point_count}.`, + ); + } + const conf = header.has_confidence + ? float32View(header, raw, dataStart, 'conf') + : undefined; + return { header, xyz, rgb, conf }; +}; + +const readBody = async ( + response: Response, + onProgress?: (receivedBytes: number, totalBytes: number | null) => void, +): Promise<ArrayBuffer> => { + if (!response.body || !onProgress) return response.arrayBuffer(); + const totalBytes = Number(response.headers.get('Content-Length')) || null; + const reader = response.body.getReader(); + const chunks: Uint8Array[] = []; + let receivedBytes = 0; + for (;;) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + receivedBytes += value.byteLength; + onProgress(receivedBytes, totalBytes); + } + const merged = new Uint8Array(receivedBytes); + let offset = 0; + for (const chunk of chunks) { + merged.set(chunk, offset); + offset += chunk.byteLength; + } + return merged.buffer; +}; + +/** Download and parse a drmw cloud, reporting byte progress along the way. */ +export const fetchWebCloud = async ( + url: string, + onProgress?: (receivedBytes: number, totalBytes: number | null) => void, +): Promise<DrmwCloud> => { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`The cloud download failed: HTTP ${response.status}.`); + } + return parseWebCloud(await readBody(response, onProgress)); +}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite/client" /> diff --git a/tsconfig.json b/tsconfig.json index a273b0c..3c2ed2d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2020", "lib": [ "dom", "dom.iterable", @@ -14,7 +14,7 @@ "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, diff --git a/vite.config.ts b/vite.config.mts similarity index 79% rename from vite.config.ts rename to vite.config.mts index b1e14d1..4ede8c3 100644 --- a/vite.config.ts +++ b/vite.config.mts @@ -4,9 +4,6 @@ import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], - define: { - 'process.env': process.env, - }, server: { host: true, }, diff --git a/yarn.lock b/yarn.lock index b87bd2f..ced009f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,6234 +1,5278 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" - integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== - dependencies: - "@babel/highlight" "^7.25.7" - picocolors "^1.0.0" - -"@babel/compat-data@^7.25.7": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.8.tgz#0376e83df5ab0eb0da18885c0140041f0747a402" - integrity sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA== - -"@babel/core@^7.25.2": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.8.tgz#a57137d2a51bbcffcfaeba43cb4dd33ae3e0e1c6" - integrity sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.25.7" - "@babel/generator" "^7.25.7" - "@babel/helper-compilation-targets" "^7.25.7" - "@babel/helper-module-transforms" "^7.25.7" - "@babel/helpers" "^7.25.7" - "@babel/parser" "^7.25.8" - "@babel/template" "^7.25.7" - "@babel/traverse" "^7.25.7" - "@babel/types" "^7.25.8" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56" - integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA== - dependencies: - "@babel/types" "^7.25.7" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^3.0.2" - -"@babel/helper-compilation-targets@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz#11260ac3322dda0ef53edfae6e97b961449f5fa4" - integrity sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A== - dependencies: - "@babel/compat-data" "^7.25.7" - "@babel/helper-validator-option" "^7.25.7" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz#dba00d9523539152906ba49263e36d7261040472" - integrity sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw== - dependencies: - "@babel/traverse" "^7.25.7" - "@babel/types" "^7.25.7" - -"@babel/helper-module-transforms@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz#2ac9372c5e001b19bc62f1fe7d96a18cb0901d1a" - integrity sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ== - dependencies: - "@babel/helper-module-imports" "^7.25.7" - "@babel/helper-simple-access" "^7.25.7" - "@babel/helper-validator-identifier" "^7.25.7" - "@babel/traverse" "^7.25.7" - -"@babel/helper-plugin-utils@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz#8ec5b21812d992e1ef88a9b068260537b6f0e36c" - integrity sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw== - -"@babel/helper-simple-access@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz#5eb9f6a60c5d6b2e0f76057004f8dacbddfae1c0" - integrity sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ== - dependencies: - "@babel/traverse" "^7.25.7" - "@babel/types" "^7.25.7" - -"@babel/helper-string-parser@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" - integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== - -"@babel/helper-validator-identifier@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" - integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== - -"@babel/helper-validator-option@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz#97d1d684448228b30b506d90cace495d6f492729" - integrity sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ== - -"@babel/helpers@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.7.tgz#091b52cb697a171fe0136ab62e54e407211f09c2" - integrity sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA== - dependencies: - "@babel/template" "^7.25.7" - "@babel/types" "^7.25.7" - -"@babel/highlight@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" - integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== - dependencies: - "@babel/helper-validator-identifier" "^7.25.7" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.7", "@babel/parser@^7.25.8": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2" - integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ== - dependencies: - "@babel/types" "^7.25.8" - -"@babel/plugin-transform-react-jsx-self@^7.24.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.7.tgz#3d11df143131fd8f5486a1f7d3839890f88f8c85" - integrity sha512-JD9MUnLbPL0WdVK8AWC7F7tTG2OS6u/AKKnsK+NdRhUiVdnzyR1S3kKQCaRLOiaULvUiqK6Z4JQE635VgtCFeg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.7" - -"@babel/plugin-transform-react-jsx-source@^7.24.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.7.tgz#a0d8372310d5ea5b0447dfa03a8485f960eff7be" - integrity sha512-S/JXG/KrbIY06iyJPKfxr0qRxnhNOdkNXYBl/rmwgDd72cQLH9tEGkDm/yJPGvcSIUoikzfjMios9i+xT/uv9w== - dependencies: - "@babel/helper-plugin-utils" "^7.25.7" - -"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.8", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" - integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/template@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769" - integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA== - dependencies: - "@babel/code-frame" "^7.25.7" - "@babel/parser" "^7.25.7" - "@babel/types" "^7.25.7" - -"@babel/traverse@^7.25.7": - version "7.25.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8" - integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg== - dependencies: - "@babel/code-frame" "^7.25.7" - "@babel/generator" "^7.25.7" - "@babel/parser" "^7.25.7" - "@babel/template" "^7.25.7" - "@babel/types" "^7.25.7" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.7", "@babel/types@^7.25.8": - version "7.25.8" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1" - integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg== - dependencies: - "@babel/helper-string-parser" "^7.25.7" - "@babel/helper-validator-identifier" "^7.25.7" - to-fast-properties "^2.0.0" - -"@choojs/findup@^0.2.0": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@choojs/findup/-/findup-0.2.1.tgz#ac13c59ae7be6e1da64de0779a0a7f03d75615a3" - integrity sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw== - dependencies: - commander "^2.15.1" - -"@emotion/babel-plugin@^11.12.0": - version "11.12.0" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz#7b43debb250c313101b3f885eba634f1d723fcc2" - integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.2" - "@emotion/memoize" "^0.9.0" - "@emotion/serialize" "^1.2.0" - babel-plugin-macros "^3.1.0" - convert-source-map "^1.5.0" - escape-string-regexp "^4.0.0" - find-root "^1.1.0" - source-map "^0.5.7" - stylis "4.2.0" - -"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0": - version "11.13.1" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7" - integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== - dependencies: - "@emotion/memoize" "^0.9.0" - "@emotion/sheet" "^1.4.0" - "@emotion/utils" "^1.4.0" - "@emotion/weak-memoize" "^0.4.0" - stylis "4.2.0" - -"@emotion/hash@^0.9.2": - version "0.9.2" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" - integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== - -"@emotion/is-prop-valid@^1.3.0": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz#8d5cf1132f836d7adbe42cf0b49df7816fc88240" - integrity sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw== - dependencies: - "@emotion/memoize" "^0.9.0" - -"@emotion/memoize@^0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" - integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== - -"@emotion/react@^11.4.1": - version "11.13.3" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.3.tgz#a69d0de2a23f5b48e0acf210416638010e4bd2e4" - integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.12.0" - "@emotion/cache" "^11.13.0" - "@emotion/serialize" "^1.3.1" - "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" - "@emotion/utils" "^1.4.0" - "@emotion/weak-memoize" "^0.4.0" - hoist-non-react-statics "^3.3.1" - -"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0", "@emotion/serialize@^1.3.1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.2.tgz#e1c1a2e90708d5d85d81ccaee2dfeb3cc0cccf7a" - integrity sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA== - dependencies: - "@emotion/hash" "^0.9.2" - "@emotion/memoize" "^0.9.0" - "@emotion/unitless" "^0.10.0" - "@emotion/utils" "^1.4.1" - csstype "^3.0.2" - -"@emotion/sheet@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" - integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== - -"@emotion/styled@^11.3.0": - version "11.13.0" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.13.0.tgz#633fd700db701472c7a5dbef54d6f9834e9fb190" - integrity sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.12.0" - "@emotion/is-prop-valid" "^1.3.0" - "@emotion/serialize" "^1.3.0" - "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" - "@emotion/utils" "^1.4.0" - -"@emotion/unitless@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" - integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== - -"@emotion/use-insertion-effect-with-fallbacks@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf" - integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== - -"@emotion/utils@^1.4.0", "@emotion/utils@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.1.tgz#b3adbb43de12ee2149541c4f1337d2eb7774f0ad" - integrity sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA== - -"@emotion/weak-memoize@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" - integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== - -"@esbuild/android-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" - integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== - -"@esbuild/android-arm@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" - integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== - -"@esbuild/android-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" - integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== - -"@esbuild/darwin-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" - integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== - -"@esbuild/darwin-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" - integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== - -"@esbuild/freebsd-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" - integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== - -"@esbuild/freebsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" - integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== - -"@esbuild/linux-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" - integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== - -"@esbuild/linux-arm@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" - integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== - -"@esbuild/linux-ia32@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" - integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== - -"@esbuild/linux-loong64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" - integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== - -"@esbuild/linux-mips64el@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" - integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== - -"@esbuild/linux-ppc64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" - integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== - -"@esbuild/linux-riscv64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" - integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== - -"@esbuild/linux-s390x@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" - integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== - -"@esbuild/linux-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" - integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== - -"@esbuild/netbsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" - integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== - -"@esbuild/openbsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" - integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== - -"@esbuild/sunos-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" - integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== - -"@esbuild/win32-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" - integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== - -"@esbuild/win32-ia32@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" - integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== - -"@esbuild/win32-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" - integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== - -"@eslint-community/eslint-utils@^4.2.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": - version "4.11.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" - integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== - -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" - integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== - -"@fortawesome/fontawesome-common-types@6.6.0": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz#31ab07ca6a06358c5de4d295d4711b675006163f" - integrity sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw== - -"@fortawesome/fontawesome-svg-core@^6.6.0": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz#2a24c32ef92136e98eae2ff334a27145188295ff" - integrity sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg== - dependencies: - "@fortawesome/fontawesome-common-types" "6.6.0" - -"@fortawesome/free-brands-svg-icons@^6.6.0": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz#2797f2cc66d21e7e47fa64e680b8835e8d30e825" - integrity sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ== - dependencies: - "@fortawesome/fontawesome-common-types" "6.6.0" - -"@fortawesome/free-solid-svg-icons@^6.6.0": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz#061751ca43be4c4d814f0adbda8f006164ec9f3b" - integrity sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA== - dependencies: - "@fortawesome/fontawesome-common-types" "6.6.0" - -"@fortawesome/react-fontawesome@^0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz#68b058f9132b46c8599875f6a636dad231af78d4" - integrity sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g== - dependencies: - prop-types "^15.8.1" - -"@humanwhocodes/config-array@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" - integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== - dependencies: - "@humanwhocodes/object-schema" "^2.0.3" - debug "^4.3.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@mapbox/geojson-rewind@^0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz#591a5d71a9cd1da1a0bf3420b3bea31b0fc7946a" - integrity sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA== - dependencies: - get-stream "^6.0.1" - minimist "^1.2.6" - -"@mapbox/geojson-types@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz#9aecf642cb00eab1080a57c4f949a65b4a5846d6" - integrity sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw== - -"@mapbox/jsonlint-lines-primitives@^2.0.2", "@mapbox/jsonlint-lines-primitives@~2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234" - integrity sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ== - -"@mapbox/mapbox-gl-supported@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz#f60b6a55a5d8e5ee908347d2ce4250b15103dc8e" - integrity sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg== - -"@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2" - integrity sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ== - -"@mapbox/tiny-sdf@^1.1.1": - version "1.2.5" - resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz#424c620a96442b20402552be70a7f62a8407cc59" - integrity sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw== - -"@mapbox/tiny-sdf@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz#9a1d33e5018093e88f6a4df2343e886056287282" - integrity sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA== - -"@mapbox/unitbezier@^0.0.0": - version "0.0.0" - resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e" - integrity sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA== - -"@mapbox/unitbezier@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz#d32deb66c7177e9e9dfc3bbd697083e2e657ff01" - integrity sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw== - -"@mapbox/vector-tile@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666" - integrity sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw== - dependencies: - "@mapbox/point-geometry" "~0.1.0" - -"@mapbox/whoots-js@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" - integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== - -"@maplibre/maplibre-gl-style-spec@^20.3.1": - version "20.4.0" - resolved "https://registry.yarnpkg.com/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.4.0.tgz#408339e051fb51e022b40af2235e0beb037937ea" - integrity sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw== - dependencies: - "@mapbox/jsonlint-lines-primitives" "~2.0.2" - "@mapbox/unitbezier" "^0.0.1" - json-stringify-pretty-compact "^4.0.0" - minimist "^1.2.8" - quickselect "^2.0.0" - rw "^1.3.3" - tinyqueue "^3.0.0" - -"@mui/core-downloads-tracker@^5.16.7": - version "5.16.7" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz#182a325a520f7ebd75de051fceabfc0314cfd004" - integrity sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ== - -"@mui/icons-material@^5.0.1", "@mui/icons-material@^5.15.20": - version "5.16.7" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.16.7.tgz#e27f901af792065efc9f3d75d74a66af7529a10a" - integrity sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q== - dependencies: - "@babel/runtime" "^7.23.9" - -"@mui/material@^5.0.2", "@mui/material@^5.15.20": - version "5.16.7" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.7.tgz#6e814e2eefdaf065a769cecf549c3569e107a50b" - integrity sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg== - dependencies: - "@babel/runtime" "^7.23.9" - "@mui/core-downloads-tracker" "^5.16.7" - "@mui/system" "^5.16.7" - "@mui/types" "^7.2.15" - "@mui/utils" "^5.16.6" - "@popperjs/core" "^2.11.8" - "@types/react-transition-group" "^4.4.10" - clsx "^2.1.0" - csstype "^3.1.3" - prop-types "^15.8.1" - react-is "^18.3.1" - react-transition-group "^4.4.5" - -"@mui/private-theming@^5.16.6": - version "5.16.6" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.16.6.tgz#547671e7ae3f86b68d1289a0b90af04dfcc1c8c9" - integrity sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw== - dependencies: - "@babel/runtime" "^7.23.9" - "@mui/utils" "^5.16.6" - prop-types "^15.8.1" - -"@mui/styled-engine@^5.16.6": - version "5.16.6" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.6.tgz#60110c106dd482dfdb7e2aa94fd6490a0a3f8852" - integrity sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g== - dependencies: - "@babel/runtime" "^7.23.9" - "@emotion/cache" "^11.11.0" - csstype "^3.1.3" - prop-types "^15.8.1" - -"@mui/system@^5.16.7": - version "5.16.7" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.7.tgz#4583ca5bf3b38942e02c15a1e622ba869ac51393" - integrity sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA== - dependencies: - "@babel/runtime" "^7.23.9" - "@mui/private-theming" "^5.16.6" - "@mui/styled-engine" "^5.16.6" - "@mui/types" "^7.2.15" - "@mui/utils" "^5.16.6" - clsx "^2.1.0" - csstype "^3.1.3" - prop-types "^15.8.1" - -"@mui/types@^7.2.15": - version "7.2.18" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.18.tgz#4b6385ed2f7828ef344113cdc339d6fdf8e4bc23" - integrity sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg== - -"@mui/utils@^5.16.6": - version "5.16.6" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.6.tgz#905875bbc58d3dcc24531c3314a6807aba22a711" - integrity sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA== - dependencies: - "@babel/runtime" "^7.23.9" - "@mui/types" "^7.2.15" - "@types/prop-types" "^15.7.12" - clsx "^2.1.1" - prop-types "^15.8.1" - react-is "^18.3.1" - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@petamoriken/float16@^3.4.7": - version "3.8.7" - resolved "https://registry.yarnpkg.com/@petamoriken/float16/-/float16-3.8.7.tgz#16073fb1b9867eaa5b254573484d09100700aaa4" - integrity sha512-/Ri4xDDpe12NT6Ex/DRgHzLlobiQXEW/hmG08w1wj/YU7hLemk97c+zHQFp0iZQ9r7YqgLEXZR2sls4HxBf9NA== - -"@plotly/d3-sankey-circular@0.33.1": - version "0.33.1" - resolved "https://registry.yarnpkg.com/@plotly/d3-sankey-circular/-/d3-sankey-circular-0.33.1.tgz#15d1e0337e0e4b1135bdf0e2195c88adacace1a7" - integrity sha512-FgBV1HEvCr3DV7RHhDsPXyryknucxtfnLwPtCKKxdolKyTFYoLX/ibEfX39iFYIL7DYbVeRtP43dbFcrHNE+KQ== - dependencies: - d3-array "^1.2.1" - d3-collection "^1.0.4" - d3-shape "^1.2.0" - elementary-circuits-directed-graph "^1.0.4" - -"@plotly/d3-sankey@0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@plotly/d3-sankey/-/d3-sankey-0.7.2.tgz#ddd5290d3b02c60037ced018a162644a2ccef33b" - integrity sha512-2jdVos1N3mMp3QW0k2q1ph7Gd6j5PY1YihBrwpkFnKqO+cqtZq3AdEYUeSGXMeLsBDQYiqTVcihYfk8vr5tqhw== - dependencies: - d3-array "1" - d3-collection "1" - d3-shape "^1.2.0" - -"@plotly/d3@3.8.2": - version "3.8.2" - resolved "https://registry.yarnpkg.com/@plotly/d3/-/d3-3.8.2.tgz#06a93a1dfc1377c1a441c24ddb156fc8da786f4a" - integrity sha512-wvsNmh1GYjyJfyEBPKJLTMzgf2c2bEbSIL50lmqVUi+o1NHaLPi1Lb4v7VxXXJn043BhNyrxUrWI85Q+zmjOVA== - -"@plotly/mapbox-gl@1.13.4": - version "1.13.4" - resolved "https://registry.yarnpkg.com/@plotly/mapbox-gl/-/mapbox-gl-1.13.4.tgz#cb854d70902dd02af753f728855152efe568524f" - integrity sha512-sR3/Pe5LqT/fhYgp4rT4aSFf1rTsxMbGiH6Hojc7PH36ny5Bn17iVFUjpzycafETURuFbLZUfjODO8LvSI+5zQ== - dependencies: - "@mapbox/geojson-rewind" "^0.5.2" - "@mapbox/geojson-types" "^1.0.2" - "@mapbox/jsonlint-lines-primitives" "^2.0.2" - "@mapbox/mapbox-gl-supported" "^1.5.0" - "@mapbox/point-geometry" "^0.1.0" - "@mapbox/tiny-sdf" "^1.1.1" - "@mapbox/unitbezier" "^0.0.0" - "@mapbox/vector-tile" "^1.3.1" - "@mapbox/whoots-js" "^3.1.0" - csscolorparser "~1.0.3" - earcut "^2.2.2" - geojson-vt "^3.2.1" - gl-matrix "^3.2.1" - grid-index "^1.1.0" - murmurhash-js "^1.0.0" - pbf "^3.2.1" - potpack "^1.0.1" - quickselect "^2.0.0" - rw "^1.3.3" - supercluster "^7.1.0" - tinyqueue "^2.0.3" - vt-pbf "^3.1.1" - -"@plotly/point-cluster@^3.1.9": - version "3.1.9" - resolved "https://registry.yarnpkg.com/@plotly/point-cluster/-/point-cluster-3.1.9.tgz#8ffec77fbf5041bf15401079e4fdf298220291c1" - integrity sha512-MwaI6g9scKf68Orpr1pHZ597pYx9uP8UEFXLPbsCmuw3a84obwz6pnMXGc90VhgDNeNiLEdlmuK7CPo+5PIxXw== - dependencies: - array-bounds "^1.0.1" - binary-search-bounds "^2.0.4" - clamp "^1.0.1" - defined "^1.0.0" - dtype "^2.0.0" - flatten-vertex-data "^1.0.2" - is-obj "^1.0.1" - math-log2 "^1.0.1" - parse-rect "^1.2.0" - pick-by-alias "^1.2.0" - -"@popperjs/core@^2.11.8", "@popperjs/core@^2.9.0": - version "2.11.8" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" - integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== - -"@react-leaflet/core@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-2.1.0.tgz#383acd31259d7c9ae8fb1b02d5e18fe613c2a13d" - integrity sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg== - -"@remirror/core-constants@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" - integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== - -"@remix-run/router@1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.20.0.tgz#03554155b45d8b529adf635b2f6ad1165d70d8b4" - integrity sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg== - -"@tanstack/query-core@5.59.13": - version "5.59.13" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.59.13.tgz#8c962980af174bbd446b7e9b9999f7432897df80" - integrity sha512-Oou0bBu/P8+oYjXsJQ11j+gcpLAMpqW42UlokQYEz4dE7+hOtVO9rVuolJKgEccqzvyFzqX4/zZWY+R/v1wVsQ== - -"@tanstack/react-query@^5.8.4": - version "5.59.15" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.59.15.tgz#fa1c5b4d96e6a148ec761f214304bbf5ac1906be" - integrity sha512-QbVlAkTI78wB4Mqgf2RDmgC0AOiJqer2c5k9STOOSXGv1S6ZkY37r/6UpE8DbQ2Du0ohsdoXgFNEyv+4eDoPEw== - dependencies: - "@tanstack/query-core" "5.59.13" - -"@tiptap/core@^2.0.3", "@tiptap/core@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.8.0.tgz#4b5707d3ac1d61fcbb840371fc04990c1cb466b8" - integrity sha512-xsqDI4BNzYRWRtBq7+/38ThhqEr7uG9Njip1x+9/wgR3vWPBFnBkYJTz6jSxS35NRE6BSnERm4/B/vrLuY1Hdw== - -"@tiptap/extension-blockquote@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.8.0.tgz#331603a27587b03382c061ef72aa5a274287e1f0" - integrity sha512-m3CKrOIvV7fY1Ak2gYf5LkKiz6AHxHpg6wxfVaJvdBqXgLyVtHo552N+A4oSHOSRbB4AG9EBQ2NeBM8cdEQ4MA== - -"@tiptap/extension-bold@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.8.0.tgz#a327aa16583f37832a1faa56c2a66cc3c6ae5efe" - integrity sha512-U1YkZBxDkSLNvPNiqxB5g42IeJHr27C7zDb/yGQN2xL4UBeg4O9xVhCFfe32f6tLwivSL0dar4ScElpaCJuqow== - -"@tiptap/extension-bubble-menu@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.8.0.tgz#41fe2ccd525c4d3a7e6e75a795f730ee53bd8cae" - integrity sha512-swg+myJPN60LduQvLMF4hVBqP5LOIN01INZBzBI8egz8QufqtSyRCgXl7Xcma0RT5xIXnZSG9XOqNFf2rtkjKA== - dependencies: - tippy.js "^6.3.7" - -"@tiptap/extension-bullet-list@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.8.0.tgz#f529d9b85852ee1d354960d40b8cc4654c7de345" - integrity sha512-H4O2X0ozbc/ce9/XF1H98sqWVUdtt7jzy7hMBunwmY8ZxI4dHtcRkeg81CZbpKTqOqRrMCLWjE3M2tgiDXrDkA== - -"@tiptap/extension-code-block@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.8.0.tgz#c71d29892ba0c967ba0009706250cfe659d6b202" - integrity sha512-POuA5Igx+Dto0DTazoBFAQTj/M/FCdkqRVD9Uhsxhv49swPyANTJRr05vgbgtHB+NDDsZfCawVh7pI0IAD/O0w== - -"@tiptap/extension-code@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.8.0.tgz#caa33cd0bd9f13ac8c16c4183b42d91cca245716" - integrity sha512-VSFn3sFF6qPpOGkXFhik8oYRH5iByVJpFEFd/duIEftmS0MdPzkbSItOpN3mc9xsJ5dCX80LYaResSj5hr5zkA== - -"@tiptap/extension-color@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-color/-/extension-color-2.8.0.tgz#597e1ea2e675e3c01ba64933008eacd296913abd" - integrity sha512-b0ZIDaZKTDVdTb0PMgtOiPzgCkYhvDldjzdWyPLsjWup5x9/zPasH5X/2SfMuwtjt+cKj6YBPveJjF7w5ApK7w== - -"@tiptap/extension-document@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.8.0.tgz#7dc5d2622168ad5b81134a92fccf49d7be53f141" - integrity sha512-mp7Isx1sVc/ifeW4uW/PexGQ9exN3NRUOebSpnLfqXeWYk4y1RS1PA/3+IHkOPVetbnapgPjFx/DswlCP3XLjA== - -"@tiptap/extension-dropcursor@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.8.0.tgz#79807b569fa6e640557b56e4b8dfbb5a0a39a645" - integrity sha512-rAFvx44YuT6dtS1c+ALw0ROAGI16l5L1HxquL4hR1gtxDcTieST5xhw5bkshXlmrlfotZXPrhokzqA7qjhZtJw== - -"@tiptap/extension-floating-menu@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.8.0.tgz#06f4cea1aae9d45cf8878498a957180ee58ae148" - integrity sha512-H4QT61CrkLqisnGGC7zgiYmsl2jXPHl89yQCbdlkQN7aw11H7PltcJS2PJguL0OrRVJS/Mv/VTTUiMslmsEV5g== - dependencies: - tippy.js "^6.3.7" - -"@tiptap/extension-gapcursor@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.8.0.tgz#0621f6ef8eb4a1f4b7cb61126b31b987489cfc9b" - integrity sha512-Be1LWCmvteQInOnNVN+HTqc1XWsj1bCl+Q7et8qqNjtGtTaCbdCp8ppcH1SKJxNTM/RLUtPyJ8FDgOTj51ixCA== - -"@tiptap/extension-hard-break@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.8.0.tgz#bb450fcb7ab15b846c2bb556fbdb36a336c1a51a" - integrity sha512-vqiIfviNiCmy/pJTHuDSCAGL2O4QDEdDmAvGJu8oRmElUrnlg8DbJUfKvn6DWQHNSQwRb+LDrwWlzAYj1K9u6A== - -"@tiptap/extension-heading@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.8.0.tgz#1b7711860fe9f4336fb8933110a129150faa4e39" - integrity sha512-4inWgrTPiqlivPmEHFOM5ck2UsmOsbKKPtqga6bALvWPmCv24S6/EBwFp8Jz4YABabXDnkviihmGu0LpP9D69w== - -"@tiptap/extension-highlight@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-highlight/-/extension-highlight-2.8.0.tgz#3970f42a5a116745fbb2b82cfc5055adb04158e9" - integrity sha512-vyqX7D449nuARhI0AyRqtIZReFg3sfc/U/q1p3JOjtUoW6z2jmDTzshiKRrSg+Jf7Hhzj1pqwU+6+CpelPPDpA== - -"@tiptap/extension-history@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.8.0.tgz#06505cbdaa29a9791911eddbee54304ee32b1d5c" - integrity sha512-u5YS0J5Egsxt8TUWMMAC3QhPZaak+IzQeyHch4gtqxftx96tprItY7AD/A3pGDF2uCSnN+SZrk6yVexm6EncDw== - -"@tiptap/extension-horizontal-rule@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.8.0.tgz#34310d91bac4ddabdcb4b7fc350eea5c87224ab3" - integrity sha512-Sn/MI8WVFBoIYSIHA9NJryJIyCEzZdRysau8pC5TFnfifre0QV1ksPz2bgF+DyCD69ozQiRdBBHDEwKe47ZbfQ== - -"@tiptap/extension-image@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.8.0.tgz#e1b135c1fb079048bb4261a0546c3374fe6192d7" - integrity sha512-5CReomgHGTUgxaX8P3i6qiC9VRWcWQgVoYtds4ZM52LVx/oGwMxQ4ECyzdVYKaRW+6PrNnAe6ew3Qpd5Wk0cIg== - -"@tiptap/extension-italic@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.8.0.tgz#913ee8cadedc8303f90f129468f774fb7cd3f0af" - integrity sha512-PwwSE2LTYiHI47NJnsfhBmPiLE8IXZYqaSoNPU6flPrk1KxEzqvRI1joKZBmD9wuqzmHJ93VFIeZcC+kfwi8ZA== - -"@tiptap/extension-link@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.8.0.tgz#bc2ca4af87881210f41451ea2c722254b31b0d81" - integrity sha512-p67hCG/pYCiOK/oCTPZnlkw9Ei7KJ7kCKFaluTcAmr5j8IBdYfDqSMDNCT4vGXBvKFh4X6xD7S7QvOqcH0Gn9A== - dependencies: - linkifyjs "^4.1.0" - -"@tiptap/extension-list-item@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.8.0.tgz#8e5c50713da80f1aa9e31c34d992677a746e7c05" - integrity sha512-o7OGymGxB0B9x3x2prp3KBDYFuBYGc5sW69O672jk8G52DqhzzndgPnkk0qUn8nXAUKuDGbJmpmHVA2kagqnRg== - -"@tiptap/extension-ordered-list@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.8.0.tgz#792817e5b2ad8a1cf5140f2ce3450d1673d58512" - integrity sha512-sCvNbcTS1+5QTTXwUPFa10vf5I1pr8sGcOTIh0G+a5ZkS5+6FxT12k7VLzPt39QyNbOi+77U2o4Xr4XyaEkfSg== - -"@tiptap/extension-paragraph@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.8.0.tgz#6f3d673d7f1143a64da3c1db2d2128c835a58c41" - integrity sha512-XgxxNNbuBF48rAGwv7/s6as92/xjm/lTZIGTq9aG13ClUKFtgdel7C33SpUCcxg3cO2WkEyllXVyKUiauFZw/A== - -"@tiptap/extension-placeholder@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.8.0.tgz#2a7feffaba01167e4e890bc308852044241124f9" - integrity sha512-BMqv/C9Tcjd7L1/OphUAJTZhWfpWs0rTQJ0bs3RRGsC8L+K20Fg+li45vw7M0teojpfrw57zwJogJd/m23Zr1Q== - -"@tiptap/extension-strike@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.8.0.tgz#2b2a90f9b9addc36bdf60ff81f3cbc45720ab94e" - integrity sha512-ezkDiXxQ3ME/dDMMM7tAMkKRi6UWw7tIu+Mx7Os0z8HCGpVBk1gFhLlhEd8I5rJaPZr4tK1wtSehMA9bscFGQw== - -"@tiptap/extension-text-align@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text-align/-/extension-text-align-2.8.0.tgz#831f0e1adf14f48be35a57b40e6e680ca69c53d3" - integrity sha512-Y6s/DF+P4lxpAnvSrnmt4xGwQT/AJJJm0aA1wu5GuPKpAQ+K4C7K6rE6uGNAXtR39GlewC7KdmcvA+CYhL8xlw== - -"@tiptap/extension-text-style@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text-style/-/extension-text-style-2.8.0.tgz#32e30ccf3853202eba2169ba5db30b9470df9644" - integrity sha512-jJp0vcZ2Ty7RvIL0VU6dm1y+fTfXq1lN2GwtYzYM0ueFuESa+Qo8ticYOImyWZ3wGJGVrjn7OV9r0ReW0/NYkQ== - -"@tiptap/extension-text@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.8.0.tgz#d24d9e627a595aa87585d885f8ee3cd2a959d787" - integrity sha512-EDAdFFzWOvQfVy7j3qkKhBpOeE5thkJaBemSWfXI93/gMVc0ZCdLi24mDvNNgUHlT+RjlIoQq908jZaaxLKN2A== - -"@tiptap/extension-underline@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-2.8.0.tgz#e856d76d51247b6309159830d0ba50ecc4c585ab" - integrity sha512-1ouuHwZJphT8OosAmp6x8e+Wly3cUd1pNWBiOutJX+6QRGBXJnIKFCzn8YOTlWhg1YQigisG7dNF3YdlyuRNHw== - -"@tiptap/pm@^2.0.3", "@tiptap/pm@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.8.0.tgz#c79ad5e0f4a00cf306f15ac4497bf2fb0a40c784" - integrity sha512-eMGpRooUMvKz/vOpnKKppApMSoNM325HxTdAJvTlVAmuHp5bOY5kyY1kfUlePRiVx1t1UlFcXs3kecFwkkBD3Q== - dependencies: - prosemirror-changeset "^2.2.1" - prosemirror-collab "^1.3.1" - prosemirror-commands "^1.6.0" - prosemirror-dropcursor "^1.8.1" - prosemirror-gapcursor "^1.3.2" - prosemirror-history "^1.4.1" - prosemirror-inputrules "^1.4.0" - prosemirror-keymap "^1.2.2" - prosemirror-markdown "^1.13.0" - prosemirror-menu "^1.2.4" - prosemirror-model "^1.22.3" - prosemirror-schema-basic "^1.2.3" - prosemirror-schema-list "^1.4.1" - prosemirror-state "^1.4.3" - prosemirror-tables "^1.4.0" - prosemirror-trailing-node "^3.0.0" - prosemirror-transform "^1.10.0" - prosemirror-view "^1.33.10" - -"@tiptap/react@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.8.0.tgz#cd91281eab13da0371358fc69d765d89c5f258e0" - integrity sha512-o/aSCjO5Nu4MsNpTF+N1SzYzVQvvBiclmTOZX2E6usZ8jre5zmKfXHDSZnjGSRTK6z6kw5KW8wpjRQha03f9mg== - dependencies: - "@tiptap/extension-bubble-menu" "^2.8.0" - "@tiptap/extension-floating-menu" "^2.8.0" - "@types/use-sync-external-store" "^0.0.6" - fast-deep-equal "^3" - use-sync-external-store "^1.2.2" - -"@tiptap/starter-kit@^2.0.3": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.8.0.tgz#ab49f4039c564bc9803d8b4c8ac7dbcbf8a35475" - integrity sha512-r7UwaTrECkQoheWVZKFDqtL5tBx07x7IFT+prfgnsVlYFutGWskVVqzCDvD3BDmrg5PzeCWYZrQGlPaLib7tjg== - dependencies: - "@tiptap/core" "^2.8.0" - "@tiptap/extension-blockquote" "^2.8.0" - "@tiptap/extension-bold" "^2.8.0" - "@tiptap/extension-bullet-list" "^2.8.0" - "@tiptap/extension-code" "^2.8.0" - "@tiptap/extension-code-block" "^2.8.0" - "@tiptap/extension-document" "^2.8.0" - "@tiptap/extension-dropcursor" "^2.8.0" - "@tiptap/extension-gapcursor" "^2.8.0" - "@tiptap/extension-hard-break" "^2.8.0" - "@tiptap/extension-heading" "^2.8.0" - "@tiptap/extension-history" "^2.8.0" - "@tiptap/extension-horizontal-rule" "^2.8.0" - "@tiptap/extension-italic" "^2.8.0" - "@tiptap/extension-list-item" "^2.8.0" - "@tiptap/extension-ordered-list" "^2.8.0" - "@tiptap/extension-paragraph" "^2.8.0" - "@tiptap/extension-strike" "^2.8.0" - "@tiptap/extension-text" "^2.8.0" - "@tiptap/pm" "^2.8.0" - -"@turf/area@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@turf/area/-/area-7.1.0.tgz#c8b506cfa9f8b06570c090c9cf915fa080ae7b66" - integrity sha512-w91FEe02/mQfMPRX2pXua48scFuKJ2dSVMF2XmJ6+BJfFiCPxp95I3+Org8+ZsYv93CDNKbf0oLNEPnuQdgs2g== - dependencies: - "@turf/helpers" "^7.1.0" - "@turf/meta" "^7.1.0" - "@types/geojson" "^7946.0.10" - tslib "^2.6.2" - -"@turf/bbox@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@turf/bbox/-/bbox-7.1.0.tgz#45a9287c084f7b79577ee88b7b539d83562b923b" - integrity sha512-PdWPz9tW86PD78vSZj2fiRaB8JhUHy6piSa/QXb83lucxPK+HTAdzlDQMTKj5okRCU8Ox/25IR2ep9T8NdopRA== - dependencies: - "@turf/helpers" "^7.1.0" - "@turf/meta" "^7.1.0" - "@types/geojson" "^7946.0.10" - tslib "^2.6.2" - -"@turf/centroid@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@turf/centroid/-/centroid-7.1.0.tgz#7cd55ec0bab79b5fc0ef03a4870a8cbc75e6207f" - integrity sha512-1Y1b2l+ZB1CZ+ITjUCsGqC4/tSjwm/R4OUfDztVqyyCq/VvezkLmTNqvXTGXgfP0GXkpv68iCfxF5M7QdM5pJQ== - dependencies: - "@turf/helpers" "^7.1.0" - "@turf/meta" "^7.1.0" - "@types/geojson" "^7946.0.10" - tslib "^2.6.2" - -"@turf/helpers@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-7.1.0.tgz#eb734e291c9c205822acdd289fe20e91c3cb1641" - integrity sha512-dTeILEUVeNbaEeoZUOhxH5auv7WWlOShbx7QSd4s0T4Z0/iz90z9yaVCtZOLbU89umKotwKaJQltBNO9CzVgaQ== - dependencies: - "@types/geojson" "^7946.0.10" - tslib "^2.6.2" - -"@turf/meta@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-7.1.0.tgz#b2af85afddd0ef08aeae8694a12370a4f06b6d13" - integrity sha512-ZgGpWWiKz797Fe8lfRj7HKCkGR+nSJ/5aKXMyofCvLSc2PuYJs/qyyifDPWjASQQCzseJ7AlF2Pc/XQ/3XkkuA== - dependencies: - "@turf/helpers" "^7.1.0" - "@types/geojson" "^7946.0.10" - -"@types/babel__core@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.8" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" - integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*": - version "7.20.6" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" - integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== - dependencies: - "@babel/types" "^7.20.7" - -"@types/d3-array@^3.0.3": - version "3.2.1" - resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" - integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== - -"@types/d3-color@*": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" - integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== - -"@types/d3-ease@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" - integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== - -"@types/d3-interpolate@^3.0.1": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" - integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== - dependencies: - "@types/d3-color" "*" - -"@types/d3-path@*": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" - integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== - -"@types/d3-scale@^4.0.2": - version "4.0.8" - resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" - integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== - dependencies: - "@types/d3-time" "*" - -"@types/d3-shape@^3.1.0": - version "3.1.6" - resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" - integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== - dependencies: - "@types/d3-path" "*" - -"@types/d3-time@*", "@types/d3-time@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" - integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== - -"@types/d3-timer@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" - integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== - -"@types/geojson-vt@3.2.5": - version "3.2.5" - resolved "https://registry.yarnpkg.com/@types/geojson-vt/-/geojson-vt-3.2.5.tgz#b6c356874991d9ab4207533476dfbcdb21e38408" - integrity sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g== - dependencies: - "@types/geojson" "*" - -"@types/geojson@*", "@types/geojson@^7946.0.10", "@types/geojson@^7946.0.14": - version "7946.0.14" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613" - integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg== - -"@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/linkify-it@^5": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" - integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== - -"@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz#0ef017b75eedce02ff6243b4189210e2e6d5e56d" - integrity sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA== - -"@types/mapbox__vector-tile@^1.3.4": - version "1.3.4" - resolved "https://registry.yarnpkg.com/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz#ad757441ef1d34628d9e098afd9c91423c1f8734" - integrity sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg== - dependencies: - "@types/geojson" "*" - "@types/mapbox__point-geometry" "*" - "@types/pbf" "*" - -"@types/markdown-it@^14.0.0": - version "14.1.2" - resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" - integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== - dependencies: - "@types/linkify-it" "^5" - "@types/mdurl" "^2" - -"@types/mdurl@^2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" - integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== - -"@types/node@^18.16.1": - version "18.19.55" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.55.tgz#29c3f8e1485a92ec96636957ddec55aabc6e856e" - integrity sha512-zzw5Vw52205Zr/nmErSEkN5FLqXPuKX/k5d1D7RKHATGqU7y6YfX9QxZraUzUrFGqH6XzOzG196BC35ltJC4Cw== - dependencies: - undici-types "~5.26.4" - -"@types/parse-json@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" - integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== - -"@types/pbf@*", "@types/pbf@^3.0.5": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404" - integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA== - -"@types/prop-types@*", "@types/prop-types@^15.7.12": - version "15.7.13" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451" - integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA== - -"@types/react-dom@^18.0.7": - version "18.3.1" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.1.tgz#1e4654c08a9cdcfb6594c780ac59b55aad42fe07" - integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ== - dependencies: - "@types/react" "*" - -"@types/react-transition-group@^4.4.10": - version "4.4.11" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5" - integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA== - dependencies: - "@types/react" "*" - -"@types/react@*", "@types/react@^18.0.22": - version "18.3.11" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.11.tgz#9d530601ff843ee0d7030d4227ea4360236bd537" - integrity sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ== - dependencies: - "@types/prop-types" "*" - csstype "^3.0.2" - -"@types/semver@^7.3.12": - version "7.5.8" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" - integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== - -"@types/supercluster@^7.1.3": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@types/supercluster/-/supercluster-7.1.3.tgz#1a1bc2401b09174d9c9e44124931ec7874a72b27" - integrity sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA== - dependencies: - "@types/geojson" "*" - -"@types/use-sync-external-store@^0.0.6": - version "0.0.6" - resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" - integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== - -"@typescript-eslint/eslint-plugin@^5.60.1": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" - integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== - dependencies: - "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/type-utils" "5.62.0" - "@typescript-eslint/utils" "5.62.0" - debug "^4.3.4" - graphemer "^1.4.0" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^5.60.1": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" - integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== - dependencies: - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - -"@typescript-eslint/type-utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" - integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== - dependencies: - "@typescript-eslint/typescript-estree" "5.62.0" - "@typescript-eslint/utils" "5.62.0" - debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== - -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" - -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - -"@vitejs/plugin-react@^4.0.1": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz#1e13f666fe3135b477220d3c13b783704636b6e4" - integrity sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg== - dependencies: - "@babel/core" "^7.25.2" - "@babel/plugin-transform-react-jsx-self" "^7.24.7" - "@babel/plugin-transform-react-jsx-source" "^7.24.7" - "@types/babel__core" "^7.20.5" - react-refresh "^0.14.2" - -abs-svg-path@^0.1.1, abs-svg-path@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf" - integrity sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA== - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.9.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.13.0.tgz#2a30d670818ad16ddd6a35d3842dacec9e5d7ca3" - integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w== - -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -almost-equal@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/almost-equal/-/almost-equal-1.1.0.tgz#f851c631138757994276aa2efbe8dfa3066cccdd" - integrity sha512-0V/PkoculFl5+0Lp47JoxUcO0xSxhIBvm+BxHdD/OgXNmdRpRHCFnKVuUoWyS9EzQP+otSGv0m9Lb4yVkQBn2A== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-bounds@^1.0.0, array-bounds@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-bounds/-/array-bounds-1.0.1.tgz#da11356b4e18e075a4f0c86e1f179a67b7d7ea31" - integrity sha512-8wdW3ZGk6UjMPJx/glyEt0sLzzwAE1bhToPsO1W2pbpR2gULyxe3BjSiuJFheP50T/GgODVPz2fuMUmIywt8cQ== - -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - -array-find-index@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== - -array-includes@^3.1.6, array-includes@^3.1.8: - version "3.1.8" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" - integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - is-string "^1.0.7" - -array-normalize@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/array-normalize/-/array-normalize-1.1.4.tgz#d75cec57383358af38efdf6a78071aa36ae4174c" - integrity sha512-fCp0wKFLjvSPmCn4F5Tiw4M3lpMZoHlCjfcs7nNzuj3vqQQ1/a8cgB9DXcpDSn18c+coLnaW7rqfcYCvKbyJXg== - dependencies: - array-bounds "^1.0.0" - -array-range@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-range/-/array-range-1.0.1.tgz#f56e46591843611c6a56f77ef02eda7c50089bfc" - integrity sha512-shdaI1zT3CVNL2hnx9c0JMc0ZogGaxDs5e85akgHWKYa0yVbIyp06Ind3dVkTj/uuFrzaHBOyqFzo+VV6aXgtA== - -array-rearrange@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/array-rearrange/-/array-rearrange-2.2.2.tgz#fa1a2acf8d02e88dd0c9602aa0e06a79158b2283" - integrity sha512-UfobP5N12Qm4Qu4fwLDIi2v6+wZsSf6snYSxAMeKhrh37YGnNWZPRmVEKc/2wfms53TLQnzfpG8wCx2Y/6NG1w== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array.prototype.findlast@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" - integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-shim-unscopables "^1.0.2" - -array.prototype.flat@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.tosorted@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" - integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.3" - es-errors "^1.3.0" - es-shim-unscopables "^1.0.2" - -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -attr-accept@^2.2.2: - version "2.2.4" - resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.4.tgz#e28749d5975732586aea03c8912e2d0f1d1d77e7" - integrity sha512-2pA6xFIbdTUDCAwjN8nQwI+842VwzbDUXO2IYlpPXQIORgKnavorcr4Ce3rwh+zsNg9zK7QPsdvDj3Lum4WX4w== - -autosuggest-highlight@^3.1.1: - version "3.3.4" - resolved "https://registry.yarnpkg.com/autosuggest-highlight/-/autosuggest-highlight-3.3.4.tgz#d71b575ba8eab40b5adba73df9244e9ba88cc387" - integrity sha512-j6RETBD2xYnrVcoV1S5R4t3WxOlWZKyDQjkwnggDPSjF5L4jV98ZltBpvPvbkM1HtoSe5o+bNrTHyjPbieGeYA== - dependencies: - remove-accents "^0.4.2" - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -axios@^1.6.0: - version "1.7.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" - integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -babel-plugin-macros@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" - integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== - dependencies: - "@babel/runtime" "^7.12.5" - cosmiconfig "^7.0.0" - resolve "^1.19.0" - -babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-arraybuffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" - integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== - -base64-js@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -big-integer@^1.6.16: - version "1.6.52" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" - integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== - -binary-search-bounds@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz#125e5bd399882f71e6660d4bf1186384e989fba7" - integrity sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA== - -bit-twiddle@^1.0.0, bit-twiddle@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bit-twiddle/-/bit-twiddle-1.0.2.tgz#0c6c1fabe2b23d17173d9a61b7b7093eb9e1769e" - integrity sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA== - -bitmap-sdf@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/bitmap-sdf/-/bitmap-sdf-1.0.4.tgz#e87b8b1d84ee846567cfbb29d60eedd34bca5b6f" - integrity sha512-1G3U4n5JE6RAiALMxu0p1XmeZkTeCwGKykzsLTCqVzfSDaN6S7fKnkIkfejogz+iwqBWc0UYAIKnKHNN7pSfDg== - -bl@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" - integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== - dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -broadcast-channel@^3.4.1: - version "3.7.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" - integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== - dependencies: - "@babel/runtime" "^7.7.2" - detect-node "^2.1.0" - js-sha3 "0.8.0" - microseconds "0.2.0" - nano-time "1.0.0" - oblivious-set "1.0.0" - rimraf "3.0.2" - unload "2.2.0" - -browserslist@^4.24.0: - version "4.24.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" - integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== - dependencies: - caniuse-lite "^1.0.30001663" - electron-to-chromium "^1.5.28" - node-releases "^2.0.18" - update-browserslist-db "^1.1.0" - -buffer-from@^1.0.0, buffer-from@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -caniuse-lite@^1.0.30001663: - version "1.0.30001669" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz#fda8f1d29a8bfdc42de0c170d7f34a9cf19ed7a3" - integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w== - -canvas-fit@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/canvas-fit/-/canvas-fit-1.5.0.tgz#ae13be66ade42f5be0e487e345fce30a5e5b5e5f" - integrity sha512-onIcjRpz69/Hx5bB5HGbYKUF2uC6QT6Gp+pfpGm3A7mPfcluSLV5v4Zu+oflDUwLdUw0rLIBhUbi0v8hM4FJQQ== - dependencies: - element-size "^1.1.1" - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -clamp@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634" - integrity sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA== - -clsx@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== - -clsx@^2.0.0, clsx@^2.1.0, clsx@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" - integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== - -color-alpha@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/color-alpha/-/color-alpha-1.0.4.tgz#c141dc926e95fc3db647d0e14e5bc3651c29e040" - integrity sha512-lr8/t5NPozTSqli+duAN+x+no/2WaKTeWvxhHGN+aXT6AJ8vPlzLa7UriyjWak0pSC2jHol9JgjBYnnHsGha9A== - dependencies: - color-parse "^1.3.8" - -color-alpha@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-alpha/-/color-alpha-1.1.3.tgz#71250189e9f02bba8261a94d5e7d5f5606d1749a" - integrity sha512-krPYBO1RSO5LH4AGb/b6z70O1Ip2o0F0+0cVFN5FN99jfQtZFT08rQyg+9oOBNJYAn3SRwJIFC8jUEOKz7PisA== - dependencies: - color-parse "^1.4.1" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-id@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/color-id/-/color-id-1.1.0.tgz#5e9159b99a73ac98f74820cb98a15fde3d7e034c" - integrity sha512-2iRtAn6dC/6/G7bBIo0uupVrIne1NsQJvJxZOBCzQOfk7jRq97feaDZ3RdzuHakRXXnHGNwglto3pqtRx1sX0g== - dependencies: - clamp "^1.0.1" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-2.0.0.tgz#03ff6b1b5aec9bb3cf1ed82400c2790dfcd01d2d" - integrity sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow== - -color-normalize@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/color-normalize/-/color-normalize-1.5.0.tgz#ee610af9acb15daf73e77a945a847b18e40772da" - integrity sha512-rUT/HDXMr6RFffrR53oX3HGWkDOP9goSAQGBkUaAYKjOE2JxozccdGyufageWDlInRAjm/jYPrf/Y38oa+7obw== - dependencies: - clamp "^1.0.1" - color-rgba "^2.1.1" - dtype "^2.0.0" - -color-normalize@^1.5.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/color-normalize/-/color-normalize-1.5.2.tgz#d6c8beb02966849548f91a6ac0274c6f19924509" - integrity sha512-yYMIoyFJmUoKbCK6sBShljBWfkt8DXVfaZJn9/zvRJkF9eQJDbZhcYC6LdOVy40p4tfVwYYb9cXl8oqpu7pzBw== - dependencies: - color-rgba "^2.2.0" - dtype "^2.0.0" - -color-parse@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/color-parse/-/color-parse-2.0.0.tgz#6bcf1f0f1fafffe68cacc2dde7a19b3a8c3d7bcd" - integrity sha512-g2Z+QnWsdHLppAbrpcFWo629kLOnOPtpxYV69GCqm92gqSgyXbzlfyN3MXs0412fPBkFmiuS+rXposgBgBa6Kg== - dependencies: - color-name "^1.0.0" - -color-parse@^1.3.8, color-parse@^1.4.1, color-parse@^1.4.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/color-parse/-/color-parse-1.4.3.tgz#6dadfb49128c554c60c49d63f3d025f2c5a7ff22" - integrity sha512-BADfVl/FHkQkyo8sRBwMYBqemqsgnu7JZAwUgvBvuwwuNUZAhSvLTbsEErS5bQXzOjDR0dWzJ4vXN2Q+QoPx0A== - dependencies: - color-name "^1.0.0" - -color-parse@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/color-parse/-/color-parse-2.0.2.tgz#37b46930424924060988edf25b24e6ffb4a1dc3f" - integrity sha512-eCtOz5w5ttWIUcaKLiktF+DxZO1R9KLNY/xhbV6CkhM7sR3GhVghmt6X6yOnzeaM24po+Z9/S1apbXMwA3Iepw== - dependencies: - color-name "^2.0.0" - -color-rgba@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/color-rgba/-/color-rgba-2.1.1.tgz#4633b83817c7406c90b3d7bf4d1acfa48dde5c83" - integrity sha512-VaX97wsqrMwLSOR6H7rU1Doa2zyVdmShabKrPEIFywLlHoibgD3QW9Dw6fSqM4+H/LfjprDNAUUW31qEQcGzNw== - dependencies: - clamp "^1.0.1" - color-parse "^1.3.8" - color-space "^1.14.6" - -color-rgba@^2.1.1, color-rgba@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/color-rgba/-/color-rgba-2.4.0.tgz#ae85819c530262c29fc2da129fc7c8f9efc57015" - integrity sha512-Nti4qbzr/z2LbUWySr7H9dk3Rl7gZt7ihHAxlgT4Ho90EXWkjtkL1avTleu9yeGuqrt/chxTB6GKK8nZZ6V0+Q== - dependencies: - color-parse "^1.4.2" - color-space "^2.0.0" - -color-rgba@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/color-rgba/-/color-rgba-3.0.0.tgz#77090bdcdb2951c1735e20099ddd50401675375b" - integrity sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg== - dependencies: - color-parse "^2.0.0" - color-space "^2.0.0" - -color-space@^1.14.6: - version "1.16.0" - resolved "https://registry.yarnpkg.com/color-space/-/color-space-1.16.0.tgz#611781bca41cd8582a1466fd9e28a7d3d89772a2" - integrity sha512-A6WMiFzunQ8KEPFmj02OnnoUnqhmSaHaZ/0LVFcPTdlvm8+3aMJ5x1HRHy3bDHPkovkf4sS0f4wsVvwk71fKkg== - dependencies: - hsluv "^0.0.3" - mumath "^3.3.4" - -color-space@^2.0.0, color-space@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-space/-/color-space-2.0.1.tgz#da39871175baf4a5785ba519397df04b8d67e0fa" - integrity sha512-nKqUYlo0vZATVOFHY810BSYjmCARrG7e5R3UE3CQlyjJTvv5kSSmPG1kzm/oDyyqjehM+lW1RnEt9It9GNa5JA== - -combine-errors@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/combine-errors/-/combine-errors-3.0.3.tgz#f4df6740083e5703a3181110c2b10551f003da86" - integrity sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q== - dependencies: - custom-error-instance "2.1.1" - lodash.uniqby "4.5.0" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@2, commander@^2.15.1: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concat-stream@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -convert-source-map@^1.5.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -core-js@^2.4.0: - version "2.6.12" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== - 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" - -country-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/country-regex/-/country-regex-1.1.0.tgz#51c333dcdf12927b7e5eeb9c10ac8112a6120896" - integrity sha512-iSPlClZP8vX7MC3/u6s3lrDuoQyhQukh5LyABJ3hvfzbQ3Yyayd4fp04zjLnfi267B/B2FkumcWWgrbban7sSA== - -crelt@^1.0.0: - version "1.0.6" - resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" - integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== - -cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -css-font-size-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz#854875ace9aca6a8d2ee0d345a44aae9bb6db6cb" - integrity sha512-Q+svMDbMlelgCfH/RVDKtTDaf5021O486ZThQPIpahnIjUkMUslC+WuOQSWTgGSrNCH08Y7tYNEmmy0hkfMI8Q== - -css-font-stretch-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz#50cee9b9ba031fb5c952d4723139f1e107b54b10" - integrity sha512-KmugPO2BNqoyp9zmBIUGwt58UQSfyk1X5DbOlkb2pckDXFSAfjsD5wenb88fNrD6fvS+vu90a/tsPpb9vb0SLg== - -css-font-style-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz#5c3532813f63b4a1de954d13cea86ab4333409e4" - integrity sha512-0Fn0aTpcDktnR1RzaBYorIxQily85M2KXRpzmxQPgh8pxUN9Fcn00I8u9I3grNr1QXVgCl9T5Imx0ZwKU973Vg== - -css-font-weight-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz#9bc04671ac85bc724b574ef5d3ac96b0d604fd97" - integrity sha512-5So8/NH+oDD+EzsnF4iaG4ZFHQ3vaViePkL1ZbZ5iC/KrsCY+WHq/lvOgrtmuOQ9pBBZ1ADGpaf+A4lj1Z9eYA== - -css-font@^1.0.0, css-font@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-font/-/css-font-1.2.0.tgz#e73cbdc11fd87c8e6c928ad7098a9771c8c2b6e3" - integrity sha512-V4U4Wps4dPDACJ4WpgofJ2RT5Yqwe1lEH6wlOOaIxMi0gTjdIijsc5FmxQlZ7ZZyKQkkutqqvULOp07l9c7ssA== - dependencies: - css-font-size-keywords "^1.0.0" - css-font-stretch-keywords "^1.0.1" - css-font-style-keywords "^1.0.1" - css-font-weight-keywords "^1.0.0" - css-global-keywords "^1.0.1" - css-system-font-keywords "^1.0.0" - pick-by-alias "^1.2.0" - string-split-by "^1.0.0" - unquote "^1.1.0" - -css-global-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-global-keywords/-/css-global-keywords-1.0.1.tgz#72a9aea72796d019b1d2a3252de4e5aaa37e4a69" - integrity sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ== - -css-loader@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.2.tgz#64671541c6efe06b0e22e750503106bdd86880f8" - integrity sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.33" - postcss-modules-extract-imports "^3.1.0" - postcss-modules-local-by-default "^4.0.5" - postcss-modules-scope "^3.2.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.5.4" - -css-mediaquery@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0" - integrity sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q== - -css-system-font-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed" - integrity sha512-1umTtVd/fXS25ftfjB71eASCrYhilmEsvDEI6wG/QplnmlfmVM5HkZ/ZX46DT5K3eblFPgLUHt5BRCb0YXkSFA== - -csscolorparser@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" - integrity sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -csstype@^3.0.2, csstype@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - -custom-error-instance@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/custom-error-instance/-/custom-error-instance-2.1.1.tgz#3cf6391487a6629a6247eb0ca0ce00081b7e361a" - integrity sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg== - -d3-array@1, d3-array@^1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" - integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== - -"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: - version "3.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" - integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== - dependencies: - internmap "1 - 2" - -d3-collection@1, d3-collection@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" - integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== - -"d3-color@1 - 3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" - integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== - -d3-dispatch@1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58" - integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA== - -d3-ease@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" - integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== - -d3-force@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b" - integrity sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg== - dependencies: - d3-collection "1" - d3-dispatch "1" - d3-quadtree "1" - d3-timer "1" - -"d3-format@1 - 3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" - integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== - -d3-format@^1.4.5: - version "1.4.5" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" - integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== - -d3-geo-projection@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/d3-geo-projection/-/d3-geo-projection-2.9.0.tgz#826db62f748e8ecd67cd00aced4c26a236ec030c" - integrity sha512-ZULvK/zBn87of5rWAfFMc9mJOipeSo57O+BBitsKIXmU4rTVAnX1kSsJkE0R+TxY8pGNoM1nbyRRE7GYHhdOEQ== - dependencies: - commander "2" - d3-array "1" - d3-geo "^1.12.0" - resolve "^1.1.10" - -d3-geo@^1.12.0, d3-geo@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f" - integrity sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg== - dependencies: - d3-array "1" - -d3-hierarchy@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83" - integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ== - -"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" - integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== - dependencies: - d3-color "1 - 3" - -d3-path@1: - version "1.0.9" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" - integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== - -d3-path@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" - integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== - -d3-quadtree@1: - version "1.0.7" - resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135" - integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA== - -d3-scale@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" - integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== - dependencies: - d3-array "2.10.0 - 3" - d3-format "1 - 3" - d3-interpolate "1.2.0 - 3" - d3-time "2.1.1 - 3" - d3-time-format "2 - 4" - -d3-shape@^1.2.0: - version "1.3.7" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" - integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== - dependencies: - d3-path "1" - -d3-shape@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" - integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== - dependencies: - d3-path "^3.1.0" - -"d3-time-format@2 - 4": - version "4.1.0" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" - integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== - dependencies: - d3-time "1 - 3" - -d3-time-format@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.3.0.tgz#107bdc028667788a8924ba040faf1fbccd5a7850" - integrity sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ== - dependencies: - d3-time "1" - -d3-time@1, d3-time@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" - integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== - -"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" - integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== - dependencies: - d3-array "2 - 3" - -d3-timer@1: - version "1.0.10" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" - integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== - -d3-timer@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" - integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== - -d@1, d@^1.0.1, d@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.2.tgz#2aefd554b81981e7dccf72d6842ae725cb17e5de" - integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== - dependencies: - es5-ext "^0.10.64" - type "^2.7.2" - -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -date-fns@^2.19.0: - version "2.30.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" - integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== - dependencies: - "@babel/runtime" "^7.21.0" - -date-fns@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf" - integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== - -debug@2: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.6: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== - dependencies: - ms "^2.1.3" - -decimal.js-light@^2.4.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" - integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== - -decode-uri-component@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -defined@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" - integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -detect-kerning@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/detect-kerning/-/detect-kerning-2.1.2.tgz#4ecd548e4a5a3fc880fe2a50609312d000fa9fc2" - integrity sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw== - -detect-node@^2.0.4, detect-node@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-helpers@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - -dompurify@^2.4.3: - version "2.5.7" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.5.7.tgz#6e0d36b9177db5a99f18ade1f28579db5ab839d7" - integrity sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q== - -draw-svg-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/draw-svg-path/-/draw-svg-path-1.0.0.tgz#6f116d962dd314b99ea534d6f58dd66cdbd69379" - integrity sha512-P8j3IHxcgRMcY6sDzr0QvJDLzBnJJqpTG33UZ2Pvp8rw0apCHhJCWqYprqrXjrgHnJ6tuhP1iTJSAodPDHxwkg== - dependencies: - abs-svg-path "~0.1.1" - normalize-svg-path "~0.1.0" - -dtype@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dtype/-/dtype-2.0.0.tgz#cd052323ce061444ecd2e8f5748f69a29be28434" - integrity sha512-s2YVcLKdFGS0hpFqJaTwscsyt0E8nNFdmo73Ocd81xNPj4URI4rj6D60A+vFMIw7BXWlb4yRkEwfBqcZzPGiZg== - -dup@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dup/-/dup-1.0.0.tgz#51fc5ac685f8196469df0b905e934b20af5b4029" - integrity sha512-Bz5jxMMC0wgp23Zm15ip1x8IhYRqJvF3nFC0UInJUDkN1z4uNPk9jTnfCUJXbOGiQ1JbXLQsiV41Fb+HXcj5BA== - -duplexify@^3.4.5: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -earcut@^2.1.5, earcut@^2.2.2, earcut@^2.2.3: - version "2.2.4" - resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.4.tgz#6d02fd4d68160c114825d06890a92ecaae60343a" - integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ== - -earcut@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/earcut/-/earcut-3.0.0.tgz#a8d5bf891224eaea8287201b5e787c6c0318af89" - integrity sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg== - -electron-to-chromium@^1.5.28: - version "1.5.39" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.39.tgz#5cbe5200b43dff7b7c2bcb6bdacf65d514c76bb2" - integrity sha512-4xkpSR6CjuiaNyvwiWDI85N9AxsvbPawB8xc7yzLPonYTuP19BVgYweKyUMFtHEZgIcHWMt1ks5Cqx2m+6/Grg== - -element-size@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/element-size/-/element-size-1.1.1.tgz#64e5f159d97121631845bcbaecaf279c39b5e34e" - integrity sha512-eaN+GMOq/Q+BIWy0ybsgpcYImjGIdNLyjLFJU4XsLHXYQao5jCNb36GyN6C2qwmDDYSfIBmKpPpr4VnBdLCsPQ== - -elementary-circuits-directed-graph@^1.0.4: - version "1.3.1" - resolved "https://registry.yarnpkg.com/elementary-circuits-directed-graph/-/elementary-circuits-directed-graph-1.3.1.tgz#31c5a1c69517de833127247e5460472168e9e1c1" - integrity sha512-ZEiB5qkn2adYmpXGnJKkxT8uJHlW/mxmBpmeqawEHzPxh9HkLD4/1mFYX5l0On+f6rcPIt8/EWlRU2Vo3fX6dQ== - dependencies: - strongly-connected-components "^1.0.1" - -end-of-stream@^1.0.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -entities@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-errors@^1.2.1, es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-iterator-helpers@^1.0.19: - version "1.1.0" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz#f6d745d342aea214fe09497e7152170dc333a7a6" - integrity sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.3" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - globalthis "^1.0.4" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - iterator.prototype "^1.1.3" - safe-array-concat "^1.1.2" - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" - integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== - dependencies: - hasown "^2.0.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14: - version "0.10.64" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" - integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== - dependencies: - es6-iterator "^2.0.3" - es6-symbol "^3.1.3" - esniff "^2.0.1" - next-tick "^1.1.0" - -es6-iterator@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@^3.1.3: - version "3.1.4" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c" - integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== - dependencies: - d "^1.0.2" - ext "^1.7.0" - -es6-weak-map@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" - integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== - dependencies: - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - es6-symbol "^3.1.1" - -esbuild@^0.18.10: - version "0.18.20" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" - integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== - optionalDependencies: - "@esbuild/android-arm" "0.18.20" - "@esbuild/android-arm64" "0.18.20" - "@esbuild/android-x64" "0.18.20" - "@esbuild/darwin-arm64" "0.18.20" - "@esbuild/darwin-x64" "0.18.20" - "@esbuild/freebsd-arm64" "0.18.20" - "@esbuild/freebsd-x64" "0.18.20" - "@esbuild/linux-arm" "0.18.20" - "@esbuild/linux-arm64" "0.18.20" - "@esbuild/linux-ia32" "0.18.20" - "@esbuild/linux-loong64" "0.18.20" - "@esbuild/linux-mips64el" "0.18.20" - "@esbuild/linux-ppc64" "0.18.20" - "@esbuild/linux-riscv64" "0.18.20" - "@esbuild/linux-s390x" "0.18.20" - "@esbuild/linux-x64" "0.18.20" - "@esbuild/netbsd-x64" "0.18.20" - "@esbuild/openbsd-x64" "0.18.20" - "@esbuild/sunos-x64" "0.18.20" - "@esbuild/win32-arm64" "0.18.20" - "@esbuild/win32-ia32" "0.18.20" - "@esbuild/win32-x64" "0.18.20" - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escodegen@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" - integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionalDependencies: - source-map "~0.6.1" - -eslint-config-prettier@^8.8.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" - integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== - -eslint-plugin-react-hooks@^4.6.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" - integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== - -eslint-plugin-react@^7.32.2: - version "7.37.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz#56493d7d69174d0d828bc83afeffe96903fdadbd" - integrity sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg== - dependencies: - array-includes "^3.1.8" - array.prototype.findlast "^1.2.5" - array.prototype.flatmap "^1.3.2" - array.prototype.tosorted "^1.1.4" - doctrine "^2.1.0" - es-iterator-helpers "^1.0.19" - estraverse "^5.3.0" - hasown "^2.0.2" - jsx-ast-utils "^2.4.1 || ^3.0.0" - minimatch "^3.1.2" - object.entries "^1.1.8" - object.fromentries "^2.0.8" - object.values "^1.2.0" - prop-types "^15.8.1" - resolve "^2.0.0-next.5" - semver "^6.3.1" - string.prototype.matchall "^4.0.11" - string.prototype.repeat "^1.0.0" - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@^8.43.0: - version "8.57.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" - integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.1" - "@humanwhocodes/config-array" "^0.13.0" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - -esniff@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" - integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== - dependencies: - d "^1.0.1" - es5-ext "^0.10.62" - event-emitter "^0.3.5" - type "^2.7.2" - -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - -esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== - dependencies: - d "1" - es5-ext "~0.10.14" - -eventemitter3@^4.0.1, eventemitter3@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -ext@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" - integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== - dependencies: - type "^2.7.2" - -fakerest@^3.0.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/fakerest/-/fakerest-3.5.0.tgz#db0cc2285b01f36a70cdfd764c3c1f658c492bb9" - integrity sha512-ltEVKsobk1ZkiTmQ5pbN3frhTMNneZN585g3XnR7jJaVItfx8sBGOvlYjpDIpAb+KhCVP35M629wxFH0XDOopg== - dependencies: - babel-runtime "^6.26.0" - lodash "^4.17.21" - -falafel@^2.1.0: - version "2.2.5" - resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.2.5.tgz#3ccb4970a09b094e9e54fead2deee64b4a589d56" - integrity sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ== - dependencies: - acorn "^7.1.1" - isarray "^2.0.1" - -fast-deep-equal@^3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-equals@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d" - integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== - -fast-glob@^3.2.9: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-isnumeric@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/fast-isnumeric/-/fast-isnumeric-1.1.4.tgz#e165786ff471c439e9ace2b8c8e66cceb47e2ea4" - integrity sha512-1mM8qOr2LYz8zGaUdmiqRDiuue00Dxjgcb1NQR7TnhLVh6sQyngP9xvLo7Sl7LZpP/sk5eb+bcyWXw530NTBZw== - dependencies: - is-string-blank "^1.0.1" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -file-selector@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.5.0.tgz#21c7126dc9728b31a2742d91cab20d55e67e4fb4" - integrity sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA== - dependencies: - tslib "^2.0.3" - -file-selector@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc" - integrity sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw== - dependencies: - tslib "^2.4.0" - -filepond-plugin-file-validate-type@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.9.tgz#84787c027354a459d91382e77adc9463ceb0182d" - integrity sha512-Tzv07aNdZvjUXDRA3XL16QMEvh6llDrXlcZ6W0eTHQ+taHaVg/JKJTFs/AViO+6ZcpPCcQStbhYEL2HoS+vldw== - -filepond@^4.31.2: - version "4.31.4" - resolved "https://registry.yarnpkg.com/filepond/-/filepond-4.31.4.tgz#1ed6d8385084d71253f88fd471c875019c4c60a1" - integrity sha512-3kR87Rsw2OP8CktlIehdbFjWB33GEH5iLs8izl2OcZGOszUFWYTjRY6pffLpjz/gRE61u1F67rZ2++ujFiUxPQ== - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== - -find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== - dependencies: - flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" - -flatted@^3.2.9: - version "3.3.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" - integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== - -flatten-vertex-data@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz#889fd60bea506006ca33955ee1105175fb620219" - integrity sha512-BvCBFK2NZqerFTdMDgqfHBwxYWnxeCkwONsw6PvBMcUXqo8U/KDWwmXhqx1x2kLIg7DqIsJfOaJFOmlua3Lxuw== - dependencies: - dtype "^2.0.0" - -follow-redirects@^1.15.6: - version "1.15.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" - integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== - -font-atlas@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/font-atlas/-/font-atlas-2.1.0.tgz#aa2d6dcf656a6c871d66abbd3dfbea2f77178348" - integrity sha512-kP3AmvX+HJpW4w3d+PiPR2X6E1yvsBXt2yhuCw+yReO9F1WYhvZwx3c95DGZGwg9xYzDGrgJYa885xmVA+28Cg== - dependencies: - css-font "^1.0.0" - -font-measure@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/font-measure/-/font-measure-1.2.2.tgz#41dbdac5d230dbf4db08865f54da28a475e83026" - integrity sha512-mRLEpdrWzKe9hbfaF3Qpr06TAjquuBVP5cHy4b3hyeNdjc9i0PO6HniGsX5vjL5OWv7+Bd++NiooNpT/s8BvIA== - dependencies: - css-font "^1.2.0" - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -form-data@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" - integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -from2@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -geojson-vt@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7" - integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg== - -geojson-vt@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-4.0.2.tgz#1162f6c7d61a0ba305b1030621e6e111f847828a" - integrity sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A== - -geotiff@^2.0.7: - version "2.1.3" - resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-2.1.3.tgz#993f40f2aa6aa65fb1e0451d86dd22ca8e66910c" - integrity sha512-PT6uoF5a1+kbC3tHmZSUsLHBp2QJlHasxxxxPW47QIY1VBKpFB+FcDvX+MxER6UzgLQZ0xDzJ9s48B9JbOCTqA== - dependencies: - "@petamoriken/float16" "^3.4.7" - lerc "^3.0.0" - pako "^2.0.4" - parse-headers "^2.0.2" - quick-lru "^6.1.1" - web-worker "^1.2.0" - xml-utils "^1.0.2" - zstddec "^0.1.0" - -get-canvas-context@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-canvas-context/-/get-canvas-context-1.0.2.tgz#d6e7b50bc4e4c86357cd39f22647a84b73601e93" - integrity sha512-LnpfLf/TNzr9zVOGiIY6aKCz8EKuXmlYNV7CM2pUjBa/B+c2I15tS7KLySep75+FuerJdmArvJLcsAXWEy2H0A== - -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - -get-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - -gl-mat4@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gl-mat4/-/gl-mat4-1.2.0.tgz#49d8a7636b70aa00819216635f4a3fd3f4669b26" - integrity sha512-sT5C0pwB1/e9G9AvAoLsoaJtbMGjfd/jfxo8jMCKqYYEnjZuFvqV5rehqar0538EmssjdDeiEWnKyBSTw7quoA== - -gl-matrix@^3.2.1, gl-matrix@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.3.tgz#fc1191e8320009fd4d20e9339595c6041ddc22c9" - integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA== - -gl-text@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/gl-text/-/gl-text-1.4.0.tgz#223f89b2719f1dbea581368a66a0edf0def63174" - integrity sha512-o47+XBqLCj1efmuNyCHt7/UEJmB9l66ql7pnobD6p+sgmBUdzfMZXIF0zD2+KRfpd99DJN+QXdvTFAGCKCVSmQ== - dependencies: - bit-twiddle "^1.0.2" - color-normalize "^1.5.0" - css-font "^1.2.0" - detect-kerning "^2.1.2" - es6-weak-map "^2.0.3" - flatten-vertex-data "^1.0.2" - font-atlas "^2.1.0" - font-measure "^1.2.2" - gl-util "^3.1.2" - is-plain-obj "^1.1.0" - object-assign "^4.1.1" - parse-rect "^1.2.0" - parse-unit "^1.0.1" - pick-by-alias "^1.2.0" - regl "^2.0.0" - to-px "^1.0.1" - typedarray-pool "^1.1.0" - -gl-util@^3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/gl-util/-/gl-util-3.1.3.tgz#1e9a724f844b802597c6e30565d4c1e928546861" - integrity sha512-dvRTggw5MSkJnCbh74jZzSoTOGnVYK+Bt+Ckqm39CVcl6+zSsxqWk4lr5NKhkqXHL6qvZAU9h17ZF8mIskY9mA== - dependencies: - is-browser "^2.0.1" - is-firefox "^1.0.3" - is-plain-obj "^1.1.0" - number-is-integer "^1.0.1" - object-assign "^4.1.0" - pick-by-alias "^1.2.0" - weak-map "^1.0.5" - -glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-prefix@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-4.0.0.tgz#e9cc79aab9be1d03287e156a3f912dd0895463ed" - integrity sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA== - dependencies: - ini "^4.1.3" - kind-of "^6.0.3" - which "^4.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" - -globalthis@^1.0.3, globalthis@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" - integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== - dependencies: - define-properties "^1.2.1" - gopd "^1.0.1" - -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -glsl-inject-defines@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz#dd1aacc2c17fcb2bd3fc32411c6633d0d7b60fd4" - integrity sha512-W49jIhuDtF6w+7wCMcClk27a2hq8znvHtlGnrYkSWEr8tHe9eA2dcnohlcAmxLYBSpSSdzOkRdyPTrx9fw49+A== - dependencies: - glsl-token-inject-block "^1.0.0" - glsl-token-string "^1.0.1" - glsl-tokenizer "^2.0.2" - -glsl-resolve@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/glsl-resolve/-/glsl-resolve-0.0.1.tgz#894bef73910d792c81b5143180035d0a78af76d3" - integrity sha512-xxFNsfnhZTK9NBhzJjSBGX6IOqYpvBHxxmo+4vapiljyGNCY0Bekzn0firQkQrazK59c1hYxMDxYS8MDlhw4gA== - dependencies: - resolve "^0.6.1" - xtend "^2.1.2" - -glsl-token-assignments@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/glsl-token-assignments/-/glsl-token-assignments-2.0.2.tgz#a5d82ab78499c2e8a6b83cb69495e6e665ce019f" - integrity sha512-OwXrxixCyHzzA0U2g4btSNAyB2Dx8XrztY5aVUCjRSh4/D0WoJn8Qdps7Xub3sz6zE73W3szLrmWtQ7QMpeHEQ== - -glsl-token-defines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glsl-token-defines/-/glsl-token-defines-1.0.0.tgz#cb892aa959936231728470d4f74032489697fa9d" - integrity sha512-Vb5QMVeLjmOwvvOJuPNg3vnRlffscq2/qvIuTpMzuO/7s5kT+63iL6Dfo2FYLWbzuiycWpbC0/KV0biqFwHxaQ== - dependencies: - glsl-tokenizer "^2.0.0" - -glsl-token-depth@^1.1.0, glsl-token-depth@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/glsl-token-depth/-/glsl-token-depth-1.1.2.tgz#23c5e30ee2bd255884b4a28bc850b8f791e95d84" - integrity sha512-eQnIBLc7vFf8axF9aoi/xW37LSWd2hCQr/3sZui8aBJnksq9C7zMeUYHVJWMhFzXrBU7fgIqni4EhXVW4/krpg== - -glsl-token-descope@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/glsl-token-descope/-/glsl-token-descope-1.0.2.tgz#0fc90ab326186b82f597b2e77dc9e21efcd32076" - integrity sha512-kS2PTWkvi/YOeicVjXGgX5j7+8N7e56srNDEHDTVZ1dcESmbmpmgrnpjPcjxJjMxh56mSXYoFdZqb90gXkGjQw== - dependencies: - glsl-token-assignments "^2.0.0" - glsl-token-depth "^1.1.0" - glsl-token-properties "^1.0.0" - glsl-token-scope "^1.1.0" - -glsl-token-inject-block@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/glsl-token-inject-block/-/glsl-token-inject-block-1.1.0.tgz#e1015f5980c1091824adaa2625f1dfde8bd00034" - integrity sha512-q/m+ukdUBuHCOtLhSr0uFb/qYQr4/oKrPSdIK2C4TD+qLaJvqM9wfXIF/OOBjuSA3pUoYHurVRNao6LTVVUPWA== - -glsl-token-properties@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/glsl-token-properties/-/glsl-token-properties-1.0.1.tgz#483dc3d839f0d4b5c6171d1591f249be53c28a9e" - integrity sha512-dSeW1cOIzbuUoYH0y+nxzwK9S9O3wsjttkq5ij9ZGw0OS41BirKJzzH48VLm8qLg+au6b0sINxGC0IrGwtQUcA== - -glsl-token-scope@^1.1.0, glsl-token-scope@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/glsl-token-scope/-/glsl-token-scope-1.1.2.tgz#a1728e78df24444f9cb93fd18ef0f75503a643b1" - integrity sha512-YKyOMk1B/tz9BwYUdfDoHvMIYTGtVv2vbDSLh94PT4+f87z21FVdou1KNKgF+nECBTo0fJ20dpm0B1vZB1Q03A== - -glsl-token-string@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/glsl-token-string/-/glsl-token-string-1.0.1.tgz#59441d2f857de7c3449c945666021ece358e48ec" - integrity sha512-1mtQ47Uxd47wrovl+T6RshKGkRRCYWhnELmkEcUAPALWGTFe2XZpH3r45XAwL2B6v+l0KNsCnoaZCSnhzKEksg== - -glsl-token-whitespace-trim@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glsl-token-whitespace-trim/-/glsl-token-whitespace-trim-1.0.0.tgz#46d1dfe98c75bd7d504c05d7d11b1b3e9cc93b10" - integrity sha512-ZJtsPut/aDaUdLUNtmBYhaCmhIjpKNg7IgZSfX5wFReMc2vnj8zok+gB/3Quqs0TsBSX/fGnqUUYZDqyuc2xLQ== - -glsl-tokenizer@^2.0.0, glsl-tokenizer@^2.0.2: - version "2.1.5" - resolved "https://registry.yarnpkg.com/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz#1c2e78c16589933c274ba278d0a63b370c5fee1a" - integrity sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA== - dependencies: - through2 "^0.6.3" - -glslify-bundle@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glslify-bundle/-/glslify-bundle-5.1.1.tgz#30d2ddf2e6b935bf44d1299321e3b729782c409a" - integrity sha512-plaAOQPv62M1r3OsWf2UbjN0hUYAB7Aph5bfH58VxJZJhloRNbxOL9tl/7H71K7OLJoSJ2ZqWOKk3ttQ6wy24A== - dependencies: - glsl-inject-defines "^1.0.1" - glsl-token-defines "^1.0.0" - glsl-token-depth "^1.1.1" - glsl-token-descope "^1.0.2" - glsl-token-scope "^1.1.1" - glsl-token-string "^1.0.1" - glsl-token-whitespace-trim "^1.0.0" - glsl-tokenizer "^2.0.2" - murmurhash-js "^1.0.0" - shallow-copy "0.0.1" - -glslify-deps@^1.2.5: - version "1.3.2" - resolved "https://registry.yarnpkg.com/glslify-deps/-/glslify-deps-1.3.2.tgz#c09ee945352bfc07ac2d8a1cc9e3de776328c72b" - integrity sha512-7S7IkHWygJRjcawveXQjRXLO2FTjijPDYC7QfZyAQanY+yGLCFHYnPtsGT9bdyHiwPTw/5a1m1M9hamT2aBpag== - dependencies: - "@choojs/findup" "^0.2.0" - events "^3.2.0" - glsl-resolve "0.0.1" - glsl-tokenizer "^2.0.0" - graceful-fs "^4.1.2" - inherits "^2.0.1" - map-limit "0.0.1" - resolve "^1.0.0" - -glslify@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glslify/-/glslify-7.1.1.tgz#454d9172b410cb49864029c86d5613947fefd30b" - integrity sha512-bud98CJ6kGZcP9Yxcsi7Iz647wuDz3oN+IZsjCRi5X1PI7t/xPKeL0mOwXJjo+CRZMqvq0CkSJiywCcY7kVYog== - dependencies: - bl "^2.2.1" - concat-stream "^1.5.2" - duplexify "^3.4.5" - falafel "^2.1.0" - from2 "^2.3.0" - glsl-resolve "0.0.1" - glsl-token-whitespace-trim "^1.0.0" - glslify-bundle "^5.0.0" - glslify-deps "^1.2.5" - minimist "^1.2.5" - resolve "^1.1.5" - stack-trace "0.0.9" - static-eval "^2.0.5" - through2 "^2.0.1" - xtend "^4.0.0" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graceful-fs@^4.1.2, graceful-fs@^4.2.4: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -grid-index@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" - integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-hover@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-hover/-/has-hover-1.0.1.tgz#3d97437aeb199c62b8ac08acbdc53d3bc52c17f7" - integrity sha512-0G6w7LnlcpyDzpeGUTuT0CEw05+QlMuGVk1IHNAlHrGJITGodjZu3x8BNDUMfKJSZXNB2ZAclqc1bvrd+uUpfg== - dependencies: - is-browser "^2.0.1" - -has-passive-events@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-passive-events/-/has-passive-events-1.0.0.tgz#75fc3dc6dada182c58f24ebbdc018276d1ea3515" - integrity sha512-2vSj6IeIsgvsRMyeQ0JaCX5Q3lX4zMn5HpoVc7MEhQ6pv8Iq9rsXjsp+E5ZwaT7T0xhMT0KmU8gtt1EFVdbJiw== - dependencies: - is-browser "^2.0.1" - -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -history@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" - integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== - dependencies: - "@babel/runtime" "^7.7.6" - -hoist-non-react-statics@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" - -hotscript@^1.0.12: - version "1.0.13" - resolved "https://registry.yarnpkg.com/hotscript/-/hotscript-1.0.13.tgz#6eb5de757e9b33444ffc22555e98dbc17fa31fb4" - integrity sha512-C++tTF1GqkGYecL+2S1wJTfoH6APGAsbb7PAWQ3iVIwgG/EFseAfEVOKFgAFq4yK3+6j1EjUD4UQ9dRJHX/sSQ== - -hsluv@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/hsluv/-/hsluv-0.0.3.tgz#829107dafb4a9f8b52a1809ed02e091eade6754c" - integrity sha512-08iL2VyCRbkQKBySkSh6m8zMUa3sADAxGVWs3Z1aPcUkTJeK0ETG4Fc27tEmQBGUAXZjIsXOZqBvacuVNSC/fQ== - -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -ieee754@^1.1.12: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.2.0: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflection@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/inflection/-/inflection-3.0.0.tgz#6a956fa90d72a27d22e6b32ec1064877593ee23b" - integrity sha512-1zEJU1l19SgJlmwqsEyFTbScw/tkMHFenUo//Y0i+XEP83gDFdMvPizAD/WGcE+l1ku12PcTVHQhO6g5E0UCMw== - -inflection@~1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" - integrity sha512-lRy4DxuIFWXlJU7ed8UiTJOSTqStqYdEb4CEbtXfNbkdj3nH1L+reUWiE10VWcJS2yR7tge8Z74pJjtBjNwj0w== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.3.tgz#4c359675a6071a46985eb39b14e4a2c0ec98a795" - integrity sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg== - -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" - -"internmap@1 - 2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" - integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== - -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-async-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== - dependencies: - has-tostringtag "^1.0.0" - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-browser@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-browser/-/is-browser-2.1.0.tgz#fc084d59a5fced307d6708c59356bad7007371a9" - integrity sha512-F5rTJxDQ2sW81fcfOR1GnCXT6sVJC104fCyfj+mjpwNEwaPYSn5fte5jiHmBg3DHsIoL/l8Kvw5VN5SsTRcRFQ== - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.13.0: - version "2.15.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" - integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== - dependencies: - hasown "^2.0.2" - -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - -is-date-object@^1.0.1, is-date-object@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-finalizationregistry@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" - integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== - dependencies: - call-bind "^1.0.2" - -is-finite@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" - integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== - -is-firefox@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-firefox/-/is-firefox-1.0.3.tgz#2a2a1567783a417f6e158323108f3861b0918562" - integrity sha512-6Q9ITjvWIm0Xdqv+5U12wgOKEM2KoBw4Y926m0OFkvlCxnbG94HKAsVz8w3fWcfAS5YA2fJORXX1dLrkprCCxA== - -is-generator-function@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-iexplorer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-iexplorer/-/is-iexplorer-1.0.0.tgz#1d72bc66d3fe22eaf6170dda8cf10943248cfc76" - integrity sha512-YeLzceuwg3K6O0MLM3UyUUjKAlyULetwryFp1mHy1I5PfArK0AEqlfa+MR4gkJjcbuJXoDJCvXbyqZVf5CR2Sg== - -is-map@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" - integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== - -is-mobile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-mobile/-/is-mobile-4.0.0.tgz#bba396eb9656e2739afde3053d7191da310fc758" - integrity sha512-mlcHZA84t1qLSuWkt2v0I2l61PYdyQDt4aG1mLIXF5FDMm4+haBCxCPYSr/uwqQNRk1MiTizn0ypEuRAOLRAew== - -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== - -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-set@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" - integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== - -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string-blank@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-string-blank/-/is-string-blank-1.0.1.tgz#866dca066d41d2894ebdfd2d8fe93e586e583a03" - integrity sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-svg-path@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-svg-path/-/is-svg-path-1.0.2.tgz#77ab590c12b3d20348e5c7a13d0040c87784dda0" - integrity sha512-Lj4vePmqpPR1ZnRctHv8ltSh1OrSxHkhUkd7wi+VQdcdP15/KvQFyk7LhNuM7ZW0EVbJz8kZLVmL9quLrfq4Kg== - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - -is-weakmap@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" - integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-weakset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" - integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - -isarray@^2.0.1, isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isexe@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" - integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== - -iterator.prototype@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.3.tgz#016c2abe0be3bbdb8319852884f60908ac62bf9c" - integrity sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ== - dependencies: - define-properties "^1.2.1" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - reflect.getprototypeof "^1.0.4" - set-function-name "^2.0.1" - -js-base64@^3.7.2: - version "3.7.7" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" - integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== - -js-sha256@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.10.1.tgz#b40104ba1368e823fdd5f41b66b104b15a0da60d" - integrity sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw== - -js-sha256@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" - integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== - -js-sha3@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsesc@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json-stringify-pretty-compact@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz#cf4844770bddee3cb89a6170fe4b00eee5dbf1d4" - integrity sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonexport@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/jsonexport/-/jsonexport-3.2.0.tgz#e5b4905ea1f6c8f8e0f62e4ceb26e4a31f1c93a8" - integrity sha512-GbO9ugb0YTZatPd/hqCGR0FSwbr82H6OzG04yzdrG7XOe4QZ0jhQ+kOsB29zqkzoYJLmLxbbrFiuwbQu891XnQ== - -"jsx-ast-utils@^2.4.1 || ^3.0.0": - version "3.3.5" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" - integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== - dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - object.assign "^4.1.4" - object.values "^1.1.6" - -jwt-decode@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" - integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== - -jwt-decode@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" - integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== - -kdbush@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" - integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== - -kdbush@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-4.0.2.tgz#2f7b7246328b4657dd122b6c7f025fbc2c868e39" - integrity sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA== - -keycloak-js@^22.0.4: - version "22.0.5" - resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-22.0.5.tgz#8264cec1ff27015e1e53310e108cef3f796a5809" - integrity sha512-a7ZwCZeHl8tpeJBy102tZtAnHslDUOA1Nf/sHNF3HYLchKpwoDuaitwIUiS2GnNUe+tlNKLlCqZS+Mi5K79m1w== - dependencies: - base64-js "^1.5.1" - js-sha256 "^0.9.0" - -keycloak-js@^23.0.1: - version "23.0.7" - resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-23.0.7.tgz#9d2fad3253e087a49573bd9ee0569e327531135c" - integrity sha512-OmszsKzBhhm5yP4W1q/tMd+nNnKpOAdeVYcoGhphlv8Fj1bNk4wRTYzp7pn5BkvueLz7fhvKHz7uOc33524YrA== - dependencies: - base64-js "^1.5.1" - js-sha256 "^0.10.1" - jwt-decode "^4.0.0" - -keyv@^4.5.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -leaflet-draw@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/leaflet-draw/-/leaflet-draw-1.0.4.tgz#45be92f378ed253e7202fdeda1fcc71885198d46" - integrity sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ== - -leaflet-tilelayer-swiss@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/leaflet-tilelayer-swiss/-/leaflet-tilelayer-swiss-2.3.0.tgz#c59f55c70e41923f35327f03d07470da7f152bfb" - integrity sha512-Wt8sOgYyTqrcVikVOuO4SXRantdFJ4iKRG0nM/RL/w53NcLLlpQfIa4JU8TFtynPMnjnJIAnogM2ix/qNebRWQ== - -leaflet@^1.9.4: - version "1.9.4" - resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d" - integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA== - -lerc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lerc/-/lerc-3.0.0.tgz#36f36fbd4ba46f0abf4833799fff2e7d6865f5cb" - integrity sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -linkify-it@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" - integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== - dependencies: - uc.micro "^2.0.0" - -linkifyjs@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.3.tgz#0edbc346428a7390a23ea2e5939f76112c9ae07f" - integrity sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg== - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash-es@^4.17.15: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" - integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== - -lodash._baseiteratee@~4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz#34a9b5543572727c3db2e78edae3c0e9e66bd102" - integrity sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ== - dependencies: - lodash._stringtopath "~4.8.0" - -lodash._basetostring@~4.12.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz#9327c9dc5158866b7fa4b9d42f4638e5766dd9df" - integrity sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw== - -lodash._baseuniq@~4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" - integrity sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A== - dependencies: - lodash._createset "~4.0.0" - lodash._root "~3.0.0" - -lodash._createset@~4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" - integrity sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA== - -lodash._root@~3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - integrity sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ== - -lodash._stringtopath@~4.8.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz#941bcf0e64266e5fc1d66fed0a6959544c576824" - integrity sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ== - dependencies: - lodash._basetostring "~4.12.0" - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.throttle@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" - integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== - -lodash.uniqby@4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz#a3a17bbf62eeb6240f491846e97c1c4e2a5e1e21" - integrity sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ== - dependencies: - lodash._baseiteratee "~4.7.0" - lodash._baseuniq "~4.6.0" - -lodash@^4.17.21, lodash@~4.17.5: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -map-limit@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/map-limit/-/map-limit-0.0.1.tgz#eb7961031c0f0e8d001bf2d56fab685d58822f38" - integrity sha512-pJpcfLPnIF/Sk3taPW21G/RQsEEirGaFpCW3oXRwH9dnFHPHNGjNyvh++rdmC2fNqEaTw2MhYJraoJWAHx8kEg== - dependencies: - once "~1.3.0" - -maplibre-gl@^4.5.2: - version "4.7.1" - resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-4.7.1.tgz#06a524438ee2aafbe8bcd91002a4e01468ea5486" - integrity sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA== - dependencies: - "@mapbox/geojson-rewind" "^0.5.2" - "@mapbox/jsonlint-lines-primitives" "^2.0.2" - "@mapbox/point-geometry" "^0.1.0" - "@mapbox/tiny-sdf" "^2.0.6" - "@mapbox/unitbezier" "^0.0.1" - "@mapbox/vector-tile" "^1.3.1" - "@mapbox/whoots-js" "^3.1.0" - "@maplibre/maplibre-gl-style-spec" "^20.3.1" - "@types/geojson" "^7946.0.14" - "@types/geojson-vt" "3.2.5" - "@types/mapbox__point-geometry" "^0.1.4" - "@types/mapbox__vector-tile" "^1.3.4" - "@types/pbf" "^3.0.5" - "@types/supercluster" "^7.1.3" - earcut "^3.0.0" - geojson-vt "^4.0.2" - gl-matrix "^3.4.3" - global-prefix "^4.0.0" - kdbush "^4.0.2" - murmurhash-js "^1.0.0" - pbf "^3.3.0" - potpack "^2.0.0" - quickselect "^3.0.0" - supercluster "^8.0.1" - tinyqueue "^3.0.0" - vt-pbf "^3.1.3" - -markdown-it@^14.0.0: - version "14.1.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" - integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== - dependencies: - argparse "^2.0.1" - entities "^4.4.0" - linkify-it "^5.0.0" - mdurl "^2.0.0" - punycode.js "^2.3.1" - uc.micro "^2.1.0" - -match-sorter@^6.0.2: - version "6.4.0" - resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.4.0.tgz#ae9c166cb3c9efd337690b3160c0e28cb8377c13" - integrity sha512-d4664ahzdL1QTTvmK1iI0JsrxWeJ6gn33qkYtnPg3mcn+naBLtXSgSPOe+X2vUgtgGwaAk3eiaj7gwKjjMAq+Q== - dependencies: - "@babel/runtime" "^7.23.8" - remove-accents "0.5.0" - -math-log2@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/math-log2/-/math-log2-1.0.1.tgz#fb8941be5f5ebe8979e718e6273b178e58694565" - integrity sha512-9W0yGtkaMAkf74XGYVy4Dqw3YUMnTNB2eeiw9aQbUl4A3KmuCEHTt2DgAB07ENzOYAjsYSAYufkAq0Zd+jU7zA== - -mdurl@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" - integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -microseconds@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" - integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mouse-change@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/mouse-change/-/mouse-change-1.4.0.tgz#c2b77e5bfa34a43ce1445c8157a4e4dc9895c14f" - integrity sha512-vpN0s+zLL2ykyyUDh+fayu9Xkor5v/zRD9jhSqjRS1cJTGS0+oakVZzNm5n19JvvEj0you+MXlYTpNxUDQUjkQ== - dependencies: - mouse-event "^1.0.0" - -mouse-event-offset@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/mouse-event-offset/-/mouse-event-offset-3.0.2.tgz#dfd86a6e248c6ba8cad53b905d5037a2063e9984" - integrity sha512-s9sqOs5B1Ykox3Xo8b3Ss2IQju4UwlW6LSR+Q5FXWpprJ5fzMLefIIItr3PH8RwzfGy6gxs/4GAmiNuZScE25w== - -mouse-event@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/mouse-event/-/mouse-event-1.0.5.tgz#b3789edb7109997d5a932d1d01daa1543a501732" - integrity sha512-ItUxtL2IkeSKSp9cyaX2JLUuKk2uMoxBg4bbOWVd29+CskYJR9BGsUqtXenNzKbnDshvupjUewDIYVrOB6NmGw== - -mouse-wheel@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mouse-wheel/-/mouse-wheel-1.2.0.tgz#6d2903b1ea8fb48e61f1b53b9036773f042cdb5c" - integrity sha512-+OfYBiUOCTWcTECES49neZwL5AoGkXE+lFjIvzwNCnYRlso+EnfvovcBxGoyQ0yQt806eSPjS675K0EwWknXmw== - dependencies: - right-now "^1.0.0" - signum "^1.0.0" - to-px "^1.0.1" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -mumath@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/mumath/-/mumath-3.3.4.tgz#48d4a0f0fd8cad4e7b32096ee89b161a63d30bbf" - integrity sha512-VAFIOG6rsxoc7q/IaY3jdjmrsuX9f15KlRLYTHmixASBZkZEKC1IFqE2BC5CdhXmK6WLM1Re33z//AGmeRI6FA== - dependencies: - almost-equal "^1.1.0" - -murmurhash-js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" - integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw== - -nano-time@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" - integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA== - dependencies: - big-integer "^1.6.16" - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -native-promise-only@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11" - integrity sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg== - -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -needle@^2.5.2: - version "2.9.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" - integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -next-tick@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" - integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== - -node-polyglot@^2.2.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/node-polyglot/-/node-polyglot-2.6.0.tgz#3d5889664253d90babc0fcd3c12ae0ac7b98289f" - integrity sha512-ZZFkaYzIfGfBvSM6QhA9dM8EEaUJOVewzGSRcXWbJELXDj0lajAtKaENCYxvF5yE+TgHg6NQb0CmgYMsMdcNJQ== - dependencies: - hasown "^2.0.2" - object.entries "^1.1.8" - warning "^4.0.3" - -node-releases@^2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== - -normalize-svg-path@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz#0e614eca23c39f0cffe821d6be6cd17e569a766c" - integrity sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg== - dependencies: - svg-arc-to-cubic-bezier "^3.0.0" - -normalize-svg-path@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-0.1.0.tgz#456360e60ece75fbef7b5d7e160480e7ffd16fe5" - integrity sha512-1/kmYej2iedi5+ROxkRESL/pI02pkg0OBnaR4hJkSIX6+ORzepwbuUXfrdZaPjysTsJInj0Rj5NuX027+dMBvA== - -number-is-integer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-integer/-/number-is-integer-1.0.1.tgz#e59bca172ffed27318e79c7ceb6cb72c095b2152" - integrity sha512-Dq3iuiFBkrbmuQjGFFF3zckXNCQoSD37/SdSbgcBailUx6knDvDwb5CympBgcoWHy36sfS12u74MHYkXyHq6bg== - dependencies: - is-finite "^1.0.1" - -object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.1: - version "1.13.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" - integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.4, object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.entries@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -object.fromentries@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" - integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" - -object.values@^1.1.6, object.values@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -oblivious-set@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" - integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== - -ol@^8.1.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/ol/-/ol-8.2.0.tgz#145153eab0ea3b5d04f51f46d6c69c224cccd5c3" - integrity sha512-/m1ddd7Jsp4Kbg+l7+ozR5aKHAZNQOBAoNZ5pM9Jvh4Etkf0WGkXr9qXd7PnhmwiC1Hnc2Toz9XjCzBBvexfXw== - dependencies: - color-rgba "^3.0.0" - color-space "^2.0.1" - earcut "^2.2.3" - geotiff "^2.0.7" - pbf "3.2.1" - rbush "^3.0.1" - -once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -once@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" - integrity sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w== - dependencies: - wrappy "1" - -optionator@^0.9.3: - version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" - integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.5" - -orderedmap@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2" - integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g== - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -pako@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" - integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parenthesis@^3.1.5: - version "3.1.8" - resolved "https://registry.yarnpkg.com/parenthesis/-/parenthesis-3.1.8.tgz#3457fccb8f05db27572b841dad9d2630b912f125" - integrity sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw== - -parse-headers@^2.0.2: - version "2.0.5" - resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" - integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - 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" - -parse-rect@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/parse-rect/-/parse-rect-1.2.0.tgz#e0a5b0dbaaaee637a0a1eb9779969e19399d8dec" - integrity sha512-4QZ6KYbnE6RTwg9E0HpLchUM9EZt6DnDxajFZZDSV4p/12ZJEvPO702DZpGvRYEPo00yKDys7jASi+/w7aO8LA== - dependencies: - pick-by-alias "^1.2.0" - -parse-svg-path@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb" - integrity sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ== - -parse-unit@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-unit/-/parse-unit-1.0.1.tgz#7e1bb6d5bef3874c28e392526a2541170291eecf" - integrity sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pbf@3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.2.1.tgz#b4c1b9e72af966cd82c6531691115cc0409ffe2a" - integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ== - dependencies: - ieee754 "^1.1.12" - resolve-protobuf-schema "^2.1.0" - -pbf@^3.2.1, pbf@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.3.0.tgz#1790f3d99118333cc7f498de816028a346ef367f" - integrity sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q== - dependencies: - ieee754 "^1.1.12" - resolve-protobuf-schema "^2.1.0" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - -pick-by-alias@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pick-by-alias/-/pick-by-alias-1.2.0.tgz#5f7cb2b1f21a6e1e884a0c87855aa4a37361107b" - integrity sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw== - -picocolors@^1.0.0, picocolors@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== - -picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -plotly.js@^2.32.0: - version "2.35.2" - resolved "https://registry.yarnpkg.com/plotly.js/-/plotly.js-2.35.2.tgz#01ea5d0196c8dfa95bc8aab84781d391c80fee73" - integrity sha512-s0knlWzRvLQXxzf3JQ6qbm8FpwKuMjkr+6r04f8/yCEByAQ+I0jkUzY/hSGRGb+u7iljTh9hgpEiiJP90vjyeQ== - dependencies: - "@plotly/d3" "3.8.2" - "@plotly/d3-sankey" "0.7.2" - "@plotly/d3-sankey-circular" "0.33.1" - "@plotly/mapbox-gl" "1.13.4" - "@turf/area" "^7.1.0" - "@turf/bbox" "^7.1.0" - "@turf/centroid" "^7.1.0" - base64-arraybuffer "^1.0.2" - canvas-fit "^1.5.0" - color-alpha "1.0.4" - color-normalize "1.5.0" - color-parse "2.0.0" - color-rgba "2.1.1" - country-regex "^1.1.0" - css-loader "^7.1.2" - d3-force "^1.2.1" - d3-format "^1.4.5" - d3-geo "^1.12.1" - d3-geo-projection "^2.9.0" - d3-hierarchy "^1.1.9" - d3-interpolate "^3.0.1" - d3-time "^1.1.0" - d3-time-format "^2.2.3" - fast-isnumeric "^1.1.4" - gl-mat4 "^1.2.0" - gl-text "^1.4.0" - has-hover "^1.0.1" - has-passive-events "^1.0.0" - is-mobile "^4.0.0" - maplibre-gl "^4.5.2" - mouse-change "^1.4.0" - mouse-event-offset "^3.0.2" - mouse-wheel "^1.2.0" - native-promise-only "^0.8.1" - parse-svg-path "^0.1.2" - point-in-polygon "^1.1.0" - polybooljs "^1.2.2" - probe-image-size "^7.2.3" - regl "npm:@plotly/regl@^2.1.2" - regl-error2d "^2.0.12" - regl-line2d "^3.1.3" - regl-scatter2d "^3.3.1" - regl-splom "^1.0.14" - strongly-connected-components "^1.0.1" - style-loader "^4.0.0" - superscript-text "^1.0.0" - svg-path-sdf "^1.1.3" - tinycolor2 "^1.4.2" - to-px "1.0.1" - topojson-client "^3.1.0" - webgl-context "^2.2.0" - world-calendars "^1.0.3" - -point-in-polygon@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/point-in-polygon/-/point-in-polygon-1.1.0.tgz#b0af2616c01bdee341cbf2894df643387ca03357" - integrity sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw== - -polybooljs@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/polybooljs/-/polybooljs-1.2.2.tgz#c7127b014a63e1d00c70608abbd8dc0967ffeea3" - integrity sha512-ziHW/02J0XuNuUtmidBc6GXE8YohYydp3DWPWXYsd7O721TjcmN+k6ezjdwkDqep+gnWnFY+yqZHvzElra2oCg== - -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - -postcss-modules-extract-imports@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" - integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== - -postcss-modules-local-by-default@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f" - integrity sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz#a43d28289a169ce2c15c00c4e64c0858e43457d5" - integrity sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" - integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^8.4.27, postcss@^8.4.33: - version "8.4.47" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" - integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== - dependencies: - nanoid "^3.3.7" - picocolors "^1.1.0" - source-map-js "^1.2.1" - -potpack@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14" - integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ== - -potpack@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/potpack/-/potpack-2.0.0.tgz#61f4dd2dc4b3d5e996e3698c0ec9426d0e169104" - integrity sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw== - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier@^2.8.8: - version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== - -probe-image-size@^7.2.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.3.tgz#d49c64be540ec8edea538f6f585f65a9b3ab4309" - integrity sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w== - dependencies: - lodash.merge "^4.6.2" - needle "^2.5.2" - stream-parser "~0.3.1" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - -proper-lockfile@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" - integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== - dependencies: - graceful-fs "^4.2.4" - retry "^0.12.0" - signal-exit "^3.0.2" - -prosemirror-changeset@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz#dae94b63aec618fac7bb9061648e6e2a79988383" - integrity sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ== - dependencies: - prosemirror-transform "^1.0.0" - -prosemirror-collab@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33" - integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ== - dependencies: - prosemirror-state "^1.0.0" - -prosemirror-commands@^1.0.0, prosemirror-commands@^1.6.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.6.1.tgz#2bcf65bc73f10ead8e24265b4de550c46c39afac" - integrity sha512-tNy4uaGWzvuUYXDke7B28krndIrdQJhSh0OLpubtwtEwFbjItOj/eoAfPvstBJyyV0S2+b5t4G+4XPXdxar6pg== - dependencies: - prosemirror-model "^1.0.0" - prosemirror-state "^1.0.0" - prosemirror-transform "^1.10.2" - -prosemirror-dropcursor@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz#49b9fb2f583e0d0f4021ff87db825faa2be2832d" - integrity sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw== - dependencies: - prosemirror-state "^1.0.0" - prosemirror-transform "^1.1.0" - prosemirror-view "^1.1.0" - -prosemirror-gapcursor@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz#5fa336b83789c6199a7341c9493587e249215cb4" - integrity sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ== - dependencies: - prosemirror-keymap "^1.0.0" - prosemirror-model "^1.0.0" - prosemirror-state "^1.0.0" - prosemirror-view "^1.0.0" - -prosemirror-history@^1.0.0, prosemirror-history@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.4.1.tgz#cc370a46fb629e83a33946a0e12612e934ab8b98" - integrity sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ== - dependencies: - prosemirror-state "^1.2.2" - prosemirror-transform "^1.0.0" - prosemirror-view "^1.31.0" - rope-sequence "^1.3.0" - -prosemirror-inputrules@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz#ef1519bb2cb0d1e0cec74bad1a97f1c1555068bb" - integrity sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg== - dependencies: - prosemirror-state "^1.0.0" - prosemirror-transform "^1.0.0" - -prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz#14a54763a29c7b2704f561088ccf3384d14eb77e" - integrity sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ== - dependencies: - prosemirror-state "^1.0.0" - w3c-keyname "^2.2.0" - -prosemirror-markdown@^1.13.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz#23feb6652dacb3dd78ffd8f131da37c20e4e4cf8" - integrity sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw== - dependencies: - "@types/markdown-it" "^14.0.0" - markdown-it "^14.0.0" - prosemirror-model "^1.20.0" - -prosemirror-menu@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz#3cfdc7c06d10f9fbd1bce29082c498bd11a0a79a" - integrity sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA== - dependencies: - crelt "^1.0.0" - prosemirror-commands "^1.0.0" - prosemirror-history "^1.0.0" - prosemirror-state "^1.0.0" - -prosemirror-model@^1.0.0, prosemirror-model@^1.19.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.22.3, prosemirror-model@^1.8.1: - version "1.23.0" - resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.23.0.tgz#652058182ed90dc15c8f0f2cf2df488306fa1dcd" - integrity sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ== - dependencies: - orderedmap "^2.0.0" - -prosemirror-schema-basic@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz#649c349bb21c61a56febf9deb71ac68fca4cedf2" - integrity sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA== - dependencies: - prosemirror-model "^1.19.0" - -prosemirror-schema-list@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz#78b8d25531db48ca9688836dbde50e13ac19a4a1" - integrity sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg== - dependencies: - prosemirror-model "^1.0.0" - prosemirror-state "^1.0.0" - prosemirror-transform "^1.7.3" - -prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.1, prosemirror-state@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.3.tgz#94aecf3ffd54ec37e87aa7179d13508da181a080" - integrity sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q== - dependencies: - prosemirror-model "^1.0.0" - prosemirror-transform "^1.0.0" - prosemirror-view "^1.27.0" - -prosemirror-tables@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.5.0.tgz#3ba1ea3d53852505cc0d2037ce386973bb639a7d" - integrity sha512-VMx4zlYWm7aBlZ5xtfJHpqa3Xgu3b7srV54fXYnXgsAcIGRqKSrhiK3f89omzzgaAgAtDOV4ImXnLKhVfheVNQ== - dependencies: - prosemirror-keymap "^1.1.2" - prosemirror-model "^1.8.1" - prosemirror-state "^1.3.1" - prosemirror-transform "^1.2.1" - prosemirror-view "^1.13.3" - -prosemirror-trailing-node@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz#5bc223d4fc1e8d9145e4079ec77a932b54e19e04" - integrity sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ== - dependencies: - "@remirror/core-constants" "3.0.0" - escape-string-regexp "^4.0.0" - -prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.2.1, prosemirror-transform@^1.7.3: - version "1.10.2" - resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz#8ebac4e305b586cd96595aa028118c9191bbf052" - integrity sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ== - dependencies: - prosemirror-model "^1.21.0" - -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.33.10: - version "1.34.3" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.34.3.tgz#24b5d2f9196580c23bbe04e9e7a6797cd3a049f6" - integrity sha512-mKZ54PrX19sSaQye+sef+YjBbNu2voNwLS1ivb6aD2IRmxRGW64HU9B644+7OfJStGLyxvOreKqEgfvXa91WIA== - dependencies: - prosemirror-model "^1.20.0" - prosemirror-state "^1.0.0" - prosemirror-transform "^1.1.0" - -protocol-buffers-schema@^3.3.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03" - integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw== - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -punycode.js@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" - integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -query-string@^7.1.1, query-string@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" - integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== - dependencies: - decode-uri-component "^0.2.2" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-lru@^6.1.1: - version "6.1.2" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-6.1.2.tgz#e9a90524108629be35287d0b864e7ad6ceb3659e" - integrity sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ== - -quickselect@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" - integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== - -quickselect@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603" - integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g== - -ra-core@^4.16.20: - version "4.16.20" - resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-4.16.20.tgz#ad11785c706f8256ec0e9d371be2e13c8ca40e80" - integrity sha512-+xJSVBD3zTG9Zxy042SpAWsozuIKCNvN/cZeUr5lINHY3/zOcZ2bbJ+Ce7IMcKnlvxS4g3tvqq4HuTQpItFEcg== - dependencies: - clsx "^1.1.1" - date-fns "^2.19.0" - eventemitter3 "^4.0.7" - inflection "~1.12.0" - jsonexport "^3.2.0" - lodash "~4.17.5" - prop-types "^15.6.1" - query-string "^7.1.1" - react-is "^17.0.2" - react-query "^3.32.1" - -ra-core@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-5.2.3.tgz#5c93fc1ee8d62b8ec6df1f2a6b76cf581f2b7654" - integrity sha512-uaJX54igNS9VPK1jiSfwjtDaW6P70cxuZvRBBPqqNoN04fldVHUKUaC7wY/LsWnIRCPATz+TU93RaKYDUNw8FQ== - dependencies: - "@tanstack/react-query" "^5.8.4" - clsx "^2.1.1" - date-fns "^3.6.0" - eventemitter3 "^5.0.1" - hotscript "^1.0.12" - inflection "^3.0.0" - jsonexport "^3.2.0" - lodash "~4.17.5" - query-string "^7.1.3" - react-error-boundary "^4.0.13" - react-is "^18.2.0" - -ra-data-fakerest@^4.14.0: - version "4.16.20" - resolved "https://registry.yarnpkg.com/ra-data-fakerest/-/ra-data-fakerest-4.16.20.tgz#39bdea40232ab2816046efbfe1af46c63b24f831" - integrity sha512-5biBGgpDt5y1enljOrat8Sb0qsoyH/AJKWHyxB7QoELY+IOSakROLJg0Kk8+KYytI/ojHHDjMuuyT2xlH6J2YQ== - dependencies: - fakerest "^3.0.0" - -ra-data-simple-rest@^4.15.2: - version "4.16.20" - resolved "https://registry.yarnpkg.com/ra-data-simple-rest/-/ra-data-simple-rest-4.16.20.tgz#33896001f23ecf1d0ef127a40bfb4c4308b1b155" - integrity sha512-x2Z8ZHP81WIu6jLHEKM0+1VnTrx8HLscoafS501JL2VwPqap9klupQ1AqLJF6lEDQ9lI4uuCh8zofuWrc5BLjw== - dependencies: - query-string "^7.1.1" - -ra-i18n-polyglot@^4.16.20: - version "4.16.20" - resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-4.16.20.tgz#9d0e3211b7163c83d6a9acc281cb29642d6b64ec" - integrity sha512-O8++vWMMUnPDSChcHDjv6teIF14Gnr0rV9Phq0168X4RhjIbxyv3T0QpGKyOzXykCsCj+2pGrK/8Gj8fGJCSug== - dependencies: - node-polyglot "^2.2.2" - ra-core "^4.16.20" - -ra-i18n-polyglot@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/ra-i18n-polyglot/-/ra-i18n-polyglot-5.2.3.tgz#fef1b268c48fe8d40eff536aa1a961c756d78f9d" - integrity sha512-e7njr3BtPLdqQaqlWdjqjYWEWPZ4qca9QCVQBSxMkIZNoUf1mbKTHq+TDfnlCgnSKmADS9l8FRIcP+7fV6RNEQ== - dependencies: - node-polyglot "^2.2.2" - ra-core "^5.2.3" - -ra-input-rich-text@^4.15.1: - version "4.16.20" - resolved "https://registry.yarnpkg.com/ra-input-rich-text/-/ra-input-rich-text-4.16.20.tgz#961307f9e2164d6d64925fc279fdd1df9fb64595" - integrity sha512-4KDx/rQNcUg8ic+pDFBbtUqWphq8Es5UYBQPj7ljmZX5ggq1yyDZx71dnGsjg8x0aDJ7AbQwHGZF2yWswHgeKQ== - dependencies: - "@tiptap/core" "^2.0.3" - "@tiptap/extension-color" "^2.0.3" - "@tiptap/extension-highlight" "^2.0.3" - "@tiptap/extension-image" "^2.0.3" - "@tiptap/extension-link" "^2.0.3" - "@tiptap/extension-placeholder" "^2.0.3" - "@tiptap/extension-text-align" "^2.0.3" - "@tiptap/extension-text-style" "^2.0.3" - "@tiptap/extension-underline" "^2.0.3" - "@tiptap/pm" "^2.0.3" - "@tiptap/react" "^2.0.3" - "@tiptap/starter-kit" "^2.0.3" - clsx "^1.1.1" - -ra-keycloak@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ra-keycloak/-/ra-keycloak-1.0.1.tgz#173ac40366aca7651b7987a0d5b1df7e2fbbbec6" - integrity sha512-WU+AMBrRPgfKCTkyuix1xbEBJte9qBEJ9uboLs7eK0awDH2M0lAZmJcmgP827L/OYbIA5RaFKUSWPSdvDwpAqA== - dependencies: - jwt-decode "^3.1.2" - keycloak-js "^23.0.1" - react-admin "^4.4.0" - -ra-language-english@^4.16.20: - version "4.16.20" - resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-4.16.20.tgz#0dc8f646fb4fc4daaddf4c1be1bb74a3d63c3f91" - integrity sha512-Zr4uq3u86uz3i3R2ztFt9eF0+f8MYCkACJbvgTJlcXdnkQoJc/05SrwN9loFI8XAzC+Z1klcQM6jjKzq0T4Umg== - dependencies: - ra-core "^4.16.20" - -ra-language-english@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/ra-language-english/-/ra-language-english-5.2.3.tgz#c4494e4edc591473cda5010276129f8938c11e1e" - integrity sha512-MkY5kWUtAmQVwvGA674UcY1X7cops8Bmk8PshHIGFlhNNJR4rlrNcl1n4Ypt9M18SbGWhltYvjYMpcILdtKdjQ== - dependencies: - ra-core "^5.2.3" - -ra-language-french@^4.15.1: - version "4.16.20" - resolved "https://registry.yarnpkg.com/ra-language-french/-/ra-language-french-4.16.20.tgz#f550c7c73cfd1e6f4719657b4863288d829e39e6" - integrity sha512-EBp+kO3GGg6eBOAeuaEmajE4VntFmJd4/9iuXafOTJbJJGPLOX1ZaU4e7up5HXl/IgABBpWWlAZ63b4oT8nHBw== - dependencies: - ra-core "^4.16.20" - -ra-ui-materialui@^4.16.20: - version "4.16.20" - resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-4.16.20.tgz#d783f135a26a42eac1018f080886697faa518b09" - integrity sha512-i2DQ2219YTbjhXfBjkMIKmWiqZRQDyW+yMAgehfSjVbVD17QhoWG3fib4ILA58ADe4trLhFNg4WPkO3y8q37dw== - dependencies: - autosuggest-highlight "^3.1.1" - clsx "^1.1.1" - css-mediaquery "^0.1.2" - dompurify "^2.4.3" - hotscript "^1.0.12" - inflection "~1.12.0" - jsonexport "^3.2.0" - lodash "~4.17.5" - prop-types "^15.7.0" - query-string "^7.1.1" - react-dropzone "^12.0.4" - react-error-boundary "^3.1.4" - react-query "^3.32.1" - react-transition-group "^4.4.1" - -ra-ui-materialui@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/ra-ui-materialui/-/ra-ui-materialui-5.2.3.tgz#7942933973077222a3f1964afe92f1937f4fe4bb" - integrity sha512-H2KOBzbjTv81iryRL8V17cQHihWVAzRkO2xa1D4+SmjJRAtYPrel46BqtOgHbgT3/3TeuMNIbCPF9sWm7JlRvg== - dependencies: - "@tanstack/react-query" "^5.8.4" - autosuggest-highlight "^3.1.1" - clsx "^2.1.1" - css-mediaquery "^0.1.2" - dompurify "^2.4.3" - hotscript "^1.0.12" - inflection "^3.0.0" - jsonexport "^3.2.0" - lodash "~4.17.5" - query-string "^7.1.3" - react-dropzone "^14.2.3" - react-error-boundary "^4.0.13" - react-transition-group "^4.4.5" - -raf@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - -rbush@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf" - integrity sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w== - dependencies: - quickselect "^2.0.0" - -react-admin@^4.4.0: - version "4.16.20" - resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-4.16.20.tgz#50cfc98ac1c135d4bfb546ba0ad350f99c566c24" - integrity sha512-9QLc5uTI669dGceC+VT08LG2vytMMSpvwg2lJJS4wqJJykQKlZXJelpOLAGWlggEuAXtyC2nMG8S6sgl7+hBDQ== - dependencies: - "@emotion/react" "^11.4.1" - "@emotion/styled" "^11.3.0" - "@mui/icons-material" "^5.0.1" - "@mui/material" "^5.0.2" - history "^5.1.0" - ra-core "^4.16.20" - ra-i18n-polyglot "^4.16.20" - ra-language-english "^4.16.20" - ra-ui-materialui "^4.16.20" - react-hook-form "^7.43.9" - react-router "^6.1.0" - react-router-dom "^6.1.0" - -react-admin@^5.1.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-5.2.3.tgz#5840c0ff085e9cf0f24ee7c1f4837c7a29533157" - integrity sha512-rUtk86O1SCIlXkTfLYrzSyJe2xIqanI+7R/xRkWX2Aodw3OGxMBsdzyScqa9Bn9CbyhDSVlZHVCSHNf9a5PWlA== - dependencies: - "@emotion/react" "^11.4.1" - "@emotion/styled" "^11.3.0" - "@mui/icons-material" "^5.15.20" - "@mui/material" "^5.15.20" - ra-core "^5.2.3" - ra-i18n-polyglot "^5.2.3" - ra-language-english "^5.2.3" - ra-ui-materialui "^5.2.3" - react-hook-form "^7.53.0" - react-router "^6.22.0" - react-router-dom "^6.22.0" - -react-dom@^18.2.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" - integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== - dependencies: - loose-envify "^1.1.0" - scheduler "^0.23.2" - -react-dropzone-uploader@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/react-dropzone-uploader/-/react-dropzone-uploader-2.11.0.tgz#6579e83f4f98c8248a3f3b5825c436ca3d6441cf" - integrity sha512-1DpdPMGKP7vYL5SeCh13HCl+Xrz0F6jGrDPU5Tj2ojEIXGMCtfflrZhyXdr7u40IkQ+hYjAUEEtJW24SiY8WRA== - dependencies: - "@babel/runtime" "^7.1.2" - -react-dropzone@^12.0.4: - version "12.1.0" - resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-12.1.0.tgz#e097b37e9da6f9e324efc757b7434ebc6f3dc2cb" - integrity sha512-iBYHA1rbopIvtzokEX4QubO6qk5IF/x3BtKGu74rF2JkQDXnwC4uO/lHKpaw4PJIV6iIAYOlwLv2FpiGyqHNog== - dependencies: - attr-accept "^2.2.2" - file-selector "^0.5.0" - prop-types "^15.8.1" - -react-dropzone@^14.2.3: - version "14.2.9" - resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.9.tgz#193a33f9035e29fc91abf24e50de5d66cfa7c8c0" - integrity sha512-jRZsMC7h48WONsOLHcmhyn3cRWJoIPQjPApvt/sJVfnYaB3Qltn025AoRTTJaj4WdmmgmLl6tUQg1s0wOhpodQ== - dependencies: - attr-accept "^2.2.2" - file-selector "^0.6.0" - prop-types "^15.8.1" - -react-error-boundary@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" - integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== - dependencies: - "@babel/runtime" "^7.12.5" - -react-error-boundary@^4.0.13: - version "4.1.0" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.1.0.tgz#7f6cb4ed1df6b820c35ab69aeae257493fc73ed7" - integrity sha512-GFnM3kyswd+9Oy7oX1lxdr39ANHD3ty6cyAK4Kyku+w8Aq9fnK7+yRytKOaPLzOhgtGq18AfTXmDtwlojBPTRg== - dependencies: - "@babel/runtime" "^7.12.5" - -react-filepond@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/react-filepond/-/react-filepond-7.1.2.tgz#8f04d06396a59b5980dda479c095ed23e6d1f68b" - integrity sha512-vrlx9o/n+jGikh6jLey+PpUGYtEZn8MdeB6JFeX/5/N/Mu75IHG+MFQbv8R27bNXvS41mDtV9Fo5DG+s6siVIA== - -react-hook-form@^7.43.9, react-hook-form@^7.53.0: - version "7.53.0" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab" - integrity sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ== - -react-is@^16.13.1, react-is@^16.7.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -react-is@^17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - -react-is@^18.2.0, react-is@^18.3.1: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" - integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== - -react-leaflet-draw@^0.20.4: - version "0.20.4" - resolved "https://registry.yarnpkg.com/react-leaflet-draw/-/react-leaflet-draw-0.20.4.tgz#e3f68dc783cbe00d24ff3f2e02d5208c49f7c971" - integrity sha512-u5JHdow2Z9G2AveyUEOTWHXhdhzXdEVQifkNfSaVbEn0gvD+2xW03TQN444zVqovDBvIrBcVWo1VajL4zgl6yg== - dependencies: - fast-deep-equal "^3.1.3" - lodash-es "^4.17.15" - -react-leaflet@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-4.2.1.tgz#c300e9eccaf15cb40757552e181200aa10b94780" - integrity sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q== - dependencies: - "@react-leaflet/core" "^2.1.0" - -react-plotly.js@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/react-plotly.js/-/react-plotly.js-2.6.0.tgz#ad6b68ee64f1b5cfa142ee92c59687f9c2c09209" - integrity sha512-g93xcyhAVCSt9kV1svqG1clAEdL6k3U+jjuSzfTV7owaSU9Go6Ph8bl25J+jKfKvIGAEYpe4qj++WHJuc9IaeA== - dependencies: - prop-types "^15.8.1" - -react-query@^3.32.1: - version "3.39.3" - resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.3.tgz#4cea7127c6c26bdea2de5fb63e51044330b03f35" - integrity sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g== - dependencies: - "@babel/runtime" "^7.5.5" - broadcast-channel "^3.4.1" - match-sorter "^6.0.2" - -react-refresh@^0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" - integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== - -react-router-dom@^6.1.0, react-router-dom@^6.22.0: - version "6.27.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.27.0.tgz#8d7972a425fd75f91c1e1ff67e47240c5752dc3f" - integrity sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g== - dependencies: - "@remix-run/router" "1.20.0" - react-router "6.27.0" - -react-router@6.27.0, react-router@^6.1.0, react-router@^6.22.0: - version "6.27.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.27.0.tgz#db292474926c814c996c0ff3ef0162d1f9f60ed4" - integrity sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw== - dependencies: - "@remix-run/router" "1.20.0" - -react-smooth@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a" - integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w== - dependencies: - fast-equals "^5.0.1" - prop-types "^15.8.1" - react-transition-group "^4.4.5" - -react-transition-group@^4.4.1, react-transition-group@^4.4.5: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - -react@^18.2.0: - version "18.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" - integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== - dependencies: - loose-envify "^1.1.0" - -"readable-stream@>=1.0.33-1 <1.1.0-0": - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -recharts-scale@^0.4.4: - version "0.4.5" - resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" - integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== - dependencies: - decimal.js-light "^2.4.1" - -recharts@^2.9.1: - version "2.13.0" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.13.0.tgz#a293322ea357491393cc7ad6fcbb1e5f8e99bc93" - integrity sha512-sbfxjWQ+oLWSZEWmvbq/DFVdeRLqqA6d0CDjKx2PkxVVdoXo16jvENCE+u/x7HxOO+/fwx//nYRwb8p8X6s/lQ== - dependencies: - clsx "^2.0.0" - eventemitter3 "^4.0.1" - lodash "^4.17.21" - react-is "^18.3.1" - react-smooth "^4.0.0" - recharts-scale "^0.4.4" - tiny-invariant "^1.3.1" - victory-vendor "^36.6.8" - -reflect.getprototypeof@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" - integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.1" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" - which-builtin-type "^1.1.3" - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regexp.prototype.flags@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz#b3ae40b1d2499b8350ab2c3fe6ef3845d3a96f42" - integrity sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.2" - -regl-error2d@^2.0.12: - version "2.0.12" - resolved "https://registry.yarnpkg.com/regl-error2d/-/regl-error2d-2.0.12.tgz#3b976e13fe641d5242a154fcacc80aecfa0a9881" - integrity sha512-r7BUprZoPO9AbyqM5qlJesrSRkl+hZnVKWKsVp7YhOl/3RIpi4UDGASGJY0puQ96u5fBYw/OlqV24IGcgJ0McA== - dependencies: - array-bounds "^1.0.1" - color-normalize "^1.5.0" - flatten-vertex-data "^1.0.2" - object-assign "^4.1.1" - pick-by-alias "^1.2.0" - to-float32 "^1.1.0" - update-diff "^1.1.0" - -regl-line2d@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/regl-line2d/-/regl-line2d-3.1.3.tgz#03669c676a9e3a06973d34c68ada2400792724a4" - integrity sha512-fkgzW+tTn4QUQLpFKsUIE0sgWdCmXAM3ctXcCgoGBZTSX5FE2A0M7aynz7nrZT5baaftLrk9te54B+MEq4QcSA== - dependencies: - array-bounds "^1.0.1" - array-find-index "^1.0.2" - array-normalize "^1.1.4" - color-normalize "^1.5.0" - earcut "^2.1.5" - es6-weak-map "^2.0.3" - flatten-vertex-data "^1.0.2" - object-assign "^4.1.1" - parse-rect "^1.2.0" - pick-by-alias "^1.2.0" - to-float32 "^1.1.0" - -regl-scatter2d@^3.2.3, regl-scatter2d@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/regl-scatter2d/-/regl-scatter2d-3.3.1.tgz#0956952901ab30743dbdfb4c67fd358075e9b939" - integrity sha512-seOmMIVwaCwemSYz/y4WE0dbSO9svNFSqtTh5RE57I7PjGo3tcUYKtH0MTSoshcAsreoqN8HoCtnn8wfHXXfKQ== - dependencies: - "@plotly/point-cluster" "^3.1.9" - array-range "^1.0.1" - array-rearrange "^2.2.2" - clamp "^1.0.1" - color-id "^1.1.0" - color-normalize "^1.5.0" - color-rgba "^2.1.1" - flatten-vertex-data "^1.0.2" - glslify "^7.0.0" - is-iexplorer "^1.0.0" - object-assign "^4.1.1" - parse-rect "^1.2.0" - pick-by-alias "^1.2.0" - to-float32 "^1.1.0" - update-diff "^1.1.0" - -regl-splom@^1.0.14: - version "1.0.14" - resolved "https://registry.yarnpkg.com/regl-splom/-/regl-splom-1.0.14.tgz#58800b7bbd7576aa323499a1966868a6c9ea1456" - integrity sha512-OiLqjmPRYbd7kDlHC6/zDf6L8lxgDC65BhC8JirhP4ykrK4x22ZyS+BnY8EUinXKDeMgmpRwCvUmk7BK4Nweuw== - dependencies: - array-bounds "^1.0.1" - array-range "^1.0.1" - color-alpha "^1.0.4" - flatten-vertex-data "^1.0.2" - parse-rect "^1.2.0" - pick-by-alias "^1.2.0" - raf "^3.4.1" - regl-scatter2d "^3.2.3" - -regl@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/regl/-/regl-2.1.0.tgz#7dae71e9ff20f29c4f42f510c70cd92ebb6b657c" - integrity sha512-oWUce/aVoEvW5l2V0LK7O5KJMzUSKeiOwFuJehzpSFd43dO5spP9r+sSUfhKtsky4u6MCqWJaRL+abzExynfTg== - -"regl@npm:@plotly/regl@^2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@plotly/regl/-/regl-2.1.2.tgz#fd31e3e820ed8824d59a67ab5e766bb101b810b6" - integrity sha512-Mdk+vUACbQvjd0m/1JJjOOafmkp/EpmHjISsopEz5Av44CBq7rPC05HHNbYGKVyNUF2zmEoBS/TT0pd0SPFFyw== - -remove-accents@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" - integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== - -remove-accents@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.4.tgz#73704abf7dae3764295d475d2b6afac4ea23e4d9" - integrity sha512-EpFcOa/ISetVHEXqu+VwI96KZBmq+a8LJnGkaeFw45epGlxIZz5dhEEnNZMsQXgORu3qaMoLX4qJCzOik6ytAg== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-protobuf-schema@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758" - integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ== - dependencies: - protocol-buffers-schema "^3.3.1" - -resolve@^0.6.1: - version "0.6.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-0.6.3.tgz#dd957982e7e736debdf53b58a4dd91754575dd46" - integrity sha512-UHBY3viPlJKf85YijDUcikKX6tmF4SokIDp518ZDVT92JNDcG5uKIthaT/owt3Sar0lwtOafsQuwrg22/v2Dwg== - -resolve@^1.0.0, resolve@^1.1.10, resolve@^1.1.5, resolve@^1.19.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^2.0.0-next.5: - version "2.0.0-next.5" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" - integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -right-now@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/right-now/-/right-now-1.0.0.tgz#6e89609deebd7dcdaf8daecc9aea39cf585a0918" - integrity sha512-DA8+YS+sMIVpbsuKgy+Z67L9Lxb1p05mNxRpDPNksPDEFir4vmBlUtuN9jkTGn9YMMdlBuK7XQgFiz6ws+yhSg== - -rimraf@3.0.2, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rollup@^3.27.1: - version "3.29.5" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.5.tgz#8a2e477a758b520fb78daf04bca4c522c1da8a54" - integrity sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w== - optionalDependencies: - fsevents "~2.3.2" - -rope-sequence@^1.3.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425" - integrity sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rw@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" - integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== - -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-buffer@^5.1.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" - integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== - -scheduler@^0.23.2: - version "0.23.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" - integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== - dependencies: - loose-envify "^1.1.0" - -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.7, semver@^7.5.4: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - 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" - -set-function-name@^2.0.1, set-function-name@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - -shallow-copy@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" - integrity sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signum@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/signum/-/signum-1.0.0.tgz#74a7d2bf2a20b40eba16a92b152124f1d559fa77" - integrity sha512-yodFGwcyt59XRh7w5W3jPcIQb3Bwi21suEfT7MAWnBX3iCdklJpgDgvGT9o04UonglZN5SNMfJFkHIR/jO8GHw== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map@^0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - -source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -stack-trace@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" - integrity sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ== - -static-eval@^2.0.5: - version "2.1.1" - resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.1.1.tgz#71ac6a13aa32b9e14c5b5f063c362176b0d584ba" - integrity sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA== - dependencies: - escodegen "^2.1.0" - -stream-parser@~0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773" - integrity sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ== - dependencies: - debug "2" - -stream-shift@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" - integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== - -string-split-by@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/string-split-by/-/string-split-by-1.0.0.tgz#53895fb3397ebc60adab1f1e3a131f5372586812" - integrity sha512-KaJKY+hfpzNyet/emP81PJA9hTVSfxNLS9SFTWxdCnnW1/zOOwiV248+EfoX7IQFcBaOp4G5YE6xTJMF+pLg6A== - dependencies: - parenthesis "^3.1.5" - -string.prototype.matchall@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" - integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - regexp.prototype.flags "^1.5.2" - set-function-name "^2.0.2" - side-channel "^1.0.6" - -string.prototype.repeat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" - integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strongly-connected-components@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz#0920e2b4df67c8eaee96c6b6234fe29e873dba99" - integrity sha512-i0TFx4wPcO0FwX+4RkLJi1MxmcTv90jNZgxMu9XRnMXMeFUY1VJlIoXpZunPUvUUqbCT1pg5PEkFqqpcaElNaA== - -style-loader@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-4.0.0.tgz#0ea96e468f43c69600011e0589cb05c44f3b17a5" - integrity sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA== - -stylis@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" - integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== - -supercluster@^7.1.0: - version "7.1.5" - resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.5.tgz#65a6ce4a037a972767740614c19051b64b8be5a3" - integrity sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg== - dependencies: - kdbush "^3.0.0" - -supercluster@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5" - integrity sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ== - dependencies: - kdbush "^4.0.2" - -superscript-text@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/superscript-text/-/superscript-text-1.0.0.tgz#e7cb2752567360df50beb0610ce8df3d71d8dfd8" - integrity sha512-gwu8l5MtRZ6koO0icVTlmN5pm7Dhh1+Xpe9O4x6ObMAsW+3jPbW14d1DsBq1F4wiI+WOFjXF35pslgec/G8yCQ== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svg-arc-to-cubic-bezier@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6" - integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g== - -svg-path-bounds@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz#00312f672b08afc432a66ddfbd06db40cec8d0d0" - integrity sha512-H4/uAgLWrppIC0kHsb2/dWUYSmb4GE5UqH06uqWBcg6LBjX2fu0A8+JrO2/FJPZiSsNOKZAhyFFgsLTdYUvSqQ== - dependencies: - abs-svg-path "^0.1.1" - is-svg-path "^1.0.1" - normalize-svg-path "^1.0.0" - parse-svg-path "^0.1.2" - -svg-path-sdf@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/svg-path-sdf/-/svg-path-sdf-1.1.3.tgz#92957a31784c0eaf68945472c8dc6bf9e6d126fc" - integrity sha512-vJJjVq/R5lSr2KLfVXVAStktfcfa1pNFjFOgyJnzZFXlO/fDZ5DmM8FpnSKKzLPfEYTVeXuVBTHF296TpxuJVg== - dependencies: - bitmap-sdf "^1.0.0" - draw-svg-path "^1.0.0" - is-svg-path "^1.0.1" - parse-svg-path "^0.1.2" - svg-path-bounds "^1.0.1" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -through2@^0.6.3: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - integrity sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg== - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" - -through2@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -tiny-invariant@^1.3.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" - integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== - -tinycolor2@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" - integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== - -tinyqueue@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" - integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== - -tinyqueue@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-3.0.0.tgz#101ea761ccc81f979e29200929e78f1556e3661e" - integrity sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g== - -tippy.js@^6.3.7: - version "6.3.7" - resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c" - integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ== - dependencies: - "@popperjs/core" "^2.9.0" - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-float32@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/to-float32/-/to-float32-1.1.0.tgz#39bd3b11eadccd490c08f5f9171da5127b6f3946" - integrity sha512-keDnAusn/vc+R3iEiSDw8TOF7gPiTLdK1ArvWtYbJQiVfmRg6i/CAvbKq3uIS0vWroAC7ZecN3DjQKw3aSklUg== - -to-px@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-px/-/to-px-1.0.1.tgz#5bbaed5e5d4f76445bcc903c293a2307dd324646" - integrity sha512-2y3LjBeIZYL19e5gczp14/uRWFDtDUErJPVN3VU9a7SJO+RjGRtYR47aMN2bZgGlxvW4ZcEz2ddUPVHXcMfuXw== - dependencies: - parse-unit "^1.0.1" - -to-px@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/to-px/-/to-px-1.1.0.tgz#b6b269ed5db0cc9aefc15272a4c8bcb2ca1e99ca" - integrity sha512-bfg3GLYrGoEzrGoE05TAL/Uw+H/qrf2ptr9V3W7U0lkjjyYnIfgxmVLUfhQ1hZpIQwin81uxhDjvUkDYsC0xWw== - dependencies: - parse-unit "^1.0.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -topojson-client@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.1.0.tgz#22e8b1ed08a2b922feeb4af6f53b6ef09a467b99" - integrity sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw== - dependencies: - commander "2" - -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.0.3, tslib@^2.4.0, tslib@^2.6.2: - version "2.8.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" - integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -tus-js-client@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/tus-js-client/-/tus-js-client-4.2.3.tgz#a72b44e93bdf1961085d644b2717e232f1ee1b57" - integrity sha512-UkQUCeDWKh5AwArcasIJWcL5EP66XPypKQtsdPu82wNnTea8eAUHdpDx3DcfZgDERAiCII895zMYkXri4M1wzw== - dependencies: - buffer-from "^1.1.2" - combine-errors "^3.0.3" - is-stream "^2.0.0" - js-base64 "^3.7.2" - lodash.throttle "^4.1.1" - proper-lockfile "^4.1.2" - url-parse "^1.5.7" - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type@^2.7.2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486" - integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== - -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - -typedarray-pool@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/typedarray-pool/-/typedarray-pool-1.2.0.tgz#e7e90720144ba02b9ed660438af6f3aacfe33ac3" - integrity sha512-YTSQbzX43yvtpfRtIDAYygoYtgT+Rpjuxy9iOpczrjpXLgGoyG7aS5USJXV2d3nn8uHTeb9rXDvzS27zUg5KYQ== - dependencies: - bit-twiddle "^1.0.0" - dup "^1.0.0" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - -typescript@^5.1.6: - version "5.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" - integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== - -uc.micro@^2.0.0, uc.micro@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" - integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -unload@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" - integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== - dependencies: - "@babel/runtime" "^7.6.2" - detect-node "^2.0.4" - -unquote@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg== - -update-browserslist-db@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" - integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.0" - -update-diff@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/update-diff/-/update-diff-1.1.0.tgz#f510182d81ee819fb82c3a6b22b62bbdeda7808f" - integrity sha512-rCiBPiHxZwT4+sBhEbChzpO5hYHjm91kScWgdHf4Qeafs6Ba7MBl+d9GlGv72bcTZQO0sLmtQS1pHSWoCLtN/A== - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url-parse@^1.5.7: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - -use-sync-external-store@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" - integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== - -util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -victory-vendor@^36.6.8: - version "36.9.2" - resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801" - integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ== - dependencies: - "@types/d3-array" "^3.0.3" - "@types/d3-ease" "^3.0.0" - "@types/d3-interpolate" "^3.0.1" - "@types/d3-scale" "^4.0.2" - "@types/d3-shape" "^3.1.0" - "@types/d3-time" "^3.0.0" - "@types/d3-timer" "^3.0.0" - d3-array "^3.1.6" - d3-ease "^3.0.1" - d3-interpolate "^3.0.1" - d3-scale "^4.0.2" - d3-shape "^3.1.0" - d3-time "^3.0.0" - d3-timer "^3.0.1" - -vite@^4.3.9: - version "4.5.5" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.5.tgz#639b9feca5c0a3bfe3c60cb630ef28bf219d742e" - integrity sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ== - dependencies: - esbuild "^0.18.10" - postcss "^8.4.27" - rollup "^3.27.1" - optionalDependencies: - fsevents "~2.3.2" - -vt-pbf@^3.1.1, vt-pbf@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.3.tgz#68fd150756465e2edae1cc5c048e063916dcfaac" - integrity sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA== - dependencies: - "@mapbox/point-geometry" "0.1.0" - "@mapbox/vector-tile" "^1.3.1" - pbf "^3.2.1" - -w3c-keyname@^2.2.0: - version "2.2.8" - resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" - integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== - -warning@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" - integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== - dependencies: - loose-envify "^1.0.0" - -weak-map@^1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/weak-map/-/weak-map-1.0.8.tgz#394c18a9e8262e790544ed8b55c6a4ddad1cb1a3" - integrity sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw== - -web-worker@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.3.0.tgz#e5f2df5c7fe356755a5fb8f8410d4312627e6776" - integrity sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA== - -webgl-context@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/webgl-context/-/webgl-context-2.2.0.tgz#8f37d7257cf6df1cd0a49e6a7b1b721b94cc86a0" - integrity sha512-q/fGIivtqTT7PEoF07axFIlHNk/XCPaYpq64btnepopSWvKNFkoORlQYgqDigBIuGA1ExnFd/GnSUnBNEPQY7Q== - dependencies: - get-canvas-context "^1.0.1" - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-builtin-type@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.4.tgz#592796260602fc3514a1b5ee7fa29319b72380c3" - integrity sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w== - dependencies: - function.prototype.name "^1.1.6" - has-tostringtag "^1.0.2" - is-async-function "^2.0.0" - is-date-object "^1.0.5" - is-finalizationregistry "^1.0.2" - is-generator-function "^1.0.10" - is-regex "^1.1.4" - is-weakref "^1.0.2" - isarray "^2.0.5" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.2" - which-typed-array "^1.1.15" - -which-collection@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" - integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== - dependencies: - is-map "^2.0.3" - is-set "^2.0.3" - is-weakmap "^2.0.2" - is-weakset "^2.0.3" - -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -which@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" - integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== - dependencies: - isexe "^3.1.1" - -word-wrap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" - integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== - -world-calendars@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/world-calendars/-/world-calendars-1.0.3.tgz#b25c5032ba24128ffc41d09faf4a5ec1b9c14335" - integrity sha512-sAjLZkBnsbHkHWVhrsCU5Sa/EVuf9QqgvrN8zyJ2L/F9FR9Oc6CvVK0674+PGAtmmmYQMH98tCUSO4QLQv3/TQ== - dependencies: - object-assign "^4.1.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -xml-utils@^1.0.2: - version "1.10.1" - resolved "https://registry.yarnpkg.com/xml-utils/-/xml-utils-1.10.1.tgz#fa0c9b38545760532d4cf89003f90c3b24e7200f" - integrity sha512-Dn6vJ1Z9v1tepSjvnCpwk5QqwIPcEFKdgnjqfYOABv1ngSofuAhtlugcUC3ehS1OHdgDWSG6C5mvj+Qm15udTQ== - -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -xtend@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.2.0.tgz#eef6b1f198c1c8deafad8b1765a04dad4a01c5a9" - integrity sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zstddec@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.1.0.tgz#7050f3f0e0c3978562d0c566b3e5a427d2bad7ec" - integrity sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg== +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.25.7, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.29.7" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/compat-data@npm:7.29.7" + checksum: 10c0/47913f05e08a45a1c9df38c02b4b49e391005085b489432647a1abe112e5d9c75e3be8ea5972b7f6da4ec5d1339922ceb9ea02b8a25d4ed1cb8636e5261f344e + languageName: node + linkType: hard + +"@babel/core@npm:^7.24.4": + version: 7.29.7 + resolution: "@babel/core@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.7" + "@babel/helper-compilation-targets": "npm:^7.29.7" + "@babel/helper-module-transforms": "npm:^7.29.7" + "@babel/helpers": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/template": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/112fb09c24de7a1de64d1de2c31fe65c4e6af4cb2fb6e6d99ea5373e6fc51e75b88581c0efae4c4c68f119a02a988c7106e95011a41530a2fb8ed793c7eaa07b + languageName: node + linkType: hard + +"@babel/generator@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/generator@npm:7.25.7" + dependencies: + "@babel/types": "npm:^7.25.7" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^3.0.2" + checksum: 10c0/c03a26c79864d60d04ce36b649c3fa0d6fd7b2bf6a22e22854a0457aa09206508392dd73ee40e7bc8d50b3602f9ff068afa47770cda091d332e7db1ca382ee96 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.29.7, @babel/generator@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/generator@npm:7.29.8" + dependencies: + "@babel/parser": "npm:^7.29.8" + "@babel/types": "npm:^7.29.8" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/7b896696314a659652393b76d78276e236acd0f7fae40a9a1af7f01c76aeafc630dd0966aad6f9386d35d7c674a8e6e2d8e217c44d25fb11460e68afa9ba8441 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-compilation-targets@npm:7.29.7" + dependencies: + "@babel/compat-data": "npm:^7.29.7" + "@babel/helper-validator-option": "npm:^7.29.7" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/4c15fd4c69a0a7047799a28a88460c19cede0a0ee8af994ea169114986f4af48b92c7393a4a3fee0456c11a656eece3448a6ed06354453d6c27cccf17195453b + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.16.7": + version: 7.25.7 + resolution: "@babel/helper-module-imports@npm:7.25.7" + dependencies: + "@babel/traverse": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10c0/0fd0c3673835e5bf75558e184bcadc47c1f6dd2fe2016d53ebe1e5a6ae931a44e093015c2f9a6651c1a89f25c76d9246710c2b0b460b95ee069c464f2837fa2c + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" + dependencies: + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-transforms@npm:7.29.7" + dependencies: + "@babel/helper-module-imports": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + "@babel/traverse": "npm:^7.29.7" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ee5a2172c24a42be696836f4b0d947489c9729d8adf5821885cf77d1ad5333e3c447368e9a71f67df1099570490553dccf9f888ef0a92a48aa63cb086bd8c7e1 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/helper-string-parser@npm:7.25.7" + checksum: 10c0/73ef2ceb81f8294678a0afe8ab0103729c0370cac2e830e0d5128b03be5f6a2635838af31d391d763e3c5a4460ed96f42fd7c9b552130670d525be665913bc4c + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.25.7, @babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-option@npm:7.29.7" + checksum: 10c0/d2a06c6d0ac40ba4a2f219fc2cab249c7a94bacdb2686273b7f9598571c908809b48468ff588915a346e6cc7296f60b581023d1d498b747fed06f779d335c2cc + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helpers@npm:7.29.7" + dependencies: + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/218e8d10953647c9f44775f5a022b227a182674853b5ea8631889deb7e1a3e4bc870388aaecf59bb8bd92a87f9a96220ed3f70a35bffec6bcf9169ecb67891ac + languageName: node + linkType: hard + +"@babel/parser@npm:^7.24.4, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/parser@npm:7.29.8" + dependencies: + "@babel/types": "npm:^7.29.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/acc890c5e6a6dd40863a47b50bac111d7185ee6fbbe163ebe11d5214854ca2adb901462ad4d718a65090ef84bd2230e9e8ab45a2e0caccc685f1f57ab0bb1e28 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.25.7": + version: 7.25.8 + resolution: "@babel/parser@npm:7.25.8" + dependencies: + "@babel/types": "npm:^7.25.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/a1a13845b7e8dda4c970791814a4bbf60004969882f18f470e260ad822d2e1f8941948f851e9335895563610f240fa6c98481ce8019865e469502bbf21daafa4 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": + version: 7.25.7 + resolution: "@babel/runtime@npm:7.25.7" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10c0/86b7829d2fc9343714a9afe92757cf96c4dc799006ca61d73cda62f4b9e29bfa1ce36794955bc6cb4c188f5b10db832c949339895e1bbe81a69022d9d578ce29 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e + languageName: node + linkType: hard + +"@babel/template@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/template@npm:7.25.7" + dependencies: + "@babel/code-frame": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + checksum: 10c0/8ae9e36e4330ee83d4832531d1d9bec7dc2ef6a2a8afa1ef1229506fd60667abcb17f306d1c3d7e582251270597022990c845d5d69e7add70a5aea66720decb9 + languageName: node + linkType: hard + +"@babel/template@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.25.7": + version: 7.25.7 + resolution: "@babel/traverse@npm:7.25.7" + dependencies: + "@babel/code-frame": "npm:^7.25.7" + "@babel/generator": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.7" + "@babel/template": "npm:^7.25.7" + "@babel/types": "npm:^7.25.7" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/75d73e52c507a7a7a4c7971d6bf4f8f26fdd094e0d3a0193d77edf6a5efa36fc3db91ec5cc48e8b94e6eb5d5ad21af0a1040e71309172851209415fd105efb1a + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.29.7": + version: 7.29.8 + resolution: "@babel/traverse@npm:7.29.8" + dependencies: + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.8" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.8" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.8" + debug: "npm:^4.3.1" + checksum: 10c0/87a28989c434add26d787776ac6d30f749b89cb030f2a605c89f671a516a6fa165ac0476f07e2b69feed70128b2734cae1cbbf41dfe95ccf22081bd8f8b91923 + languageName: node + linkType: hard + +"@babel/types@npm:^7.25.7, @babel/types@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/types@npm:7.25.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.7" + "@babel/helper-validator-identifier": "npm:^7.25.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/55ca2d6df6426c98db2769ce884ce5e9de83a512ea2dd7bcf56c811984dc14351cacf42932a723630c5afcff2455809323decd645820762182f10b7b5252b59f + languageName: node + linkType: hard + +"@babel/types@npm:^7.29.7, @babel/types@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/types@npm:7.29.8" + dependencies: + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/be7c279f0abf2a086c633e21b49c7ca80275d05283cc5a268b67a708c9914bd0c944f1422b3eb3cb37682a2af5d560abf520ccf9b01b53ecbfe6b71fbc3fdde6 + languageName: node + linkType: hard + +"@dimforge/rapier3d-compat@npm:~0.12.0": + version: 0.12.0 + resolution: "@dimforge/rapier3d-compat@npm:0.12.0" + checksum: 10c0/c66c24f90649c0fc870679c12e7fec1a111080d44450169b57561f957d7b6b284ad8a3ceeba95533e213176ea171351acebd3dd43885fafb33f18bfbd9d507db + languageName: node + linkType: hard + +"@emotion/babel-plugin@npm:^11.13.5": + version: 11.13.5 + resolution: "@emotion/babel-plugin@npm:11.13.5" + dependencies: + "@babel/helper-module-imports": "npm:^7.16.7" + "@babel/runtime": "npm:^7.18.3" + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/serialize": "npm:^1.3.3" + babel-plugin-macros: "npm:^3.1.0" + convert-source-map: "npm:^1.5.0" + escape-string-regexp: "npm:^4.0.0" + find-root: "npm:^1.1.0" + source-map: "npm:^0.5.7" + stylis: "npm:4.2.0" + checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb + languageName: node + linkType: hard + +"@emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + "@emotion/sheet": "npm:^1.4.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + stylis: "npm:4.2.0" + checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724 + languageName: node + linkType: hard + +"@emotion/hash@npm:^0.9.2": + version: 0.9.2 + resolution: "@emotion/hash@npm:0.9.2" + checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2 + languageName: node + linkType: hard + +"@emotion/is-prop-valid@npm:^1.3.0": + version: 1.3.1 + resolution: "@emotion/is-prop-valid@npm:1.3.1" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + checksum: 10c0/123215540c816ff510737ec68dcc499c53ea4deb0bb6c2c27c03ed21046e2e69f6ad07a7a174d271c6cfcbcc9ea44e1763e0cf3875c92192f7689216174803cd + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 + languageName: node + linkType: hard + +"@emotion/react@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + hoist-non-react-statics: "npm:^3.3.1" + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031 + languageName: node + linkType: hard + +"@emotion/serialize@npm:^1.3.3": + version: 1.3.3 + resolution: "@emotion/serialize@npm:1.3.3" + dependencies: + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/unitless": "npm:^0.10.0" + "@emotion/utils": "npm:^1.4.2" + csstype: "npm:^3.0.2" + checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4 + languageName: node + linkType: hard + +"@emotion/sheet@npm:^1.4.0": + version: 1.4.0 + resolution: "@emotion/sheet@npm:1.4.0" + checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7 + languageName: node + linkType: hard + +"@emotion/styled@npm:^11.14.0": + version: 11.14.1 + resolution: "@emotion/styled@npm:11.14.1" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/is-prop-valid": "npm:^1.3.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/2bbf8451df49c967e41fbcf8111a7f6dafe6757f0cc113f2f6e287206c45ac1d54dc8a95a483b7c0cee8614b8a8d08155bded6453d6721de1f8cc8d5b9216963 + languageName: node + linkType: hard + +"@emotion/unitless@npm:^0.10.0": + version: 0.10.0 + resolution: "@emotion/unitless@npm:0.10.0" + checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 + languageName: node + linkType: hard + +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08 + languageName: node + linkType: hard + +"@emotion/utils@npm:^1.4.2": + version: 1.4.2 + resolution: "@emotion/utils@npm:1.4.2" + checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a + languageName: node + linkType: hard + +"@emotion/weak-memoize@npm:^0.4.0": + version: 0.4.0 + resolution: "@emotion/weak-memoize@npm:0.4.0" + checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": + version: 4.10.1 + resolution: "@eslint-community/eslint-utils@npm:4.10.1" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/b514586655698bc6b74db72a496c77e813c78b63e36a83429845ac7057dd77113b0b6c31e6e590d5baaeee2abae6ea22363a41209bcafa078d895ab83ce83011 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.12.2": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d + languageName: node + linkType: hard + +"@eslint/config-array@npm:^0.23.5": + version: 0.23.5 + resolution: "@eslint/config-array@npm:0.23.5" + dependencies: + "@eslint/object-schema": "npm:^3.0.5" + debug: "npm:^4.3.1" + minimatch: "npm:^10.2.4" + checksum: 10c0/b24833c4c76e78ee075d306cd3f095db46b2db0f90cc13a6ee6e4275f9889731c05bf5403ab5fefb79c756e07ac9184ed0e04570341382f9eccbccc80e6d1a0c + languageName: node + linkType: hard + +"@eslint/config-helpers@npm:^0.7.0": + version: 0.7.0 + resolution: "@eslint/config-helpers@npm:0.7.0" + dependencies: + "@eslint/core": "npm:^1.2.1" + checksum: 10c0/fd40d57d6f1db49f7b647048b88a433dc7f6522ef3edf855a43cb526ef4fc40622ceed0dc8de2e03d254f30f8e035370570de1d4bd8e7c2b1200131451e0d331 + languageName: node + linkType: hard + +"@eslint/core@npm:^1.2.1": + version: 1.2.1 + resolution: "@eslint/core@npm:1.2.1" + dependencies: + "@types/json-schema": "npm:^7.0.15" + checksum: 10c0/10979b40588ecfef771fcb5013a542a35fb30692cc95a65f3481b0b36fbd89f5679efeb30d57f4eed35203d859aabace2a620177d6c536f71b299a1af2f3398f + languageName: node + linkType: hard + +"@eslint/js@npm:^10.0.1": + version: 10.0.1 + resolution: "@eslint/js@npm:10.0.1" + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + checksum: 10c0/9f3fcaf71ba7fdf65d82e8faad6ecfe97e11801cc3c362b306a88ea1ed1344ae0d35330dddb0e8ad18f010f6687a70b75491b9e01c8af57acd7987cee6b3ec6c + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^3.0.5": + version: 3.0.5 + resolution: "@eslint/object-schema@npm:3.0.5" + checksum: 10c0/1db337431f520b99e9edda64ef5fafd7ec6a029843eeb608753025125b6649d861d843cffafafd3c4e37926d7d5f9ec0c6a8e3665c13c3da2144e8132892e92e + languageName: node + linkType: hard + +"@eslint/plugin-kit@npm:^0.7.2": + version: 0.7.2 + resolution: "@eslint/plugin-kit@npm:0.7.2" + dependencies: + "@eslint/core": "npm:^1.2.1" + levn: "npm:^0.4.1" + checksum: 10c0/aafba08077bcd6d7dde6c2e21db18086046a88f914f29971a84cac9ad2d48952ded1b293e665e523805297eff756522dafa16f0062195e2c7143dcd1d47d11ed + languageName: node + linkType: hard + +"@humanfs/core@npm:^0.19.2": + version: 0.19.2 + resolution: "@humanfs/core@npm:0.19.2" + dependencies: + "@humanfs/types": "npm:^0.15.0" + checksum: 10c0/d0a1d52d7b30c27d49475a53072d1510b81c5803e44b342fb8faf3887f1aa27593a1e6dc76a45268e7892d3f4e198146659281f6b6d55eacf3fd5a38bac30c5c + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.6": + version: 0.16.8 + resolution: "@humanfs/node@npm:0.16.8" + dependencies: + "@humanfs/core": "npm:^0.19.2" + "@humanfs/types": "npm:^0.15.0" + "@humanwhocodes/retry": "npm:^0.4.0" + checksum: 10c0/56140579db811af4e160b195d45d0f29acf644d192c93fe24c9e594ebf06f19dfc157494a07c84540b8a071c0e4b37209c2362765d31734f4d0be869c2422e25 + languageName: node + linkType: hard + +"@humanfs/types@npm:^0.15.0": + version: 0.15.0 + resolution: "@humanfs/types@npm:0.15.0" + checksum: 10c0/fc26b9a024b0e55f7eaf64036df94345bf5d36d6a41ef80ef38e78f1f7430ce26cf435af736adae58913baae18eac3f38c18739054a3d379102015978eae862e + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@mui/core-downloads-tracker@npm:^9.3.1": + version: 9.3.1 + resolution: "@mui/core-downloads-tracker@npm:9.3.1" + checksum: 10c0/ca04394edbcfb62ad88174477f69bd6e472a61446ad70fa1574091a6a3e9dcdac46d4a7f4551c8189990974a861c82cf8983b023fbd6f1d1172ab192d1c79a79 + languageName: node + linkType: hard + +"@mui/icons-material@npm:^5.16.12 || ^6.0.0 || ^7.0.0 || ^9.0.0, @mui/icons-material@npm:^9.3.1": + version: 9.3.1 + resolution: "@mui/icons-material@npm:9.3.1" + dependencies: + "@babel/runtime": "npm:^7.29.7" + peerDependencies: + "@mui/material": ^9.3.1 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/108c331f96e885799246e1fd55bde61a0e7fbdb681440345ccae7e720ca63dfc2a3137849fbb2147b4252a4dc7012ffe96184966db786593379f8d38fc31718a + languageName: node + linkType: hard + +"@mui/material@npm:^5.16.12 || ^6.0.0 || ^7.0.0 || ^9.0.0, @mui/material@npm:^9.3.1": + version: 9.3.1 + resolution: "@mui/material@npm:9.3.1" + dependencies: + "@babel/runtime": "npm:^7.29.7" + "@mui/core-downloads-tracker": "npm:^9.3.1" + "@mui/system": "npm:^9.3.0" + "@mui/types": "npm:^9.3.0" + "@mui/utils": "npm:^9.3.0" + "@popperjs/core": "npm:^2.11.8" + "@types/react-transition-group": "npm:^4.4.12" + clsx: "npm:^2.1.1" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.8" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material-pigment-css": ^9.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@mui/material-pigment-css": + optional: true + "@types/react": + optional: true + checksum: 10c0/88000cb5ffbce365a8186eefeb5b3d2d918b8d349e2dec82cd7c5b58e4a3aa1b32ef46513c4c5c16f87427798752fc917ae5db7ccca51198ae7637333629baa9 + languageName: node + linkType: hard + +"@mui/private-theming@npm:^9.3.0": + version: 9.3.0 + resolution: "@mui/private-theming@npm:9.3.0" + dependencies: + "@babel/runtime": "npm:^7.29.7" + "@mui/utils": "npm:^9.3.0" + prop-types: "npm:^15.8.1" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/20be542d0106654f9924fffa63df2d9417d3a8ca23bacab8a15bfdb175d89e8bb91d0b5bae3006d1fbc715a3bc65db671f4208190572d01a7fb377dbfc267649 + languageName: node + linkType: hard + +"@mui/styled-engine@npm:^9.3.0": + version: 9.3.0 + resolution: "@mui/styled-engine@npm:9.3.0" + dependencies: + "@babel/runtime": "npm:^7.29.7" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/sheet": "npm:^1.4.0" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 10c0/4bec2589181d7d18c585e051648a3afe1bfff83c7582ac5dc8e0137d5998eed0ef03bc90e97cf30c9fe653e07e11863b23217d708094236ace076c6263661d22 + languageName: node + linkType: hard + +"@mui/system@npm:^9.3.0": + version: 9.3.0 + resolution: "@mui/system@npm:9.3.0" + dependencies: + "@babel/runtime": "npm:^7.29.7" + "@mui/private-theming": "npm:^9.3.0" + "@mui/styled-engine": "npm:^9.3.0" + "@mui/types": "npm:^9.3.0" + "@mui/utils": "npm:^9.3.0" + clsx: "npm:^2.1.1" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10c0/b0c8f8c91a12d1e99ffd0c883ac679b5684a0a4f503fbd966e885f15d222488f3029bf2c433d0dc82381524d59e79536c7d547c5e38cc836b02b99ce2cec52f5 + languageName: node + linkType: hard + +"@mui/types@npm:^9.3.0": + version: 9.3.0 + resolution: "@mui/types@npm:9.3.0" + dependencies: + "@babel/runtime": "npm:^7.29.7" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/4a97c551c75a00ef7057a36a0503e3462170f344e2f78895e940123dc744943ea14ff507efb39e29d5b83ec518a657a002baf8982ac8672565f521a59bfd56fc + languageName: node + linkType: hard + +"@mui/utils@npm:^9.3.0": + version: 9.3.0 + resolution: "@mui/utils@npm:9.3.0" + dependencies: + "@babel/runtime": "npm:^7.29.7" + "@mui/types": "npm:^9.3.0" + "@types/prop-types": "npm:^15.7.15" + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.8" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/a8c2a3aad433171c616a149c592a9507ec1e13c79eb2fa519d95b81dc51425e6931e48026d6a7d331daac7f25baf14a9201c0986429d4069f7ad679940c92d0f + languageName: node + linkType: hard + +"@oxc-project/types@npm:=0.144.0": + version: 0.144.0 + resolution: "@oxc-project/types@npm:0.144.0" + checksum: 10c0/997c6c33f09706af604ece0e99c698965757887aca4b42c5fb5ddcb11c39876c1e824b188b1e6582276355468a9b13f955d3b822d3b532cd6bedbeb64b603ff0 + languageName: node + linkType: hard + +"@popperjs/core@npm:^2.11.8": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 + languageName: node + linkType: hard + +"@react-leaflet/core@npm:^3.0.0": + version: 3.0.0 + resolution: "@react-leaflet/core@npm:3.0.0" + peerDependencies: + leaflet: ^1.9.0 + react: ^19.0.0 + react-dom: ^19.0.0 + checksum: 10c0/1e20f92ea99d378121d7ba57b9571ca3a67a86247729d8cd5726ded26105fcbffbbdf727da34b2ad5976438979819a38c93b94389313abb3ff80ca81632609a6 + languageName: node + linkType: hard + +"@redocly/ajv@npm:8.11.2": + version: 8.11.2 + resolution: "@redocly/ajv@npm:8.11.2" + dependencies: + fast-deep-equal: "npm:^3.1.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + uri-js-replace: "npm:^1.0.1" + checksum: 10c0/249ca2e237f7b1248ee1018ba1ad3a739cb9f16e5f7fe821875948806980d65246c79ef7d5e7bd8db773c120e2cd5ce15aa47883893608e1965ca4d45c5572f4 + languageName: node + linkType: hard + +"@redocly/config@npm:0.22.0": + version: 0.22.0 + resolution: "@redocly/config@npm:0.22.0" + checksum: 10c0/4eeaf82d9c72abcecfaecd0a6d8b109cab3bcb74fa25cd4fccd2de5d7dfd221b0ffe1d3f2ae832a2d86fcfb3c41e7560304102a4618c387e9339bf18848124ae + languageName: node + linkType: hard + +"@redocly/openapi-core@npm:^1.34.6": + version: 1.34.19 + resolution: "@redocly/openapi-core@npm:1.34.19" + dependencies: + "@redocly/ajv": "npm:8.11.2" + "@redocly/config": "npm:0.22.0" + colorette: "npm:1.4.0" + https-proxy-agent: "npm:7.0.6" + js-levenshtein: "npm:1.1.6" + js-yaml: "npm:4.3.1" + minimatch: "npm:5.1.9" + pluralize: "npm:8.0.0" + yaml-ast-parser: "npm:0.0.43" + checksum: 10c0/1cf46f5158a2620520d0e2eeb17a2f697bda4ad6df8b615d39456e56fd6abbbc9e7d971be91802a96d2842c6f02fc9e3c8ba0c4bfb24206989be93536a61a412 + languageName: node + linkType: hard + +"@rolldown/binding-android-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-android-arm64@npm:1.2.4" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-darwin-arm64@npm:1.2.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-darwin-x64@npm:1.2.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-freebsd-x64@npm:1.2.4" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.4" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.4" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.2.4": + version: 1.2.4 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:^1.0.0, @rolldown/pluginutils@npm:^1.0.1": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd + languageName: node + linkType: hard + +"@tanstack/query-core@npm:5.101.4": + version: 5.101.4 + resolution: "@tanstack/query-core@npm:5.101.4" + checksum: 10c0/a407960303431e6498c6518cf6f8cc7283d3e4a95dbdc5e9fa42c217b9dd7afaffb82a4f2d36b38f578f745e8cf69ff8551abb947f5abcc0a290f3e3e75dc654 + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^5.83.0": + version: 5.101.4 + resolution: "@tanstack/react-query@npm:5.101.4" + dependencies: + "@tanstack/query-core": "npm:5.101.4" + peerDependencies: + react: ^18 || ^19 + checksum: 10c0/92e7c5b44740e753b05a6a19c166ea1a000df64c1bf5edb315d08a5b555a9f16a9d35288ac38f650bd38fc3015b135c392749fd6db24d09db42bfaf758d119fa + languageName: node + linkType: hard + +"@tweenjs/tween.js@npm:~23.1.3": + version: 23.1.3 + resolution: "@tweenjs/tween.js@npm:23.1.3" + checksum: 10c0/811b30f5f0e7409fb41833401c501c2d6f600eb5f43039dd9067a7f70aff6dad5f5ce1233848e13f0b33a269a160d9c133f344d986cbff4f1f6b72ddecd06c89 + languageName: node + linkType: hard + +"@types/esrecurse@npm:^4.3.1": + version: 4.3.1 + resolution: "@types/esrecurse@npm:4.3.1" + checksum: 10c0/90dad74d5da3ad27606d8e8e757322f33171cfeaa15ad558b615cf71bb2a516492d18f55f4816384685a3eb2412142e732bbae9a4a7cd2cf3deb7572aa4ebe03 + languageName: node + linkType: hard + +"@types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 + languageName: node + linkType: hard + +"@types/geojson@npm:*": + version: 7946.0.14 + resolution: "@types/geojson@npm:7946.0.14" + checksum: 10c0/54f3997708fa2970c03eeb31f7e4540a0eb6387b15e9f8a60513a1409c23cafec8d618525404573468b59c6fecbfd053724b3327f7fca416729c26271d799f55 + languageName: node + linkType: hard + +"@types/json-schema@npm:^7.0.15": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/leaflet@npm:^1.9.12": + version: 1.9.22 + resolution: "@types/leaflet@npm:1.9.22" + dependencies: + "@types/geojson": "npm:*" + checksum: 10c0/ce2e09d0f208244478c6103ba64726b33a474f89f303860c7b0494ca1bb44f1744285d93c90093c95669497c7dbdc7c206495af0b741fe6e251bf106b0415593 + languageName: node + linkType: hard + +"@types/parse-json@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/parse-json@npm:4.0.2" + checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 + languageName: node + linkType: hard + +"@types/prop-types@npm:^15.7.15": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + +"@types/react-dom@npm:^19.2.4": + version: 19.2.4 + resolution: "@types/react-dom@npm:19.2.4" + peerDependencies: + "@types/react": ^19.2.0 + checksum: 10c0/b7d854ce17bb51a3a067168268a90f0123d10dc490f63f1ff5409f07ac715febeb8f5b7b473404a9d437106571e0312fc2937a945634d590abfc9aa46a14b01b + languageName: node + linkType: hard + +"@types/react-transition-group@npm:^4.4.12": + version: 4.4.12 + resolution: "@types/react-transition-group@npm:4.4.12" + peerDependencies: + "@types/react": "*" + checksum: 10c0/0441b8b47c69312c89ec0760ba477ba1a0808a10ceef8dc1c64b1013ed78517332c30f18681b0ec0b53542731f1ed015169fed1d127cc91222638ed955478ec7 + languageName: node + linkType: hard + +"@types/react@npm:^19.2.18": + version: 19.2.18 + resolution: "@types/react@npm:19.2.18" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/d04216172b4b4362b310017c210dfbe019fb4f4e7dffd0313e70b3acb3051d5c3e76e7e84e3cf4c6a3c824993ffadfe3949e589a6398a09d4200e7670e2962de + languageName: node + linkType: hard + +"@types/stats.js@npm:*": + version: 0.17.4 + resolution: "@types/stats.js@npm:0.17.4" + checksum: 10c0/4fe0429998519f0476f03a25b4900b4d4a1474606478657271e40a884f7936ba902ea564b1c95cfd33a8e84af46cef6e1e98bb23e86fd3b6676cd5b974987151 + languageName: node + linkType: hard + +"@types/three@npm:^0.185.4": + version: 0.185.4 + resolution: "@types/three@npm:0.185.4" + dependencies: + "@dimforge/rapier3d-compat": "npm:~0.12.0" + "@tweenjs/tween.js": "npm:~23.1.3" + "@types/stats.js": "npm:*" + "@types/webxr": "npm:>=0.5.17" + fflate: "npm:~0.8.2" + meshoptimizer: "npm:~1.1.1" + checksum: 10c0/f9cc28d3f343067e56741017bb0ac0257d8d2994707c2ee69512b668a57c8cd8892798978e54b2224920e9495eebe0878fcd8ea18b9a5ab555a7d445c689f804 + languageName: node + linkType: hard + +"@types/trusted-types@npm:^2.0.7": + version: 2.0.7 + resolution: "@types/trusted-types@npm:2.0.7" + checksum: 10c0/4c4855f10de7c6c135e0d32ce462419d8abbbc33713b31d294596c0cc34ae1fa6112a2f9da729c8f7a20707782b0d69da3b1f8df6645b0366d08825ca1522e0c + languageName: node + linkType: hard + +"@types/webxr@npm:>=0.5.17": + version: 0.5.24 + resolution: "@types/webxr@npm:0.5.24" + checksum: 10c0/ff59ffd390d06ca3f89ab2531d583ac10bc5e2ab82e5a01ecc40fbc365224a3375e7caa5b14649cf6141db21fb024940da7ad2bd8faa3cd497a6665257cb53b5 + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.67.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.12.2" + "@typescript-eslint/scope-manager": "npm:8.67.0" + "@typescript-eslint/type-utils": "npm:8.67.0" + "@typescript-eslint/utils": "npm:8.67.0" + "@typescript-eslint/visitor-keys": "npm:8.67.0" + ignore: "npm:^7.0.5" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + "@typescript-eslint/parser": ^8.67.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/5962baaa764cd6350dfdf51c9a09fdd9ee6be65982ac562ee9b732f851744158e5006bf8ce61556a93534d831be8300fd89d7b79b6b4d7170dc2381d987dc8d9 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/parser@npm:8.67.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:8.67.0" + "@typescript-eslint/types": "npm:8.67.0" + "@typescript-eslint/typescript-estree": "npm:8.67.0" + "@typescript-eslint/visitor-keys": "npm:8.67.0" + debug: "npm:^4.4.3" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/8f6f8fbea429509ca0c95c4d48a45da0c890172a590606d65587e75a3ca762c3e5678a20a63fc9e386b0b21b7aa643ba9724354ceaa75bc8f62d0b22cb0198c8 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/project-service@npm:8.67.0" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.67.0" + "@typescript-eslint/types": "npm:^8.67.0" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/d8f89dc209f2186c04fb1c42c1a5c69c21696a7a57c5f2be2222682a6440b49ec32687ae85cbd1c1c164431635fbc37bb9404b336e5748966e270ee1347e028c + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/scope-manager@npm:8.67.0" + dependencies: + "@typescript-eslint/types": "npm:8.67.0" + "@typescript-eslint/visitor-keys": "npm:8.67.0" + checksum: 10c0/8f1fe7dffcb6929ad66dcdca77dd1e4a703f18ab3ab1d685458af74dad10b9be05795e64bd58ff60b45cda8d45737240c84874674d39140c2689e00e3ee82bb9 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.67.0, @typescript-eslint/tsconfig-utils@npm:^8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.67.0" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/c19161347ea3d7a0081653c4d399275a3e0d66f87a24131fb5a358e6b55bc27d709781dbda16382f3f721d790338dcf42c65c9e6c26077123e9f3d076d37a894 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/type-utils@npm:8.67.0" + dependencies: + "@typescript-eslint/types": "npm:8.67.0" + "@typescript-eslint/typescript-estree": "npm:8.67.0" + "@typescript-eslint/utils": "npm:8.67.0" + debug: "npm:^4.4.3" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/0970ea126f9b63a6eb9ca3525a6bf3065dffb3f61a00bd0ee84967140e7ecffe45cafda0cd2c90233dcc06b19c8fb98f4cd90ba8ebbb12deaf9501b9f5b54d8c + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:8.67.0, @typescript-eslint/types@npm:^8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/types@npm:8.67.0" + checksum: 10c0/b892a00d4cbea9604a0abf6eedd0ea019b27df4220a1d90e0035101cb4f846722c9e3eeadce40b423c1e0240709bbc787c6ca49bcc4f8c25ba23b4bd0436492a + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.67.0" + dependencies: + "@typescript-eslint/project-service": "npm:8.67.0" + "@typescript-eslint/tsconfig-utils": "npm:8.67.0" + "@typescript-eslint/types": "npm:8.67.0" + "@typescript-eslint/visitor-keys": "npm:8.67.0" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.5.0" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/449350fcf4f4ee55a6bb7a73302c4eef072a1282d366344cb625c0f17231eb4088251e6ce61fb48d0d536febb9a28f3725b9bd78ac8d0dbf9c161cf51745870c + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/utils@npm:8.67.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.67.0" + "@typescript-eslint/types": "npm:8.67.0" + "@typescript-eslint/typescript-estree": "npm:8.67.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/cccaca1aeba52ec332ee95f3367b84eaf5dd9d60a0d1b4332ebe41b775fa7e8b8faf412de4d88b73a22f84b01f6ab48496c61863407b08fcbef9af429b6b89f1 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.67.0": + version: 8.67.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.67.0" + dependencies: + "@typescript-eslint/types": "npm:8.67.0" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/d43e6151cd1cdbcfb2f9fa54946198baaebaf0b7a9057ccb2f1aeba1aa3eb46708a97cd7773dcfd0eba3bd45642a5520f4607001cd27d8958e41aa56b2fe7a35 + languageName: node + linkType: hard + +"@vitejs/plugin-react@npm:^6.0.5": + version: 6.0.5 + resolution: "@vitejs/plugin-react@npm:6.0.5" + dependencies: + "@rolldown/pluginutils": "npm:^1.0.1" + peerDependencies: + "@rolldown/plugin-babel": ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + "@rolldown/plugin-babel": + optional: true + babel-plugin-react-compiler: + optional: true + checksum: 10c0/fb02246fe3652d7fb746190bdd098b9e29918dfcf8c67b9c0c37300ce789e82331b2ba761530ac6ac9a61390b774d44f401787bd1c87a6c866d1e74a745cc1a0 + languageName: node + linkType: hard + +"abbrev@npm:^5.0.0": + version: 5.0.0 + resolution: "abbrev@npm:5.0.0" + checksum: 10c0/8e88f5c798ea4562d28c5a3e9ad69e3879890bc5d695d8f2dffb8609be4c890aacc8f80ef4553fdd2c6a62d70c2ce8bc57b38074e383beb7487bdafa9ed42ea5 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.16.0": + version: 8.18.0 + resolution: "acorn@npm:8.18.0" + bin: + acorn: bin/acorn + checksum: 10c0/be771be2135cc07910cf76f444ad514d7dcfd6d4a8026e597e93155275abc8ef61eee12211d52146e9d962874269b634f397464942087be013c89d0c54c5f8e5 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"ajv@npm:^6.14.0": + version: 6.15.0 + resolution: "ajv@npm:6.15.0" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/67966499dd272ecde1c2e467084411132891523d057487587879d39ac04207f4351b7b2324c83198013967fbfa632c1612adc960114a30770fbe07a0773b32c2 + languageName: node + linkType: hard + +"ansi-colors@npm:^4.1.3": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"array-buffer-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "array-buffer-byte-length@npm:1.0.1" + dependencies: + call-bind: "npm:^1.0.5" + is-array-buffer: "npm:^3.0.4" + checksum: 10c0/f5cdf54527cd18a3d2852ddf73df79efec03829e7373a8322ef5df2b4ef546fb365c19c71d6b42d641cb6bfe0f1a2f19bc0ece5b533295f86d7c3d522f228917 + languageName: node + linkType: hard + +"array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + is-array-buffer: "npm:^3.0.5" + checksum: 10c0/74e1d2d996941c7a1badda9cabb7caab8c449db9086407cad8a1b71d2604cc8abf105db8ca4e02c04579ec58b7be40279ddb09aea4784832984485499f48432d + languageName: node + linkType: hard + +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": + version: 3.1.8 + resolution: "array-includes@npm:3.1.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + is-string: "npm:^1.0.7" + checksum: 10c0/5b1004d203e85873b96ddc493f090c9672fd6c80d7a60b798da8a14bff8a670ff95db5aafc9abc14a211943f05220dacf8ea17638ae0af1a6a47b8c0b48ce370 + languageName: node + linkType: hard + +"array.prototype.findlast@npm:^1.2.5": + version: 1.2.5 + resolution: "array.prototype.findlast@npm:1.2.5" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ddc952b829145ab45411b9d6adcb51a8c17c76bf89c9dd64b52d5dffa65d033da8c076ed2e17091779e83bc892b9848188d7b4b33453c5565e65a92863cb2775 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.1": + version: 1.3.2 + resolution: "array.prototype.flat@npm:1.3.2" + dependencies: + call-bind: "npm:^1.0.2" + define-properties: "npm:^1.2.0" + es-abstract: "npm:^1.22.1" + es-shim-unscopables: "npm:^1.0.0" + checksum: 10c0/a578ed836a786efbb6c2db0899ae80781b476200617f65a44846cb1ed8bd8b24c8821b83703375d8af639c689497b7b07277060024b9919db94ac3e10dc8a49b + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flatmap@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ba899ea22b9dc9bf276e773e98ac84638ed5e0236de06f13d63a90b18ca9e0ec7c97d622d899796e3773930b946cd2413d098656c0c5d8cc58c6f25c21e6bd54 + languageName: node + linkType: hard + +"array.prototype.tosorted@npm:^1.1.4": + version: 1.1.4 + resolution: "array.prototype.tosorted@npm:1.1.4" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.3" + es-errors: "npm:^1.3.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/eb3c4c4fc0381b0bf6dba2ea4d48d367c2827a0d4236a5718d97caaccc6b78f11f4cadf090736e86301d295a6aa4967ed45568f92ced51be8cbbacd9ca410943 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.3": + version: 1.0.3 + resolution: "arraybuffer.prototype.slice@npm:1.0.3" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.5" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.22.3" + es-errors: "npm:^1.2.1" + get-intrinsic: "npm:^1.2.3" + is-array-buffer: "npm:^3.0.4" + is-shared-array-buffer: "npm:^1.0.2" + checksum: 10c0/d32754045bcb2294ade881d45140a5e52bda2321b9e98fa514797b7f0d252c4c5ab0d1edb34112652c62fa6a9398def568da63a4d7544672229afea283358c36 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + is-array-buffer: "npm:^3.0.4" + checksum: 10c0/2f2459caa06ae0f7f615003f9104b01f6435cc803e11bd2a655107d52a1781dc040532dc44d93026b694cc18793993246237423e13a5337e86b43ed604932c06 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + +"attr-accept@npm:^2.2.2": + version: 2.2.4 + resolution: "attr-accept@npm:2.2.4" + checksum: 10c0/602d88b40cb039f1159b86e389ca4f908c13dba513753f7c511e69499ba6216c153519f31a484bac9c9efa633f8f6a4ec25b4f777bd55198f8cb2514cef04618 + languageName: node + linkType: hard + +"autosuggest-highlight@npm:^3.1.1": + version: 3.3.4 + resolution: "autosuggest-highlight@npm:3.3.4" + dependencies: + remove-accents: "npm:^0.4.2" + checksum: 10c0/8cdbb3ecfdd4c60ff00b42c7e6039b1b45b0662447fc28afef4e0f109dda3c69455dd06ae673c6dc91101f8c555b91947680d21076ed7d6a76bf619b4a46d968 + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 + languageName: node + linkType: hard + +"babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + cosmiconfig: "npm:^7.0.0" + resolve: "npm:^1.19.0" + checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.11.12": + version: 2.11.15 + resolution: "baseline-browser-mapping@npm:2.11.15" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/d89578cc74cd2d788c3cf2dad73bf2a21c4d1bac27fa5498da17caa1c135fe1f99c968b6f8e41c2eaafde21ac8092ec00ad28746aa01e80156908c2bdcc91f76 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.1.4 + resolution: "brace-expansion@npm:2.1.4" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/6c0a0e2573eac1dc565b52b1e1bfbeba39bf1830d106ebbc61ff1eaefcf610e9111cd3baa091addd18e33292135c99e019d3184d966389d85dc958fdcdc1449f + languageName: node + linkType: hard + +"brace-expansion@npm:^5.0.8": + version: 5.0.9 + resolution: "brace-expansion@npm:5.0.9" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/3dea38884a1c3c8b1c9c44a7402a0c76fca460f70cffb3127242b0b4cbf4472019e022ade021eec44838ff19f1dac2625dfd11dd459d7e1e055b0698a8d52fec + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0": + version: 4.28.8 + resolution: "browserslist@npm:4.28.8" + dependencies: + baseline-browser-mapping: "npm:^2.11.12" + caniuse-lite: "npm:^1.0.30001809" + electron-to-chromium: "npm:^1.5.402" + node-releases: "npm:^2.0.53" + update-browserslist-db: "npm:^1.3.0" + bin: + browserslist: cli.js + checksum: 10c0/047dd517c8d1f1f56a253d752c3127b8ad01c58e4cbb7e21cb5cd07ebd13e083a2237c3afb13eb60674282c9132bd4534aea2c3ff7c6f7d29be0687a00cbd537 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.1" + checksum: 10c0/a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d + languageName: node + linkType: hard + +"call-bind@npm:^1.0.8, call-bind@npm:^1.0.9": + version: 1.0.9 + resolution: "call-bind@npm:1.0.9" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + get-intrinsic: "npm:^1.3.0" + set-function-length: "npm:^1.2.2" + checksum: 10c0/a6621f6da1444481919ce3b4983dff725691e0754d3507ae483ce56e54985f2da7d6f1df512c56dbf28660745cf1ca52553f1fc9aef5557f3ce353ef14fab714 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001809": + version: 1.0.30001809 + resolution: "caniuse-lite@npm:1.0.30001809" + checksum: 10c0/cac2ed4e66cc6c4cbf126b94d2c02012566eb92f93c89949290f99edcd2bdc4ed1290b5c537ccb9b42c773936a479ed468e5d4e9b8938b126a0947090e42e008 + languageName: node + linkType: hard + +"change-case@npm:^5.4.4": + version: 5.4.4 + resolution: "change-case@npm:5.4.4" + checksum: 10c0/2a9c2b9c9ad6ab2491105aaf506db1a9acaf543a18967798dcce20926c6a173aa63266cb6189f3086e3c14bf7ae1f8ea4f96ecc466fcd582310efa00372f3734 + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + +"colorette@npm:1.4.0": + version: 1.4.0 + resolution: "colorette@npm:1.4.0" + checksum: 10c0/4955c8f7daafca8ae7081d672e4bd89d553bd5782b5846d5a7e05effe93c2f15f7e9c0cb46f341b59f579a39fcf436241ff79594899d75d5f3460c03d607fe9e + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"convert-source-map@npm:^1.5.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie@npm:^1.0.1": + version: 1.1.1 + resolution: "cookie@npm:1.1.1" + checksum: 10c0/79c4ddc0fcad9c4f045f826f42edf54bcc921a29586a4558b0898277fa89fb47be95bc384c2253f493af7b29500c830da28341274527328f18eba9f58afa112c + languageName: node + linkType: hard + +"cosmiconfig@npm:^7.0.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"css-mediaquery@npm:^0.1.2": + version: 0.1.2 + resolution: "css-mediaquery@npm:0.1.2" + checksum: 10c0/b7825a78f52ce8a8198e004fcad0d7be1d3c9a0463ecd05ba31a0f2c94fb81468ad6f4d7bf715a6ca775696e7a17500c2a339b5216a6d0f789cbf78f9454d048 + languageName: node + linkType: hard + +"csstype@npm:^3.0.2": + version: 3.1.3 + resolution: "csstype@npm:3.1.3" + checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 + languageName: node + linkType: hard + +"csstype@npm:^3.2.2, csstype@npm:^3.2.3": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-buffer@npm:1.0.1" + dependencies: + call-bind: "npm:^1.0.6" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/8984119e59dbed906a11fcfb417d7d861936f16697a0e7216fe2c6c810f6b5e8f4a5281e73f2c28e8e9259027190ac4a33e2a65fdd7fa86ac06b76e838918583 + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/7986d40fc7979e9e6241f85db8d17060dd9a71bd53c894fa29d126061715e322a4cd47a00b0b8c710394854183d4120462b980b8554012acc1c0fa49df7ad38c + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-length@npm:1.0.1" + dependencies: + call-bind: "npm:^1.0.7" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/b7d9e48a0cf5aefed9ab7d123559917b2d7e0d65531f43b2fd95b9d3a6b46042dd3fca597c42bba384e66b70d7ad66ff23932f8367b241f53d93af42cfe04ec2 + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/f8a4534b5c69384d95ac18137d381f18a5cfae1f0fc1df0ef6feef51ef0d568606d970b69e02ea186c6c0f0eac77fe4e6ad96fec2569cc86c3afcc7475068c55 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.0": + version: 1.0.0 + resolution: "data-view-byte-offset@npm:1.0.0" + dependencies: + call-bind: "npm:^1.0.6" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/21b0d2e53fd6e20cc4257c873bf6d36d77bd6185624b84076c0a1ddaa757b49aaf076254006341d35568e89f52eecd1ccb1a502cfb620f2beca04f48a6a62a8f + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/fa7aa40078025b7810dcffc16df02c480573b7b53ef1205aa6a61533011005c1890e5ba17018c692ce7c900212b547262d33279fde801ad9843edc0863bf78c4 + languageName: node + linkType: hard + +"date-fns@npm:^3.6.0": + version: 3.6.0 + resolution: "date-fns@npm:3.6.0" + checksum: 10c0/0b5fb981590ef2f8e5a3ba6cd6d77faece0ea7f7158948f2eaae7bbb7c80a8f63ae30b01236c2923cf89bb3719c33aeb150c715ea4fe4e86e37dcf06bed42fb6 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"decode-uri-component@npm:^0.2.2": + version: 0.2.2 + resolution: "decode-uri-component@npm:0.2.2" + checksum: 10c0/1f4fa54eb740414a816b3f6c24818fbfcabd74ac478391e9f4e2282c994127db02010ce804f3d08e38255493cfe68608b3f5c8e09fd6efc4ae46c807691f7a31 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"deepreefmap-frontend@workspace:.": + version: 0.0.0-use.local + resolution: "deepreefmap-frontend@workspace:." + dependencies: + "@eslint/js": "npm:^10.0.1" + "@mui/icons-material": "npm:^9.3.1" + "@mui/material": "npm:^9.3.1" + "@types/leaflet": "npm:^1.9.12" + "@types/react": "npm:^19.2.18" + "@types/react-dom": "npm:^19.2.4" + "@types/three": "npm:^0.185.4" + "@vitejs/plugin-react": "npm:^6.0.5" + eslint: "npm:^10.8.1" + eslint-config-prettier: "npm:^10.1.8" + eslint-plugin-react: "npm:^7.37.5" + eslint-plugin-react-hooks: "npm:^7.1.1" + globals: "npm:^17.11.0" + keycloak-js: "npm:^26.2.4" + leaflet: "npm:^1.9.4" + openapi-typescript: "npm:^7.13.0" + prettier: "npm:^3.9.6" + react: "npm:^19.2.8" + react-admin: "npm:^5.15.1" + react-dom: "npm:^19.2.8" + react-leaflet: "npm:^5.0.0" + react-router-dom: "npm:^7.18.2" + three: "npm:^0.185.1" + typescript: "npm:^5.9.3" + typescript-eslint: "npm:^8.67.0" + vite: "npm:^8.2.1" + languageName: unknown + linkType: soft + +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"diacritic@npm:^0.0.2": + version: 0.0.2 + resolution: "diacritic@npm:0.0.2" + checksum: 10c0/1d9dd0a1188a8186d4fce4a695fc8cb0d65c31a8b3c59cd926636e49a05b30d6bb3f4144018be40bdf0a4937d16bb6705f3b1d1ff9684a426d922fb039f8d8ae + languageName: node + linkType: hard + +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/b6416aaff1f380bf56c3b552f31fdf7a69b45689368deca72d28636f41c16bb28ec3ebc40ace97db4c1afc0ceeb8120e8492fe0046841c94c2933b2e30a7d5ac + languageName: node + linkType: hard + +"dom-helpers@npm:^5.0.1": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": "npm:^7.8.7" + csstype: "npm:^3.0.2" + checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c + languageName: node + linkType: hard + +"dompurify@npm:^3.2.4": + version: 3.4.13 + resolution: "dompurify@npm:3.4.13" + dependencies: + "@types/trusted-types": "npm:^2.0.7" + dependenciesMeta: + "@types/trusted-types": + optional: true + checksum: 10c0/9c2a1a71e1a1d8b77953db7a39ffc935ef4ed4f10fe40770232128c2cbfd4c3dc677d3d7bae51eb24cc52d9ff3548612dc540ecb4343e89b26e809d2be2e0cfe + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.402": + version: 1.5.411 + resolution: "electron-to-chromium@npm:1.5.411" + checksum: 10c0/3106a5ccfc036d125a096ab86e314c6fee4af60ef931f19d94304b4a859ffc1036709ffee923823aa35bff4c1e37694e9f6afa9e54ea7da54feb00f8ff8d69e7 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + languageName: node + linkType: hard + +"es-abstract-get@npm:^1.0.0": + version: 1.0.0 + resolution: "es-abstract-get@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.2" + is-callable: "npm:^1.2.7" + object-inspect: "npm:^1.13.4" + checksum: 10c0/f9b4838ae719752207383a6d95a74590f891122bf26b92f5e72eeedbe53771029e4561f1cf75ea19330b71bcf3d4f536fb0c8f7e2b601fe24d284f46e488c7e3 + languageName: node + linkType: hard + +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3": + version: 1.23.3 + resolution: "es-abstract@npm:1.23.3" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + arraybuffer.prototype.slice: "npm:^1.0.3" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.7" + data-view-buffer: "npm:^1.0.1" + data-view-byte-length: "npm:^1.0.1" + data-view-byte-offset: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-set-tostringtag: "npm:^2.0.3" + es-to-primitive: "npm:^1.2.1" + function.prototype.name: "npm:^1.1.6" + get-intrinsic: "npm:^1.2.4" + get-symbol-description: "npm:^1.0.2" + globalthis: "npm:^1.0.3" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.0.3" + has-symbols: "npm:^1.0.3" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.0.7" + is-array-buffer: "npm:^3.0.4" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.1" + is-negative-zero: "npm:^2.0.3" + is-regex: "npm:^1.1.4" + is-shared-array-buffer: "npm:^1.0.3" + is-string: "npm:^1.0.7" + is-typed-array: "npm:^1.1.13" + is-weakref: "npm:^1.0.2" + object-inspect: "npm:^1.13.1" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.5" + regexp.prototype.flags: "npm:^1.5.2" + safe-array-concat: "npm:^1.1.2" + safe-regex-test: "npm:^1.0.3" + string.prototype.trim: "npm:^1.2.9" + string.prototype.trimend: "npm:^1.0.8" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.2" + typed-array-byte-length: "npm:^1.0.1" + typed-array-byte-offset: "npm:^1.0.2" + typed-array-length: "npm:^1.0.6" + unbox-primitive: "npm:^1.0.2" + which-typed-array: "npm:^1.1.15" + checksum: 10c0/d27e9afafb225c6924bee9971a7f25f20c314f2d6cb93a63cada4ac11dcf42040896a6c22e5fb8f2a10767055ed4ddf400be3b1eb12297d281726de470b75666 + languageName: node + linkType: hard + +"es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.2": + version: 1.24.2 + resolution: "es-abstract@npm:1.24.2" + dependencies: + array-buffer-byte-length: "npm:^1.0.2" + arraybuffer.prototype.slice: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + data-view-buffer: "npm:^1.0.2" + data-view-byte-length: "npm:^1.0.2" + data-view-byte-offset: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-set-tostringtag: "npm:^2.1.0" + es-to-primitive: "npm:^1.3.0" + function.prototype.name: "npm:^1.1.8" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + get-symbol-description: "npm:^1.1.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.1.0" + is-array-buffer: "npm:^3.0.5" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" + is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" + is-shared-array-buffer: "npm:^1.0.4" + is-string: "npm:^1.1.1" + is-typed-array: "npm:^1.1.15" + is-weakref: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + object-inspect: "npm:^1.13.4" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.7" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.4" + safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" + safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" + string.prototype.trim: "npm:^1.2.10" + string.prototype.trimend: "npm:^1.0.9" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.3" + typed-array-byte-length: "npm:^1.0.3" + typed-array-byte-offset: "npm:^1.0.4" + typed-array-length: "npm:^1.0.7" + unbox-primitive: "npm:^1.1.0" + which-typed-array: "npm:^1.1.19" + checksum: 10c0/67a5bf21ef5c7d775e6f6131a836323900b4d87194cf544394ac68fe31c57fa53828b978af4a4f551ef307f83a2f910a16b6b982760ad3ddc3dc471f98d5fd1b + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: "npm:^1.2.4" + checksum: 10c0/6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.2.1, es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-iterator-helpers@npm:^1.2.1": + version: 1.4.0 + resolution: "es-iterator-helpers@npm:1.4.0" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.2" + es-errors: "npm:^1.3.0" + es-set-tostringtag: "npm:^2.1.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.3.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + iterator.prototype: "npm:^1.1.5" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/839a5e881446e1b4ab270a9ad5d01a23b83a38fadb9e526674df171357e90ad3111dc8c0b15e7d30db1958f2c3332dd963fab7d55f406a660d098b2b7eefb218 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0": + version: 1.0.0 + resolution: "es-object-atoms@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/1fed3d102eb27ab8d983337bb7c8b159dd2a1e63ff833ec54eea1311c96d5b08223b433060ba240541ca8adba9eee6b0a60cdbf2f80634b784febc9cc8b687b4 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.1.1, es-object-atoms@npm:^1.1.2": + version: 1.1.2 + resolution: "es-object-atoms@npm:1.1.2" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/1772861f094f739d6f41b579cfb9a18579daffeb434552a370a5fbef50a32d22227e27b63fdbb757b7ddd429d1b42fe52ccae7966d9302a2ec221b6f1b41bbc4 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.0.3": + version: 2.0.3 + resolution: "es-set-tostringtag@npm:2.0.3" + dependencies: + get-intrinsic: "npm:^1.2.4" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.1" + checksum: 10c0/f22aff1585eb33569c326323f0b0d175844a1f11618b86e193b386f8be0ea9474cfbe46df39c45d959f7aa8f6c06985dc51dd6bce5401645ec5a74c4ceaa836a + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": + version: 1.0.2 + resolution: "es-shim-unscopables@npm:1.0.2" + dependencies: + hasown: "npm:^2.0.0" + checksum: 10c0/f495af7b4b7601a4c0cfb893581c352636e5c08654d129590386a33a0432cf13a7bdc7b6493801cadd990d838e2839b9013d1de3b880440cb537825e834fe783 + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.2.1": + version: 1.2.1 + resolution: "es-to-primitive@npm:1.2.1" + dependencies: + is-callable: "npm:^1.1.4" + is-date-object: "npm:^1.0.1" + is-symbol: "npm:^1.0.2" + checksum: 10c0/0886572b8dc075cb10e50c0af62a03d03a68e1e69c388bd4f10c0649ee41b1fbb24840a1b7e590b393011b5cdbe0144b776da316762653685432df37d6de60f1 + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.3.0": + version: 1.3.4 + resolution: "es-to-primitive@npm:1.3.4" + dependencies: + es-abstract-get: "npm:^1.0.0" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.1.0" + is-symbol: "npm:^1.1.1" + checksum: 10c0/b10029f8b0b13841bade224ff39005a13d76d9cb803cd1efb18cc96a24414e83e2e7ed15d7ad9d0d0bda66884afd211b7b579b8fa31940e05b060934aa7077d8 + languageName: node + linkType: hard + +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"eslint-config-prettier@npm:^10.1.8": + version: 10.1.8 + resolution: "eslint-config-prettier@npm:10.1.8" + peerDependencies: + eslint: ">=7.0.0" + bin: + eslint-config-prettier: bin/cli.js + checksum: 10c0/e1bcfadc9eccd526c240056b1e59c5cd26544fe59feb85f38f4f1f116caed96aea0b3b87868e68b3099e55caaac3f2e5b9f58110f85db893e83a332751192682 + languageName: node + linkType: hard + +"eslint-plugin-react-hooks@npm:^7.1.1": + version: 7.1.1 + resolution: "eslint-plugin-react-hooks@npm:7.1.1" + dependencies: + "@babel/core": "npm:^7.24.4" + "@babel/parser": "npm:^7.24.4" + hermes-parser: "npm:^0.25.1" + zod: "npm:^3.25.0 || ^4.0.0" + zod-validation-error: "npm:^3.5.0 || ^4.0.0" + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + checksum: 10c0/cee8454915d71ac5d70a0d8f4f260e76eaf45fcd4162747dd4282b792ee5616d187351dabe6cdcff9040c79d0cec625635c4fd0777276be119efa88ebe058525 + languageName: node + linkType: hard + +"eslint-plugin-react@npm:^7.37.5": + version: 7.37.5 + resolution: "eslint-plugin-react@npm:7.37.5" + dependencies: + array-includes: "npm:^3.1.8" + array.prototype.findlast: "npm:^1.2.5" + array.prototype.flatmap: "npm:^1.3.3" + array.prototype.tosorted: "npm:^1.1.4" + doctrine: "npm:^2.1.0" + es-iterator-helpers: "npm:^1.2.1" + estraverse: "npm:^5.3.0" + hasown: "npm:^2.0.2" + jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" + minimatch: "npm:^3.1.2" + object.entries: "npm:^1.1.9" + object.fromentries: "npm:^2.0.8" + object.values: "npm:^1.2.1" + prop-types: "npm:^15.8.1" + resolve: "npm:^2.0.0-next.5" + semver: "npm:^6.3.1" + string.prototype.matchall: "npm:^4.0.12" + string.prototype.repeat: "npm:^1.0.0" + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + checksum: 10c0/c850bfd556291d4d9234f5ca38db1436924a1013627c8ab1853f77cac73ec19b020e861e6c7b783436a48b6ffcdfba4547598235a37ad4611b6739f65fd8ad57 + languageName: node + linkType: hard + +"eslint-scope@npm:^9.1.2": + version: 9.1.2 + resolution: "eslint-scope@npm:9.1.2" + dependencies: + "@types/esrecurse": "npm:^4.3.1" + "@types/estree": "npm:^1.0.8" + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/9fb8bca5a73e5741efb6cec84467027b6cb6f4203ff9b43a938e272c5cd30800bde46a5c20dfd1609f840225f0b62b7673be391b20acadf8658ca9fa4729b3dd + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 + languageName: node + linkType: hard + +"eslint@npm:^10.8.1": + version: 10.8.1 + resolution: "eslint@npm:10.8.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.8.0" + "@eslint-community/regexpp": "npm:^4.12.2" + "@eslint/config-array": "npm:^0.23.5" + "@eslint/config-helpers": "npm:^0.7.0" + "@eslint/core": "npm:^1.2.1" + "@eslint/plugin-kit": "npm:^0.7.2" + "@humanfs/node": "npm:^0.16.6" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.4.2" + "@types/estree": "npm:^1.0.6" + ajv: "npm:^6.14.0" + cross-spawn: "npm:^7.0.6" + debug: "npm:^4.3.2" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^9.1.2" + eslint-visitor-keys: "npm:^5.0.1" + espree: "npm:^11.2.0" + esquery: "npm:^1.7.0" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^8.0.0" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + minimatch: "npm:^10.2.5" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + bin: + eslint: bin/eslint.js + checksum: 10c0/0c4720a43ef2052829db18e37aa79adb5f0a62137bdf4a4f8e9507bbbbf888ba0669fc03e4b7139d8f27b59b051bbbcd0a3988b068c30fa934a766d60a81f599 + languageName: node + linkType: hard + +"espree@npm:^11.2.0": + version: 11.2.0 + resolution: "espree@npm:11.2.0" + dependencies: + acorn: "npm:^8.16.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^5.0.1" + checksum: 10c0/cf87e18ffd9dc113eb8d16588e7757701bc10c9934a71cce8b89c2611d51672681a918307bd6b19ac3ccd0e7ba1cbccc2f815b36b52fa7e73097b251014c3d81 + languageName: node + linkType: hard + +"esquery@npm:^1.7.0": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"eventemitter3@npm:^5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fflate@npm:~0.8.2": + version: 0.8.3 + resolution: "fflate@npm:0.8.3" + checksum: 10c0/eab181ca37f5348ae76d4b6f840e0026e30220e33153289ac942222d8b9638237d486507dbcc09878d724095bd354993a2ee48bbee99c8f2c6440d4448719aa7 + languageName: node + linkType: hard + +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" + dependencies: + flat-cache: "npm:^4.0.0" + checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 + languageName: node + linkType: hard + +"file-selector@npm:^0.6.0": + version: 0.6.0 + resolution: "file-selector@npm:0.6.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/477ca1b56274db9fee1a8a623c4bfef580389726a5fef843af8c1f2f17f70ec2d1e41b29115777c92e120a15f1cca734c6ef36bb48bfa2ee027c68da16cd0d28 + languageName: node + linkType: hard + +"filter-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "filter-obj@npm:1.1.0" + checksum: 10c0/071e0886b2b50238ca5026c5bbf58c26a7c1a1f720773b8c7813d16ba93d0200de977af14ac143c5ac18f666b2cfc83073f3a5fe6a4e996c49e0863d5500fccf + languageName: node + linkType: hard + +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.4" + checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.3.1 + resolution: "flatted@npm:3.3.1" + checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf + languageName: node + linkType: hard + +"for-each@npm:^0.3.3": + version: 0.3.3 + resolution: "for-each@npm:0.3.3" + dependencies: + is-callable: "npm:^1.1.3" + checksum: 10c0/22330d8a2db728dbf003ec9182c2d421fbcd2969b02b4f97ec288721cda63eb28f2c08585ddccd0f77cb2930af8d958005c9e72f47141dc51816127a118f39aa + languageName: node + linkType: hard + +"for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee + languageName: node + linkType: hard + +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin<compat/fsevents>": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.6": + version: 1.1.6 + resolution: "function.prototype.name@npm:1.1.6" + dependencies: + call-bind: "npm:^1.0.2" + define-properties: "npm:^1.2.0" + es-abstract: "npm:^1.22.1" + functions-have-names: "npm:^1.2.3" + checksum: 10c0/9eae11294905b62cb16874adb4fc687927cda3162285e0ad9612e6a1d04934005d46907362ea9cdb7428edce05a2f2c3dabc3b2d21e9fd343e9bb278230ad94b + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.8": + version: 1.2.0 + resolution: "function.prototype.name@npm:1.2.0" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + hasown: "npm:^2.0.4" + is-callable: "npm:^1.2.7" + is-document.all: "npm:^1.0.0" + checksum: 10c0/b20e6370ef4f7d56d0bedf5719f6684a517a8dd3334209b4d9f51e8834859302a584187156bf024cda9f50ba2479e4d6764ac34af9532ea47d2f4d9fa6bcf90d + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + has-proto: "npm:^1.0.1" + has-symbols: "npm:^1.0.3" + hasown: "npm:^2.0.0" + checksum: 10c0/0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d + languageName: node + linkType: hard + +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.0.2": + version: 1.0.2 + resolution: "get-symbol-description@npm:1.0.2" + dependencies: + call-bind: "npm:^1.0.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.4" + checksum: 10c0/867be6d63f5e0eb026cb3b0ef695ec9ecf9310febb041072d2e142f260bd91ced9eeb426b3af98791d1064e324e653424afa6fd1af17dee373bea48ae03162bc + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/d6a7d6afca375779a4b307738c9e80dbf7afc0bdbe5948768d54ab9653c865523d8920e670991a925936eb524b7cb6a6361d199a760b21d0ca7620194455aa4b + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 + languageName: node + linkType: hard + +"globals@npm:^17.11.0": + version: 17.11.0 + resolution: "globals@npm:17.11.0" + checksum: 10c0/5e1be3d816e04d4d0d01b1cdd40e46b5fb6b5e9f15aece9a5919b060e0fb1b3f1b9e7327dce97ef19e05513a6d65872e0834999ddea251557556dddd0f5fde30 + languageName: node + linkType: hard + +"globalthis@npm:^1.0.3, globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.1.3" + checksum: 10c0/505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63 + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": + version: 1.0.2 + resolution: "has-bigints@npm:1.0.2" + checksum: 10c0/724eb1485bfa3cdff6f18d95130aa190561f00b3fcf9f19dc640baf8176b5917c143b81ec2123f8cddb6c05164a198c94b13e1377c497705ccc8e1a80306e83b + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.0.1, has-proto@npm:^1.0.3": + version: 1.0.3 + resolution: "has-proto@npm:1.0.3" + checksum: 10c0/35a6989f81e9f8022c2f4027f8b48a552de714938765d019dbea6bb547bd49ce5010a3c7c32ec6ddac6e48fc546166a3583b128f5a7add8b058a6d8b4afec205 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: 10c0/e6922b4345a3f37069cdfe8600febbca791c94988c01af3394d86ca3360b4b93928bbf395859158f88099cb10b19d98e3bbab7c9ff2c1bd09cf665ee90afa2c3 + languageName: node + linkType: hard + +"has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.0, hasown@npm:^2.0.1, hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"hasown@npm:^2.0.4": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/2d8de939e270b70618f8cebb69746620db10617dbb495bc66ddad326955ea24d3ca4af133aff3eb7c1853e0218f867bc2b050ec26fe02e3aea58f880ffc5e506 + languageName: node + linkType: hard + +"hermes-estree@npm:0.25.1": + version: 0.25.1 + resolution: "hermes-estree@npm:0.25.1" + checksum: 10c0/48be3b2fa37a0cbc77a112a89096fa212f25d06de92781b163d67853d210a8a5c3784fac23d7d48335058f7ed283115c87b4332c2a2abaaccc76d0ead1a282ac + languageName: node + linkType: hard + +"hermes-parser@npm:^0.25.1": + version: 0.25.1 + resolution: "hermes-parser@npm:0.25.1" + dependencies: + hermes-estree: "npm:0.25.1" + checksum: 10c0/3abaa4c6f1bcc25273f267297a89a4904963ea29af19b8e4f6eabe04f1c2c7e9abd7bfc4730ddb1d58f2ea04b6fee74053d8bddb5656ec6ebf6c79cc8d14202c + languageName: node + linkType: hard + +"hoist-non-react-statics@npm:^3.3.1": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + +"https-proxy-agent@npm:7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"ignore@npm:^7.0.5": + version: 7.0.6 + resolution: "ignore@npm:7.0.6" + checksum: 10c0/fc01ef1d14efbe003439b60538726351e81483d1b6f55bdbb3a4465c6346d9481afad5b350dfbd604ddd7049618ef9093ff26dc147984aabc303c56ba53ea3b5 + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1": + version: 3.3.0 + resolution: "import-fresh@npm:3.3.0" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"index-to-position@npm:^1.1.0": + version: 1.2.0 + resolution: "index-to-position@npm:1.2.0" + checksum: 10c0/d7ac9fae9fad1d7fbeb7bd92e1553b26e8b10522c2d80af5c362828428a41360e21fc5915d7b8c8227eb0f0d37b12099846ac77381a04d6c0059eb81749e374d + languageName: node + linkType: hard + +"inflection@npm:^3.0.0": + version: 3.0.0 + resolution: "inflection@npm:3.0.0" + checksum: 10c0/6f7016bc4d037fb8f07c8707edc622772d26467e97af02341fb5b891ca35cde7968726dd5c9b18275202aed17b169e053bc6ed1b3adf541c800c4ffa20236d7c + languageName: node + linkType: hard + +"internal-slot@npm:^1.0.7": + version: 1.0.7 + resolution: "internal-slot@npm:1.0.7" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.0" + side-channel: "npm:^1.0.4" + checksum: 10c0/f8b294a4e6ea3855fc59551bbf35f2b832cf01fd5e6e2a97f5c201a071cc09b49048f856e484b67a6c721da5e55736c5b6ddafaf19e2dbeb4a3ff1821680de6c + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/03966f5e259b009a9bf1a78d60da920df198af4318ec004f57b8aef1dd3fe377fbc8cce63a96e8c810010302654de89f9e19de1cd8ad0061d15be28a695465c7 + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.4": + version: 3.0.4 + resolution: "is-array-buffer@npm:3.0.4" + dependencies: + call-bind: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.1" + checksum: 10c0/42a49d006cc6130bc5424eae113e948c146f31f9d24460fc0958f855d9d810e6fd2e4519bf19aab75179af9c298ea6092459d8cafdec523cd19e529b26eab860 + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/c5c9f25606e86dbb12e756694afbbff64bc8b348d1bc989324c037e1068695131930199d6ad381952715dad3a9569333817f0b1a72ce5af7f883ce802e49c83d + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.0.0 + resolution: "is-async-function@npm:2.0.0" + dependencies: + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/787bc931576aad525d751fc5ce211960fe91e49ac84a5c22d6ae0bc9541945fbc3f686dc590c3175722ce4f6d7b798a93f6f8ff4847fdb2199aea6f4baf5d668 + languageName: node + linkType: hard + +"is-bigint@npm:^1.0.1": + version: 1.0.4 + resolution: "is-bigint@npm:1.0.4" + dependencies: + has-bigints: "npm:^1.0.1" + checksum: 10c0/eb9c88e418a0d195ca545aff2b715c9903d9b0a5033bc5922fec600eb0c3d7b1ee7f882dbf2e0d5a6e694e42391be3683e4368737bd3c4a77f8ac293e7773696 + languageName: node + linkType: hard + +"is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: "npm:^1.0.2" + checksum: 10c0/f4f4b905ceb195be90a6ea7f34323bf1c18e3793f18922e3e9a73c684c29eeeeff5175605c3a3a74cc38185fe27758f07efba3dbae812e5c5afbc0d2316b40e4 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.1.0": + version: 1.1.2 + resolution: "is-boolean-object@npm:1.1.2" + dependencies: + call-bind: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/6090587f8a8a8534c0f816da868bc94f32810f08807aa72fa7e79f7e11c466d281486ffe7a788178809c2aa71fe3e700b167fe80dd96dad68026bfff8ebf39f7 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/36ff6baf6bd18b3130186990026f5a95c709345c39cd368468e6c1b6ab52201e9fd26d8e1f4c066357b4938b0f0401e1a5000e08257787c1a02f3a719457001e + languageName: node + linkType: hard + +"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0": + version: 2.15.1 + resolution: "is-core-module@npm:2.15.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/53432f10c69c40bfd2fa8914133a68709ff9498c86c3bf5fca3cdf3145a56fd2168cbf4a43b29843a6202a120a5f9c5ffba0a4322e1e3441739bc0b641682612 + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.1": + version: 1.0.1 + resolution: "is-data-view@npm:1.0.1" + dependencies: + is-typed-array: "npm:^1.1.13" + checksum: 10c0/a3e6ec84efe303da859107aed9b970e018e2bee7ffcb48e2f8096921a493608134240e672a2072577e5f23a729846241d9634806e8a0e51d9129c56d5f65442d + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.2": + version: 1.0.2 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/ef3548a99d7e7f1370ce21006baca6d40c73e9f15c941f89f0049c79714c873d03b02dae1c64b3f861f55163ecc16da06506c5b8a1d4f16650b3d9351c380153 + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.1": + version: 1.0.5 + resolution: "is-date-object@npm:1.0.5" + dependencies: + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/eed21e5dcc619c48ccef804dfc83a739dbb2abee6ca202838ee1bd5f760fe8d8a93444f0d49012ad19bb7c006186e2884a1b92f6e1c056da7fd23d0a9ad5992e + languageName: node + linkType: hard + +"is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/1a4d199c8e9e9cac5128d32e6626fa7805175af9df015620ac0d5d45854ccf348ba494679d872d37301032e35a54fc7978fba1687e8721b2139aea7870cafa2f + languageName: node + linkType: hard + +"is-document.all@npm:^1.0.0": + version: 1.0.0 + resolution: "is-document.all@npm:1.0.0" + dependencies: + call-bound: "npm:^1.0.4" + checksum: 10c0/955c20ed5bf01d49da8243b4c714947a6ff64b6d9ba0e12bdbfa654a3e7c47f72cc01c6cd2905e85512d02bc3a1290edd73857bca8842566ff9dcfb7c3f92dae + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97 + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.10": + version: 1.0.10 + resolution: "is-generator-function@npm:1.0.10" + dependencies: + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/df03514df01a6098945b5a0cfa1abff715807c8e72f57c49a0686ad54b3b74d394e2d8714e6f709a71eb00c9630d48e73ca1796c1ccc84ac95092c1fecc0d98b + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.3": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e + languageName: node + linkType: hard + +"is-number-object@npm:^1.0.4": + version: 1.0.7 + resolution: "is-number-object@npm:1.0.7" + dependencies: + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/aad266da1e530f1804a2b7bd2e874b4869f71c98590b3964f9d06cc9869b18f8d1f4778f838ecd2a11011bce20aeecb53cb269ba916209b79c24580416b74b1b + languageName: node + linkType: hard + +"is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/97b451b41f25135ff021d85c436ff0100d84a039bb87ffd799cbcdbea81ef30c464ced38258cdd34f080be08fc3b076ca1f472086286d2aa43521d6ec6a79f53 + languageName: node + linkType: hard + +"is-regex@npm:^1.1.4": + version: 1.1.4 + resolution: "is-regex@npm:1.1.4" + dependencies: + call-bind: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/bb72aae604a69eafd4a82a93002058c416ace8cde95873589a97fc5dac96a6c6c78a9977d487b7b95426a8f5073969124dd228f043f9f604f041f32fcc465fc1 + languageName: node + linkType: hard + +"is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 + languageName: node + linkType: hard + +"is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "is-shared-array-buffer@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.7" + checksum: 10c0/adc11ab0acbc934a7b9e5e9d6c588d4ec6682f6fea8cda5180721704fa32927582ede5b123349e32517fdadd07958973d24716c80e7ab198970c47acc09e59c7 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/65158c2feb41ff1edd6bbd6fd8403a69861cf273ff36077982b5d4d68e1d59278c71691216a4a64632bd76d4792d4d1d2553901b6666d84ade13bba5ea7bc7db + languageName: node + linkType: hard + +"is-string@npm:^1.0.5, is-string@npm:^1.0.7": + version: 1.0.7 + resolution: "is-string@npm:1.0.7" + dependencies: + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/905f805cbc6eedfa678aaa103ab7f626aac9ebbdc8737abb5243acaa61d9820f8edc5819106b8fcd1839e33db21de9f0116ae20de380c8382d16dc2a601921f6 + languageName: node + linkType: hard + +"is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/2f518b4e47886bb81567faba6ffd0d8a8333cf84336e2e78bf160693972e32ad00fe84b0926491cc598dee576fdc55642c92e62d0cbe96bf36f643b6f956f94d + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": + version: 1.0.4 + resolution: "is-symbol@npm:1.0.4" + dependencies: + has-symbols: "npm:^1.0.2" + checksum: 10c0/9381dd015f7c8906154dbcbf93fad769de16b4b961edc94f88d26eb8c555935caa23af88bda0c93a18e65560f6d7cca0fd5a3f8a8e1df6f1abbb9bead4502ef7 + languageName: node + linkType: hard + +"is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/f08f3e255c12442e833f75a9e2b84b2d4882fdfd920513cf2a4a2324f0a5b076c8fd913778e3ea5d258d5183e9d92c0cd20e04b03ab3df05316b049b2670af1e + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13": + version: 1.1.13 + resolution: "is-typed-array@npm:1.1.13" + dependencies: + which-typed-array: "npm:^1.1.14" + checksum: 10c0/fa5cb97d4a80e52c2cc8ed3778e39f175a1a2ae4ddf3adae3187d69586a1fd57cfa0b095db31f66aa90331e9e3da79184cea9c6abdcd1abc722dc3c3edd51cca + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2": + version: 1.0.2 + resolution: "is-weakref@npm:1.0.2" + dependencies: + call-bind: "npm:^1.0.2" + checksum: 10c0/1545c5d172cb690c392f2136c23eec07d8d78a7f57d0e41f10078aa4f5daf5d7f57b6513a67514ab4f073275ad00c9822fc8935e00229d0a2089e1c02685d4b1 + languageName: node + linkType: hard + +"is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/8e0a9c07b0c780949a100e2cab2b5560a48ecd4c61726923c1a9b77b6ab0aa0046c9e7fb2206042296817045376dee2c8ab1dabe08c7c3dfbf195b01275a085b + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.3 + resolution: "is-weakset@npm:2.0.3" + dependencies: + call-bind: "npm:^1.0.7" + get-intrinsic: "npm:^1.2.4" + checksum: 10c0/8ad6141b6a400e7ce7c7442a13928c676d07b1f315ab77d9912920bf5f4170622f43126f111615788f26c3b1871158a6797c862233124507db0bcc33a9537d1a + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"iterator.prototype@npm:^1.1.5": + version: 1.1.5 + resolution: "iterator.prototype@npm:1.1.5" + dependencies: + define-data-property: "npm:^1.1.4" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + get-proto: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/f7a262808e1b41049ab55f1e9c29af7ec1025a000d243b83edf34ce2416eedd56079b117fa59376bb4a724110690f13aa8427f2ee29a09eec63a7e72367626d0 + languageName: node + linkType: hard + +"js-levenshtein@npm:1.1.6": + version: 1.1.6 + resolution: "js-levenshtein@npm:1.1.6" + checksum: 10c0/14045735325ea1fd87f434a74b11d8a14380f090f154747e613529c7cff68b5ee607f5230fa40665d5fb6125a3791f4c223f73b9feca754f989b059f5c05864f + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:4.3.1": + version: 4.3.1 + resolution: "js-yaml@npm:4.3.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/13c500ca322e0c3f8c81686e6ecda96d2ea37b45247a420c17c7db36932d6965cc27391abc2d1a104501600e7f0d947a5f8b7be6db619c4fefa87901b3512807 + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.0.2 + resolution: "jsesc@npm:3.0.2" + bin: + jsesc: bin/jsesc + checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonexport@npm:^3.2.0": + version: 3.2.0 + resolution: "jsonexport@npm:3.2.0" + bin: + jsonexport: bin/jsonexport.js + checksum: 10c0/eaca567412e72facb9bd765586bfcdb0525781eae49af35776c1d7b53a807a51f4bd00aab9c12647337772bbf8be9a0b66a23cada6a8a97365b5ac6296bb81cd + languageName: node + linkType: hard + +"jsx-ast-utils@npm:^2.4.1 || ^3.0.0": + version: 3.3.5 + resolution: "jsx-ast-utils@npm:3.3.5" + dependencies: + array-includes: "npm:^3.1.6" + array.prototype.flat: "npm:^1.3.1" + object.assign: "npm:^4.1.4" + object.values: "npm:^1.1.6" + checksum: 10c0/a32679e9cb55469cb6d8bbc863f7d631b2c98b7fc7bf172629261751a6e7bc8da6ae374ddb74d5fbd8b06cf0eb4572287b259813d92b36e384024ed35e4c13e1 + languageName: node + linkType: hard + +"keycloak-js@npm:^26.2.4": + version: 26.2.4 + resolution: "keycloak-js@npm:26.2.4" + checksum: 10c0/ad1f3e7c07b6b84fd805129e21aefe94b32e48ecdde1c586294720712c0971ff4c49613c025476d30dbff22e5d61a4fc4d294050d5f11e1ecc0646bef2f02311 + languageName: node + linkType: hard + +"keyv@npm:^4.5.4": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"leaflet@npm:^1.9.4": + version: 1.9.4 + resolution: "leaflet@npm:1.9.4" + checksum: 10c0/f639441dbb7eb9ae3fcd29ffd7d3508f6c6106892441634b0232fafb9ffb1588b05a8244ec7085de2c98b5ed703894df246898477836cfd0ce5b96d4717b5ca1 + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lightningcss-android-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-android-arm64@npm:1.33.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-arm64@npm:1.33.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-x64@npm:1.33.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-freebsd-x64@npm:1.33.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.33.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-musl@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.33.0": + version: 1.33.0 + resolution: "lightningcss@npm:1.33.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.33.0" + lightningcss-darwin-arm64: "npm:1.33.0" + lightningcss-darwin-x64: "npm:1.33.0" + lightningcss-freebsd-x64: "npm:1.33.0" + lightningcss-linux-arm-gnueabihf: "npm:1.33.0" + lightningcss-linux-arm64-gnu: "npm:1.33.0" + lightningcss-linux-arm64-musl: "npm:1.33.0" + lightningcss-linux-x64-gnu: "npm:1.33.0" + lightningcss-linux-x64-musl: "npm:1.33.0" + lightningcss-win32-arm64-msvc: "npm:1.33.0" + lightningcss-win32-x64-msvc: "npm:1.33.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/ce1f8279fbae636dbf37fa6e7385d5f98ed881d72af3362f24afbd4685e19c1fcdfecf17e5dd77f2ebee3d0c23ade276230d85842d07292229a2cffba8ff20a3 + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash@npm:^4.17.21, lodash@npm:~4.18.1": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 + languageName: node + linkType: hard + +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"meshoptimizer@npm:~1.1.1": + version: 1.1.1 + resolution: "meshoptimizer@npm:1.1.1" + checksum: 10c0/aacd816cbe206e49f16c88ed8b13deafec40cdb8b844080b6d614b4d945dfc7242d5dccfbc1269375a5d4dce2f354261c107d3ce6e564961bd9a220e4616dafb + languageName: node + linkType: hard + +"minimatch@npm:5.1.9": + version: 5.1.9 + resolution: "minimatch@npm:5.1.9" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/4202718683815a7288b13e470160a4f9560cf392adef4f453927505817e01ef6b3476ecde13cfcaed17e7326dd3b69ad44eb2daeb19a217c5500f9277893f1d6 + languageName: node + linkType: hard + +"minimatch@npm:^10.2.2, minimatch@npm:^10.2.4, minimatch@npm:^10.2.5": + version: 10.2.6 + resolution: "minimatch@npm:10.2.6" + dependencies: + brace-expansion: "npm:^5.0.8" + checksum: 10c0/4559a836243b98bd4d17ea9f7edae698717c76399eea7be374f3737f33164e4907f19e9726891ddeb122f750a5a7fa80d2ac43e851d6e5984dc4ff42ec127d3a + languageName: node + linkType: hard + +"minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.17": + version: 3.3.18 + resolution: "nanoid@npm:3.3.18" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/b994b4e396730f8be2520923284e2040d61eaee55cc6d4935ef6d38d34bafdc46133eda4d3faea5073bda545aa6079d82b886caeac5c731cf9ac18bcc1301425 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 13.0.1 + resolution: "node-gyp@npm:13.0.1" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + nopt: "npm:^10.0.0" + proc-log: "npm:^7.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + undici: "npm:^8.4.1" + which: "npm:^7.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/424077bc9e9bbe953a8e86db473ba818cbc6a121714008c977fd589e21e5f0c811fbf22faac730dc7182450b5e52df301811d01ae3373898658d999b7710f4e6 + languageName: node + linkType: hard + +"node-polyglot@npm:^2.2.2": + version: 2.6.0 + resolution: "node-polyglot@npm:2.6.0" + dependencies: + hasown: "npm:^2.0.2" + object.entries: "npm:^1.1.8" + warning: "npm:^4.0.3" + checksum: 10c0/da61df331c6895b5cdd89856eb4fb9283866cfba42da0ef2b599557fa1d5962d19cccf251ce6e2f77a5153dd75dbd0b12f2c2366b224b8ae82f451e4bfb482a9 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.53": + version: 2.0.53 + resolution: "node-releases@npm:2.0.53" + checksum: 10c0/db9fb29b21ec12e223ead8bd9406100ff14fce01678a9a4865e288098456b84869431421a739f216aa520b6f57d9425f6537662501ae8a2d9e1771bfa87751bc + languageName: node + linkType: hard + +"nopt@npm:^10.0.0": + version: 10.0.1 + resolution: "nopt@npm:10.0.1" + dependencies: + abbrev: "npm:^5.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/980d89257f9587f3e1f77877ddbf905d6aa3b738ec33e49a4fa1a059a0dd82eb28063982b150654a7ae9de386f2ead60e56172db7d37cf56de545f7392a2a26a + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.1": + version: 1.13.2 + resolution: "object-inspect@npm:1.13.2" + checksum: 10c0/b97835b4c91ec37b5fd71add84f21c3f1047d1d155d00c0fcd6699516c256d4fcc6ff17a1aced873197fe447f91a3964178fd2a67a1ee2120cdaf60e81a050b4 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d + languageName: node + linkType: hard + +"object.assign@npm:^4.1.4, object.assign@npm:^4.1.5": + version: 4.1.5 + resolution: "object.assign@npm:4.1.5" + dependencies: + call-bind: "npm:^1.0.5" + define-properties: "npm:^1.2.1" + has-symbols: "npm:^1.0.3" + object-keys: "npm:^1.1.1" + checksum: 10c0/60108e1fa2706f22554a4648299b0955236c62b3685c52abf4988d14fffb0e7731e00aa8c6448397e3eb63d087dcc124a9f21e1980f36d0b2667f3c18bacd469 + languageName: node + linkType: hard + +"object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/3b2732bd860567ea2579d1567525168de925a8d852638612846bd8082b3a1602b7b89b67b09913cbb5b9bd6e95923b2ae73580baa9d99cb4e990564e8cbf5ddc + languageName: node + linkType: hard + +"object.entries@npm:^1.1.8": + version: 1.1.8 + resolution: "object.entries@npm:1.1.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/db9ea979d2956a3bc26c262da4a4d212d36f374652cc4c13efdd069c1a519c16571c137e2893d1c46e1cb0e15c88fd6419eaf410c945f329f09835487d7e65d3 + languageName: node + linkType: hard + +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.1" + checksum: 10c0/d4b8c1e586650407da03370845f029aa14076caca4e4d4afadbc69cfb5b78035fd3ee7be417141abdb0258fa142e59b11923b4c44d8b1255b28f5ffcc50da7db + languageName: node + linkType: hard + +"object.fromentries@npm:^2.0.8": + version: 2.0.8 + resolution: "object.fromentries@npm:2.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/cd4327e6c3369cfa805deb4cbbe919bfb7d3aeebf0bcaba291bb568ea7169f8f8cdbcabe2f00b40db0c20cd20f08e11b5f3a5a36fb7dd3fe04850c50db3bf83b + languageName: node + linkType: hard + +"object.values@npm:^1.1.6": + version: 1.2.0 + resolution: "object.values@npm:1.2.0" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/15809dc40fd6c5529501324fec5ff08570b7d70fb5ebbe8e2b3901afec35cf2b3dc484d1210c6c642cd3e7e0a5e18dd1d6850115337fef46bdae14ab0cb18ac3 + languageName: node + linkType: hard + +"object.values@npm:^1.2.1": + version: 1.2.1 + resolution: "object.values@npm:1.2.1" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/3c47814fdc64842ae3d5a74bc9d06bdd8d21563c04d9939bf6716a9c00596a4ebc342552f8934013d1ec991c74e3671b26710a0c51815f0b603795605ab6b2c9 + languageName: node + linkType: hard + +"openapi-typescript@npm:^7.13.0": + version: 7.13.0 + resolution: "openapi-typescript@npm:7.13.0" + dependencies: + "@redocly/openapi-core": "npm:^1.34.6" + ansi-colors: "npm:^4.1.3" + change-case: "npm:^5.4.4" + parse-json: "npm:^8.3.0" + supports-color: "npm:^10.2.2" + yargs-parser: "npm:^21.1.1" + peerDependencies: + typescript: ^5.x + bin: + openapi-typescript: bin/cli.js + checksum: 10c0/56d25e160fee33a231646d0648407ca4e65415ff7696b6545bca948828a941a3aeb55585fe34159b71ba8ad93bac55d025db46815132ea092d07ef7386efa462 + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"own-keys@npm:^1.0.1": + version: 1.0.2 + resolution: "own-keys@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.4" + get-intrinsic: "npm:^1.3.0" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10c0/84b0d9959475231166c3d4b9abd1b8af8042911e2e5625761eed980d1a1e4381cf52b34d907cae21ee8766c79de943f3cd85eba636149dee5e64cceff3ccdb78 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parse-json@npm:^8.3.0": + version: 8.3.0 + resolution: "parse-json@npm:8.3.0" + dependencies: + "@babel/code-frame": "npm:^7.26.2" + index-to-position: "npm:^1.1.0" + type-fest: "npm:^4.39.1" + checksum: 10c0/0eb5a50f88b8428c8f7a9cf021636c16664f0c62190323652d39e7bdf62953e7c50f9957e55e17dc2d74fc05c89c11f5553f381dbc686735b537ea9b101c7153 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.4, picomatch@npm:^4.0.5": + version: 4.0.5 + resolution: "picomatch@npm:4.0.5" + checksum: 10c0/947bc6b6e1ff1e6c5aaf95b107a0839d12802f4f7b867663f67d47accba939ca1cb582cf99dfc30438efa1c4648ac5990967e783e8929c36b03e8440704ef1bd + languageName: node + linkType: hard + +"pluralize@npm:8.0.0": + version: 8.0.0 + resolution: "pluralize@npm:8.0.0" + checksum: 10c0/2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33 + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0": + version: 1.0.0 + resolution: "possible-typed-array-names@npm:1.0.0" + checksum: 10c0/d9aa22d31f4f7680e20269db76791b41c3a32c01a373e25f8a4813b4d45f7456bfc2b6d68f752dc4aab0e0bb0721cb3d76fb678c9101cb7a16316664bc2c73fd + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.1.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 + languageName: node + linkType: hard + +"postcss@npm:^8.5.25": + version: 8.5.26 + resolution: "postcss@npm:8.5.26" + dependencies: + nanoid: "npm:^3.3.17" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/2bdafc00d96bd57b6649a52e458864a4bf58ee56cfdbe4aea1472b5cccc127e6c1ad653bd0bec50d211e650eb0b9270c80e1e72aff2e2fa40d9e7363234d6e43 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"prettier@npm:^3.9.6": + version: 3.9.6 + resolution: "prettier@npm:3.9.6" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/9f7ddae234035868f3daab3dc34e6de7e54622185afb017378029f0c09a9c023248ccf6f34def0b17a0538e18c47aa1b3188bab72965d1bf735919baa0747e99 + languageName: node + linkType: hard + +"proc-log@npm:^7.0.0": + version: 7.0.0 + resolution: "proc-log@npm:7.0.0" + checksum: 10c0/b89c2d862604f35fec795477b0c7e376feab3ba0d4f4d291c4e959567442697cf451ac557d0623c1cc38af45a78128b983410f397a10c5d3a67f76c33de4754b + languageName: node + linkType: hard + +"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"query-string@npm:^7.1.3": + version: 7.1.3 + resolution: "query-string@npm:7.1.3" + dependencies: + decode-uri-component: "npm:^0.2.2" + filter-obj: "npm:^1.1.0" + split-on-first: "npm:^1.0.0" + strict-uri-encode: "npm:^2.0.0" + checksum: 10c0/a896c08e9e0d4f8ffd89a572d11f668c8d0f7df9c27c6f49b92ab31366d3ba0e9c331b9a620ee747893436cd1f2f821a6327e2bc9776bde2402ac6c270b801b2 + languageName: node + linkType: hard + +"ra-core@npm:^5.15.0": + version: 5.15.0 + resolution: "ra-core@npm:5.15.0" + dependencies: + date-fns: "npm:^3.6.0" + eventemitter3: "npm:^5.0.1" + inflection: "npm:^3.0.0" + jsonexport: "npm:^3.2.0" + lodash: "npm:^4.17.21" + query-string: "npm:^7.1.3" + react-error-boundary: "npm:^4.0.13" + react-is: "npm:^18.2.0 || ^19.0.0" + peerDependencies: + "@tanstack/react-query": ^5.83.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + react-hook-form: ^7.72.0 + react-router: ^6.28.1 || ^7.1.1 + react-router-dom: ^6.28.1 || ^7.1.1 + checksum: 10c0/5506b70db8b247fc42b890f1474d51bc8fd220180f898d3add6750bcc6e4626c38ed77a3aee4e5f923c59395eaadbb0d43158d7e3e3c8a359c1505db6ad15d56 + languageName: node + linkType: hard + +"ra-i18n-polyglot@npm:^5.15.0": + version: 5.15.0 + resolution: "ra-i18n-polyglot@npm:5.15.0" + dependencies: + node-polyglot: "npm:^2.2.2" + ra-core: "npm:^5.15.0" + checksum: 10c0/ca0be9f0245cf34fd391b2416e736da11627f551c66c48ae9e3602ffb3c37f4e891f0e2b7a094e9e53db4a45b58d640620af74cb1cf259f3f547db3bbc55c520 + languageName: node + linkType: hard + +"ra-language-english@npm:^5.15.0": + version: 5.15.0 + resolution: "ra-language-english@npm:5.15.0" + dependencies: + ra-core: "npm:^5.15.0" + checksum: 10c0/ccb77addb61d7715d1e730eabef6c0dd6c6ff85c094e091b671cc3256228fc02abeeb594434af2abf97e46b858bbf8fe0b7a86df58b29013fbc502de6a6c2570 + languageName: node + linkType: hard + +"ra-ui-materialui@npm:^5.15.1": + version: 5.15.1 + resolution: "ra-ui-materialui@npm:5.15.1" + dependencies: + autosuggest-highlight: "npm:^3.1.1" + clsx: "npm:^2.1.1" + css-mediaquery: "npm:^0.1.2" + diacritic: "npm:^0.0.2" + dompurify: "npm:^3.2.4" + inflection: "npm:^3.0.0" + jsonexport: "npm:^3.2.0" + lodash: "npm:~4.18.1" + query-string: "npm:^7.1.3" + react-dropzone: "npm:^14.2.3" + react-error-boundary: "npm:^4.0.13" + react-hotkeys-hook: "npm:^5.1.0" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@mui/icons-material": ^5.16.12 || ^6.0.0 || ^7.0.0 || ^9.0.0 + "@mui/material": ^5.16.12 || ^6.0.0 || ^7.0.0 || ^9.0.0 + "@mui/system": ^5.15.20 || ^6.0.0 || ^7.0.0 || ^9.0.0 + "@mui/utils": ^5.15.20 || ^6.0.0 || ^7.0.0 || ^9.0.0 + "@tanstack/react-query": ^5.83.0 + csstype: ^3.1.3 + ra-core: ^5.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + react-hook-form: "*" + react-is: ^18.0.0 || ^19.0.0 + react-router: ^6.28.1 || ^7.1.1 + react-router-dom: ^6.28.1 || ^7.1.1 + checksum: 10c0/f54692078ed4c2786cb75121196480d41eb493d96b0695de4d030118a9cf190f98ea3bf931c5e73a2708372b766441e0a299f408d0d6c5bdb37529d4031d6061 + languageName: node + linkType: hard + +"react-admin@npm:^5.15.1": + version: 5.15.1 + resolution: "react-admin@npm:5.15.1" + dependencies: + "@emotion/react": "npm:^11.14.0" + "@emotion/styled": "npm:^11.14.0" + "@mui/icons-material": "npm:^5.16.12 || ^6.0.0 || ^7.0.0 || ^9.0.0" + "@mui/material": "npm:^5.16.12 || ^6.0.0 || ^7.0.0 || ^9.0.0" + "@tanstack/react-query": "npm:^5.83.0" + ra-core: "npm:^5.15.0" + ra-i18n-polyglot: "npm:^5.15.0" + ra-language-english: "npm:^5.15.0" + ra-ui-materialui: "npm:^5.15.1" + react-hook-form: "npm:^7.72.0" + react-router: "npm:^6.28.1 || ^7.1.1" + react-router-dom: "npm:^6.28.1 || ^7.1.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/89a3591a66a80f60ef02d50d472244b956cf8745d9a7d4d90a2690e0850138ba238e7da677c363b31ba7cc501ca5fb7d34eb733d6376b98287bd34a7a88d6265 + languageName: node + linkType: hard + +"react-dom@npm:^19.2.8": + version: 19.2.8 + resolution: "react-dom@npm:19.2.8" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.8 + checksum: 10c0/41ba2247b76f687fcfe5bbc99f514d6b851d8c8041c2f5ded36ed05bd7fdc5208cacbac9de51e3e6633e77f96f44cec9f0d4a5a55184dba4e00738f224439134 + languageName: node + linkType: hard + +"react-dropzone@npm:^14.2.3": + version: 14.2.9 + resolution: "react-dropzone@npm:14.2.9" + dependencies: + attr-accept: "npm:^2.2.2" + file-selector: "npm:^0.6.0" + prop-types: "npm:^15.8.1" + peerDependencies: + react: ">= 16.8 || 18.0.0" + checksum: 10c0/07c69f5c43500edae8916af54bff53bd27e7a4ba127228d130a5346e49f41ff16b47357d97871b4167d6cd9e65e65b9052d1ea7e59d16caeabf1565a73026f58 + languageName: node + linkType: hard + +"react-error-boundary@npm:^4.0.13": + version: 4.1.0 + resolution: "react-error-boundary@npm:4.1.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + peerDependencies: + react: ">=16.13.1" + checksum: 10c0/97eb114503ec2e88fb7d5cea02df43f38a7d9cec95497fd9ddd342750fd7ea01ca4b83ac92db9842d2a7b61e813e33440eb48d1901317c5d77584134ab90c788 + languageName: node + linkType: hard + +"react-hook-form@npm:^7.72.0": + version: 7.85.0 + resolution: "react-hook-form@npm:7.85.0" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + checksum: 10c0/53e81f4116129efa8e08c620cd334f74541174a82f3e4ce7a8839193c69a561b9bed070b4a46f24654c4860c474ddbb504d2ad402297708c8c2b80f1f6116d59 + languageName: node + linkType: hard + +"react-hotkeys-hook@npm:^5.1.0": + version: 5.3.3 + resolution: "react-hotkeys-hook@npm:5.3.3" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/311f3b749cdd5ba20832fb2ea97cd94248f25ebe8417f0da2a76be7a2ca0fa94dfd2fe9eb1d399f41305f65937690825d167708f5d5c27652cb90a97ce0dbf06 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-is@npm:^18.2.0 || ^19.0.0, react-is@npm:^19.2.8": + version: 19.2.8 + resolution: "react-is@npm:19.2.8" + checksum: 10c0/ed5322c84efe035c8fc814b1614ff5ca7fb8c2872a7c045197daf99f9b1bd68f32a2c79ffcbcfcff2fc5d93924ff9f9447400898f5b1534e6302bb0736a257f0 + languageName: node + linkType: hard + +"react-leaflet@npm:^5.0.0": + version: 5.0.0 + resolution: "react-leaflet@npm:5.0.0" + dependencies: + "@react-leaflet/core": "npm:^3.0.0" + peerDependencies: + leaflet: ^1.9.0 + react: ^19.0.0 + react-dom: ^19.0.0 + checksum: 10c0/f0b6fb797cc2d81bc8cbb54e0cb32aefa689d35ca5f52d203ce3c5a1d14668e51244f10844bd2c88d5cb4aca5eb05bc280794e6d1652727ff9da2358e89d1b86 + languageName: node + linkType: hard + +"react-router-dom@npm:^6.28.1 || ^7.1.1, react-router-dom@npm:^7.18.2": + version: 7.18.2 + resolution: "react-router-dom@npm:7.18.2" + dependencies: + react-router: "npm:7.18.2" + peerDependencies: + react: ">=18" + react-dom: ">=18" + checksum: 10c0/234926d664a5b5c355aeb8642f505784facbb63321231428f24e5ea7fb859876443082d63eef4b8167a01ea8d8dab231a666736343c3e4a86a05b2fcb88927b5 + languageName: node + linkType: hard + +"react-router@npm:7.18.2, react-router@npm:^6.28.1 || ^7.1.1": + version: 7.18.2 + resolution: "react-router@npm:7.18.2" + dependencies: + cookie: "npm:^1.0.1" + set-cookie-parser: "npm:^2.6.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/513b04adf020fcf95124557418e003d16b175765045332858d5817b045e49dfe33b529c4871c57b0cddf24fa5432589ffc45d1a9a5b398b069f02adb755fab15 + languageName: node + linkType: hard + +"react-transition-group@npm:^4.4.5": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": "npm:^7.5.5" + dom-helpers: "npm:^5.0.1" + loose-envify: "npm:^1.4.0" + prop-types: "npm:^15.6.2" + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 10c0/2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 + languageName: node + linkType: hard + +"react@npm:^19.2.8": + version: 19.2.8 + resolution: "react@npm:19.2.8" + checksum: 10c0/5f86bdb56426652fd6d989d30a6f2e603c057272c47c9ca3a3fbe190a3a39ee9ccce937d63cfc039717abed1b8891d6a499134bc35311acc07eafdacd86537cd + languageName: node + linkType: hard + +"reflect.getprototypeof@npm:^1.0.10, reflect.getprototypeof@npm:^1.0.9": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.1" + which-builtin-type: "npm:^1.2.1" + checksum: 10c0/7facec28c8008876f8ab98e80b7b9cb4b1e9224353fd4756dda5f2a4ab0d30fa0a5074777c6df24e1e0af463a2697513b0a11e548d99cf52f21f7bc6ba48d3ac + languageName: node + linkType: hard + +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.2": + version: 1.5.3 + resolution: "regexp.prototype.flags@npm:1.5.3" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/e1a7c7dc42cc91abf73e47a269c4b3a8f225321b7f617baa25821f6a123a91d23a73b5152f21872c566e699207e1135d075d2251cd3e84cc96d82a910adf6020 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/83b88e6115b4af1c537f8dabf5c3744032cb875d63bc05c288b1b8c0ef37cbe55353f95d8ca817e8843806e3e150b118bc624e4279b24b4776b4198232735a77 + languageName: node + linkType: hard + +"remove-accents@npm:^0.4.2": + version: 0.4.4 + resolution: "remove-accents@npm:0.4.4" + checksum: 10c0/7eaa1b6f586f22636c8ba7f0ac1f04887d5574d029598d4ea94fdbee6ff02fc70114e3227a673c297d6c6fa43c239696cf651baea1daccbaa6cbf1be0d85914d + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve@npm:^1.19.0": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + languageName: node + linkType: hard + +"resolve@npm:^2.0.0-next.5": + version: 2.0.0-next.5 + resolution: "resolve@npm:2.0.0-next.5" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/a6c33555e3482ea2ec4c6e3d3bf0d78128abf69dca99ae468e64f1e30acaa318fd267fb66c8836b04d558d3e2d6ed875fe388067e7d8e0de647d3c21af21c43a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin<compat/resolve>": + version: 2.0.0-next.5 + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin<compat/resolve>::version=2.0.0-next.5&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/78ad6edb8309a2bfb720c2c1898f7907a37f858866ce11a5974643af1203a6a6e05b2fa9c53d8064a673a447b83d42569260c306d43628bff5bb101969708355 + languageName: node + linkType: hard + +"rolldown@npm:~1.2.1": + version: 1.2.4 + resolution: "rolldown@npm:1.2.4" + dependencies: + "@oxc-project/types": "npm:=0.144.0" + "@rolldown/binding-android-arm64": "npm:1.2.4" + "@rolldown/binding-darwin-arm64": "npm:1.2.4" + "@rolldown/binding-darwin-x64": "npm:1.2.4" + "@rolldown/binding-freebsd-x64": "npm:1.2.4" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.4" + "@rolldown/binding-linux-arm64-gnu": "npm:1.2.4" + "@rolldown/binding-linux-arm64-musl": "npm:1.2.4" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.4" + "@rolldown/binding-linux-s390x-gnu": "npm:1.2.4" + "@rolldown/binding-linux-x64-gnu": "npm:1.2.4" + "@rolldown/binding-linux-x64-musl": "npm:1.2.4" + "@rolldown/binding-openharmony-arm64": "npm:1.2.4" + "@rolldown/binding-win32-arm64-msvc": "npm:1.2.4" + "@rolldown/binding-win32-x64-msvc": "npm:1.2.4" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10c0/438c2222db940fab6e2db1f1cf84ab27c83310959e056fa389d7e2208bcbf58929f970f4dca5bd9c255f319bf657ef1a4a7ea6725213491d28764d867e5407a4 + languageName: node + linkType: hard + +"safe-array-concat@npm:^1.1.2": + version: 1.1.2 + resolution: "safe-array-concat@npm:1.1.2" + dependencies: + call-bind: "npm:^1.0.7" + get-intrinsic: "npm:^1.2.4" + has-symbols: "npm:^1.0.3" + isarray: "npm:^2.0.5" + checksum: 10c0/12f9fdb01c8585e199a347eacc3bae7b5164ae805cdc8c6707199dbad5b9e30001a50a43c4ee24dc9ea32dbb7279397850e9208a7e217f4d8b1cf5d90129dec9 + languageName: node + linkType: hard + +"safe-array-concat@npm:^1.1.3": + version: 1.1.4 + resolution: "safe-array-concat@npm:1.1.4" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + get-intrinsic: "npm:^1.3.0" + has-symbols: "npm:^1.1.0" + isarray: "npm:^2.0.5" + checksum: 10c0/95fb4904ab1d9360a666fe5ba6d88f1c4a3a39682739e4512cff809fc6b5722a94bd95189211015bfb45859a7ffbc3340ea303ae22721c91c59e8946d310975a + languageName: node + linkType: hard + +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10c0/831f1c9aae7436429e7862c7e46f847dfe490afac20d0ee61bae06108dbf5c745a0de3568ada30ccdd3eeb0864ca8331b2eef703abd69bfea0745b21fd320750 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.0.3": + version: 1.0.3 + resolution: "safe-regex-test@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.6" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.1.4" + checksum: 10c0/900bf7c98dc58f08d8523b7012b468e4eb757afa624f198902c0643d7008ba777b0bdc35810ba0b758671ce887617295fb742b3f3968991b178ceca54cb07603 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 + languageName: node + linkType: hard + +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 + languageName: node + linkType: hard + +"semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.7.3": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + +"set-cookie-parser@npm:^2.6.0": + version: 2.7.2 + resolution: "set-cookie-parser@npm:2.7.2" + checksum: 10c0/4381a9eb7ee951dfe393fe7aacf76b9a3b4e93a684d2162ab35594fa4053cc82a4d7d7582bf397718012c9adcf839b8cd8f57c6c42901ea9effe33c752da4a45 + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.1, set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316 + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/ca5c3ccbba479d07c30460e367e66337cec825560b11e8ba9c5ebe13a2a0d6021ae34eddf94ff3dfe17a3104dc1f191519cb6c48378b503e5c3f36393938776a + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-list@npm:1.0.1" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.4" + checksum: 10c0/d346c787fd2f9f1c2fdea14f00e8250118db0e7596d85a6cb9faa75f105d31a73a8f7a341c93d7df2a2429098c3d37a77bd3be9e88c37094b8c01807bc77c7a2 + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4": + version: 1.0.6 + resolution: "side-channel@npm:1.0.6" + dependencies: + call-bind: "npm:^1.0.7" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.4" + object-inspect: "npm:^1.13.1" + checksum: 10c0/d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.1 + resolution: "side-channel@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.4" + side-channel-list: "npm:^1.0.1" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/dc0ab81d67f61bda9247d053ce93f41c3fd8ad2bdcb9cf9d8d2f8540d488f26d87a5e99ebfc07eea49ec025867b2452b705442d974b1478f0395e69f6bfb3270 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 + languageName: node + linkType: hard + +"split-on-first@npm:^1.0.0": + version: 1.1.0 + resolution: "split-on-first@npm:1.1.0" + checksum: 10c0/56df8344f5a5de8521898a5c090023df1d8b8c75be6228f56c52491e0fc1617a5236f2ac3a066adb67a73231eac216ccea7b5b4a2423a543c277cb2f48d24c29 + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10c0/de4e45706bb4c0354a4b1122a2b8cc45a639e86206807ce0baf390ee9218d3ef181923fa4d2b67443367c491aa255c5fbaa64bb74648e3c5b48299928af86c09 + languageName: node + linkType: hard + +"strict-uri-encode@npm:^2.0.0": + version: 2.0.0 + resolution: "strict-uri-encode@npm:2.0.0" + checksum: 10c0/010cbc78da0e2cf833b0f5dc769e21ae74cdc5d5f5bd555f14a4a4876c8ad2c85ab8b5bdf9a722dc71a11dcd3184085e1c3c0bd50ec6bb85fffc0f28cf82597d + languageName: node + linkType: hard + +"string.prototype.matchall@npm:^4.0.12": + version: 4.0.12 + resolution: "string.prototype.matchall@npm:4.0.12" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.6" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + regexp.prototype.flags: "npm:^1.5.3" + set-function-name: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/1a53328ada73f4a77f1fdf1c79414700cf718d0a8ef6672af5603e709d26a24f2181208144aed7e858b1bcc1a0d08567a570abfb45567db4ae47637ed2c2f85c + languageName: node + linkType: hard + +"string.prototype.repeat@npm:^1.0.0": + version: 1.0.0 + resolution: "string.prototype.repeat@npm:1.0.0" + dependencies: + define-properties: "npm:^1.1.3" + es-abstract: "npm:^1.17.5" + checksum: 10c0/94c7978566cffa1327d470fd924366438af9b04b497c43a9805e476e2e908aa37a1fd34cc0911156c17556dab62159d12c7b92b3cc304c3e1281fe4c8e668f40 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.10": + version: 1.2.11 + resolution: "string.prototype.trim@npm:1.2.11" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.2" + es-object-atoms: "npm:^1.1.2" + has-property-descriptors: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/b153cf8ed06db82ff40e27829e88e5c13f45eff9799f1d5707626e25989b488b059d6f5d57011e07f77745e28451e16735f295bc59c8ae146a4fd73a442366b0 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.9": + version: 1.2.9 + resolution: "string.prototype.trim@npm:1.2.9" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/dcef1a0fb61d255778155006b372dff8cc6c4394bc39869117e4241f41a2c52899c0d263ffc7738a1f9e61488c490b05c0427faa15151efad721e1a9fb2663c2 + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimend@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/0a0b54c17c070551b38e756ae271865ac6cc5f60dabf2e7e343cceae7d9b02e1a1120a824e090e79da1b041a74464e8477e2da43e2775c85392be30a6f60963c + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.9": + version: 1.0.10 + resolution: "string.prototype.trimend@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.2" + checksum: 10c0/cc09233181769047a5330becfd5740fec5f0c8137886e7b553626788b00f75df9f34db1159bc52dbb7fc389b8ebb6e1dab44c8c9e31eb600039729a542013286 + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366 + languageName: node + linkType: hard + +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 + languageName: node + linkType: hard + +"supports-color@npm:^10.2.2": + version: 10.2.2 + resolution: "supports-color@npm:10.2.2" + checksum: 10c0/fb28dd7e0cdf80afb3f2a41df5e068d60c8b4f97f7140de2eaed5b42e075d82a0e980b20a2c0efd2b6d73cfacb55555285d8cc719fa0472220715aefeaa1da7c + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.22 + resolution: "tar@npm:7.5.22" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/1311f6be85a8157ac4c9147bae43e13923d2a1aae15e4aa1bd5239e4e03d2cf53cfe103dde7f35832fbb4c938b042856bc8e9a0afd29abd05e2d1608788c4fea + languageName: node + linkType: hard + +"three@npm:^0.185.1": + version: 0.185.1 + resolution: "three@npm:0.185.1" + checksum: 10c0/d15d3fa934f53c9fd6706965ff9946f55653bc728cf582e68acb03421c0179d7662f7d270e14bcf72923e140c36779e7fba6b0f6e542e07deade7342f63c8bdf + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c + languageName: node + linkType: hard + +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 + languageName: node + linkType: hard + +"ts-api-utils@npm:^2.5.0": + version: 2.5.0 + resolution: "ts-api-utils@npm:2.5.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/767849383c114e7f1971fa976b20e73ac28fd0c70d8d65c0004790bf4d8f89888c7e4cf6d5949f9c1beae9bc3c64835bef77bbe27fddf45a3c7b60cebcf85c8c + languageName: node + linkType: hard + +"tslib@npm:^2.4.0": + version: 2.8.0 + resolution: "tslib@npm:2.8.0" + checksum: 10c0/31e4d14dc1355e9b89e4d3c893a18abb7f90b6886b089c2da91224d0a7752c79f3ddc41bc1aa0a588ac895bd97bb99c5bc2bfdb2f86de849f31caeb3ba79bbe5 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"type-fest@npm:^4.39.1": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "typed-array-buffer@npm:1.0.2" + dependencies: + call-bind: "npm:^1.0.7" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/9e043eb38e1b4df4ddf9dde1aa64919ae8bb909571c1cc4490ba777d55d23a0c74c7d73afcdd29ec98616d91bb3ae0f705fad4421ea147e1daf9528200b562da + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/1105071756eb248774bc71646bfe45b682efcad93b55532c6ffa4518969fb6241354e4aa62af679ae83899ec296d69ef88f1f3763657cdb3a4d29321f7b83079 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.1": + version: 1.0.1 + resolution: "typed-array-byte-length@npm:1.0.1" + dependencies: + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + has-proto: "npm:^1.0.3" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/fcebeffb2436c9f355e91bd19e2368273b88c11d1acc0948a2a306792f1ab672bce4cfe524ab9f51a0505c9d7cd1c98eff4235c4f6bfef6a198f6cfc4ff3d4f3 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/6ae083c6f0354f1fce18b90b243343b9982affd8d839c57bbd2c174a5d5dc71be9eb7019ffd12628a96a4815e7afa85d718d6f1e758615151d5f35df841ffb3e + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.2": + version: 1.0.2 + resolution: "typed-array-byte-offset@npm:1.0.2" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + has-proto: "npm:^1.0.3" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/d2628bc739732072e39269389a758025f75339de2ed40c4f91357023c5512d237f255b633e3106c461ced41907c1bf9a533c7e8578066b0163690ca8bc61b22f + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-byte-offset@npm:1.0.4" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + reflect.getprototypeof: "npm:^1.0.9" + checksum: 10c0/3d805b050c0c33b51719ee52de17c1cd8e6a571abdf0fffb110e45e8dd87a657e8b56eee94b776b13006d3d347a0c18a730b903cf05293ab6d92e99ff8f77e53 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.6": + version: 1.0.6 + resolution: "typed-array-length@npm:1.0.6" + dependencies: + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + has-proto: "npm:^1.0.3" + is-typed-array: "npm:^1.1.13" + possible-typed-array-names: "npm:^1.0.0" + checksum: 10c0/74253d7dc488eb28b6b2711cf31f5a9dcefc9c41b0681fd1c178ed0a1681b4468581a3626d39cd4df7aee3d3927ab62be06aa9ca74e5baf81827f61641445b77 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.8 + resolution: "typed-array-length@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.9" + for-each: "npm:^0.3.5" + gopd: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + possible-typed-array-names: "npm:^1.1.0" + reflect.getprototypeof: "npm:^1.0.10" + checksum: 10c0/5319f740fc426a3217182c2f7c87656acb0903e046de5a938e30167337d26abf1bb3ad4b32833a72521a4cc58223aec80627b38b357d0a3d5fd64881427e77ab + languageName: node + linkType: hard + +"typescript-eslint@npm:^8.67.0": + version: 8.67.0 + resolution: "typescript-eslint@npm:8.67.0" + dependencies: + "@typescript-eslint/eslint-plugin": "npm:8.67.0" + "@typescript-eslint/parser": "npm:8.67.0" + "@typescript-eslint/typescript-estree": "npm:8.67.0" + "@typescript-eslint/utils": "npm:8.67.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/6ec860f6763be8a5fabf143ff8b6d4e43b98be0c781afc4725eda578ba0e0cecfcc1f06bd1b56fcccc41fa3fc15e4661ec32120d28f2e7abe4953e91ee346de1 + languageName: node + linkType: hard + +"typescript@npm:^5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin<compat/typescript>": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.0.2": + version: 1.0.2 + resolution: "unbox-primitive@npm:1.0.2" + dependencies: + call-bind: "npm:^1.0.2" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.0.3" + which-boxed-primitive: "npm:^1.0.2" + checksum: 10c0/81ca2e81134167cc8f75fa79fbcc8a94379d6c61de67090986a2273850989dd3bae8440c163121b77434b68263e34787a675cbdcb34bb2f764c6b9c843a11b66 + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + which-boxed-primitive: "npm:^1.1.1" + checksum: 10c0/7dbd35ab02b0e05fe07136c72cb9355091242455473ec15057c11430129bab38b7b3624019b8778d02a881c13de44d63cd02d122ee782fb519e1de7775b5b982 + languageName: node + linkType: hard + +"undici@npm:^8.4.1": + version: 8.10.0 + resolution: "undici@npm:8.10.0" + checksum: 10c0/37ae2b1db8f65c3a003504e2040e96887b99f9db16ba07dcf49c37ed8b7f8c72f4452f2d46f52f7c9e49518fe8325e3b5e7e02ac3a2424886bd67d4b62a0ecab + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.3.0": + version: 1.3.1 + resolution: "update-browserslist-db@npm:1.3.1" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/2a924f5aa283af2d918e5cd941c7e8d77b05adf2ffbe5ca461506b1ff05fee6e8bca74c0f1ba37eb28f00ec93b15e80de24c7222825835aec2dd7bf0a0f6a417 + languageName: node + linkType: hard + +"uri-js-replace@npm:^1.0.1": + version: 1.0.1 + resolution: "uri-js-replace@npm:1.0.1" + checksum: 10c0/0be6c972c84c316e29667628ce7b4ce4de7fc77cec9a514f70c4a3336eea8d1d783c71c9988ac5da333f0f6a85a04a7ae05a3c4aa43af6cd07b7a4d85c8d9f11 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"vite@npm:^8.2.1": + version: 8.2.1 + resolution: "vite@npm:8.2.1" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.33.0" + picomatch: "npm:^4.0.5" + postcss: "npm:^8.5.25" + rolldown: "npm:~1.2.1" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.4.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/e958dd07502deeb552f04dba59418ff1eb63183add416fd7a3e3badabf5d36edc6834b94c916f9f9233f976bb1671e3e9989d90e869059af07b0d43f7fc078c2 + languageName: node + linkType: hard + +"warning@npm:^4.0.3": + version: 4.0.3 + resolution: "warning@npm:4.0.3" + dependencies: + loose-envify: "npm:^1.0.0" + checksum: 10c0/aebab445129f3e104c271f1637fa38e55eb25f968593e3825bd2f7a12bd58dc3738bb70dc8ec85826621d80b4acfed5a29ebc9da17397c6125864d72301b937e + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.0.2": + version: 1.0.2 + resolution: "which-boxed-primitive@npm:1.0.2" + dependencies: + is-bigint: "npm:^1.0.1" + is-boolean-object: "npm:^1.1.0" + is-number-object: "npm:^1.0.4" + is-string: "npm:^1.0.5" + is-symbol: "npm:^1.0.3" + checksum: 10c0/0a62a03c00c91dd4fb1035b2f0733c341d805753b027eebd3a304b9cb70e8ce33e25317add2fe9b5fea6f53a175c0633ae701ff812e604410ddd049777cd435e + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: "npm:^1.1.0" + is-boolean-object: "npm:^1.2.1" + is-number-object: "npm:^1.1.1" + is-string: "npm:^1.1.1" + is-symbol: "npm:^1.1.1" + checksum: 10c0/aceea8ede3b08dede7dce168f3883323f7c62272b49801716e8332ff750e7ae59a511ae088840bc6874f16c1b7fd296c05c949b0e5b357bfe3c431b98c417abe + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + function.prototype.name: "npm:^1.1.6" + has-tostringtag: "npm:^1.0.2" + is-async-function: "npm:^2.0.0" + is-date-object: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.1.0" + is-generator-function: "npm:^1.0.10" + is-regex: "npm:^1.2.1" + is-weakref: "npm:^1.0.2" + isarray: "npm:^2.0.5" + which-boxed-primitive: "npm:^1.1.0" + which-collection: "npm:^1.0.2" + which-typed-array: "npm:^1.1.16" + checksum: 10c0/8dcf323c45e5c27887800df42fbe0431d0b66b1163849bb7d46b5a730ad6a96ee8bfe827d078303f825537844ebf20c02459de41239a0a9805e2fcb3cae0d471 + languageName: node + linkType: hard + +"which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: "npm:^2.0.3" + is-set: "npm:^2.0.3" + is-weakmap: "npm:^2.0.2" + is-weakset: "npm:^2.0.3" + checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "which-typed-array@npm:1.1.15" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/4465d5348c044032032251be54d8988270e69c6b7154f8fcb2a47ff706fe36f7624b3a24246b8d9089435a8f4ec48c1c1025c5d6b499456b9e5eff4f48212983 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": + version: 1.1.22 + resolution: "which-typed-array@npm:1.1.22" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.9" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/e59db184a4e78b461fac3b05fafc1e7badbbedafbf04a967ee1de73717f1f9723a79699e7b5de71d449541cb5da8353efc01a4f8a72a152479850e54fa196c40 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^7.0.0": + version: 7.0.0 + resolution: "which@npm:7.0.0" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/ca0b54f198f78bbc4b7c02e34bda8d335cb352e0adb4cbca1c37b1a957af3a879a82c4c27ca6525bc942f548d8b64f816ef6528360af9f3de55ffb9b979b620d + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yaml-ast-parser@npm:0.0.43": + version: 0.0.43 + resolution: "yaml-ast-parser@npm:0.0.43" + checksum: 10c0/4d2f1e761067b2c6abdd882279a406f879258787af470a6d4a659cb79cb2ab056b870b25f1f80f46ed556e8b499d611d247806376f53edf3412f72c0a8ea2e98 + languageName: node + linkType: hard + +"yaml@npm:^1.10.0": + version: 1.10.2 + resolution: "yaml@npm:1.10.2" + checksum: 10c0/5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"zod-validation-error@npm:^3.5.0 || ^4.0.0": + version: 4.0.2 + resolution: "zod-validation-error@npm:4.0.2" + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + checksum: 10c0/0ccfec48c46de1be440b719cd02044d4abb89ed0e14c13e637cd55bf29102f67ccdba373f25def0fc7130e5f15025be4d557a7edcc95d5a3811599aade689e1b + languageName: node + linkType: hard + +"zod@npm:^3.25.0 || ^4.0.0": + version: 4.4.3 + resolution: "zod@npm:4.4.3" + checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3 + languageName: node + linkType: hard