diff --git a/frontend/.env.sample b/frontend/.env.sample index d7043ef83..c0e0e590d 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -35,6 +35,13 @@ VITE_MIN_TRAINING_AREA_SIZE = 5797 # Default value: 5242880 bytes (5 MB). VITE_MAX_TRAINING_AREA_UPLOAD_FILE_SIZE = 5242880 + +# The name of the application. +# This is used in the geojson-to-osm utility function to include the app name in the XML root element. +# Data type: String (e.g., fAIr-AI Assisted Mapping). +# Default value: "fAIr". +VITE_APP_NAME = "fAIr" + # The current version of the application. # This is used in the OSM redirect callback when a training area is opened in OSM. # Data type: String (e.g., v1.1). diff --git a/frontend/.gitignore b/frontend/.gitignore index e440e68b2..85c8ff909 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -24,4 +24,5 @@ dist-ssr *.sw? tsconfig.app.tsbuildinfo -tsconfig.node.tsbuildinfo \ No newline at end of file +tsconfig.node.tsbuildinfo +docs/.vitepress/cache \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 4115ec6ec..595ea8bad 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -2,6 +2,8 @@ This project is a frontend web application built using **React 18**, **TypeScript**, and **Vite**. The app leverages modern libraries such as **@hotosm/ui**, and **Shoelace** for UI components, and **React Router** for client-side routing. +For detailed information on how to use and contribute to this project, please visit our [Documentation](https://f-a-ir-docs.vercel.app/). + ## Table of Contents - [Installation](#installation) @@ -65,7 +67,6 @@ Here's an overview of the folder structure: │ ├── enums/ - Reusable enums. | |── features/ - Contains the main features of the application. │ ├── hook/ - Reusable hooks. -│ ├── layouts/ - Core layouts of the application. │ ├── services/ - Axios API clients and services. │ ├── styles/ - Global styles. │ ├── types/ - Reusable types. @@ -78,7 +79,7 @@ Here's an overview of the folder structure: ## Codebase Standards -The project standards are crucial for maintaining code quality, consistency, and scalability in a React application. By establishing and adhering to a set of best practices, developers can ensure that the codebase remains clean, organized, and easy to maintain. +The project utilized the following codebase standards: #### ESLint @@ -110,6 +111,10 @@ We use the `kebab-case` to name all files. This helps to keep your codebase cons See [the documentation](./docs/) for more information on the architectural decisions. +#### File Organization + +The file organization is inspired by [Bulletproof React](https://github.com/alan2207/bulletproof-react) with a few modifications. + ## Contributing Please refer to the [CONTRIBUTING](../CONTRIBUTING.md) guide for more information. @@ -138,8 +143,9 @@ fAIr also bundles portions of the following open source software. - [React Router (MIT)](https://github.com/remix-run/react-router). - [Terra Draw (MIT)](https://github.com/JamesLMilner/terra-draw). - [Vaul (MIT)](https://github.com/emilkowalski/vaul). +- [Vitest (MIT)](https://github.com/vitest-dev/vitest). - [XMLBuilder2 (MIT)](https://github.com/oozcitak/xmlbuilder2). - [React Helmet Async (Apache-2.0)](https://github.com/staylor/react-helmet-async). - [Maplibre GL JS (BSD-3-Clause)](https://github.com/maplibre/maplibre-gl-js). - [PMTiles (BSD-3-Clause)](https://github.com/protomaps/PMTiles). -- [React Medium Image Zoom (BSD-3-Clause)](https://github.com/rpearce/react-medium-image-zoom). \ No newline at end of file +- [React Medium Image Zoom (BSD-3-Clause)](https://github.com/rpearce/react-medium-image-zoom). diff --git a/frontend/docs/.vitepress/config.ts b/frontend/docs/.vitepress/config.ts new file mode 100644 index 000000000..17aaecae4 --- /dev/null +++ b/frontend/docs/.vitepress/config.ts @@ -0,0 +1,71 @@ +import { defineConfig } from 'vitepress'; + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "fAIr", + description: "AI-powered assistant that amplify your mapping efforts intelligently and quickly, helping you map smarter and faster.", + ignoreDeadLinks: true, + cleanUrls: true, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + { text: 'Get Started', link: '/getting-started' } + ], + + sidebar: [ + { + text: 'fAIr Documentation', + items: [ + { + text: 'Introduction', items: [ + { text: 'Getting Started', link: '/getting-started' }, + { text: 'Dependencies', link: '/dependencies' }, + ] + }, + { + text: 'Architecture Decision Records', + link: '/architecture/README.md', + items: [ + { + text: 'Web Framework', items: [ + { text: 'ADR1', link: '/architecture/adr-choose-web-framework/adr1.md' }, + { text: 'ADR2', link: '/architecture/adr-choose-web-framework/adr2.md' }, + + ] + }, + { + text: 'Styling Library', items: [ + { text: 'ADR1', link: '/architecture/adr-choose-styling-library/adr1.md' }, + { text: 'ADR2', link: '/architecture/adr-choose-styling-library/adr2.md' }, + { text: 'ADR3', link: '/architecture/adr-choose-styling-library/adr3.md' }, + ] + }, + { + text: 'Webmap Library', items: [ + { text: 'ADR1', link: '/architecture/adr-choose-webmap-library/adr1.md' }, + { text: 'ADR2', link: '/architecture/adr-choose-webmap-library/adr2.md' }, + ] + }, + { + text: 'Drawing Library', items: [ + { text: 'ADR1', link: '/architecture/adr-choose-drawing-library/adr1.md' }, + { text: 'ADR2', link: '/architecture/adr-choose-drawing-library/adr2.md' }, + ] + }, + { text: 'Package Manager', link: '/architecture/adr-choose-package-manager/adr1.md' }, + { text: 'Programming Language', link: '/architecture/adr-choose-language/adr1.md' }, + { text: 'Bundler', link: '/architecture/adr-choose-bundler/adr1.md' }, + { text: 'Testing Library', link: '/architecture/adr-choose-testing-library/adr1.md' } + ] + }, + + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/hotosm/fair' } + ] + } +}) diff --git a/frontend/docs/README.md b/frontend/docs/architecture/README.md similarity index 70% rename from frontend/docs/README.md rename to frontend/docs/architecture/README.md index 1a83d90d7..d287f2548 100644 --- a/frontend/docs/README.md +++ b/frontend/docs/architecture/README.md @@ -1,4 +1,4 @@ -## Architectural Decisions +# Architectural Decisions The `architecture` folder comprises of subfolders and markdowns that documents the architectural decisions made when deciding on the tools used in developing this project. The template used is based on the simple and popular template -- [Documenting architecture decisions - Michael Nygard](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions), however slightly modified to include the **Date** of the decision. The folder is updated continually when decision making need arises until the end of the project. @@ -14,13 +14,14 @@ In the `architecture` folder there are subfolders for each decision making. In e ## Quick Links -1. [Framework Decision](./architecture/adr-choose-web-framework/adr1.md) -2. [Styling Library Decision](./architecture/adr-choose-styling-library/adr1.md) -3. [Web mapping Library Decision](./architecture/adr-choose-webmap-library/adr1.md) -4. [Drawing Library Decision](./architecture/adr-choose-drawing-library/adr1.md) -5. [Package Manager Decision](./architecture/adr-choose-package-manager/adr1.md) -6. [Programming Language Decision](./architecture/adr-choose-language/adr1.md) -7. [Bundler Decision](./architecture/adr-choose-bundler/adr1.md) +1. [Framework Decision](./adr-choose-web-framework/adr1.md) +2. [Styling Library Decision](./adr-choose-styling-library/adr1.md) +3. [Web mapping Library Decision](./adr-choose-webmap-library/adr1.md) +4. [Drawing Library Decision](./adr-choose-drawing-library/adr1.md) +5. [Package Manager Decision](./adr-choose-package-manager/adr1.md) +6. [Programming Language Decision](./adr-choose-language/adr1.md) +7. [Bundler Decision](./adr-choose-bundler/adr1.md) +8. [Testing Library Decision](./adr-choose-testing-library/adr1.md) ## References diff --git a/frontend/docs/architecture/adr-choose-testing-library/adr1.md b/frontend/docs/architecture/adr-choose-testing-library/adr1.md new file mode 100644 index 000000000..23e3297de --- /dev/null +++ b/frontend/docs/architecture/adr-choose-testing-library/adr1.md @@ -0,0 +1,39 @@ +# Architecture Decision Record 1: Use Vitest as the Testing Library + +Date: 31/12/2024 + +# Context + +We are building a web application that requires a robust and maintainable testing strategy to ensure code quality, reliability, and confidence in our continuous integration pipeline. We want a solution that is easy to set up, widely supported in the JavaScript community, and provides both unit and integration testing capabilities. + +## Decision Drivers + +- Developer Experience: We want a testing framework with a fast feedback loop, minimal configuration, and good integration with Vite. + +- Performance and Modern Features: We need a tool that executes tests quickly and supports cutting-edge features such as native ESM, TS integration, and fast in-memory testing. + +## Considered Options + +- Use Jest + - Pros: Widely adopted, minimal configuration, built-in mocking and assertion, and good performance. + - Cons: Primarily oriented around JavaScript/TypeScript; might not seamlessly integrate with other environments. Experimental support for ECMAScript modules. Does not integrate natively with Vite. + +- Use Mocha + Chai + - Pros: Highly customizable, flexible setup with well-known assertion library (Chai). + - Cons: Requires additional libraries for mocking, slightly more complex configuration. + +- Use Vitest + - Pros: Built on top of Vite, offering lightning-fast test runs and modern ESM support; straightforward setup; good integration with React and TypeScript. + - Cons: Newer project with a smaller community compared to Jest; may have more limited ecosystem for plugins. + +# Decision + +We will use Vitest as our testing tool. It integrates easily with modern front-end tooling, requires minimal configuration, has native ESM support and TypeScript integration making it suitable for our current and future needs. + +# Status + +Accepted. + +# Consequences + +By choosing Vitest, we gain a testing framework that is quick to run and easy to configure, encouraging a fast feedback loop during development. Vitest’s seamless integration with modern build tools and frameworks allows us to maintain a simpler development environment. However, because Vitest is newer and has a smaller ecosystem than Jest, we may need to be mindful of plugin availability and community support for very specialized testing needs. \ No newline at end of file diff --git a/frontend/docs/dependencies.md b/frontend/docs/dependencies.md new file mode 100644 index 000000000..500c7cdb4 --- /dev/null +++ b/frontend/docs/dependencies.md @@ -0,0 +1,27 @@ +# Dependencies + +This document lists the major dependencies used in this application along with their licenses, brief descriptions, and links to their respective pages or repositories. + +| Dependency | License | Description | Link | +|---------------|---------------|--------------------------------------------------|-------------------------------------------| +| React | MIT | A JavaScript library for building user interfaces| [React](https://reactjs.org/) | +| @shoelace-style/shoelace | MIT | A collection of professionally designed, every day UI components built on a framework-agnostic technology | [Shoelace](https://shoelace.style/) | +| @tanstack/react-query | MIT | Hooks for fetching, caching and updating asynchronous data in React | [React Query](https://tanstack.com/query/latest) | +| @tanstack/react-table | MIT | Headless UI for building powerful tables & datagrids | [React Table](https://tanstack.com/table/latest) | +| @terraformer/wkt | MIT | Well-Known Text (WKT) parser and serializer for Terraformer | [Terraformer WKT](https://github.com/terraformer-js/terraformer) | +| @turf/js | MIT | A modular geospatial engine written in JavaScript and TypeScript | [Turf JS](https://github.com/Turfjs/turf) | +| clsx | MIT | A tiny utility for constructing `className` strings conditionally | [Clsx](https://github.com/lukeed/clsx) | +| framer-motion | MIT | A production-ready motion library for React | [Framer Motion](https://www.framer.com/motion/) | +| react-confetti-explosion | MIT | Confetti explosion animation for React | [React Confetti Explosion](https://github.com/herrethan/react-confetti-explosion) | +| react-dropzone | MIT | Simple React hook to create a HTML5-compliant drag'n'drop zone for files | [React Dropzone](https://github.com/react-dropzone/react-dropzone) | +| react-error-boundary | MIT | Simple reusable React error boundary component | [React Error Boundary](https://github.com/bvaughn/react-error-boundary) | +| react-markdown | MIT | Render Markdown as React components | [React Markdown](https://github.com/remarkjs/react-markdown) | +| remark-gfm | MIT | Plugin to support GitHub Flavored Markdown (GFM) | [Remark GFM](https://github.com/remarkjs/remark-gfm) | +| tailwind-merge | MIT | Utility to merge Tailwind CSS classes | [Tailwind Merge](https://github.com/dcastil/tailwind-merge) | +| terra-draw | MIT | A drawing library for interactive maps | [Terra Draw](https://github.com/JamesLMilner/terra-draw) | +| vaul | MIT | A drawer component for React | [Vaul](https://github.com/emilkowalski/vaul) | +| xmlbuilder2 | MIT | A modern, fast, and powerful XML builder for Node.js | [Xmlbuilder2](https://github.com/oozcitak/xmlbuilder2) | +| maplibre-gl | BSD-3-Clause | A mapping library for rendering maps with WebGL | [MapLibre GL](https://github.com/maplibre/maplibre-gl-js) | +| pmtiles | BSD-3-Clause | A compact, efficient format for storing and serving vector tiles | [PMTiles](https://github.com/protomaps/PMTiles) | +| react-medium-image-zoom | BSD-3-Clause | Medium.com style image zoom for React | [React Medium Image Zoom](https://github.com/rpearce/react-medium-image-zoom) | +| react-helmet-async | Apache-2.0 | Thread-safe Helmet for React 16+ and friends | [React Helmet Async](https://github.com/staylor/react-helmet-async) | diff --git a/frontend/docs/getting-started.md b/frontend/docs/getting-started.md new file mode 100644 index 000000000..04707050e --- /dev/null +++ b/frontend/docs/getting-started.md @@ -0,0 +1,118 @@ +# Getting Started + +## Installation + +Note: This project is tested with Node v20.13.1. + +1. Clone the repository: + +```bash +git https://github.com/hotosm/fAIr.git +cd fAIr/frontend +``` + +2. Install dependencies using [pnpm](https://pnpm.io/), [npm](https://www.npmjs.com/), or [yarn](https://yarnpkg.com/). This project uses pnpm: + +```bash +pnpm install +``` + +3. Create .env file inside root dir by following .env [sample](../.env.sample). + +```bash +touch .env +``` + +4. Start the development server: + +```bash +pnpm dev +``` + +The app will be available at **http://127.0.0.1:5173**. To change the default port, you can edit the [vite config](../vite.config.mts). + +## Build + +```bash +pnpm build +``` + +This will create an optimized build of your app in the dist/ folder, which can be deployed. + +## Folder Structure + +Here's an overview of the folder structure: + +```markdown +├── public/ - Static assets like favicon, robots.txt and manifests. +├── src/ - Main application codes are here. +│ ├── app/ - Contains the application routes and providers. +│ ├── assets/ - Static assets specific to the app (images, icons, etc.). +│ ├── components/ - Reusable components and layouts. +│ ├── config/ - Environment variables configuration. +│ ├── constants/ - App UI contents and constants. +│ ├── enums/ - Reusable enums. +| |── features/ - Contains the main features of the application. +│ ├── hook/ - Reusable hooks. +│ ├── services/ - Axios API clients and services. +│ ├── styles/ - Global styles. +│ ├── types/ - Reusable types. +│ ├── utils/ - Utility functions, application content and constants. +│ └── main.tsx - Entry point of the React app. +├── docs/ - ARD documentation for some of the decisions made for the app. +└── vercel.json - To prevent the custom 404 page from Vercel when a route is visited. (This is just for the demo site deployed on Vercel.) +└── ... Other configuration files like tsconfig.json, vite.config.mts etc. +``` + +## Codebase Standards + +The project utilized the following codebase standards: + +#### ESLint + +ESLint is used to maintain code quality and adhering to coding standards. + +#### Prettier + +Prettier is a used to maintain consistent code formatting in the project. To format run the code below in the terminal. + +```bash +1. pnpm/npm/yarn format + +2. pnpm/npm/yarn format:check +``` +#### Tests + +[Vitest](https://vitest.dev/) is a used to write the tests in the codebase. To run the tests, run the command below: + +```bash +pnpm/npm/yarn test +``` + +#### Documentation + +[Vitepress](https://vitepress.dev/) is a used to power this documentation site. To update the docs check the `docs` folder and run the command below to preview your changes: + +```bash +pnpm/npm/yarn docs:dev +``` + +#### TypeScript + +This codebase is written in TypeScript. + +#### Absolute imports + +We use absolute imports (such as `@/components`, `@/hooks`, etc.), to make it easier to move files around and avoid messy import paths such as `../../../component`. + +#### File naming conventions + +We use the `kebab-case` to name all files. This helps to keep your codebase consistent and easier to navigate. + +#### Architectural Decisions + +See [the documentation](./architecture/README.md) for more information on the architectural decisions. + +#### File Organization + +The file organization is inspired by [Bulletproof React](https://github.com/alan2207/bulletproof-react) with a few modifications. diff --git a/frontend/docs/index.md b/frontend/docs/index.md new file mode 100644 index 000000000..3199a8e5e --- /dev/null +++ b/frontend/docs/index.md @@ -0,0 +1,26 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "Welcome to fAIr's Developer Doc." + text: "" + tagline: fAIr is an AI-powered assistant that amplify your mapping efforts intelligently and quickly, helping you map smarter and faster. + actions: + - theme: brand + text: Visit fAIr + link: https://fair-dev.hotosm.org + - theme: alt + text: Get Started + link: /getting-started +features: +- title: Developer Friendly + details: Clean and easy-to-understand codebase. +- title: Best Practices + details: Built with modern technologies and tools. +- title: Dependencies + details: Minimal dependencies for high performance. +- title: Architectural Decisions + details: Thoughtfully designed for efficiency. +--- + diff --git a/frontend/package.json b/frontend/package.json index 4353096e2..7c900119c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,12 @@ "lint": "eslint .", "preview": "vite preview", "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,json,css,scss,md}'", - "format:check": "prettier --check 'src/**/*.{js,jsx,ts,tsx,json,css,scss,md}'" + "format:check": "prettier --check 'src/**/*.{js,jsx,ts,tsx,json,css,scss,md}'", + "test": "vitest", + "coverage": "vitest run --coverage", + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" }, "dependencies": { "@shoelace-style/shoelace": "^2.16.0", @@ -52,6 +57,7 @@ "@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/parser": "^8.18.2", "@vitejs/plugin-react": "^4.3.1", + "@vitest/coverage-v8": "2.1.8", "autoprefixer": "^10.4.20", "eslint": "^9.11.1", "eslint-config-prettier": "^9.1.0", @@ -62,13 +68,16 @@ "eslint-plugin-react-refresh": "^0.4.9", "eslint-plugin-tailwindcss": "^3.17.5", "globals": "^15.9.0", + "jsdom": "^25.0.1", "postcss": "^8.4.47", "prettier": "3.3.3", "tailwindcss": "^3.4.12", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", "vite": "^5.4.1", - "vite-tsconfig-paths": "^5.0.1" + "vite-tsconfig-paths": "^5.0.1", + "vitepress": "^1.5.0", + "vitest": "^2.1.8" }, "pnpm": { "overrides": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index a5efbb1b4..88c8d9976 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -126,7 +126,10 @@ importers: version: 8.18.2(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.2(vite@5.4.8) + version: 4.3.2(vite@5.4.8(@types/node@22.10.2)) + '@vitest/coverage-v8': + specifier: 2.1.8 + version: 2.1.8(vitest@2.1.8(@types/node@22.10.2)(jsdom@25.0.1)) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.47) @@ -157,6 +160,9 @@ importers: globals: specifier: ^15.9.0 version: 15.10.0 + jsdom: + specifier: ^25.0.1 + version: 25.0.1 postcss: specifier: ^8.4.47 version: 8.4.47 @@ -174,13 +180,91 @@ importers: version: 8.8.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) vite: specifier: ^5.4.1 - version: 5.4.8 + version: 5.4.8(@types/node@22.10.2) vite-tsconfig-paths: specifier: ^5.0.1 - version: 5.0.1(typescript@5.6.2)(vite@5.4.8) + version: 5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.10.2)) + vitepress: + specifier: ^1.5.0 + version: 1.5.0(@algolia/client-search@5.18.0)(@types/node@22.10.2)(@types/react@18.3.10)(axios@1.7.7)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2) + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.2)(jsdom@25.0.1) packages: + '@algolia/autocomplete-core@1.17.7': + resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.17.7': + resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.18.0': + resolution: {integrity: sha512-DLIrAukjsSrdMNNDx1ZTks72o4RH/1kOn8Wx5zZm8nnqFexG+JzY4SANnCNEjnFQPJTTvC+KpgiNW/CP2lumng==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.18.0': + resolution: {integrity: sha512-0VpGG2uQW+h2aejxbG8VbnMCQ9ary9/ot7OASXi6OjE0SRkYQ/+pkW+q09+IScif3pmsVVYggmlMPtAsmYWHng==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.18.0': + resolution: {integrity: sha512-X1WMSC+1ve2qlMsemyTF5bIjwipOT+m99Ng1Tyl36ZjQKTa54oajBKE0BrmM8LD8jGdtukAgkUhFoYOaRbMcmQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.18.0': + resolution: {integrity: sha512-FAJRNANUOSs/FgYOJ/Njqp+YTe4TMz2GkeZtfsw1TMiA5mVNRS/nnMpxas9771aJz7KTEWvK9GwqPs0K6RMYWg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.18.0': + resolution: {integrity: sha512-I2dc94Oiwic3SEbrRp8kvTZtYpJjGtg5y5XnqubgnA15AgX59YIY8frKsFG8SOH1n2rIhUClcuDkxYQNXJLg+w==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.18.0': + resolution: {integrity: sha512-x6XKIQgKFTgK/bMasXhghoEjHhmgoP61pFPb9+TaUJ32aKOGc65b12usiGJ9A84yS73UDkXS452NjyP50Knh/g==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.18.0': + resolution: {integrity: sha512-qI3LcFsVgtvpsBGR7aNSJYxhsR+Zl46+958ODzg8aCxIcdxiK7QEVLMJMZAR57jGqW0Lg/vrjtuLFDMfSE53qA==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.18.0': + resolution: {integrity: sha512-bGvJg7HnGGm+XWYMDruZXWgMDPVt4yCbBqq8DM6EoaMBK71SYC4WMfIdJaw+ABqttjBhe6aKNRkWf/bbvYOGyw==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.18.0': + resolution: {integrity: sha512-lBssglINIeGIR+8KyzH05NAgAmn1BCrm5D2T6pMtr/8kbTHvvrm1Zvcltc5dKUQEFyyx3J5+MhNc7kfi8LdjVw==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.18.0': + resolution: {integrity: sha512-uSnkm0cdAuFwdMp4pGT5vHVQ84T6AYpTZ3I0b3k/M3wg4zXDhl3aCiY8NzokEyRLezz/kHLEEcgb/tTTobOYVw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.18.0': + resolution: {integrity: sha512-1XFjW0C3pV0dS/9zXbV44cKI+QM4ZIz9cpatXpsjRlq6SUCpLID3DZHsXyE6sTb8IhyPaUjk78GEJT8/3hviqg==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.18.0': + resolution: {integrity: sha512-0uodeNdAHz1YbzJh6C5xeQ4T6x5WGiUxUq3GOaT/R4njh5t78dq+Rb187elr7KtnjUmETVVuCvmEYaThfTHzNg==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.18.0': + resolution: {integrity: sha512-tZCqDrqJ2YE2I5ukCQrYN8oiF6u3JIdCxrtKq+eniuLkjkO78TKRnXrVcKZTmfFJyyDK8q47SfDcHzAA3nHi6w==} + engines: {node: '>= 14.0.0'} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -280,10 +364,36 @@ packages: resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@ctrl/tinycolor@4.1.0': resolution: {integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==} engines: {node: '>=14'} + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} + + '@docsearch/js@3.8.2': + resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} + + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + '@emotion/is-prop-valid@0.7.3': resolution: {integrity: sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==} @@ -479,10 +589,20 @@ packages: resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} engines: {node: '>=18.18'} + '@iconify-json/simple-icons@1.2.17': + resolution: {integrity: sha512-1vXbM6a6HV2rwXxu8ptD2OYhqrqX0ZZRepOg7nIjkvKlKq90Iici4X++A8h36bEVlV2wGjqx8uVYB0pwnPZVSw==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -822,6 +942,24 @@ packages: cpu: [x64] os: [win32] + '@shikijs/core@1.24.4': + resolution: {integrity: sha512-jjLsld+xEEGYlxAXDyGwWsKJ1sw5Pc1pnp4ai2ORpjx2UX08YYTC0NNqQYO1PaghYaR+PvgMOGuvzw2he9sk0Q==} + + '@shikijs/engine-javascript@1.24.4': + resolution: {integrity: sha512-TClaQOLvo9WEMJv6GoUsykQ6QdynuKszuORFWCke8qvi6PeLm7FcD9+7y45UenysxEWYpDL5KJaVXTngTE+2BA==} + + '@shikijs/engine-oniguruma@1.24.4': + resolution: {integrity: sha512-Do2ry6flp2HWdvpj2XOwwa0ljZBRy15HKZITzPcNIBOGSeprnA8gOooA/bLsSPuy8aJBa+Q/r34dMmC3KNL/zw==} + + '@shikijs/transformers@1.24.4': + resolution: {integrity: sha512-0jq5p9WLB7ToM/O7RWfxuIwirTJbIQsUR06jxdG3h3CEuO5m7ik8GnDsxwHhyIEfgJSZczSnVUZWFrNKy5It6g==} + + '@shikijs/types@1.24.4': + resolution: {integrity: sha512-0r0XU7Eaow0PuDxuWC1bVqmWCgm3XqizIaT7SM42K03vc69LGooT0U8ccSR44xP/hGlNx4FKhtYpV+BU6aaKAA==} + + '@shikijs/vscode-textmate@9.3.1': + resolution: {integrity: sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==} + '@shoelace-style/animations@1.2.0': resolution: {integrity: sha512-avvo1xxkLbv2dgtabdewBbqcJfV0e0zCwFqkPMnHFGbJbBHorRFfMAHh1NG9ymmXn0jW95ibUVH03E1NYXD6Gw==} @@ -936,18 +1074,30 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + '@types/mapbox__point-geometry@0.1.4': resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==} '@types/mapbox__vector-tile@1.3.4': resolution: {integrity: sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==} + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/node@22.10.2': + resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} + '@types/pbf@3.0.5': resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==} @@ -975,6 +1125,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@typescript-eslint/eslint-plugin@8.18.2': resolution: {integrity: sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1088,6 +1241,139 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 + '@vitejs/plugin-vue@5.2.1': + resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vitest/coverage-v8@2.1.8': + resolution: {integrity: sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==} + peerDependencies: + '@vitest/browser': 2.1.8 + vitest: 2.1.8 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} + + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} + + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} + + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} + + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + + '@vue/devtools-api@7.6.8': + resolution: {integrity: sha512-ma6dY/sZR36zALVsV1W7eC57c6IJPXsy8SNgZn1PLVWU4z4dPn5TIBmnF4stmdJ4sQcixqKaQ8pwjbMPzEZwiA==} + + '@vue/devtools-kit@7.6.8': + resolution: {integrity: sha512-JhJ8M3sPU+v0P2iZBF2DkdmR9L0dnT5RXJabJqX6o8KtFs3tebdvfoXV2Dm3BFuqeECuMJIfF1aCzSt+WQ4wrw==} + + '@vue/devtools-shared@7.6.8': + resolution: {integrity: sha512-9MBPO5Z3X1nYGFqTJyohl6Gmf/J7UNN1oicHdyzBVZP4jnhZ4c20MgtaHDIzWmHDHCMYVS5bwKxT3jxh7gOOKA==} + + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} + + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} + + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} + peerDependencies: + vue: 3.5.13 + + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + + '@vueuse/core@11.3.0': + resolution: {integrity: sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==} + + '@vueuse/integrations@11.3.0': + resolution: {integrity: sha512-5fzRl0apQWrDezmobchoiGTkGw238VWESxZHazfhP3RM7pDSiyXy18QbfYkILoYNTd23HPAfQTJpkUc5QbkwTw==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@11.3.0': + resolution: {integrity: sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==} + + '@vueuse/shared@11.3.0': + resolution: {integrity: sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1098,9 +1384,17 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + algoliasearch@5.18.0: + resolution: {integrity: sha512-/tfpK2A4FpS0o+S78o3YSdlqXr0MavJIDlFK3XZrlXLy7vaRXJvW5jYg3v5e/wCaF8y0IpMjkYLhoV6QqfpOgw==} + engines: {node: '>= 14.0.0'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1177,6 +1471,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + assign-symbols@1.0.0: resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} engines: {node: '>=0.10.0'} @@ -1223,6 +1521,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + birpc@0.2.19: + resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1244,6 +1545,10 @@ packages: bytewise@1.1.0: resolution: {integrity: sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.1: resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} @@ -1270,6 +1575,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -1290,6 +1599,10 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -1331,6 +1644,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1346,12 +1663,20 @@ packages: engines: {node: '>=4'} hasBin: true + cssstyle@4.1.0: + resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -1373,9 +1698,16 @@ packages: supports-color: optional: true + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1424,12 +1756,19 @@ packages: electron-to-chromium@1.5.30: resolution: {integrity: sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA==} + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + es-abstract@1.23.8: resolution: {integrity: sha512-lfab8IzDn6EpI1ibZakcgS6WsfEBiB+43cuJo+wgylx1xKXf+Sp+YR3vFuQwC/u3sxYwV8Cxe3B0DpVUu/WiJQ==} engines: {node: '>= 0.4'} @@ -1446,6 +1785,9 @@ packages: resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} engines: {node: '>= 0.4'} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -1581,10 +1923,20 @@ packages: estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -1641,6 +1993,9 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + focus-trap@7.6.2: + resolution: {integrity: sha512-9FhUxK1hVju2+AiQIDJ5Dd//9R2n2RAfJ0qfhF4IHGHgcoEUTMpbTeG/zbEuwaiYXfuAH6XE0/aCyxDdRM+W5w==} + follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} @@ -1800,6 +2155,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-html@9.0.4: + resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==} + hast-util-to-jsx-runtime@2.3.2: resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==} @@ -1809,12 +2167,37 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + hyphenate-style-name@1.1.0: resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1947,6 +2330,9 @@ packages: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1983,6 +2369,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -1997,6 +2387,22 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + iterator.prototype@1.1.4: resolution: {integrity: sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==} engines: {node: '>= 0.4'} @@ -2019,6 +2425,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -2151,16 +2566,32 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + maplibre-gl@4.7.1: resolution: {integrity: sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -2327,6 +2758,12 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minisearch@7.1.1: + resolution: {integrity: sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2355,6 +2792,9 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2387,6 +2827,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + oniguruma-to-es@0.8.1: + resolution: {integrity: sha512-dekySTEvCxCj0IgKcA2uUCO/e4ArsqpucDPcX26w9ajx+DvMWLc5eZeJaRQkd7oC/+rwif5gnT900tA34uN9Zw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2413,6 +2856,9 @@ packages: parse-entities@4.0.1: resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2428,13 +2874,26 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + pbf@3.3.0: resolution: {integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==} hasBin: true + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -2502,9 +2961,16 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + potpack@2.0.0: resolution: {integrity: sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==} + preact@10.25.4: + resolution: {integrity: sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2664,6 +3130,15 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regex-recursion@5.1.1: + resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@5.1.1: + resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} + regexp.prototype.flags@1.5.3: resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} @@ -2699,11 +3174,17 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rollup@4.23.0: resolution: {integrity: sha512-vXB4IT9/KLDrS2WRXmY22sVB2wTsTwkpxjB8Q3mnakTENcYw3FRmfdYDy/acNmls+lHmDazgrRjK/yQ6hQAtwA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2722,9 +3203,19 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2760,6 +3251,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@1.24.4: + resolution: {integrity: sha512-aVGSFAOAr1v26Hh/+GBIsRVDWJ583XYV7CuNURKRWh9gpGv4OdbisZGq96B9arMYTZhTQkmRF5BrShOSTvNqhw==} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -2776,6 +3270,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2799,6 +3296,10 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + split-string@3.1.0: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} @@ -2806,6 +3307,12 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2863,6 +3370,10 @@ packages: supercluster@8.0.1: resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2882,10 +3393,16 @@ packages: resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} engines: {node: '>=0.10.0'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + synckit@0.9.1: resolution: {integrity: sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==} engines: {node: ^14.18.0 || >=16.0.0} + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tailwind-merge@2.5.2: resolution: {integrity: sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==} @@ -2897,6 +3414,10 @@ packages: terra-draw@1.0.0-beta.8: resolution: {integrity: sha512-40kOjgOQkDDmRIkz7QZ4urjwb9v/+Zm7tPf3RqeDY4UtKm3JodZ5iz3fFm93u3nzd+QVQlOZF0VF15ew0esQ7A==} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -2916,12 +3437,37 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + tinyqueue@2.0.3: resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} tinyqueue@3.0.0: resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.70: + resolution: {integrity: sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==} + + tldts@6.1.70: + resolution: {integrity: sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==} + hasBin: true + to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -2930,6 +3476,14 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@5.0.0: + resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + engines: {node: '>=16'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -3002,6 +3556,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3068,6 +3625,11 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-tsconfig-paths@5.0.1: resolution: {integrity: sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==} peerDependencies: @@ -3076,6 +3638,37 @@ packages: vite: optional: true + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vite@5.4.8: resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3107,9 +3700,85 @@ packages: terser: optional: true + vitepress@1.5.0: + resolution: {integrity: sha512-q4Q/G2zjvynvizdB3/bupdYkCJe2umSAMv9Ju4d92E6/NXJ59z70xB0q5p/4lpRyAwflDsbwy1mLV9Q5+nlB+g==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vt-pbf@3.1.3: resolution: {integrity: sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==} + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} + engines: {node: '>=18'} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3136,6 +3805,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3148,10 +3822,29 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xmlbuilder2@3.1.1: resolution: {integrity: sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==} engines: {node: '>=12.0'} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3169,6 +3862,111 @@ packages: snapshots: + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + '@algolia/client-search': 5.18.0 + algoliasearch: 5.18.0 + + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)': + dependencies: + '@algolia/client-search': 5.18.0 + algoliasearch: 5.18.0 + + '@algolia/client-abtesting@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/client-analytics@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/client-common@5.18.0': {} + + '@algolia/client-insights@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/client-personalization@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/client-query-suggestions@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/client-search@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/ingestion@1.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/monitoring@1.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/recommend@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + + '@algolia/requester-browser-xhr@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + + '@algolia/requester-fetch@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + + '@algolia/requester-node-http@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -3304,8 +4102,37 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@bcoe/v8-coverage@0.2.3': {} + '@ctrl/tinycolor@4.1.0': {} + '@docsearch/css@3.8.2': {} + + '@docsearch/js@3.8.2(@algolia/client-search@5.18.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': + dependencies: + '@docsearch/react': 3.8.2(@algolia/client-search@5.18.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) + preact: 10.25.4 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + + '@docsearch/react@3.8.2(@algolia/client-search@5.18.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + '@docsearch/css': 3.8.2 + algoliasearch: 5.18.0 + optionalDependencies: + '@types/react': 18.3.10 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + '@emotion/is-prop-valid@0.7.3': dependencies: '@emotion/memoize': 0.7.1 @@ -3435,6 +4262,12 @@ snapshots: '@humanwhocodes/retry@0.3.0': {} + '@iconify-json/simple-icons@1.2.17': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3444,6 +4277,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -3720,6 +4555,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.23.0': optional: true + '@shikijs/core@1.24.4': + dependencies: + '@shikijs/engine-javascript': 1.24.4 + '@shikijs/engine-oniguruma': 1.24.4 + '@shikijs/types': 1.24.4 + '@shikijs/vscode-textmate': 9.3.1 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.4 + + '@shikijs/engine-javascript@1.24.4': + dependencies: + '@shikijs/types': 1.24.4 + '@shikijs/vscode-textmate': 9.3.1 + oniguruma-to-es: 0.8.1 + + '@shikijs/engine-oniguruma@1.24.4': + dependencies: + '@shikijs/types': 1.24.4 + '@shikijs/vscode-textmate': 9.3.1 + + '@shikijs/transformers@1.24.4': + dependencies: + shiki: 1.24.4 + + '@shikijs/types@1.24.4': + dependencies: + '@shikijs/vscode-textmate': 9.3.1 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@9.3.1': {} + '@shoelace-style/animations@1.2.0': {} '@shoelace-style/localize@3.2.1': {} @@ -3891,6 +4757,8 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/linkify-it@5.0.0': {} + '@types/mapbox__point-geometry@0.1.4': {} '@types/mapbox__vector-tile@1.3.4': @@ -3899,12 +4767,24 @@ snapshots: '@types/mapbox__point-geometry': 0.1.4 '@types/pbf': 3.0.5 + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/mdurl@2.0.0': {} + '@types/ms@0.7.34': {} + '@types/node@22.10.2': + dependencies: + undici-types: 6.20.0 + optional: true + '@types/pbf@3.0.5': {} '@types/prop-types@15.7.13': {} @@ -3932,6 +4812,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/web-bluetooth@0.0.20': {} + '@typescript-eslint/eslint-plugin@8.18.2(@typescript-eslint/parser@8.18.2(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.1 @@ -4092,23 +4974,191 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react@4.3.2(vite@5.4.8)': + '@vitejs/plugin-react@4.3.2(vite@5.4.8(@types/node@22.10.2))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.8 + vite: 5.4.8(@types/node@22.10.2) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.10.2))(vue@3.5.13(typescript@5.6.2))': + dependencies: + vite: 5.4.11(@types/node@22.10.2) + vue: 3.5.13(typescript@5.6.2) + + '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@22.10.2)(jsdom@25.0.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.8.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.8(@types/node@22.10.2)(jsdom@25.0.1) transitivePeerDependencies: - supports-color + '@vitest/expect@2.1.8': + dependencies: + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.8(vite@5.4.8(@types/node@22.10.2))': + dependencies: + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 5.4.8(@types/node@22.10.2) + + '@vitest/pretty-format@2.1.8': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.8': + dependencies: + '@vitest/utils': 2.1.8 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.17 + pathe: 1.1.2 + + '@vitest/spy@2.1.8': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + + '@vue/compiler-core@3.5.13': + dependencies: + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.13 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.13': + dependencies: + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/compiler-sfc@3.5.13': + dependencies: + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.4.49 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.13': + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/devtools-api@7.6.8': + dependencies: + '@vue/devtools-kit': 7.6.8 + + '@vue/devtools-kit@7.6.8': + dependencies: + '@vue/devtools-shared': 7.6.8 + birpc: 0.2.19 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-shared@7.6.8': + dependencies: + rfdc: 1.4.1 + + '@vue/reactivity@3.5.13': + dependencies: + '@vue/shared': 3.5.13 + + '@vue/runtime-core@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/runtime-dom@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.6.2))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.6.2) + + '@vue/shared@3.5.13': {} + + '@vueuse/core@11.3.0(vue@3.5.13(typescript@5.6.2))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 11.3.0 + '@vueuse/shared': 11.3.0(vue@3.5.13(typescript@5.6.2)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.6.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/integrations@11.3.0(axios@1.7.7)(focus-trap@7.6.2)(vue@3.5.13(typescript@5.6.2))': + dependencies: + '@vueuse/core': 11.3.0(vue@3.5.13(typescript@5.6.2)) + '@vueuse/shared': 11.3.0(vue@3.5.13(typescript@5.6.2)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.6.2)) + optionalDependencies: + axios: 1.7.7 + focus-trap: 7.6.2 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@11.3.0': {} + + '@vueuse/shared@11.3.0(vue@3.5.13(typescript@5.6.2))': + dependencies: + vue-demi: 0.14.10(vue@3.5.13(typescript@5.6.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 acorn@8.12.1: {} + agent-base@7.1.3: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4116,6 +5166,22 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + algoliasearch@5.18.0: + dependencies: + '@algolia/client-abtesting': 5.18.0 + '@algolia/client-analytics': 5.18.0 + '@algolia/client-common': 5.18.0 + '@algolia/client-insights': 5.18.0 + '@algolia/client-personalization': 5.18.0 + '@algolia/client-query-suggestions': 5.18.0 + '@algolia/client-search': 5.18.0 + '@algolia/ingestion': 1.18.0 + '@algolia/monitoring': 1.18.0 + '@algolia/recommend': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -4208,6 +5274,8 @@ snapshots: get-intrinsic: 1.2.6 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + assign-symbols@1.0.0: {} ast-types-flow@0.0.8: {} @@ -4248,6 +5316,8 @@ snapshots: binary-extensions@2.3.0: {} + birpc@0.2.19: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -4277,6 +5347,8 @@ snapshots: bytewise-core: 1.2.3 typewise: 1.0.3 + cac@6.7.14: {} + call-bind-apply-helpers@1.0.1: dependencies: es-errors: 1.3.0 @@ -4302,6 +5374,14 @@ snapshots: ccount@2.0.1: {} + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -4321,6 +5401,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -4361,6 +5443,10 @@ snapshots: convert-source-map@2.0.0: {} + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4380,10 +5466,19 @@ snapshots: cssesc@3.0.0: {} + cssstyle@4.1.0: + dependencies: + rrweb-cssom: 0.7.1 + csstype@3.1.3: {} damerau-levenshtein@1.0.8: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.3 @@ -4406,10 +5501,14 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.4.3: {} + decode-named-character-reference@1.0.2: dependencies: character-entities: 2.0.2 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -4454,10 +5553,14 @@ snapshots: electron-to-chromium@1.5.30: {} + emoji-regex-xs@1.0.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + entities@4.5.0: {} + es-abstract@1.23.8: dependencies: array-buffer-byte-length: 1.0.2 @@ -4533,6 +5636,8 @@ snapshots: iterator.prototype: 1.1.4 safe-array-concat: 1.1.3 + es-module-lexer@1.6.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -4730,8 +5835,16 @@ snapshots: estree-util-is-identifier-name@3.0.0: {} + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + esutils@2.0.3: {} + expect-type@1.1.0: {} + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 @@ -4789,6 +5902,10 @@ snapshots: flatted@3.3.1: {} + focus-trap@7.6.2: + dependencies: + tabbable: 6.2.0 + follow-redirects@1.15.9: {} for-each@0.3.3: @@ -4929,6 +6046,20 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-html@9.0.4: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + hast-util-to-jsx-runtime@2.3.2: dependencies: '@types/estree': 1.0.6 @@ -4957,10 +6088,38 @@ snapshots: dependencies: react-is: 16.13.1 + hookable@5.5.3: {} + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + html-url-attributes@3.0.1: {} + html-void-elements@3.0.0: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + hyphenate-style-name@1.1.0: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -5078,6 +6237,8 @@ snapshots: dependencies: isobject: 3.0.1 + is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.3 @@ -5117,6 +6278,8 @@ snapshots: call-bound: 1.0.3 get-intrinsic: 1.2.6 + is-what@4.1.16: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -5125,6 +6288,27 @@ snapshots: isobject@3.0.1: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + iterator.prototype@1.1.4: dependencies: define-data-property: 1.1.4 @@ -5153,6 +6337,34 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@25.0.1: + dependencies: + cssstyle: 4.1.0 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.16 + parse5: 7.2.1 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@2.5.2: {} json-buffer@3.0.1: {} @@ -5323,12 +6535,28 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.1.2: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + maplibre-gl@4.7.1: dependencies: '@mapbox/geojson-rewind': 0.5.2 @@ -5358,6 +6586,8 @@ snapshots: tinyqueue: 3.0.0 vt-pbf: 3.1.3 + mark.js@8.11.1: {} + markdown-table@3.0.4: {} math-intrinsics@1.1.0: {} @@ -5731,6 +6961,10 @@ snapshots: minipass@7.1.2: {} + minisearch@7.1.1: {} + + mitt@3.0.1: {} + ms@2.1.3: {} murmurhash-js@1.0.0: {} @@ -5751,6 +6985,8 @@ snapshots: normalize-range@0.1.2: {} + nwsapi@2.2.16: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -5788,6 +7024,12 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + oniguruma-to-es@0.8.1: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 5.1.1 + regex-recursion: 5.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5828,6 +7070,10 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse5@7.2.1: + dependencies: + entities: 4.5.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -5839,13 +7085,21 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + pathe@1.1.2: {} + + pathval@2.0.0: {} + pbf@3.3.0: dependencies: ieee754: 1.2.1 resolve-protobuf-schema: 2.1.0 + perfect-debounce@1.0.0: {} + picocolors@1.1.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} pify@2.3.0: {} @@ -5902,8 +7156,16 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.1 + postcss@8.4.49: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + potpack@2.0.0: {} + preact@10.25.4: {} + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -6075,6 +7337,17 @@ snapshots: regenerator-runtime@0.14.1: {} + regex-recursion@5.1.1: + dependencies: + regex: 5.1.1 + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@5.1.1: + dependencies: + regex-utilities: 2.3.0 + regexp.prototype.flags@1.5.3: dependencies: call-bind: 1.0.8 @@ -6136,6 +7409,8 @@ snapshots: reusify@1.0.4: {} + rfdc@1.4.1: {} + rollup@4.23.0: dependencies: '@types/estree': 1.0.6 @@ -6158,6 +7433,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.23.0 fsevents: 2.3.3 + rrweb-cssom@0.7.1: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -6183,10 +7460,18 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 + search-insights@2.17.3: {} + semver@6.3.1: {} semver@7.6.3: {} @@ -6224,6 +7509,15 @@ snapshots: shebang-regex@3.0.0: {} + shiki@1.24.4: + dependencies: + '@shikijs/core': 1.24.4 + '@shikijs/engine-javascript': 1.24.4 + '@shikijs/engine-oniguruma': 1.24.4 + '@shikijs/types': 1.24.4 + '@shikijs/vscode-textmate': 9.3.1 + '@types/hast': 3.0.4 + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -6252,6 +7546,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@4.1.0: {} sort-asc@0.2.0: {} @@ -6271,12 +7567,18 @@ snapshots: space-separated-tokens@2.0.2: {} + speakingurl@14.0.1: {} + split-string@3.1.0: dependencies: extend-shallow: 3.0.2 sprintf-js@1.0.3: {} + stackback@0.0.2: {} + + std-env@3.8.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -6372,6 +7674,10 @@ snapshots: dependencies: kdbush: 4.0.2 + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -6388,11 +7694,15 @@ snapshots: symbol-observable@1.2.0: {} + symbol-tree@3.2.4: {} + synckit@0.9.1: dependencies: '@pkgr/core': 0.1.1 tslib: 2.7.0 + tabbable@6.2.0: {} + tailwind-merge@2.5.2: {} tailwindcss@3.4.13: @@ -6424,6 +7734,12 @@ snapshots: terra-draw@1.0.0-beta.8: {} + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-table@0.2.0: {} theming@3.3.0(react@18.3.1): @@ -6444,16 +7760,40 @@ snapshots: tiny-warning@1.0.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.0.2: {} + tinyqueue@2.0.3: {} tinyqueue@3.0.0: {} + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tldts-core@6.1.70: {} + + tldts@6.1.70: + dependencies: + tldts-core: 6.1.70 + to-fast-properties@2.0.0: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@5.0.0: + dependencies: + tldts: 6.1.70 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -6533,6 +7873,9 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + undici-types@6.20.0: + optional: true + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -6619,31 +7962,176 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.8): + vite-node@2.1.8(@types/node@22.10.2): + dependencies: + cac: 6.7.14 + debug: 4.3.7 + es-module-lexer: 1.6.0 + pathe: 1.1.2 + vite: 5.4.8(@types/node@22.10.2) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.10.2)): dependencies: debug: 4.3.7 globrex: 0.1.2 tsconfck: 3.1.3(typescript@5.6.2) optionalDependencies: - vite: 5.4.8 + vite: 5.4.8(@types/node@22.10.2) transitivePeerDependencies: - supports-color - typescript - vite@5.4.8: + vite@5.4.11(@types/node@22.10.2): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.23.0 + optionalDependencies: + '@types/node': 22.10.2 + fsevents: 2.3.3 + + vite@5.4.8(@types/node@22.10.2): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.23.0 optionalDependencies: + '@types/node': 22.10.2 fsevents: 2.3.3 + vitepress@1.5.0(@algolia/client-search@5.18.0)(@types/node@22.10.2)(@types/react@18.3.10)(axios@1.7.7)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.2): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.18.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.17 + '@shikijs/core': 1.24.4 + '@shikijs/transformers': 1.24.4 + '@shikijs/types': 1.24.4 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.1(vite@5.4.11(@types/node@22.10.2))(vue@3.5.13(typescript@5.6.2)) + '@vue/devtools-api': 7.6.8 + '@vue/shared': 3.5.13 + '@vueuse/core': 11.3.0(vue@3.5.13(typescript@5.6.2)) + '@vueuse/integrations': 11.3.0(axios@1.7.7)(focus-trap@7.6.2)(vue@3.5.13(typescript@5.6.2)) + focus-trap: 7.6.2 + mark.js: 8.11.1 + minisearch: 7.1.1 + shiki: 1.24.4 + vite: 5.4.11(@types/node@22.10.2) + vue: 3.5.13(typescript@5.6.2) + optionalDependencies: + postcss: 8.4.47 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + + vitest@2.1.8(@types/node@22.10.2)(jsdom@25.0.1): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.8(@types/node@22.10.2)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.3.7 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.8(@types/node@22.10.2) + vite-node: 2.1.8(@types/node@22.10.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.10.2 + jsdom: 25.0.1 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vt-pbf@3.1.3: dependencies: '@mapbox/point-geometry': 0.1.0 '@mapbox/vector-tile': 1.3.1 pbf: 3.3.0 + vue-demi@0.14.10(vue@3.5.13(typescript@5.6.2)): + dependencies: + vue: 3.5.13(typescript@5.6.2) + + vue@3.5.13(typescript@5.6.2): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.6.2)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.6.2 + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.1.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -6692,6 +8180,11 @@ snapshots: dependencies: isexe: 3.1.1 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -6706,6 +8199,10 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + ws@8.18.0: {} + + xml-name-validator@5.0.0: {} + xmlbuilder2@3.1.1: dependencies: '@oozcitak/dom': 1.15.10 @@ -6713,6 +8210,8 @@ snapshots: '@oozcitak/util': 8.3.8 js-yaml: 3.14.1 + xmlchars@2.2.0: {} + yallist@3.1.1: {} yaml@2.5.1: {} diff --git a/frontend/src/app/providers/models-provider.tsx b/frontend/src/app/providers/models-provider.tsx index 7bf6f136c..95aaa40bc 100644 --- a/frontend/src/app/providers/models-provider.tsx +++ b/frontend/src/app/providers/models-provider.tsx @@ -1,17 +1,17 @@ -import { APPLICATION_ROUTES, MODELS_BASE, MODELS_ROUTES } from "@/constants"; -import { BASE_MODELS, TrainingDatasetOption, TrainingType } from "@/enums"; -import { LngLatBoundsLike } from "maplibre-gl"; -import { TOAST_NOTIFICATIONS } from "@/constants"; +import { APPLICATION_ROUTES, MODELS_BASE, MODELS_ROUTES } from '@/constants'; +import { BASE_MODELS, TrainingDatasetOption, TrainingType } from '@/enums'; +import { LngLatBoundsLike } from 'maplibre-gl'; +import { TOAST_NOTIFICATIONS } from '@/constants'; +import { useCreateTrainingDataset } from '@/features/model-creation/hooks/use-training-datasets'; +import { useGetTrainingDataset } from '@/features/models/hooks/use-dataset'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { useModelDetails } from '@/features/models/hooks/use-models'; +import { UseMutationResult } from '@tanstack/react-query'; import { TTrainingAreaFeature, TTrainingDataset, TTrainingDetails, } from "@/types"; -import { useCreateTrainingDataset } from "@/features/model-creation/hooks/use-training-datasets"; -import { useGetTrainingDataset } from "@/features/models/hooks/use-dataset"; -import { useLocation, useNavigate, useParams } from "react-router-dom"; -import { useModelDetails } from "@/features/models/hooks/use-models"; -import { UseMutationResult } from "@tanstack/react-query"; import { showErrorToast, showSuccessToast, @@ -224,8 +224,8 @@ const ModelsContext = createContext<{ validateEditMode: boolean; }>({ formData: initialFormState, - setFormData: () => {}, - handleChange: () => {}, + setFormData: () => { }, + handleChange: () => { }, createNewTrainingDatasetMutation: {} as UseMutationResult< TTrainingDataset, Error, @@ -240,13 +240,13 @@ const ModelsContext = createContext<{ >, hasLabeledTrainingAreas: false, hasAOIsWithGeometry: false, - resetState: () => {}, + resetState: () => { }, isEditMode: false, modelId: "", getFullPath: () => "", - handleModelCreationAndUpdate: () => {}, + handleModelCreationAndUpdate: () => { }, trainingDatasetCreationInProgress: false, - handleTrainingDatasetCreation: () => {}, + handleTrainingDatasetCreation: () => { }, validateEditMode: false, }); @@ -275,7 +275,7 @@ export const ModelsProvider: React.FC<{ const getFullPath = (path: string) => `${isEditMode ? MODELS_BASE + "/" + modelId : MODELS_ROUTES.CREATE_MODEL_BASE}/${path}/`; - const timeOutRef = useRef(null); + const timeOutRef = useRef(null); const isEditMode = Boolean(modelId && !pathname.includes("new")); @@ -349,6 +349,7 @@ export const ModelsProvider: React.FC<{ mutationConfig: { onSuccess: () => { showSuccessToast(TOAST_NOTIFICATIONS.trainingRequestSubmittedSuccess); + // @ts-expect-error bad type definition timeOutRef.current = setTimeout(() => { setFormData(initialFormState); }, 2000); diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index b46689671..ce7b3bd3a 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -1,7 +1,7 @@ -import { APPLICATION_ROUTES } from '@/constants'; -import { MainErrorFallback } from '@/components/errors'; -import { ModelFormsLayout, RootLayout } from '@/layouts'; -import { ProtectedRoute } from '@/app/routes/protected-route'; +import { APPLICATION_ROUTES } from "@/constants"; +import { MainErrorFallback } from "@/components/errors"; +import { ModelFormsLayout, RootLayout } from "@/components/layouts"; +import { ProtectedRoute } from "@/app/routes/protected-route"; import { Navigate, RouterProvider, diff --git a/frontend/src/app/routes/start-mapping.tsx b/frontend/src/app/routes/start-mapping.tsx index cb0219ca2..eb0280753 100644 --- a/frontend/src/app/routes/start-mapping.tsx +++ b/frontend/src/app/routes/start-mapping.tsx @@ -1,13 +1,6 @@ import useScreenSize from "@/hooks/use-screen-size"; import { APPLICATION_ROUTES } from "@/constants"; import { BASE_MODELS } from "@/enums"; -import { - BBOX, - Feature, - TileJSON, - TModelPredictions, - TModelPredictionsConfig, -} from "@/types"; import { FitToBounds, LayerControl, ZoomLevel } from "@/components/map"; import { Head } from "@/components/seo"; import { LngLatBoundsLike } from "maplibre-gl"; @@ -19,7 +12,14 @@ import { useGetTrainingDataset } from "@/features/models/hooks/use-dataset"; import { useMapInstance } from "@/hooks/use-map-instance"; import { useModelDetails } from "@/features/models/hooks/use-models"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; -import { UserProfile } from "@/components/layout"; +import { UserProfile } from "@/components/layouts"; +import { + BBOX, + Feature, + TileJSON, + TModelPredictions, + TModelPredictionsConfig, +} from "@/types"; import { BrandLogoWithDropDown, Legend, diff --git a/frontend/src/components/layout/index.ts b/frontend/src/components/layout/index.ts deleted file mode 100644 index b32086054..000000000 --- a/frontend/src/components/layout/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./footer"; -export * from "./navbar"; diff --git a/frontend/src/components/layout/footer/footer.tsx b/frontend/src/components/layouts/footer/footer.tsx similarity index 100% rename from frontend/src/components/layout/footer/footer.tsx rename to frontend/src/components/layouts/footer/footer.tsx diff --git a/frontend/src/components/layout/footer/index.ts b/frontend/src/components/layouts/footer/index.ts similarity index 100% rename from frontend/src/components/layout/footer/index.ts rename to frontend/src/components/layouts/footer/index.ts diff --git a/frontend/src/layouts/index.ts b/frontend/src/components/layouts/index.ts similarity index 66% rename from frontend/src/layouts/index.ts rename to frontend/src/components/layouts/index.ts index eeb2bc93a..ace54e76a 100644 --- a/frontend/src/layouts/index.ts +++ b/frontend/src/components/layouts/index.ts @@ -1,2 +1,4 @@ -export { RootLayout } from "./root-layout"; +export * from "./footer"; +export * from "./navbar"; export { ModelFormsLayout } from "./model-forms-layout"; +export { RootLayout } from "./root-layout"; diff --git a/frontend/src/layouts/model-forms-layout.tsx b/frontend/src/components/layouts/model-forms-layout.tsx similarity index 100% rename from frontend/src/layouts/model-forms-layout.tsx rename to frontend/src/components/layouts/model-forms-layout.tsx diff --git a/frontend/src/components/layout/navbar/index.ts b/frontend/src/components/layouts/navbar/index.ts similarity index 100% rename from frontend/src/components/layout/navbar/index.ts rename to frontend/src/components/layouts/navbar/index.ts diff --git a/frontend/src/components/layout/navbar/nav-logo.tsx b/frontend/src/components/layouts/navbar/nav-logo.tsx similarity index 100% rename from frontend/src/components/layout/navbar/nav-logo.tsx rename to frontend/src/components/layouts/navbar/nav-logo.tsx diff --git a/frontend/src/components/layout/navbar/navbar.module.css b/frontend/src/components/layouts/navbar/navbar.module.css similarity index 100% rename from frontend/src/components/layout/navbar/navbar.module.css rename to frontend/src/components/layouts/navbar/navbar.module.css diff --git a/frontend/src/components/layout/navbar/navbar.tsx b/frontend/src/components/layouts/navbar/navbar.tsx similarity index 80% rename from frontend/src/components/layout/navbar/navbar.tsx rename to frontend/src/components/layouts/navbar/navbar.tsx index 7f06dc943..b88bcaa67 100644 --- a/frontend/src/components/layout/navbar/navbar.tsx +++ b/frontend/src/components/layouts/navbar/navbar.tsx @@ -1,18 +1,18 @@ -import styles from "@/components/layout/navbar/navbar.module.css"; -import { Button } from "@/components/ui/button"; -import { Drawer } from "@/components/ui/drawer"; -import { DrawerPlacements } from "@/enums"; -import { HamburgerIcon } from "@/assets/svgs"; -import { Image } from "@/components/ui/image"; -import { Link } from "@/components/ui/link"; -import { navLinks } from "@/constants/general"; -import { NavLogo } from "@/components/layout"; -import { SHARED_CONTENT } from "@/constants"; -import { useAuth } from "@/app/providers/auth-provider"; -import { useLocation } from "react-router-dom"; -import { useLogin } from "@/hooks/use-login"; -import { UserProfile } from "@/components/layout"; -import { useState } from "react"; +import styles from '@/components/layouts/navbar/navbar.module.css'; +import { Button } from '@/components/ui/button'; +import { Drawer } from '@/components/ui/drawer'; +import { DrawerPlacements } from '@/enums'; +import { HamburgerIcon } from '@/assets/svgs'; +import { Image } from '@/components/ui/image'; +import { Link } from '@/components/ui/link'; +import { navLinks } from '@/constants/general'; +import { NavLogo } from '@/components/layouts'; +import { SHARED_CONTENT } from '@/constants'; +import { useAuth } from '@/app/providers/auth-provider'; +import { useLocation } from 'react-router-dom'; +import { useLogin } from '@/hooks/use-login'; +import { UserProfile } from '@/components/layouts'; +import { useState } from 'react'; export const NavBar = () => { const [open, setOpen] = useState(false); diff --git a/frontend/src/components/layout/navbar/user-profile.tsx b/frontend/src/components/layouts/navbar/user-profile.tsx similarity index 77% rename from frontend/src/components/layout/navbar/user-profile.tsx rename to frontend/src/components/layouts/navbar/user-profile.tsx index 082f8a8fb..1d24f9449 100644 --- a/frontend/src/components/layout/navbar/user-profile.tsx +++ b/frontend/src/components/layouts/navbar/user-profile.tsx @@ -1,13 +1,13 @@ -import SlAvatar from "@shoelace-style/shoelace/dist/react/avatar/index.js"; -import styles from "@/components/layout/navbar/navbar.module.css"; -import useScreenSize from "@/hooks/use-screen-size"; -import { DropDown } from "@/components/ui/dropdown"; -import { DropdownPlacement } from "@/enums"; -import { TCSSWithVars } from "@/types"; -import { truncateString } from "@/utils"; -import { useAuth } from "@/app/providers/auth-provider"; -import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; -import { useNavigate } from "react-router-dom"; +import SlAvatar from '@shoelace-style/shoelace/dist/react/avatar/index.js'; +import styles from '@/components/layouts/navbar/navbar.module.css'; +import useScreenSize from '@/hooks/use-screen-size'; +import { DropDown } from '@/components/ui/dropdown'; +import { DropdownPlacement } from '@/enums'; +import { TCSSWithVars } from '@/types'; +import { truncateString } from '@/utils'; +import { useAuth } from '@/app/providers/auth-provider'; +import { useDropdownMenu } from '@/hooks/use-dropdown-menu'; +import { useNavigate } from 'react-router-dom'; import { APPLICATION_ROUTES, ELEMENT_DISTANCE_FROM_NAVBAR, diff --git a/frontend/src/layouts/root-layout.tsx b/frontend/src/components/layouts/root-layout.tsx similarity index 92% rename from frontend/src/layouts/root-layout.tsx rename to frontend/src/components/layouts/root-layout.tsx index 164849956..356f8d1bc 100644 --- a/frontend/src/layouts/root-layout.tsx +++ b/frontend/src/components/layouts/root-layout.tsx @@ -1,11 +1,10 @@ import { APPLICATION_ROUTES } from "@/constants"; import { Banner } from "@/components/ui/banner"; -import { Footer } from "@/components/layout"; import { HotTracking } from "@/components/shared"; -import { NavBar } from "@/components/layout"; import { Outlet, useLocation } from "react-router-dom"; import { useEffect } from "react"; import { useScrollToTop } from "@/hooks/use-scroll-to-element"; +import { Footer, NavBar } from "@/components/layouts"; export const RootLayout = () => { const { pathname } = useLocation(); diff --git a/frontend/src/components/ui/navbar/navbar.tsx b/frontend/src/components/ui/navbar/navbar.tsx index 9a5b2bd91..dbac7f9ef 100644 --- a/frontend/src/components/ui/navbar/navbar.tsx +++ b/frontend/src/components/ui/navbar/navbar.tsx @@ -6,12 +6,11 @@ import { DrawerPlacements } from "@/enums"; import { HamburgerIcon } from "@/assets/svgs"; import { Image } from "@/components/ui/image"; import { Link } from "@/components/ui/link"; -import { NavLogo } from "@/components/layout"; import { useAuth } from "@/app/providers/auth-provider"; import { useLocation } from "react-router-dom"; import { useLogin } from "@/hooks/use-login"; -import { UserProfile } from "@/components/layout"; import { useState } from "react"; +import { NavLogo, UserProfile } from "@/components/layouts"; export const NavBar = () => { const [open, setOpen] = useState(false); diff --git a/frontend/src/config/env.ts b/frontend/src/config/env.ts index 3db6052e0..11f2c7a85 100644 --- a/frontend/src/config/env.ts +++ b/frontend/src/config/env.ts @@ -61,6 +61,14 @@ export const ENVS = { MAX_TRAINING_AREA_UPLOAD_FILE_SIZE: import.meta.env .VITE_MAX_TRAINING_AREA_UPLOAD_FILE_SIZE, + /** + The name of the application. + This is used in the geojson-to-osm utility function to include the app name in the XML root element. + Data type: String (e.g., fAIr). + Default value: "fAIr". + */ + + APP_NAME: import.meta.env.VITE_APP_NAME, /** The current version of the application. This is used in the OSM redirect callback when a training area is opened in OSM. diff --git a/frontend/src/constants/config.ts b/frontend/src/constants/config.ts index dcce05be3..cdc1133d0 100644 --- a/frontend/src/constants/config.ts +++ b/frontend/src/constants/config.ts @@ -58,6 +58,12 @@ export const MIN_TRAINING_AREA_SIZE = ENVS.MIN_TRAINING_AREA_SIZE || 5797; export const MAX_TRAINING_AREA_UPLOAD_FILE_SIZE = ENVS.MAX_TRAINING_AREA_UPLOAD_FILE_SIZE || 5 * 1024 * 1024; +/** + * The name of the application. + * This is used in the geojson-to-osm utility function to include the app name in the XML root element. + */ + +export const APP_NAME = ENVS.APP_NAME || "fAIr"; /** * The current version of the application. * This is used in the OSM redirect callback when a training area is opened in OSM. diff --git a/frontend/src/constants/ui-contents/models-content.ts b/frontend/src/constants/ui-contents/models-content.ts index a884e2147..23a8d5d6f 100644 --- a/frontend/src/constants/ui-contents/models-content.ts +++ b/frontend/src/constants/ui-contents/models-content.ts @@ -1,6 +1,4 @@ import { BASE_MODELS } from "@/enums"; -import { formatAreaInAppropriateUnit } from "@/utils"; -import { MAX_TRAINING_AREA_SIZE, MIN_TRAINING_AREA_SIZE } from "../config"; import { TModelsContent } from "@/types"; export const MODELS_CONTENT: TModelsContent = { @@ -120,7 +118,6 @@ export const MODELS_CONTENT: TModelsContent = { "Drag 'n' drop some files here, or click to select files", fleSizeInstruction: "Supports only GeoJSON (.geojson) files. (5MB max.)", - aoiAreaInstruction: `Area should be > ${formatAreaInAppropriateUnit(MIN_TRAINING_AREA_SIZE)} and < ${formatAreaInAppropriateUnit(MAX_TRAINING_AREA_SIZE)}.`, }, pageDescription: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Id fugit ducimus harum debitis deserunt cum quod quam rerum aliquid. Quibusdam sequi incidunt quasi delectus laudantium accusamus modi omnis maiores. Incidunt!", diff --git a/frontend/src/features/model-creation/components/dialogs/file-upload-dialog.tsx b/frontend/src/features/model-creation/components/dialogs/file-upload-dialog.tsx index fd447ab69..e223c79b2 100644 --- a/frontend/src/features/model-creation/components/dialogs/file-upload-dialog.tsx +++ b/frontend/src/features/model-creation/components/dialogs/file-upload-dialog.tsx @@ -1,17 +1,19 @@ import { Button } from "@/components/ui/button"; import { DeleteIcon, FileIcon, UploadIcon } from "@/components/ui/icons"; import { Dialog } from "@/components/ui/dialog"; +import { DialogProps, Feature, FeatureCollection } from "@/types"; import { FileWithPath, useDropzone } from "react-dropzone"; import { Geometry, MultiPolygon, Polygon } from "geojson"; import { SlFormatBytes } from "@shoelace-style/shoelace/dist/react"; import { Spinner } from "@/components/ui/spinner"; import { useCallback, useState } from "react"; -import { DialogProps, Feature, FeatureCollection } from "@/types"; import { MAX_ACCEPTABLE_POLYGON_IN_TRAINING_AREA_GEOJSON_FILE, MAX_GEOJSON_FILE_UPLOAD_FOR_TRAINING_AREA_LABELS, MAX_GEOJSON_FILE_UPLOAD_FOR_TRAINING_AREAS, + MAX_TRAINING_AREA_SIZE, MAX_TRAINING_AREA_UPLOAD_FILE_SIZE, + MIN_TRAINING_AREA_SIZE, MODELS_CONTENT, } from "@/constants"; import { @@ -306,10 +308,7 @@ const FileUploadDialog: React.FC = ({ {!disableFileSizeValidation && ( - { - MODELS_CONTENT.modelCreation.trainingArea.fileUploadDialog - .aoiAreaInstruction - } + {`Area should be > ${formatAreaInAppropriateUnit(MIN_TRAINING_AREA_SIZE)} and < ${formatAreaInAppropriateUnit(MAX_TRAINING_AREA_SIZE)}.`} )} diff --git a/frontend/src/features/models/components/model-card.tsx b/frontend/src/features/models/components/model-card.tsx index 42a18ee00..31f6b61d4 100644 --- a/frontend/src/features/models/components/model-card.tsx +++ b/frontend/src/features/models/components/model-card.tsx @@ -91,9 +91,7 @@ const ModelCard: React.FC = ({ model }) => {

{MODELS_CONTENT.models.modelsList.modelCard.baseModel} - - {extractDatePart(model.base_model)} - + {model.base_model}

diff --git a/frontend/src/features/models/components/training-history-table.tsx b/frontend/src/features/models/components/training-history-table.tsx index 85b2b6774..b357165cb 100644 --- a/frontend/src/features/models/components/training-history-table.tsx +++ b/frontend/src/features/models/components/training-history-table.tsx @@ -14,7 +14,6 @@ import { useAuth } from "@/app/providers/auth-provider"; import { useDialog } from "@/hooks/use-dialog"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { useState } from "react"; -import { useToastNotification } from "@/hooks/use-toast-notification"; import { useTrainingHistory } from "@/features/models/hooks/use-training"; import { useUpdateTraining } from "@/features/models/api/update-trainings"; import { @@ -22,6 +21,7 @@ import { formatDuration, roundNumber, showErrorToast, + showSuccessToast, truncateString, } from "@/utils"; @@ -249,11 +249,11 @@ const TrainingHistoryTable: React.FC = ({ const [sorting, setSorting] = useState([]); const { user, isAuthenticated } = useAuth(); const { isOpened, openDialog, closeDialog } = useDialog(); - const toast = useToastNotification(); + const { mutate } = useUpdateTraining({ mutationConfig: { onSuccess: (res) => { - toast(res.data, "success"); + showSuccessToast(res.data); }, onError: (err) => { showErrorToast(err); diff --git a/frontend/src/features/start-mapping/components/header.tsx b/frontend/src/features/start-mapping/components/header.tsx index bc2ac6546..14ad67ebf 100644 --- a/frontend/src/features/start-mapping/components/header.tsx +++ b/frontend/src/features/start-mapping/components/header.tsx @@ -13,7 +13,7 @@ import { TDownloadOptions, TQueryParams } from "@/app/routes/start-mapping"; import { TModel, TModelPredictions, TModelPredictionsConfig } from "@/types"; import { ToolTip } from "@/components/ui/tooltip"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; -import { UserProfile } from "@/components/layout"; +import { UserProfile } from "@/components/layouts"; import { ELEMENT_DISTANCE_FROM_NAVBAR, START_MAPPING_PAGE_CONTENT, diff --git a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx index 2d4ea8dd0..a3bbfc13b 100644 --- a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx +++ b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx @@ -4,7 +4,7 @@ import { DropdownPlacement } from "@/enums"; import { ELEMENT_DISTANCE_FROM_NAVBAR } from "@/constants"; import { Link } from "@/components/ui/link"; import { navLinks } from "@/constants/general"; -import { NavLogo } from "@/components/layout"; +import { NavLogo } from "@/components/layouts"; import { useNavigate } from "react-router-dom"; type BrandLogoWithDropDownProps = { diff --git a/frontend/src/features/start-mapping/components/model-details-popup.tsx b/frontend/src/features/start-mapping/components/model-details-popup.tsx index 03a9f5961..5e8b90e49 100644 --- a/frontend/src/features/start-mapping/components/model-details-popup.tsx +++ b/frontend/src/features/start-mapping/components/model-details-popup.tsx @@ -114,7 +114,7 @@ const ModelDetailsPopUp = ({

{START_MAPPING_PAGE_CONTENT.modelDetails.popover.accuracy}:{" "} - {roundNumber(model?.accuracy ?? (data?.accuracy as number), 2)}% + {roundNumber(model?.accuracy ?? (data?.accuracy as number))}%

diff --git a/frontend/src/hooks/use-clipboard.ts b/frontend/src/hooks/use-clipboard.ts index 1ecb76f28..cf730afdb 100644 --- a/frontend/src/hooks/use-clipboard.ts +++ b/frontend/src/hooks/use-clipboard.ts @@ -1,5 +1,5 @@ +import { showErrorToast, showSuccessToast } from "@/utils"; import { useState } from "react"; -import { useToastNotification } from "./use-toast-notification"; /** * Custom hook to copy text to the clipboard and display a toast notification. @@ -10,16 +10,15 @@ import { useToastNotification } from "./use-toast-notification"; */ const useCopyToClipboard = () => { const [isCopied, setIsCopied] = useState(false); - const toast = useToastNotification(); const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); setIsCopied(true); - toast("Copied to clipboard!", "success"); + showSuccessToast("Copied to clipboard!"); setTimeout(() => setIsCopied(false), 2000); } catch (error) { setIsCopied(false); - toast("Failed to copy!", "success"); + showErrorToast(undefined, "Failed to copy!"); } }; diff --git a/frontend/src/hooks/use-toast-notification.ts b/frontend/src/hooks/use-toast-notification.ts deleted file mode 100644 index 21905855a..000000000 --- a/frontend/src/hooks/use-toast-notification.ts +++ /dev/null @@ -1,42 +0,0 @@ -import "@shoelace-style/shoelace/dist/components/alert/alert.js"; - -/** - * Custom hook for displaying toast notifications. - * - * @returns {Function} toast - Function to trigger a toast notification. - * - * @param {string} message - The message to display in the notification. - * @param {"primary" | "success" | "neutral" | "warning" | "danger"} [variant="primary"] - Type of notification style. It defaults to primary. - * @param {number} [duration=3000] - Duration in milliseconds for how long the notification stays visible. - * - * @example - * const toast = useToastNotification(); - * toast("Data saved successfully", "success", 2000); - */ -export const useToastNotification = () => { - const toast = ( - message: string, - variant: - | "primary" - | "success" - | "neutral" - | "warning" - | "danger" = "primary", - duration: number = 3000, - ) => { - const alert = Object.assign(document.createElement("sl-alert"), { - variant, - closable: true, - duration, - innerHTML: ` - ${message} - `, - }); - // make the variant the classname - alert.classList.add(variant); - document.body.append(alert); - alert.toast(); - }; - - return toast; -}; diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts index 71d04afd4..4becde92e 100644 --- a/frontend/src/types/common.ts +++ b/frontend/src/types/common.ts @@ -11,9 +11,9 @@ export type ShoelaceSlotProps = { }; export type DateFilter = { - label: string; + label?: string; apiValue: string; - searchParams: string; + searchParams?: string; }; export type TQueryParams = Record< diff --git a/frontend/src/types/ui-contents.ts b/frontend/src/types/ui-contents.ts index 681cf9295..2e13b1662 100644 --- a/frontend/src/types/ui-contents.ts +++ b/frontend/src/types/ui-contents.ts @@ -101,7 +101,6 @@ export type TModelsContent = { title: string; mainInstruction: string; fleSizeInstruction: string; - aoiAreaInstruction: string; }; pageDescription: string; }; diff --git a/frontend/src/utils/__tests__/cn.test.ts b/frontend/src/utils/__tests__/cn.test.ts new file mode 100644 index 000000000..b38f091e9 --- /dev/null +++ b/frontend/src/utils/__tests__/cn.test.ts @@ -0,0 +1,28 @@ +import { cn } from "../cn"; +import { describe, expect, it } from "vitest"; + +describe("cn utility function", () => { + it("should merge class names correctly", () => { + expect(cn("class1", "class2")).toBe("class1 class2"); + }); + + it("should handle conditional class names", () => { + expect(cn("class1", false && "class2", "class3")).toBe("class1 class3"); + }); + + it("should handle arrays of class names", () => { + expect(cn(["class1", "class2"], "class3")).toBe("class1 class2 class3"); + }); + + it("should handle objects with boolean values", () => { + expect(cn({ class1: true, class2: false }, "class3")).toBe("class1 class3"); + }); + + it("should handle undefined and null values", () => { + expect(cn("class1", undefined, null, "class2")).toBe("class1 class2"); + }); + + it("should merge tailwind classes correctly", () => { + expect(cn("p-2", "p-4")).toBe("p-4"); + }); +}); diff --git a/frontend/src/utils/__tests__/date-utils.test.ts b/frontend/src/utils/__tests__/date-utils.test.ts new file mode 100644 index 000000000..d90b7bd0b --- /dev/null +++ b/frontend/src/utils/__tests__/date-utils.test.ts @@ -0,0 +1,140 @@ +import { buildDateFilterQueryString } from "../date-utils"; +import { describe, expect, it } from "vitest"; +import { extractDatePart, formatDate, formatDuration } from "../date-utils"; + +describe("buildDateFilterQueryString", () => { + it("should return an empty object if no dates are provided", () => { + const selectedFilter = { apiValue: "created_at" }; + expect(buildDateFilterQueryString(selectedFilter)).toEqual({}); + }); + + it("should return a query string with only startDate", () => { + const selectedFilter = { apiValue: "-created_at" }; + const startDate = "2024-01-01"; + expect(buildDateFilterQueryString(selectedFilter, startDate)).toEqual({ + "-created_at__gte": startDate, + }); + }); + + it("should return a query string with only endDate", () => { + const selectedFilter = { apiValue: "updated_at" }; + const endDate = "2024-12-31"; + expect( + buildDateFilterQueryString(selectedFilter, undefined, endDate), + ).toEqual({ + updated_at__lte: endDate, + }); + }); + + it("should return a query string with both startDate and endDate", () => { + const selectedFilter = { apiValue: "-updated_at" }; + const startDate = "2024-01-01"; + const endDate = "2024-12-31"; + expect( + buildDateFilterQueryString(selectedFilter, startDate, endDate), + ).toEqual({ + "-updated_at__gte": startDate, + "-updated_at__lte": endDate, + }); + }); + + it("should handle undefined selectedFilter gracefully", () => { + const startDate = "2024-01-01"; + const endDate = "2024-12-31"; + expect(buildDateFilterQueryString(undefined, startDate, endDate)).toEqual( + {}, + ); + }); +}); + +describe("formatDate", () => { + it("should format date correctly", () => { + const isoString = "2024-01-01T12:00:00Z"; + expect(formatDate(isoString)).toBe("01/01/2024, 12:00:00"); + }); + + it("should handle different times correctly", () => { + const isoString = "2024-01-01T23:59:59Z"; + expect(formatDate(isoString)).toBe("01/01/2024, 23:59:59"); + }); + + it("should handle different dates correctly", () => { + const isoString = "2024-12-31T00:00:00Z"; + expect(formatDate(isoString)).toBe("31/12/2024, 00:00:00"); + }); + + it("should handle invalid ISO strings gracefully", () => { + const isoString = "invalid-date"; + expect(formatDate(isoString)).toBe("NaN/NaN/NaN, NaN:NaN:NaN"); + }); + + it("should handle empty ISO strings gracefully", () => { + const isoString = ""; + expect(formatDate(isoString)).toBe("NaN/NaN/NaN, NaN:NaN:NaN"); + }); +}); + +describe("formatDuration", () => { + it("should format duration correctly for same start and end date", () => { + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-01T00:00:00Z"); + expect(formatDuration(startDate, endDate)).toBe("0 sec"); + }); + + it("should format duration correctly for seconds difference", () => { + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-01T00:00:30Z"); + expect(formatDuration(startDate, endDate)).toBe("30 secs"); + }); + + it("should format duration correctly for minutes difference", () => { + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-01T00:15:00Z"); + expect(formatDuration(startDate, endDate)).toBe("15 mins"); + }); + + it("should format duration correctly for hours difference", () => { + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-01T02:00:00Z"); + expect(formatDuration(startDate, endDate)).toBe("2 hrs"); + }); + + it("should format duration correctly for days difference", () => { + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-03T00:00:00Z"); + expect(formatDuration(startDate, endDate)).toBe("2 days"); + }); + + it("should format duration correctly for mixed difference", () => { + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-02T03:04:05Z"); + expect(formatDuration(startDate, endDate)).toBe( + "1 day 3 hrs 4 mins 5 secs", + ); + }); + + it("should limit the number of units in the output", () => { + const startDate = new Date("2024-01-01T00:00:00Z"); + const endDate = new Date("2024-01-02T03:04:05Z"); + expect(formatDuration(startDate, endDate, 2)).toBe("1 day 3 hrs"); + }); +}); + +describe("extractDatePart", () => { + it("should extract the year correctly", () => { + const date = new Date("2024-01-01T00:00:00Z").toISOString(); + expect(extractDatePart(date)).toBe("2024-01-01"); + }); + + it("should return N/A for TZ alone", () => { + expect(extractDatePart("T00:00:00Z")).toBe("N/A"); + }); + + it("should still work for dates that are not ISO date strings", () => { + expect(extractDatePart("2024-01-01")).toBe("2024-01-01"); + }); + + it("should return N/A for invalid date or empty date strings", () => { + expect(extractDatePart("")).toBe("N/A"); + }); +}); diff --git a/frontend/src/utils/__tests__/general-utils.test.ts b/frontend/src/utils/__tests__/general-utils.test.ts new file mode 100644 index 000000000..9fb6c0801 --- /dev/null +++ b/frontend/src/utils/__tests__/general-utils.test.ts @@ -0,0 +1,17 @@ +import * as utils from "../general-utils"; +import { describe, expect, it } from "vitest"; + +describe("uuid4", () => { + it("should generate a valid UUID4", () => { + const uuid = utils.uuid4(); + const uuidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + expect(uuid).toMatch(uuidRegex); + }); + + it("should generate unique UUIDs", () => { + const uuid1 = utils.uuid4(); + const uuid2 = utils.uuid4(); + expect(uuid1).not.toBe(uuid2); + }); +}); diff --git a/frontend/src/utils/__tests__/geo/geo-utils.test.ts b/frontend/src/utils/__tests__/geo/geo-utils.test.ts new file mode 100644 index 000000000..517a16b0a --- /dev/null +++ b/frontend/src/utils/__tests__/geo/geo-utils.test.ts @@ -0,0 +1,77 @@ +import * as geomUtils from "../../geo/geometry-utils"; +import { + createFeatureCollection, + openInIDEditor, + validateGeoJSONArea, +} from "../../geo/geo-utils"; +import { describe, expect, it, vi } from "vitest"; +import { Feature } from "geojson"; + +describe("geo-utils", () => { + describe("createFeatureCollection", () => { + it("should create an empty FeatureCollection when no features are provided", () => { + const result = createFeatureCollection(); + expect(result).toEqual({ type: "FeatureCollection", features: [] }); + }); + + it("should create a FeatureCollection with provided features", () => { + const features: Feature[] = [ + { + type: "Feature", + geometry: { type: "Point", coordinates: [0, 0] }, + properties: {}, + }, + ]; + const result = createFeatureCollection(features); + expect(result).toEqual({ type: "FeatureCollection", features }); + }); + }); + + describe("openInIDEditor", () => { + it("should open a new window with the correct URL", () => { + const openSpy = vi.spyOn(window, "open").mockImplementation(() => null); + openInIDEditor(10, 20, 30, 40, "http://example.com", "datasetId", 1); + expect(openSpy).toHaveBeenCalledWith( + expect.stringContaining("https://www.openstreetmap.org/edit?editor=id"), + "_blank", + "noreferrer", + ); + openSpy.mockRestore(); + }); + }); + + describe("validateGeoJSONArea", () => { + it("should return true if the area is smaller than MIN_TRAINING_AREA_SIZE", () => { + const feature: Feature = { + type: "Feature", + geometry: { type: "Point", coordinates: [0, 0] }, + properties: {}, + }; + vi.spyOn(geomUtils, "calculateGeoJSONArea").mockReturnValue(0); + const result = validateGeoJSONArea(feature); + expect(result).toBe(true); + }); + + it("should return true if the false is larger than MAX_TRAINING_AREA_SIZE", () => { + const feature: Feature = { + type: "Feature", + geometry: { type: "Point", coordinates: [0, 0] }, + properties: {}, + }; + vi.spyOn(geomUtils, "calculateGeoJSONArea").mockReturnValue(1000000); + const result = validateGeoJSONArea(feature); + expect(result).toBe(false); + }); + + it("should return true if the area is within the acceptable range", () => { + const feature: Feature = { + type: "Feature", + geometry: { type: "Point", coordinates: [0, 0] }, + properties: {}, + }; + vi.spyOn(geomUtils, "calculateGeoJSONArea").mockReturnValue(500); + const result = validateGeoJSONArea(feature); + expect(result).toBe(true); + }); + }); +}); diff --git a/frontend/src/utils/__tests__/geo/geojson-to-osm.test.ts b/frontend/src/utils/__tests__/geo/geojson-to-osm.test.ts new file mode 100644 index 000000000..756dc0ac9 --- /dev/null +++ b/frontend/src/utils/__tests__/geo/geojson-to-osm.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it } from "vitest"; +import { FeatureCollection } from "geojson"; +import { geojsonToOsmPolygons } from "../../geo/geojson-to-osm"; + +describe("geojsonToOsmPolygons", () => { + it("should convert a valid GeoJSON FeatureCollection to OSM XML", () => { + const geojson: FeatureCollection = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [ + [ + [102.0, 0.0], + [103.0, 1.0], + [104.0, 0.0], + [102.0, 0.0], + ], + ], + }, + properties: { + name: "Test Polygon", + }, + }, + ], + }; + + const osmXml = geojsonToOsmPolygons(geojson); + expect(osmXml).toContain(''); + expect(osmXml).toContain(" { + const invalidGeojson: any = { + type: "InvalidType", + features: [], + }; + + expect(() => geojsonToOsmPolygons(invalidGeojson)).toThrow( + "Invalid GeoJSON FeatureCollection", + ); + }); + + it("should skip unsupported geometry types", () => { + const geojson: FeatureCollection = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: { + type: "Point", + coordinates: [102.0, 0.5], + }, + properties: {}, + }, + ], + }; + + const osmXml = geojsonToOsmPolygons(geojson); + expect(osmXml).not.toContain(" { + const geojson: FeatureCollection = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [ + [ + [102.0, 0.0], + [103.0, 1.0], + [104.0, 0.0], + [102.0, 0.0], + ], + [ + [100.0, 0.0], + [101.0, 1.0], + [102.0, 0.0], + [100.0, 0.0], + ], + ], + }, + properties: { + name: "Test Polygon with Rings", + }, + }, + ], + }; + + const osmXml = geojsonToOsmPolygons(geojson); + expect(osmXml).toContain(''); + expect(osmXml).toContain(" { + it("should calculate the area of a GeoJSON Feature", () => { + const feature: Feature = { + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [ + [ + [-10, -10], + [10, -10], + [10, 10], + [-10, 10], + [-10, -10], + ], + ], + }, + properties: {}, + }; + const result = calculateGeoJSONArea(feature); + expect(result).toBeGreaterThan(0); + }); + + it("should format area into human readable string", () => { + const result = formatAreaInAppropriateUnit(12222000); + expect(result).toBe("12.2km²"); + }); + + it("should compute the bounding box of a GeoJSON Feature", () => { + const feature: Feature = { + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [ + [ + [-10, -10], + [10, -10], + [10, 10], + [-10, 10], + [-10, -10], + ], + ], + }, + properties: {}, + }; + const result = getGeoJSONFeatureBounds(feature); + expect(result).toEqual([-10, -10, 10, 10]); + }); + + it("should calculate the distance between two geographic coordinates", () => { + const result = distance(0, 0, 10, 10, "K"); + expect(result).toBeGreaterThan(0); + }); + + it("should approximate coordinates to the nearest corner tiles", () => { + const coordinates: [number, number][] = [ + [10, 10], + [20, 20], + ]; + const result = approximateGeom(coordinates); + expect(result.length).toBe(2); + }); + + it("should snap GeoJSON polygon to closest tile", () => { + const polygon: Polygon = { + type: "Polygon", + coordinates: [ + [ + [10, 10], + [20, 20], + [30, 30], + [10, 10], + ], + ], + }; + const result = snapGeoJSONPolygonToClosestTile(polygon); + expect(result.coordinates[0].length).toBe(4); + }); + + it("should handle conflation of new features with existing predictions", () => { + const existingPredictions = { + all: [], + accepted: [], + rejected: [], + }; + const newFeatures: Feature[] = [ + { + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [ + [ + [10, 10], + [20, 20], + [30, 30], + [10, 10], + ], + ], + }, + properties: {}, + }, + ]; + const predictionConfig: TModelPredictionsConfig = { + area_threshold: 6, + bbox: [0, 0, 0, 0], + checkpoint: "", + confidence: 95, + max_angle_change: 0, + model_id: "", + use_josm_q: true, + skew_tolerance: 0, + zoom_level: 21, + source: "", + tolerance: 0, + }; + const result = handleConflation( + existingPredictions, + newFeatures, + predictionConfig, + ); + expect(result.all.length).toBe(1); + }); +}); diff --git a/frontend/src/utils/__tests__/number-utils.test.ts b/frontend/src/utils/__tests__/number-utils.test.ts new file mode 100644 index 000000000..f9719a6ff --- /dev/null +++ b/frontend/src/utils/__tests__/number-utils.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { roundNumber } from "../number-utils"; + +describe("roundNumber", () => { + it("should round a number to 2 decimal places by default", () => { + expect(roundNumber(1.2345)).toBe(1.23); + expect(roundNumber(1.2367)).toBe(1.24); + }); + + it("should round a number to the specified number of decimal places", () => { + expect(roundNumber(1.2345, 3)).toBe(1.235); + expect(roundNumber(1.2367, 1)).toBe(1.2); + }); + + it("should handle rounding of whole numbers", () => { + expect(roundNumber(123)).toBe(123.0); + expect(roundNumber(123, 1)).toBe(123.0); + }); + + it("should handle negative numbers", () => { + expect(roundNumber(-1.2345)).toBe(-1.23); + expect(roundNumber(-1.2367, 3)).toBe(-1.237); + }); + + it("should handle zero", () => { + expect(roundNumber(0)).toBe(0.0); + expect(roundNumber(0, 3)).toBe(0.0); + }); +}); diff --git a/frontend/src/utils/__tests__/regex-utils.test.ts b/frontend/src/utils/__tests__/regex-utils.test.ts new file mode 100644 index 000000000..1f99c085d --- /dev/null +++ b/frontend/src/utils/__tests__/regex-utils.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from "vitest"; +import { TMS_URL_REGEX_PATTERN } from "../regex-utils"; + +describe("TMS_URL_REGEX_PATTERN", () => { + it("should match a valid TMS URL", () => { + const validUrl = "https://example.com/{z}/{x}/{y}.png"; + expect(TMS_URL_REGEX_PATTERN.test(validUrl)).toBe(true); + }); + + it("should not match a URL without https", () => { + const invalidUrl = "http://example.com/{z}/{x}/{y}.png"; + expect(TMS_URL_REGEX_PATTERN.test(invalidUrl)).toBe(false); + }); + + it("should not match a URL without placeholders", () => { + const invalidUrl = "https://example.com/1/2/3.png"; + expect(TMS_URL_REGEX_PATTERN.test(invalidUrl)).toBe(false); + }); + + it("should match a URL with additional query parameters", () => { + const validUrl = "https://example.com/{z}/{x}/{y}.png?token=abc123"; + expect(TMS_URL_REGEX_PATTERN.test(validUrl)).toBe(true); + }); + + it("should not match a URL with missing placeholders", () => { + const invalidUrl = "https://example.com/{z}/{x}/.png"; + expect(TMS_URL_REGEX_PATTERN.test(invalidUrl)).toBe(false); + }); + + it("should match a URL with different file extensions", () => { + const validUrl = "https://example.com/{z}/{x}/{y}.jpg"; + expect(TMS_URL_REGEX_PATTERN.test(validUrl)).toBe(true); + }); + + it("should not match a URL with incorrect placeholder order", () => { + const invalidUrl = "https://example.com/{x}/{z}/{y}.png"; + expect(TMS_URL_REGEX_PATTERN.test(invalidUrl)).toBe(false); + }); +}); diff --git a/frontend/src/utils/__tests__/string-utils.test.ts b/frontend/src/utils/__tests__/string-utils.test.ts new file mode 100644 index 000000000..79de08b11 --- /dev/null +++ b/frontend/src/utils/__tests__/string-utils.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from "vitest"; +import { extractTileJSONURL, truncateString } from "../string-utils"; + +describe("truncateString", () => { + it("should truncate a string longer than the specified maxLength and append ellipsis", () => { + const result = truncateString( + "This is a very long string that needs to be truncated", + 20, + ); + expect(result).toBe("This is a very lo..."); + }); + + it("should return the original string if it is shorter than the specified maxLength", () => { + const result = truncateString("Short string", 20); + expect(result).toBe("Short string"); + }); + + it("should return the original string if it is exactly the specified maxLength", () => { + const result = truncateString("Exact length string", 19); + expect(result).toBe("Exact length string"); + }); + + it("should handle undefined input gracefully", () => { + const result = truncateString(undefined, 20); + expect(result).toBeUndefined(); + }); + + it("should use the default maxLength of 30 if not specified", () => { + const result = truncateString("This string is exactly thirty.."); + expect(result).toBe("This string is exactly thir..."); + }); +}); + +describe("extractTileJSONURL", () => { + it("should extract the base URL from a TMS URL", () => { + const result = extractTileJSONURL( + "https://example.com/tiles/{z}/{x}/{y}.png", + ); + expect(result).toBe("https://example.com/tiles"); + }); + + it("should return the original URL if it does not contain the TMS pattern", () => { + const result = extractTileJSONURL("https://example.com/tiles"); + expect(result).toBe("https://example.com/tiles"); + }); + + it("should handle URLs with query parameters", () => { + const result = extractTileJSONURL( + "https://example.com/tiles/{z}/{x}/{y}.png?token=abc123", + ); + expect(result).toBe("https://example.com/tiles"); + }); + + it("should handle URLs with additional path segments", () => { + const result = extractTileJSONURL( + "https://example.com/path/to/tiles/{z}/{x}/{y}.png", + ); + expect(result).toBe("https://example.com/path/to/tiles"); + }); +}); diff --git a/frontend/src/utils/date-utils.ts b/frontend/src/utils/date-utils.ts index 2b205b90f..7ff33dd0f 100644 --- a/frontend/src/utils/date-utils.ts +++ b/frontend/src/utils/date-utils.ts @@ -11,8 +11,11 @@ import { DateFilter } from "@/types"; * @returns {string} - The extracted date part in "YYYY-MM-DD" format. */ export const extractDatePart = (isoString: string) => { - if (!isoString) return "N/A"; // Return fallback if undefined - return isoString.split("T")[0]; + const [datePart] = isoString.split("T"); + if (!datePart) { + return "N/A"; + } + return datePart; }; /** @@ -43,6 +46,7 @@ export const buildDateFilterQueryString = ( endDate?: string, ): Record => { const params: Record = {}; + if (!selectedFilter) return {}; if (startDate) { params[`${selectedFilter?.apiValue}__gte`] = startDate; } @@ -53,6 +57,17 @@ export const buildDateFilterQueryString = ( return Object.assign({}, params); }; +/** + * Formats an ISO 8601 date-time string into a human-readable date-time string. + * + * This function takes an ISO date-time string (e.g., "2024-01-01T12:00:00Z") + * and converts it into a human-readable format with the date and time separated + * by a comma. The date part is formatted as "DD/MM/YYYY", and the time part is + * formatted as "HH:MM:SS". + * + * @param {string} isoString - The ISO date-time string to format. + * @returns {string} - The formatted date-time string (e.g., "01/01/2024, 12:00:00"). + */ export const formatDate = (isoString: string): string => { const date = new Date(isoString); diff --git a/frontend/src/utils/general-utils.ts b/frontend/src/utils/general-utils.ts index 0306248e0..7621b1eb2 100644 --- a/frontend/src/utils/general-utils.ts +++ b/frontend/src/utils/general-utils.ts @@ -1,4 +1,33 @@ -import { useToastNotification } from "@/hooks/use-toast-notification"; +import "@shoelace-style/shoelace/dist/components/alert/alert.js"; + +/** + * Custom function for displaying toast notifications. + * @param {string} message - The message to display in the notification. + * @param {"primary" | "success" | "neutral" | "warning" | "danger"} [variant="primary"] - Type of notification style. It defaults to primary. + * @param {number} [duration=3000] - Duration in milliseconds for how long the notification stays visible. + * + * @example + * createToastNotification("Data saved successfully", "success", 2000); + */ + +export const createToastNotification = ( + message: string, + variant: "primary" | "success" | "neutral" | "warning" | "danger" = "primary", + duration: number = 3000, +) => { + const alert = Object.assign(document.createElement("sl-alert"), { + variant, + closable: true, + duration, + innerHTML: ` + ${message} + `, + }); + // make the variant the classname + alert.classList.add(variant); + document.body.append(alert); + alert.toast(); +}; /** * Displays an error message as a toast notification. @@ -17,7 +46,6 @@ export const showErrorToast = ( error: any | undefined = undefined, customMessage: string | undefined = undefined, ) => { - const toast = useToastNotification(); let message = "An unexpected error occurred"; if (customMessage) { message = customMessage; @@ -38,7 +66,7 @@ export const showErrorToast = ( message = error.response?.statusText; } - toast(message, "danger"); + createToastNotification(message, "danger"); }; /** @@ -47,8 +75,7 @@ export const showErrorToast = ( * @param {string} message - Optional. The message that will be displayed as the toast notification. */ export const showSuccessToast = (message: string = "") => { - const toast = useToastNotification(); - toast(message, "success"); + createToastNotification(message, "success"); }; /** @@ -57,8 +84,7 @@ export const showSuccessToast = (message: string = "") => { * @param {string} message - Optional. The message that will be displayed as the toast notification. */ export const showWarningToast = (message: string = "") => { - const toast = useToastNotification(); - toast(message, "warning"); + createToastNotification(message, "warning"); }; /** diff --git a/frontend/src/utils/geo/geo-utils.ts b/frontend/src/utils/geo/geo-utils.ts index fb285c782..3a3a7bcf4 100644 --- a/frontend/src/utils/geo/geo-utils.ts +++ b/frontend/src/utils/geo/geo-utils.ts @@ -1,6 +1,9 @@ import bbox from "@turf/bbox"; import { API_ENDPOINTS, BASE_API_URL } from "@/services"; import { calculateGeoJSONArea } from "./geometry-utils"; +import { Feature, FeatureCollection } from "geojson"; +import { geojsonToOsmPolygons } from "./geojson-to-osm"; +import { showErrorToast, showSuccessToast } from "../general-utils"; import { FAIR_VERSION, JOSM_REMOTE_URL, @@ -9,9 +12,6 @@ import { OSM_HASHTAGS, TOAST_NOTIFICATIONS, } from "@/constants"; -import { Feature, FeatureCollection } from "geojson"; -import { geojsonToOsmPolygons } from "./geojson-to-osm"; -import { showErrorToast, showSuccessToast } from "../general-utils"; /** * Creates a GeoJSON FeatureCollection diff --git a/frontend/src/utils/geo/geojson-to-osm.ts b/frontend/src/utils/geo/geojson-to-osm.ts index a0a9da77b..2982e1940 100644 --- a/frontend/src/utils/geo/geojson-to-osm.ts +++ b/frontend/src/utils/geo/geojson-to-osm.ts @@ -1,3 +1,4 @@ +import { APP_NAME, FAIR_VERSION } from "@/constants"; import { create } from "xmlbuilder2"; import { FeatureCollection, Position } from "geojson"; @@ -72,7 +73,7 @@ export const geojsonToOsmPolygons = (geojson: FeatureCollection): string => { // Create XML document const doc = create({ version: "1.0", encoding: "UTF-8" }).ele("osm", { version: "0.6", - generator: "geojson2osm-polygons", + generator: `${APP_NAME}(${FAIR_VERSION})`, }); // Assign unique negative IDs for new OSM elements diff --git a/frontend/src/utils/number-utils.ts b/frontend/src/utils/number-utils.ts index df32f4d8d..e4450c423 100644 --- a/frontend/src/utils/number-utils.ts +++ b/frontend/src/utils/number-utils.ts @@ -9,6 +9,8 @@ * @param {number} [round=2] - The number of decimal places to round to (default is 2). * @returns {string} - The rounded number as a string. */ -export const roundNumber = (num: number, round: number = 2): number => { - return Number(num.toFixed(round)); +export const roundNumber = (num: number, decimals: number = 2): number => { + const factor = 10 ** decimals; + // Add a tiny offset before rounding to counter floating-point errors + return Math.round((num + Number.EPSILON) * factor) / factor; }; diff --git a/frontend/src/utils/regex-utils.ts b/frontend/src/utils/regex-utils.ts index 711d8eddb..955a878d4 100644 --- a/frontend/src/utils/regex-utils.ts +++ b/frontend/src/utils/regex-utils.ts @@ -1 +1,9 @@ +/** + * Regular expression pattern to match TMS (Tile Map Service) URLs. + * + * This pattern ensures that the URL starts with "https://", followed by any characters, + * and contains placeholders for zoom level (`{z}`), x-coordinate (`{x}`), and y-coordinate (`{y}`). + * + * Example of a matching URL: `https://example.com/{z}/{x}/{y}.png` + */ export const TMS_URL_REGEX_PATTERN = /^https:\/\/.*\/\{z\}\/\{x\}\/\{y\}.*$/; diff --git a/frontend/src/utils/string-utils.ts b/frontend/src/utils/string-utils.ts index 942ab30fa..8bcb93aa5 100644 --- a/frontend/src/utils/string-utils.ts +++ b/frontend/src/utils/string-utils.ts @@ -1,5 +1,6 @@ /** * Truncates a string to a specified maximum length, appending ellipsis if truncated. + * The elipsis is also included in the maximum character count, as well as the spaces. * * This function takes a string and a maximum length, and if the string exceeds * the specified length, it truncates the string and appends "..." to indicate that @@ -16,6 +17,15 @@ export const truncateString = (string?: string, maxLength: number = 30) => { return string; }; +/** + * Extracts the base URL from an OpenAerialMap TMS URL. + * + * This function takes an OpenAerialMap TMS URL and removes the tile-specific + * path (/{z}/{x}/{y}) to return the base URL. + * + * @param {string} openAerialMapTMSURL - The OpenAerialMap TMS URL to be processed. + * @returns {string} - The base URL extracted from the provided TMS URL. + */ export const extractTileJSONURL = (openAerialMapTMSURL: string) => { return openAerialMapTMSURL.split("/{z}/{x}/{y}")[0]; }; diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index 94bd68522..7a5c220f5 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -17,6 +17,7 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, + "types": ["vitest/globals"], /*Absolute imports*/ "baseUrl": ".", "paths": { diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index ea9d0cd82..2cb35ca92 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -7,5 +7,5 @@ { "path": "./tsconfig.node.json" } - ] + ], } diff --git a/frontend/vercel.json b/frontend/vercel.json index 408821b11..3a8efbc88 100644 --- a/frontend/vercel.json +++ b/frontend/vercel.json @@ -4,5 +4,16 @@ "source": "/(.*)", "destination": "/" } + ], + "headers": [ + { + "source": "/assets/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "max-age=31536000, immutable" + } + ] + } ] -} +} \ No newline at end of file diff --git a/frontend/vite.config.mts b/frontend/vite.config.mts index abed9da3a..1491d5aba 100644 --- a/frontend/vite.config.mts +++ b/frontend/vite.config.mts @@ -1,6 +1,10 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import tsconfigPaths from "vite-tsconfig-paths"; + +/// + + +import react from '@vitejs/plugin-react'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vite'; // https://vitejs.dev/config/ export default defineConfig({ @@ -10,4 +14,7 @@ export default defineConfig({ host: "127.0.0.1", port: 5173, }, + test: { + environment: 'jsdom', + } });