diff --git a/.deepsource.toml b/.deepsource.toml index db512b7e..686f3d2b 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -1,5 +1,14 @@ version = 1 +test_patterns = [ + "src/**/__tests__/**", + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/testing/**", + "src/__mocks__/**", + "src/setupTests.tsx", +] + [[analyzers]] name = "javascript" enabled = true @@ -8,4 +17,4 @@ enabled = true environment = ["browser"] plugins = ["react"] style_guide = "standard" - dialect = "typescript" \ No newline at end of file + dialect = "typescript" diff --git a/.eslintrc.strict.cjs b/.eslintrc.strict.cjs deleted file mode 100644 index 15cc1b91..00000000 --- a/.eslintrc.strict.cjs +++ /dev/null @@ -1,41 +0,0 @@ -/** - * ESLint config that approximates DeepSource + SonarQube rules. - * Run with: pnpm run lint:strict (or: pnpm exec eslint --config .eslintrc.strict.cjs src/) - * - * DeepSource's JavaScript analyzer uses ESLint under the hood and supports - * style_guide (e.g. "standard"). SonarQube rules are available via eslint-plugin-sonarjs. - * This config enables rules that catch the kinds of issues both report. - */ -module.exports = { - root: true, - extends: [ - 'react-app', - 'react-app/jest', - 'plugin:sonarjs/recommended', - ], - plugins: ['sonarjs'], - overrides: [ - { - files: ['src/**/*.{ts,tsx,js,jsx}'], - rules: { - // DeepSource JS-0050: use === and !== - eqeqeq: ['error', 'always'], - // Avoid console in browser code (DeepSource JS-0002) - 'no-console': ['warn', { allow: ['warn', 'error'] }], - // Object shorthand (DeepSource JS-0240) - 'object-shorthand': ['warn', 'always'], - // Empty callbacks (DeepSource JS-0321) - 'no-empty-function': ['warn', { allow: ['arrowFunctions'] }], - // Prefer named exports when re-exporting (DeepSource JS-P1003) - 'sonarjs/prefer-single-boolean-return': 'warn', - }, - }, - ], - ignorePatterns: [ - 'build/', - 'node_modules/', - 'coverage/', - '*.config.js', - '*.config.cjs', - ], -}; diff --git a/.github/workflows/container-tests.yml b/.github/workflows/container-tests.yml index 8192fa5c..f3c41879 100644 --- a/.github/workflows/container-tests.yml +++ b/.github/workflows/container-tests.yml @@ -43,6 +43,22 @@ jobs: docker compose logs --tail=80 app || true exit 1 + - name: Test compiled styles + run: | + # The build must run Tailwind (@tailwindcss/vite); raw directives + # mean src/styles/globals.css was not compiled. Vite emits the + # stylesheet as assets/index-.css. + css_path=$(curl -s http://localhost:8008/ | grep -o 'assets/[^"]*\.css' | head -1) + test -n "$css_path" + css=$(curl -s "http://localhost:8008/$css_path") + if echo "$css" | grep -qE '@tailwind|@apply|@theme|@custom-variant|@plugin|@utility|@import ?["'\'']tailwindcss'; then + echo "Unprocessed Tailwind directives found in $css_path" + exit 1 + fi + echo "$css" | grep -q '\.flex{display:flex}' + # Theme tokens from the @theme block + echo "$css" | grep -q '\.text-11\\\.5{font-size:var(--text-11_5)}' + - name: Test DICOMweb service run: | # Poll instead of a fixed sleep: the archive can take a while to boot. diff --git a/.github/workflows/deploy-to-firebase.yml b/.github/workflows/deploy-to-firebase.yml index 1413ac5c..3f57b9c3 100644 --- a/.github/workflows/deploy-to-firebase.yml +++ b/.github/workflows/deploy-to-firebase.yml @@ -91,8 +91,8 @@ jobs: else pnpm install --frozen-lockfile --ignore-scripts fi - # allowBuilds in pnpm-workspace.yaml: core-js(+pure) and dicom-microscopy-viewer - pnpm rebuild dicom-microscopy-viewer core-js core-js-pure + # allowBuilds in pnpm-workspace.yaml: core-js-pure and dicom-microscopy-viewer + pnpm rebuild dicom-microscopy-viewer core-js-pure # Build DMV if dist files are missing (git dependency in package.json) if [ ! -f "node_modules/dicom-microscopy-viewer/dist/dynamic-import/dicomMicroscopyViewer.min.js" ]; then echo "DMV dist files missing - building from git dependency" diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 9519cc45..23e7ebe9 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -43,6 +43,9 @@ jobs: - name: Typecheck run: pnpm run typecheck + - name: React Compiler check + run: pnpm run check:compiler + - name: Build run: pnpm run build diff --git a/.husky/pre-commit b/.husky/pre-commit index 4d51a74f..f2eed12a 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,5 @@ #!/usr/bin/env sh -# Fast checks on every commit: lint + format (biome check runs both) +# Fast checks on every commit: lint + format (biome check runs both), then +# React Compiler bailouts (a few seconds) pnpm run lint +pnpm run check:compiler diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 4c608920..00000000 --- a/.npmrc +++ /dev/null @@ -1,5 +0,0 @@ -# Create React App / webpack expect a flat node_modules layout. -shamefully-hoist=true - -# react-scripts 5 peer dependency ranges are outdated. -strict-peer-dependencies=false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1bbca196..36d627b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,20 +24,36 @@ The `SlideViewer` further provides annotation tools, which enable the user to dr ## Implementation details -The app is implemented in [TypeScript](https://www.typescriptlang.org/) using the [React](https://reactjs.org/) framework. -The [antd](https://ant.design/https://ant.design/) React UI component library is used with a [customized theme](https://ant.design/docs/react/customize-theme). +The app is implemented in [TypeScript](https://www.typescriptlang.org/) 7 using [React](https://react.dev/) 19 with the [React Compiler](https://react.dev/learn/react-compiler) enabled, so components and hooks are memoized automatically and new code rarely needs `useMemo`, `useCallback` or `React.memo`. +The UI is styled with [Tailwind CSS](https://tailwindcss.com/) 4 on top of [Radix UI](https://www.radix-ui.com/) primitives (`src/components/ui`). +Icons are [lucide](https://lucide.dev/) SVGs rendered through `Icon` (`src/components/ui/icon.tsx`); add new glyphs to its `ICONS` map so `IconName` stays a closed union. -The app is built using [craco](https://github.com/gsoft-inc/craco) (with the [craco-less plugin](https://github.com/DocSpring/craco-less)), which serves as a configuration layer around [create-react-app](https://github.com/facebook/create-react-app/). +Tailwind is configured in CSS, without a `tailwind.config.js` or PostCSS config, and compiled by the `@tailwindcss/vite` plugin. `src/styles/globals.css` is the entry point: -Tests are written and run using the [jest](https://jestjs.io/) framework. +- Light and dark color tokens are RGB-triplet CSS variables on `:root` and `.dark` (the theme toggle sets the `dark` class on ``). The `@theme` block maps them to utilities (`--color-panel: rgb(var(--panel))` gives `bg-panel`, `text-panel`, `border-panel`, …), so opacity modifiers such as `bg-primary/10` keep working. +- The same `@theme` block holds the custom font sizes (`--text-11_5` gives `text-11.5`; `_` stands for the dot), radii (`rounded-card`, `rounded-tile`), shadows (`shadow-menu`, …), the `control` spacing step (`h-control`, `size-control`) and the layout sizes (`w-sidebar`, `h-header`, …). +- `dark:` utilities use `@custom-variant dark (&:where(.dark, .dark *))`. +- Radix enter/exit animations (`data-[state=open]:animate-in`, `fade-in-0`, `zoom-in-95`, `slide-in-from-top-2`, …) come from [tw-animate-css](https://github.com/Wombosvideo/tw-animate-css). + +Register every new custom font-size, radius, shadow, spacing or size key in `src/lib/utils.ts` as well, so `cn()` merges it correctly. Otherwise tailwind-merge treats an unknown `text-*` or `shadow-*` as a color. + +Components render props and call callbacks; DICOM parsing, formatting, filtering and other rules live in pure functions under `src/utils` and `src/features/*/utils`, with unit tests in sibling `__tests__` folders. +Shared sidebar layer controls (visibility toggle, settings popover, opacity row, style hooks) live in `src/components/panel`. +Transient notifications go through `publishToast` (`src/features/viewer/services/toast.ts`) and are rendered by `ToastHost`, which applies `config.messages`. + +The app is built and served with [Vite](https://vite.dev/) (`vite.config.ts`). The compiler runs through Babel (`@rolldown/plugin-babel` with `reactCompilerPreset`) in dev, build and tests. Only `VITE_*` and `REACT_APP_*` variables reach the client, as `import.meta.env.*`. + +Tests are written and run with [Vitest](https://vitest.dev/) in a jsdom environment, using [Testing Library](https://testing-library.com/). Mocks are reset before each test (`mockReset: true`); for Vitest, that means a `vi.fn(impl)` goes back to `impl`, not to returning `undefined`. + +`pnpm run check:compiler` fails when the React Compiler bails out of a component or hook, and lists each file, line and reason. It runs in the pre-commit hook and in CI. Fix the code rather than opting out. Add `'use no memo'` only for a known compiler limitation (for example a library the compiler marks incompatible, such as TanStack Table's `useReactTable`), and explain why in a `/** */` comment next to the directive. The [pnpm](https://pnpm.io/) package manager is used to manage dependencies and run scripts specified in `package.json` (`build`, `lint`, `test`, etc.). ## Coding style -Source code is linted and formatted using [Biome](https://biomejs.dev/). TypeScript is used with [strict type checking compiler options](https://www.typescriptlang.org/tsconfig#Strict_Type_Checking_Options_6173) enabled. Semicolons are not used at the end of statements (Biome uses `asNeeded`). +Source code is linted and formatted using [Biome](https://biomejs.dev/), the only linter in the project (typescript-eslint does not support TypeScript 7). TypeScript is used with [strict type checking compiler options](https://www.typescriptlang.org/tsconfig#Strict_Type_Checking_Options_6173) enabled. Semicolons are not used at the end of statements (Biome uses `asNeeded`). -Explanatory comments use JSDoc-style block comments (`/** … */`), not `//` line comments. Keep `//` only for tooling directives (`eslint-disable`, `@ts-expect-error`, `biome-ignore`), temporarily commented-out code, and shebang lines. +Explanatory comments use JSDoc-style block comments (`/** … */`), not `//` line comments. Keep `//` only for tooling directives (`biome-ignore`, `@ts-expect-error`, triple-slash references), temporarily commented-out code, and shebang lines. Use the following commands to check and fix style: diff --git a/Dockerfile b/Dockerfile index 67510f5c..64ace47d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,12 +25,11 @@ RUN corepack enable && corepack prepare pnpm@11.9.0 --activate WORKDIR /usr/local/share/mghcomputationalpathology/slim # Install dependencies first and then include code for efficient caching -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN pnpm install --frozen-lockfile -COPY craco.config.js . -COPY tsconfig.json . +COPY vite.config.ts index.html tsconfig.json ./ COPY types ./types COPY public ./public COPY scripts ./scripts diff --git a/README.md b/README.md index 361d9bab..89eae1d8 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ This feature was implemented in response to [issue #159](https://github.com/Imag ### Messages/popups configuration -Configure message popup notifications that appear at the top of the screen. By default, all message popups are enabled. +Configure the transient message popups (toasts). By default, all message popups are enabled and stack at the bottom centre of the window, at most three at a time. ```js window.config = { @@ -236,9 +236,9 @@ window.config = { **Options:** -- `disabled`: Disable specific message types or all messages -- `duration`: How long messages are shown (in seconds) -- `top`: Distance from top of screen (in pixels) +- `disabled`: Disable specific message types or all messages. Error notifications (sign-in failures, rejected or failed server requests, viewer errors) are always shown. +- `duration`: How long messages are shown (in seconds); `0` keeps them until dismissed. Error notifications always close after 3 seconds. +- `top`: Anchor the stack this many pixels from the top of the window instead of the bottom **Available message types:** @@ -268,7 +268,7 @@ messages: { **Defaults** (if not specified): - `duration`: 5 seconds -- `top`: 100 pixels +- `top`: not set (bottom centre) ### Memory monitoring configuration @@ -304,7 +304,7 @@ The following topics are documented in [docs/CONFIGURATION.md](docs/CONFIGURATIO ### Prerequisites -- [Node.js](https://nodejs.org/) (LTS recommended) +- [Node.js](https://nodejs.org/) 22.22 or newer (24 LTS recommended) - [pnpm](https://pnpm.io/) `11.9.0` (see `packageManager` in `package.json`) Download the latest release from [github.com/ImagingDataCommons/slim/releases](https://github.com/ImagingDataCommons/slim/releases), then install dependencies and build the app: @@ -444,11 +444,13 @@ Existing configs continue to work without changes: Deep links are restored after login through the OIDC `state` parameter (not `localStorage`). Silent token renewal reuses the same registered redirect URI (no additional IdP redirect URI is required). +Sign-in hashes the PKCE challenge in the code flow and checks the ID token signature against the provider's published keys in the implicit flow. Browsers only offer the Web Crypto API over HTTPS and on `localhost`, so on plain-HTTP deployments Slim does both in JavaScript instead. Serving Slim over HTTPS is still recommended because it keeps tokens from being exposed on the network. + ## Development ### Prerequisites -- [Node.js](https://nodejs.org/) (LTS recommended) +- [Node.js](https://nodejs.org/) 22.22 or newer (24 LTS recommended) - [pnpm](https://pnpm.io/) `11.9.0` (see `packageManager` in `package.json`) Install dependencies and run the app for local development: @@ -481,10 +483,13 @@ Useful scripts: | Command | Description | | ------- | ----------- | -| `pnpm run start` | Start the development server | -| `pnpm run build` | Create a production build | -| `pnpm run test` | Run lint checks and tests | -| `pnpm run lint` | Check for lint issues | +| `pnpm run start` (or `dev`) | Start the Vite development server on port 3000 | +| `pnpm run build` | Create a production build in `build/` | +| `pnpm run test` | Run the unit tests once with Vitest | +| `pnpm run test:watch` | Run Vitest in watch mode | +| `pnpm run typecheck` | Type-check with `tsc` (TypeScript 7) | +| `pnpm run check:compiler` | Fail if the React Compiler bails out on any component or hook | +| `pnpm run lint` | Check for lint and format issues with Biome | | `pnpm run lint:fix` | Auto-fix lint issues | | `pnpm run fmt` | Format source code | @@ -537,11 +542,12 @@ If neither applies, the preview uses the version in `package.json`. Editing the pnpm run start ``` - When linked, `craco.config.js` registers the DMV `dist/` folder as a webpack watch dependency so Slim rebuilds after DMV watch emits a new bundle. Restart Slim after linking or after changing `craco.config.js`. + When linked, Vite pre-bundles the DMV `dist/dynamic-import` bundle on every start and watches `dicomMicroscopyViewer.min.js`. When DMV watch emits a new bundle, the dev server re-runs the dependency optimizer and restarts itself. The worker and WebAssembly decoders under `/static/js/` are served straight from the linked `dist/` folder. Restart Slim after linking or after changing `vite.config.ts`. ### Notes -- Running `pnpm install` in Slim removes the link — re-run step 3 afterward. +- Running `pnpm install` in Slim removes the link — re-run step 2 afterward. +- If a tab that stayed open across a DMV rebuild logs `Invalid hook call`, hard-reload it: it still holds modules from the previous optimizer run. - Do not add `link:` overrides to `package.json`; the commands above are sufficient. - Slim imports OpenLayers CSS directly (`ol/ol.css`), so `ol` is listed as a direct dependency. This keeps linked dev working when DMV's transitive dependencies are not hoisted into Slim's `node_modules`. - If Slim still serves a stale DMV bundle, confirm step 3 (realpath must not contain `.pnpm`) and that DMV watch logged `[emitted] dicomMicroscopyViewer.min.js` for your change. @@ -550,8 +556,11 @@ If neither applies, the preview uses the version in `package.json`. Editing the ```bash pnpm unlink dicom-microscopy-viewer pnpm install + pnpm run start --force ``` + `--force` makes Vite discard the dependency cache that still holds the linked bundle. + ## Related projects - [dicom-microscopy-viewer](https://github.com/ImagingDataCommons/dicom-microscopy-viewer) — JavaScript library used by Slim for web-based visualization of DICOM VL Whole Slide Microscopy Image datasets diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index abe45f8f..00000000 --- a/babel.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - presets: [ - '@babel/preset-env', - '@babel/preset-react', - '@babel/preset-typescript' - ] -} diff --git a/biome.json b/biome.json index 21ebcb7e..fe9c6da2 100644 --- a/biome.json +++ b/biome.json @@ -6,12 +6,7 @@ "useIgnoreFile": true }, "files": { - "includes": [ - "src/**/*.{ts,tsx,js,jsx}", - "!src/__mocks__", - "!src/**/*.test.{ts,tsx}", - "!src/**/__tests__" - ] + "includes": ["src/**/*.{ts,tsx,js,jsx}", "!src/__mocks__"] }, "formatter": { "enabled": true, diff --git a/craco.config.js b/craco.config.js deleted file mode 100644 index 67eadbf9..00000000 --- a/craco.config.js +++ /dev/null @@ -1,161 +0,0 @@ -const fs = require('fs') -const path = require('path') -const CracoLessPlugin = require('craco-less') -const CopyWebpackPlugin = require('copy-webpack-plugin') - -/** - * When dicom-microscopy-viewer is pnpm-linked, resolve the real repo path so - * webpack can watch DMV dist/ rebuilds. The import alias must stay under - * node_modules (CRA ModuleScopePlugin blocks absolute paths outside src/). - */ -function getLinkedDmvPaths() { - const dmvEntry = path.resolve( - __dirname, - 'node_modules/dicom-microscopy-viewer', - ) - let dmvRoot - try { - dmvRoot = fs.realpathSync(dmvEntry) - } catch { - return null - } - - const isLinked = !dmvRoot.includes(`${path.sep}.pnpm${path.sep}`) - if (!isLinked) { - return null - } - - const dmvDist = path.join(dmvRoot, 'dist/dynamic-import') - const dmvBundle = path.join(dmvDist, 'dicomMicroscopyViewer.min.js') - return { dmvRoot, dmvDist, dmvBundle } -} - -module.exports = { - plugins: [ - { - plugin: CracoLessPlugin, - options: { - lessLoaderOptions: { - lessOptions: { - modifyVars: { - '@layout-header-background': '#007ea3', - '@primary-color': '#007ea3', - '@collapse-header-bg': '#e0f2f7', - '@processing-color': '#8cb8c6', - '@success-color': '#3f9c35', - '@warning-color': '#eeaf30', - '@error-color': '#96172e', - '@font-size-base': '14px' - }, - javascriptEnabled: true - } - } - } - } - ], - webpack: { - configure: (config, { env, paths }) => { - const linkedDmv = - env === 'development' ? getLinkedDmvPaths() : null - const dmvDist = - './node_modules/dicom-microscopy-viewer/dist/dynamic-import' - const dmvAlias = - 'dicom-microscopy-viewer/dist/dynamic-import/dicomMicroscopyViewer.min.js' - - config.resolve = { - ...config.resolve, - fallback: { - ...config.resolve?.fallback, - fs: false, - path: false - }, - extensions: ['.tsx', '.ts', '.js', '.wasm', '.json'], - /* We use this alias and the CopyPlugin below to support using the - * dynamic-import version of Dicom Microscopy Viewer, but only when - * building a PWA. When we build a package, we must use the bundled - * version of Dicom Microscopy Viewer so we can produce a single file - * for the viewer. - */ - alias: { - ...config.resolve?.alias, - 'dicom-microscopy-viewer': dmvAlias - } - } - config.plugins.push( - // TO DO: remove hard coded path - new CopyWebpackPlugin({ - patterns: [ - { - from: dmvDist, - to: './static/js' - } - ] - }) - ) - config.target = 'web' - config.experiments = { - asyncWebAssembly: true - } - - if (linkedDmv) { - config.watchOptions = { - ...config.watchOptions, - followSymlinks: true, - } - - config.snapshot = { - ...config.snapshot, - managedPaths: [ - /^(.+?[\\/]node_modules[\\/])(?!dicom-microscopy-viewer)/, - ], - } - - config.plugins.push({ - apply: (compiler) => { - compiler.hooks.afterCompile.tap('WatchLinkedDmvDist', (compilation) => { - compilation.contextDependencies.add(linkedDmv.dmvDist) - compilation.fileDependencies.add(linkedDmv.dmvBundle) - }) - }, - }) - - config.devServer = { - ...config.devServer, - watchFiles: { - paths: [`${linkedDmv.dmvDist}/**/*`], - options: { - followSymlinks: true, - }, - }, - } - } - - return config - } - }, - jest: { - configure: (config, { env, paths }) => { - config.transform = { - '^.+wasm.*\\.js$': '/src/__mocks__/emscriptenMock.js', - '\\.(wasm)$': '/src/__mocks__/wasmMock.js', - '\\.(css|less|sass|scss)$': '/src/__mocks__/styleMock.js', - '^.+\\.[t|j]sx?$': 'babel-jest' - } - config.transformIgnorePatterns = [ - 'node_modules/(?!(ol|dicom-microscopy-viewer|dicomweb-client|@cornerstonejs|dicomicc|rbush|color-rgba|color-parse|color-name|color-space|quickselect|earcut)/)' - ] - config.moduleNameMapper = { - 'dicom-microscopy-viewer': '/src/__mocks__/dicomMicroscopyViewerMock.js', - '@cornerstonejs/codec-libjpeg-turbo-8bit/decodewasmjs': '@cornerstonejs/codec-libjpeg-turbo-8bit/dist/libjpegturbowasm_decode', - '@cornerstonejs/codec-libjpeg-turbo-8bit/decodewasm': '@cornerstonejs/codec-libjpeg-turbo-8bit/dist/libjpegturbowasm_decode.wasm', - '@cornerstonejs/codec-charls/decodewasmjs': '@cornerstonejs/codec-charls/dist/charlswasm_decode.js', - '@cornerstonejs/codec-charls/decodewasm': '@cornerstonejs/codec-charls/dist/charlswasm_decode.wasm', - '@cornerstonejs/codec-openjpeg/decodewasmjs': '@cornerstonejs/codec-openjpeg/dist/openjpegwasm_decode.js', - '@cornerstonejs/codec-openjpeg/decodewasm': '@cornerstonejs/codec-openjpeg/dist/openjpegwasm_decode.wasm' - } - config.setupFilesAfterEnv = ['/src/setupTests.tsx'] - config.testEnvironment = 'jsdom' - return config - } - } -} diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 0c5ece85..74dba100 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -12,7 +12,10 @@ needed. Without `.env`, start/build defaults to `REACT_APP_CONFIG=local` and `SLIM_LOCAL_DICOMWEB_URL` defaults to the docker-compose DICOMweb URL. Committed `demo` / `preview` configs require `SLIM_DEMO_DICOMWEB_URL` / `SLIM_PREVIEW_DICOMWEB_URL` in `.env` or as GitHub Actions secrets/variables -(see `scripts/inject-slim-env.mjs`). +(see `scripts/slimEnv.mjs`). Builds write these to `public/config/env.js`; the +dev server serves its own copy, so several servers with different configs can +run side by side. If a server ends up without a URL, Slim shows which setting +is missing instead of starting. For the full type definitions, see [`src/AppConfig.d.ts`](../src/AppConfig.d.ts). Example configs live in [`public/config/`](../public/config/). @@ -204,9 +207,9 @@ window.config = { } ``` -When enabled, a **Select server** button appears in the header (Ant Design -`ApiOutlined` icon; often called the “link” icon in issue discussions). -Clicking it opens the **Select DICOMweb server** dialog. +When enabled, a **Select server** button (server icon) appears in the header, +and the server URL pill becomes clickable. +Either one opens the **Select DICOMweb server** dialog. Reference configs that already enable this: @@ -233,6 +236,10 @@ Persistence and behavior: - Mode is stored in `localStorage` as `slim_server_selection_mode` (`default` | `custom`). - The custom URL is stored as `slim_selected_server`. +- A stored URL without a mode (saved by older Slim versions) is treated as + `custom`, so existing users keep their server after upgrading. +- The pill is read-only text, and not focusable, when + `enableServerSelection` is off. - On a custom switch, Slim creates a temporary client with `read: true` and **`write: false`**, re-applies the current Bearer token when OIDC is in use, and maps **all** SOP-class clients to that single client (so a prior `?gcp=` diff --git a/docs/MEMORY_MONITORING.md b/docs/MEMORY_MONITORING.md index b7efeda6..b69345ad 100644 --- a/docs/MEMORY_MONITORING.md +++ b/docs/MEMORY_MONITORING.md @@ -91,13 +91,13 @@ add_header Cross-Origin-Embedder-Policy "require-corp" always; ### Monitoring Interval -The default monitoring interval is 5 seconds. This can be changed by modifying the `updateInterval` in `MemoryMonitor.ts` or when calling `memoryMonitor.startMonitoring(interval)`. +The default monitoring interval is 5 seconds. This can be changed by modifying `DEFAULT_UPDATE_INTERVAL_MS` in `MemoryMonitor.ts` or when calling `memoryMonitor.startMonitoring(interval)`. ### Thresholds -Memory warning thresholds can be adjusted in `MemoryMonitor.ts`: -- `highUsageThreshold`: 0.80 (80%) -- `criticalUsageThreshold`: 0.90 (90%) +Memory warning thresholds live in `src/utils/memoryWarning.ts` and are shared by `MemoryMonitor` and the viewer footer: +- `HIGH_MEMORY_USAGE_PERCENT`: 80 +- `CRITICAL_MEMORY_USAGE_PERCENT`: 90 ## Technical Details diff --git a/docs/plans/2026-10-01-worklist-contains-filter-design.md b/docs/plans/2026-10-01-worklist-contains-filter-design.md new file mode 100644 index 00000000..cde9ba84 --- /dev/null +++ b/docs/plans/2026-10-01-worklist-contains-filter-design.md @@ -0,0 +1,131 @@ +# Worklist "Contains" filter + +Find the studies on a server that contain a given kind of derived data, such as +parametric maps, bulk annotations or segmentations, without leaving the +worklist. + +## Goals + +- Nothing is queried until the user picks an entry. +- A short, named list that only covers what matters for slide microscopy. +- Clearing the filter returns to the normal worklist instantly. +- Works on Google Cloud Healthcare (IDC) and on standard DICOMweb servers. + +## Server findings + +Checked against the IDC proxy on 2026-10-01: + +- `SOPClassUID` is rejected as a matching key at every level (400, + "not a supported instance or study or series level attribute"). +- `SOPClassesInStudy` is rejected at the study level. +- `GET /series?Modality=X` works across all studies. Pages are capped at 5000 + rows and `offset` paging works (ANN: 7,102 series, about 4 s per page). +- Series results do not include the SOP Class UID. They do include the study + attributes the worklist shows (patient, dates, accession, study ID), but not + `ModalitiesInStudy` or `NumberOfStudyRelatedSeries`. +- A study search without `limit` returns 100 studies, and IDC holds more than + 5,000 slide microscopy studies, so the loaded worklist is only a sample. +- `StudyInstanceUID` does not accept a list of UIDs (204 No Content). +- Parametric maps are stored with modality `OT`. `OT` also contains CT + "Summary Series" (CT Image Storage), so modality alone is ambiguous. + +Standard servers (dcm4chee, Orthanc) support `SOPClassUID` at the instance +level, which PS3.18 lists as a required matching key. + +## User experience + +- A "Contains" button sits next to the worklist search input. It opens a + popover with a filter field and a grouped list: + - Annotations: Bulk annotations, 3D SR annotations, Structured reports + - Segmentations: Segmentation, Label map segmentation + - Maps: Parametric map + - Presentation states: Blending, Color, Pseudocolor, Grayscale +- The filter field matches the label, aliases (`pmap`, `ann`, `seg`), the + modality, or a pasted SOP Class UID. +- One entry at a time. The chosen entry becomes a chip, + "Contains: Parametric map ✕", and the header reads + "42 studies with Parametric map", or "Up to 343 studies with Parametric map" + while some matches are not checked yet. +- While the search runs, the header shows progress + ("Searching the server for Parametric map… page 2"). +- The text search keeps working inside the filtered set. +- The filter lives in the URL as `?contains=`. Back clears it, and a + reload or shared link restores it. +- Rows being checked show a small spinner in the left gutter; rows whose check + failed show a warning icon. +- Built on the existing Radix Popover; no new dependency. + +## Architecture + +### Catalog + +`src/features/worklist/utils/containsCatalog.ts` + +Each entry has an `id`, `label`, `group`, `aliases`, `sopClassUids` (from +`StorageClasses`), `modality`, and `needsConfirmation`. That flag is true when +the modality is shared with other SOP classes. + +| Entry | Modality | Confirm | +| --- | --- | --- | +| Bulk annotations | ANN | no | +| 3D SR annotations | SR | yes | +| Structured reports | SR | yes | +| Segmentation | SEG | yes | +| Label map segmentation | SEG | yes | +| Parametric map | OT | yes | +| Presentation states (4) | PR | yes | + +### Search strategy + +1. Try `searchForInstances({ SOPClassUID })` across all studies, with the + study attributes as `includefield`. On success the result is exact and needs no + confirmation. +2. On a 400, or when the server ignores the key and returns other SOP + classes, fall back to `searchForSeries({ Modality })` and remember that + choice per server for the session. +3. Page with `limit=5000` and `offset` until a short page comes back. Stop + after 10 pages and mark the result partial. + +The result maps each Study Instance UID to its matching Series Instance UIDs +and a study row built from the first match. These rows are the filtered +worklist, so matches outside the 100 loaded studies are found too. A loaded +row replaces the built one when available. + +### Hooks + +- `useContainsSearch(client, entry)` returns the status + (`idle | searching | done | error`), page progress, the result and `retry`. + It guards against stale responses like `useStudies`. Results are cached per + server and entry for the session. +- `useContainsRowChecks(searchClient, studyClient, entry, candidates)` checks + only the rows on the current page, 6 at a time, and caches each answer: + - Rows not in the loaded worklist: a study search by UID with + `ModalitiesInStudy=SM`. No result means no slides (for example a radiology + segmentation), so the row drops out; a result replaces the row. + - Entries matched on modality: the SOP class of the first instance of each + candidate series (`limit=1&includefield=00080016`). +- `WorklistTable` reports the study UIDs on the current page after sorting. + +## Error handling + +- A failed search shows a message with Retry in the header. The normal + worklist stays usable. +- A failed confirmation keeps the row listed, with a "Couldn't verify" tooltip. +- A partial result says "first 50,000 matches only". + +## Testing + +- Catalog matching: labels, aliases, modality, pasted UIDs. +- Search strategy: instance search success, fallback to modality on 400, + paging, page cap, and the study attributes kept from the first match. +- Row checks: drops studies without slides or of another SOP class, and + fetches rows outside the loaded worklist. +- Components: popover filtering and keyboard selection, chip, clear, URL + round trip. + +## Out of scope + +- Selecting several entries at once. +- Matching on attributes other than the SOP class (for example label or + overview image types). +- Studies that hold derived data but no slide microscopy series. diff --git a/public/index.html b/index.html similarity index 59% rename from public/index.html rename to index.html index bd8fdb85..0a995515 100644 --- a/public/index.html +++ b/index.html @@ -2,9 +2,10 @@ - + + - + + Slim @@ -55,15 +75,6 @@
- + diff --git a/package.json b/package.json index 3ffb3c1a..9beb23d1 100644 --- a/package.json +++ b/package.json @@ -2,86 +2,88 @@ "name": "slim", "version": "0.46.10", "private": true, + "type": "module", "author": "ImagingDataCommons", "packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b", + "engines": { + "node": ">=22.22.0" + }, "scripts": { - "start": "rm -rf ./node_modules/.cache/default-development && ./scripts/set-git-env.sh craco start", + "start": "./scripts/set-git-env.sh vite", "dev": "pnpm run start", "typecheck": "tsc --noEmit", - "build": "./scripts/set-git-env.sh craco build", - "build:firebase": "REACT_APP_CONFIG=gcp PUBLIC_URL=/ ./scripts/set-git-env.sh craco build", + "build": "./scripts/set-git-env.sh vite build", + "build:firebase": "REACT_APP_CONFIG=gcp PUBLIC_URL=/ ./scripts/set-git-env.sh vite build", "lint": "biome check .", - "lint:strict": "eslint --config .eslintrc.strict.cjs src/", "lint:fix": "biome check --write --unsafe .", "fmt": "biome format --write .", - "format": "biome format --write .", - "test": "craco test --watchAll=false", - "predeploy": "REACT_APP_CONFIG=demo PUBLIC_URL='https://imagingdatacommons.github.io/slim/' ./scripts/set-git-env.sh craco build", + "test": "TZ=UTC vitest run", + "test:watch": "TZ=UTC vitest", + "check:compiler": "node scripts/check-react-compiler.mjs", + "predeploy": "REACT_APP_CONFIG=demo PUBLIC_URL='https://imagingdatacommons.github.io/slim/' ./scripts/set-git-env.sh vite build", "deploy": "gh-pages -d build", "clean": "rm -rf ./build ./node_modules", "prepare": "husky" }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, "dependencies": { - "antd": "^4.22.8", - "classnames": "^2.2.6", + "@fontsource/ibm-plex-mono": "^5.3.0", + "@fontsource/ibm-plex-sans": "^5.3.0", + "@noble/curves": "^2.4.0", + "@noble/hashes": "^2.4.0", + "@radix-ui/react-checkbox": "^1.3.11", + "@radix-ui/react-collapsible": "^1.1.20", + "@radix-ui/react-dialog": "^1.1.23", + "@radix-ui/react-dropdown-menu": "^2.1.24", + "@radix-ui/react-popover": "^1.1.23", + "@radix-ui/react-select": "^2.3.7", + "@radix-ui/react-slider": "^1.4.7", + "@radix-ui/react-slot": "^1.3.3", + "@radix-ui/react-switch": "^1.3.7", + "@radix-ui/react-tooltip": "^1.2.16", + "@tanstack/react-table": "8.20.5", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "dcmjs": "^0.35.0", "detect-browser": "^5.2.1", "dicom-microscopy-viewer": "^0.48.27", "dicomweb-client": "0.10.3", - "oidc-client": "^1.11.5", + "lucide-react": "^1.49.0", + "oidc-client-ts": "^3.5.0", "ol": "^10.7.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-error-boundary": "^3.1.4", - "react-icons": "^3.11.0", - "react-router-dom": "^6.30.6", + "react": "^19.3.0", + "react-dom": "^19.3.0", + "react-error-boundary": "^6.1.6", + "react-router": "^8.4.0", "retry": "^0.13.1", + "tailwind-merge": "^3.7.0", "uuid": "^8.3.2" }, "devDependencies": { - "@babel/preset-env": "^7.15.0", - "@babel/preset-react": "^7.17.12", - "@babel/preset-typescript": "^7.17.12", + "@babel/core": "^7.29.7", "@biomejs/biome": "2.4.16", - "@craco/craco": "^6.4.0", - "@testing-library/jest-dom": "^5.16.4", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^7.1.2", + "@rolldown/plugin-babel": "^0.2.4", + "@tailwindcss/vite": "4.3.3", + "@testing-library/dom": "^10.4.2", + "@testing-library/jest-dom": "^7.0.1", + "@testing-library/react": "^16.3.3", + "@types/babel__core": "^7.20.5", "@types/d3-dispatch": "3.0.6", - "@types/jest": "^28.1.3", "@types/lodash": "^4.17.20", - "@types/node": "^14.14.9", - "@types/react": "^18.0.14", - "@types/react-dom": "^18.0.5", - "@types/react-router-dom": "^5.3.3", + "@types/node": "^24.19.0", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", "@types/retry": "^0.12.1", "@types/uuid": "^8.3.0", - "ajv": "6.12.6", - "copy-webpack-plugin": "9.1.0", - "craco-less": "^2.0.0", - "eslint": "^8.57.0", - "eslint-plugin-sonarjs": "^0.25.0", - "fraction.js": "^4.3.7", + "@vitejs/plugin-react": "^6.1.1", + "babel-plugin-react-compiler": "^1.0.0", "gh-pages": "^5.0.0", "husky": "^9.1.7", - "react-scripts": "5.0.0", - "react-test-renderer": "^18.2.0", - "sonarqube-scanner": "^4.3.0", - "typescript": "^4.7.4" + "jsdom": "^30.1.1", + "tailwindcss": "4.3.3", + "tw-animate-css": "^1.4.0", + "typescript": "^7.0.2", + "vite": "^8.3.1", + "vite-plugin-static-copy": "^4.1.1", + "vitest": "^5.0.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f181ea05..adb811fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,42 +6,68 @@ settings: overrides: '@types/d3-dispatch': 3.0.6 - axios: 1.18.0 adm-zip: 0.6.1 brace-expansion@1: 1.1.21 - brace-expansion@2: 2.1.7 - js-yaml@3: 3.15.2 - js-yaml@4: 4.3.2 - postcss: 8.5.25 nanoid@3: 3.3.19 - shell-quote: 1.9.0 - fast-uri: 3.1.8 - svgo: 2.8.4 browserslist: 4.28.7 - webpack-dev-middleware: 7.4.6 - nth-check: 2.0.1 - wrap-ansi: 7.0.0 make-dir: 3.1.0 - axios>form-data: 4.0.6 - express: 4.21.0 - jsdom>form-data: 3.0.5 - path-to-regexp: 0.1.13 - rollup: 3.30.0 semver: 7.5.2 - serialize-javascript: 7.0.5 - tar: 7.5.16 - underscore: 1.13.8 importers: .: dependencies: - antd: - specifier: ^4.22.8 - version: 4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - classnames: - specifier: ^2.2.6 - version: 2.5.1 + '@fontsource/ibm-plex-mono': + specifier: ^5.3.0 + version: 5.3.0 + '@fontsource/ibm-plex-sans': + specifier: ^5.3.0 + version: 5.3.0 + '@noble/curves': + specifier: ^2.4.0 + version: 2.4.0 + '@noble/hashes': + specifier: ^2.4.0 + version: 2.4.0 + '@radix-ui/react-checkbox': + specifier: ^1.3.11 + version: 1.3.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-collapsible': + specifier: ^1.1.20 + version: 1.1.20(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-dialog': + specifier: ^1.1.23 + version: 1.1.23(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.24 + version: 2.1.24(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-popover': + specifier: ^1.1.23 + version: 1.1.23(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-select': + specifier: ^2.3.7 + version: 2.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slider': + specifier: ^1.4.7 + version: 1.4.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': + specifier: ^1.3.3 + version: 1.3.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-switch': + specifier: ^1.3.7 + version: 1.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-tooltip': + specifier: ^1.2.16 + version: 1.2.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-table': + specifier: 8.20.5 + version: 8.20.5(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 dcmjs: specifier: ^0.35.0 version: 0.35.0 @@ -54,154 +80,128 @@ importers: dicomweb-client: specifier: 0.10.3 version: 0.10.3 - oidc-client: - specifier: ^1.11.5 - version: 1.11.5 + lucide-react: + specifier: ^1.49.0 + version: 1.49.0(@types/react@19.3.0)(react@19.3.0) + oidc-client-ts: + specifier: ^3.5.0 + version: 3.5.0 ol: specifier: ^10.7.0 version: 10.9.0 react: - specifier: ^18.2.0 - version: 18.3.1 + specifier: ^19.3.0 + version: 19.3.0 react-dom: - specifier: ^18.2.0 - version: 18.3.1(react@18.3.1) + specifier: ^19.3.0 + version: 19.3.0(react@19.3.0) react-error-boundary: - specifier: ^3.1.4 - version: 3.1.4(react@18.3.1) - react-icons: - specifier: ^3.11.0 - version: 3.11.0(react@18.3.1) - react-router-dom: - specifier: ^6.30.6 - version: 6.30.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^6.1.6 + version: 6.1.6(@types/react@19.3.0)(react@19.3.0) + react-router: + specifier: ^8.4.0 + version: 8.4.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) retry: specifier: ^0.13.1 version: 0.13.1 + tailwind-merge: + specifier: ^3.7.0 + version: 3.7.0 uuid: specifier: ^8.3.2 version: 8.3.2 devDependencies: - '@babel/preset-env': - specifier: ^7.15.0 - version: 7.29.7(@babel/core@7.29.7) - '@babel/preset-react': - specifier: ^7.17.12 - version: 7.29.7(@babel/core@7.29.7) - '@babel/preset-typescript': - specifier: ^7.17.12 - version: 7.29.7(@babel/core@7.29.7) + '@babel/core': + specifier: ^7.29.7 + version: 7.29.7 '@biomejs/biome': specifier: 2.4.16 version: 2.4.16 - '@craco/craco': - specifier: ^6.4.0 - version: 6.4.5(@types/node@14.18.63)(react-scripts@5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5))(typescript@4.9.5) + '@rolldown/plugin-babel': + specifier: ^0.2.4 + version: 0.2.4(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(rolldown@1.2.11)(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)) + '@tailwindcss/vite': + specifier: 4.3.3 + version: 4.3.3(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)) + '@testing-library/dom': + specifier: ^10.4.2 + version: 10.4.2 '@testing-library/jest-dom': - specifier: ^5.16.4 - version: 5.17.0 + specifier: ^7.0.1 + version: 7.0.1(@testing-library/dom@10.4.2)(vitest@5.0.3(@types/node@24.19.0)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1))) '@testing-library/react': - specifier: ^14.0.0 - version: 14.3.1(@types/react@18.3.30)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@testing-library/user-event': - specifier: ^7.1.2 - version: 7.2.1(@testing-library/dom@9.3.4) + specifier: ^16.3.3 + version: 16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@types/babel__core': + specifier: ^7.20.5 + version: 7.20.5 '@types/d3-dispatch': specifier: 3.0.6 version: 3.0.6 - '@types/jest': - specifier: ^28.1.3 - version: 28.1.8 '@types/lodash': specifier: ^4.17.20 version: 4.17.24 '@types/node': - specifier: ^14.14.9 - version: 14.18.63 + specifier: ^24.19.0 + version: 24.19.0 '@types/react': - specifier: ^18.0.14 - version: 18.3.30 + specifier: ^19.3.0 + version: 19.3.0 '@types/react-dom': - specifier: ^18.0.5 - version: 18.3.7(@types/react@18.3.30) - '@types/react-router-dom': - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^19.3.0 + version: 19.3.0(@types/react@19.3.0) '@types/retry': specifier: ^0.12.1 version: 0.12.5 '@types/uuid': specifier: ^8.3.0 version: 8.3.4 - ajv: - specifier: 6.12.6 - version: 6.12.6 - copy-webpack-plugin: - specifier: 9.1.0 - version: 9.1.0(webpack@5.107.2(postcss@8.5.25)) - craco-less: - specifier: ^2.0.0 - version: 2.0.0(@craco/craco@6.4.5(@types/node@14.18.63)(react-scripts@5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5))(typescript@4.9.5))(react-scripts@5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5))(webpack@5.107.2(postcss@8.5.25)) - eslint: - specifier: ^8.57.0 - version: 8.57.1 - eslint-plugin-sonarjs: - specifier: ^0.25.0 - version: 0.25.1(eslint@8.57.1) - fraction.js: - specifier: ^4.3.7 - version: 4.3.7 + '@vitejs/plugin-react': + specifier: ^6.1.1 + version: 6.1.1(@rolldown/plugin-babel@0.2.4(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(rolldown@1.2.11)(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)))(babel-plugin-react-compiler@1.0.0)(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)) + babel-plugin-react-compiler: + specifier: ^1.0.0 + version: 1.0.0 gh-pages: specifier: ^5.0.0 version: 5.0.0 husky: specifier: ^9.1.7 version: 9.1.7 - react-scripts: - specifier: 5.0.0 - version: 5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5) - react-test-renderer: - specifier: ^18.2.0 - version: 18.3.1(react@18.3.1) - sonarqube-scanner: - specifier: ^4.3.0 - version: 4.3.6 + jsdom: + specifier: ^30.1.1 + version: 30.1.1(@noble/hashes@2.4.0) + tailwindcss: + specifier: 4.3.3 + version: 4.3.3 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 typescript: - specifier: ^4.7.4 - version: 4.9.5 + specifier: ^7.0.2 + version: 7.0.2 + vite: + specifier: ^8.3.1 + version: 8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1) + vite-plugin-static-copy: + specifier: ^4.1.1 + version: 4.1.1(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)) + vitest: + specifier: ^5.0.3 + version: 5.0.3(@types/node@24.19.0)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)) packages: '@adobe/css-tools@4.5.0': resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@ant-design/colors@6.0.0': - resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} - - '@ant-design/icons-svg@4.4.2': - resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} - - '@ant-design/icons@4.8.3': - resolution: {integrity: sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==} - engines: {node: '>=8'} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@ant-design/react-slick@1.0.2': - resolution: {integrity: sha512-Wj8onxL/T8KQLFFiCA4t8eIRGpRR+UPgOdac2sYzonv+i0n3kXHmvHLLiOYL655DQx2Umii9Y9nNgL7ssu5haQ==} - peerDependencies: - react: '>=16.9.0' + '@asamuzakjp/css-color@7.1.2': + resolution: {integrity: sha512-99DHAnXDB5z6EEK+9GMpVI7Mw4oxj97dY5bpOzMnjADQWxI8rN6TvTduuFLUhUMlS7/CfVZ06tcZsus6cltnNw==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - '@apideck/better-ajv-errors@0.3.7': - resolution: {integrity: sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==} - engines: {node: '>=10'} - peerDependencies: - ajv: '>=8' + '@asamuzakjp/dom-selector@9.2.2': + resolution: {integrity: sha512-lSWTBMjAcmu2xn5yEDU7jh6QDV+C8GEKtdJ4pIQhXh26RkKQ7S3FuQlt+zZkUbTdJ4d3XyV1kPI/G0zWXxqaqw==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} @@ -215,37 +215,14 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/eslint-parser@7.29.7': - resolution: {integrity: sha512-zxt+UJTOMKvUt3yOg+D58MLuz334pHp93qifMFcjIIO+9hN6t+ufw2gi7vDPMpxvfnHRR+3VVXvIjineCcgyXw==} - engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} - peerDependencies: - '@babel/core': ^7.11.0 - eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - '@babel/generator@7.29.7': resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.29.7': - resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.29.7': resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.29.7': - resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-create-regexp-features-plugin@7.29.7': - resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.8': resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} peerDependencies: @@ -255,10 +232,6 @@ packages: resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.29.7': - resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.29.7': resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} @@ -269,30 +242,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.29.7': - resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.29.7': resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} engines: {node: '>=6.9.0'} - '@babel/helper-remap-async-to-generator@7.29.7': - resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-replace-supers@7.29.7': - resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': - resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} @@ -305,10 +258,6 @@ packages: resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.29.7': - resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.29.7': resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} @@ -318,14128 +267,4288 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7': - resolution: {integrity: sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==} + '@babel/plugin-transform-runtime@7.29.7': + resolution: {integrity: sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0 + '@babel/core': ^7.0.0-0 - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7': - resolution: {integrity: sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==} + '@babel/runtime-corejs3@7.29.7': + resolution: {integrity: sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7': - resolution: {integrity: sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7': - resolution: {integrity: sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7': - resolution: {integrity: sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==} + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7': - resolution: {integrity: sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/plugin-proposal-class-properties@7.18.6': - resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/biome@2.4.16': + resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==} + engines: {node: '>=14.21.3'} + hasBin: true - '@babel/plugin-proposal-decorators@7.29.7': - resolution: {integrity: sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/cli-darwin-arm64@2.4.16': + resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': - resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/cli-darwin-x64@2.4.16': + resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] - '@babel/plugin-proposal-numeric-separator@7.18.6': - resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/cli-linux-arm64-musl@2.4.16': + resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] - '@babel/plugin-proposal-optional-chaining@7.21.0': - resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/cli-linux-arm64@2.4.16': + resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] - '@babel/plugin-proposal-private-methods@7.18.6': - resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/cli-linux-x64-musl@2.4.16': + resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': - resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/cli-linux-x64@2.4.16': + resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] - '@babel/plugin-proposal-private-property-in-object@7.21.11': - resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead. - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/cli-win32-arm64@2.4.16': + resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@biomejs/cli-win32-x64@2.4.16': + resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] - '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@cornerstonejs/codec-charls@1.2.7': + resolution: {integrity: sha512-kNfQ7J8LIrfQUbt56fPJ0SEvevLlAAbsLHjqPS4DUDWVSuAHb5Fn0cMIV/U0SYkJRJqKvzyLJYF1hl7/xQGqLQ==, tarball: https://registry.npmjs.org/@cornerstonejs/codec-charls/-/codec-charls-1.2.7.tgz} + engines: {node: '>=0.14'} - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@cornerstonejs/codec-libjpeg-turbo-8bit@1.2.8': + resolution: {integrity: sha512-qggTc0EAso+FAB4aND1wufjY/FnaVXKaJ/PAiabSksaAgf8jhCew+K3R4e3QWJI8SqcYczZFkhpVdFVwXG9ZgQ==, tarball: https://registry.npmjs.org/@cornerstonejs/codec-libjpeg-turbo-8bit/-/codec-libjpeg-turbo-8bit-1.2.8.tgz} + engines: {node: '>=0.14'} - '@babel/plugin-syntax-decorators@7.29.7': - resolution: {integrity: sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@cornerstonejs/codec-openjpeg@1.3.6': + resolution: {integrity: sha512-bSdZXF+2GxfBkWHUr7yGLQtIwbAm9wOl3e4ARee/9Z9lfKyVqhUCixI6+ZjIn9YKTIuDcn/NZNK94XEUg1SBtw==, tarball: https://registry.npmjs.org/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-1.3.6.tgz} + engines: {node: '>=0.14'} - '@babel/plugin-syntax-flow@7.29.7': - resolution: {integrity: sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@cornerstonejs/codec-openjph@2.4.11': + resolution: {integrity: sha512-NzwCYevGSj2JOuP7BztxrOd5WnJ86gOGLv3sq36jZxxHUXoDo1YhzizHiYz+T7GQqkzbfaQcwSyxsRogzGFIPQ==, tarball: https://registry.npmjs.org/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.11.tgz} + engines: {node: '>=0.14'} - '@babel/plugin-syntax-import-assertions@7.29.7': - resolution: {integrity: sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/color-helpers@6.1.2': + resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} + engines: {node: '>=20.19.0'} - '@babel/plugin-syntax-import-attributes@7.29.7': - resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} - engines: {node: '>=6.9.0'} + '@csstools/css-calc@3.4.1': + resolution: {integrity: sha512-EtC7SoN1j6J4E4DCwg5QgbO5TGxgxIA1RXqe+W+qUM+BUcezx9wT+/tiQ/WO2yCX4i5X+Cuf9ciJ22aP4UEwWw==} + engines: {node: '>=20.19.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/css-parser-algorithms': ^4.0.1 + '@csstools/css-tokenizer': ^4.0.2 - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + '@csstools/css-color-parser@4.2.4': + resolution: {integrity: sha512-DyefytAZ735mX4Dq/WcDAXFtXhaEFvme0ZS9tVEBAc2whxUthXr0R0L2rmEPm59SrMBkGrFzQviBXMs/UtnABQ==} + engines: {node: '>=20.19.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/css-parser-algorithms': ^4.0.1 + '@csstools/css-tokenizer': ^4.0.2 - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + '@csstools/css-parser-algorithms@4.0.1': + resolution: {integrity: sha512-ShL8BqPfbKJrJiKFH0xBbN0i7Nrh9HXYRuF+pzyj93R/BL2YAsUxJeqANErzqM+0JGl7vjHp+3OIgd/DL69YIA==} + engines: {node: '>=20.19.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/css-tokenizer': ^4.0.2 - '@babel/plugin-syntax-jsx@7.29.7': - resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} - engines: {node: '>=6.9.0'} + '@csstools/css-syntax-patches-for-csstree@1.1.14': + resolution: {integrity: sha512-HpbVXyrofRXpHpgkNIjU/3EWR4WJvOkO3emNK/L6X/mTJU7bGUI3AkkpoTNXznQLp0KRjLHELTGeKI5dIkI9JQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@csstools/css-tokenizer@4.0.2': + resolution: {integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==} + engines: {node: '>=20.19.0'} - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + '@exodus/bytes@1.16.0': + resolution: {integrity: sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@babel/core': ^7.0.0-0 + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} peerDependencies: - '@babel/core': ^7.0.0-0 + react: '>=16.8.0' + react-dom: '>=16.8.0' - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@fontsource/ibm-plex-mono@5.3.0': + resolution: {integrity: sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q==} - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@fontsource/ibm-plex-sans@5.3.0': + resolution: {integrity: sha512-CbE4CbbEEZJX860XyUiRpsksXIQR8Rp2XDva2VO53NJox9tVNtusrysd2x5YkUEY3ErQ66W1IiiQL8/wihhw5w==} - '@babel/plugin-syntax-typescript@7.29.7': - resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@imagingdatacommons/dicomicc@0.2.3': + resolution: {integrity: sha512-gHRHIct+5kyTqONASiuGgjlZVEaawpwqSxfBbltLwhTVYedcA7hs0TPYJk3uR1P5RvvC2fC1lPJ0c8jhkGxtNA==} + engines: {node: '>=0.14'} - '@babel/plugin-syntax-unicode-sets-regex@7.18.6': - resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@babel/plugin-transform-arrow-functions@7.29.7': - resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - '@babel/plugin-transform-async-generator-functions@7.29.7': - resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - '@babel/plugin-transform-async-to-generator@7.29.7': - resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@babel/plugin-transform-block-scoped-functions@7.29.7': - resolution: {integrity: sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} - '@babel/plugin-transform-block-scoping@7.29.7': - resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@babel/plugin-transform-class-properties@7.29.7': - resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@noble/curves@2.4.0': + resolution: {integrity: sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==} + engines: {node: '>= 20.19.0'} - '@babel/plugin-transform-class-static-block@7.29.7': - resolution: {integrity: sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 + '@noble/hashes@2.4.0': + resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==} + engines: {node: '>= 20.19.0'} - '@babel/plugin-transform-classes@7.29.7': - resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@oxc-project/types@0.151.0': + resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==} - '@babel/plugin-transform-computed-properties@7.29.7': - resolution: {integrity: sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@petamoriken/float16@3.9.3': + resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==} - '@babel/plugin-transform-destructuring@7.29.7': - resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@radix-ui/number@1.1.3': + resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} - '@babel/plugin-transform-dotall-regex@7.29.7': - resolution: {integrity: sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@radix-ui/primitive@1.1.7': + resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} - '@babel/plugin-transform-duplicate-keys@7.29.7': - resolution: {integrity: sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-arrow@1.1.15': + resolution: {integrity: sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7': - resolution: {integrity: sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-checkbox@1.3.11': + resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==} peerDependencies: - '@babel/core': ^7.0.0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-dynamic-import@7.29.7': - resolution: {integrity: sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-collapsible@1.1.20': + resolution: {integrity: sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==} peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-explicit-resource-management@7.29.7': - resolution: {integrity: sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-exponentiation-operator@7.29.7': - resolution: {integrity: sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-export-namespace-from@7.29.7': - resolution: {integrity: sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-collection@1.1.15': + resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-flow-strip-types@7.29.7': - resolution: {integrity: sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-compose-refs@1.1.5': + resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-for-of@7.29.7': - resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-context@1.2.2': + resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-function-name@7.29.7': - resolution: {integrity: sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-dialog@1.1.23': + resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-json-strings@7.29.7': - resolution: {integrity: sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-direction@1.1.4': + resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-literals@7.29.7': - resolution: {integrity: sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-dismissable-layer@1.1.19': + resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-logical-assignment-operators@7.29.7': - resolution: {integrity: sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-dropdown-menu@2.1.24': + resolution: {integrity: sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-member-expression-literals@7.29.7': - resolution: {integrity: sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-focus-guards@1.1.6': + resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-modules-amd@7.29.7': - resolution: {integrity: sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-focus-scope@1.1.16': + resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-modules-commonjs@7.29.7': - resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-id@1.1.4': + resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-modules-systemjs@7.29.7': - resolution: {integrity: sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-menu@2.1.24': + resolution: {integrity: sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-modules-umd@7.29.7': - resolution: {integrity: sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-popover@1.1.23': + resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': - resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-popper@1.3.7': + resolution: {integrity: sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==} peerDependencies: - '@babel/core': ^7.0.0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-new-target@7.29.7': - resolution: {integrity: sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-portal@1.1.17': + resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': - resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-presence@1.1.10': + resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-numeric-separator@7.29.7': - resolution: {integrity: sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-primitive@2.1.10': + resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-object-rest-spread@7.29.7': - resolution: {integrity: sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-roving-focus@1.1.19': + resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-object-super@7.29.7': - resolution: {integrity: sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-select@2.3.7': + resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-optional-catch-binding@7.29.7': - resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-slider@1.4.7': + resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-optional-chaining@7.29.7': - resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-slot@1.3.3': + resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-parameters@7.29.7': - resolution: {integrity: sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-switch@1.3.7': + resolution: {integrity: sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-private-methods@7.29.7': - resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-tooltip@1.2.16': + resolution: {integrity: sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-private-property-in-object@7.29.7': - resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-use-callback-ref@1.1.4': + resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-property-literals@7.29.7': - resolution: {integrity: sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-use-controllable-state@1.2.6': + resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-react-constant-elements@7.29.7': - resolution: {integrity: sha512-J0wGhKan+rIiE2OhfhRptySLrJ6SjQYM6b6N1FMlhyhCcw1Mig8vQjWchyB+bgHGDvaWo6Diu6CLRMra2uMtmg==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-use-effect-event@0.0.5': + resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-react-display-name@7.29.7': - resolution: {integrity: sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-use-is-hydrated@0.1.3': + resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-react-jsx-development@7.29.7': - resolution: {integrity: sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-use-layout-effect@1.1.4': + resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-react-jsx@7.29.7': - resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-use-previous@1.1.4': + resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-react-pure-annotations@7.29.7': - resolution: {integrity: sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-use-rect@1.1.4': + resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-regenerator@7.29.7': - resolution: {integrity: sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-use-size@1.1.4': + resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==} peerDependencies: - '@babel/core': ^7.0.0-0 + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@babel/plugin-transform-regexp-modifiers@7.29.7': - resolution: {integrity: sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==} - engines: {node: '>=6.9.0'} + '@radix-ui/react-visually-hidden@1.2.11': + resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==} peerDependencies: - '@babel/core': ^7.0.0 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@babel/plugin-transform-reserved-words@7.29.7': - resolution: {integrity: sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@radix-ui/rect@1.1.3': + resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} - '@babel/plugin-transform-runtime@7.29.7': - resolution: {integrity: sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@remix-run/route-pattern@0.22.1': + resolution: {integrity: sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==} - '@babel/plugin-transform-shorthand-properties@7.29.7': - resolution: {integrity: sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-android-arm-eabi@1.2.11': + resolution: {integrity: sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@babel/plugin-transform-spread@7.29.7': - resolution: {integrity: sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-android-arm64@1.2.11': + resolution: {integrity: sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] - '@babel/plugin-transform-sticky-regex@7.29.7': - resolution: {integrity: sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-darwin-arm64@1.2.11': + resolution: {integrity: sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] - '@babel/plugin-transform-template-literals@7.29.7': - resolution: {integrity: sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-darwin-x64@1.2.11': + resolution: {integrity: sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] - '@babel/plugin-transform-typeof-symbol@7.29.7': - resolution: {integrity: sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-freebsd-x64@1.2.11': + resolution: {integrity: sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] - '@babel/plugin-transform-typescript@7.29.7': - resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': + resolution: {integrity: sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] - '@babel/plugin-transform-unicode-escapes@7.29.7': - resolution: {integrity: sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-linux-arm64-gnu@1.2.11': + resolution: {integrity: sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] - '@babel/plugin-transform-unicode-property-regex@7.29.7': - resolution: {integrity: sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-linux-arm64-musl@1.2.11': + resolution: {integrity: sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] - '@babel/plugin-transform-unicode-regex@7.29.7': - resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-linux-ppc64-gnu@1.2.11': + resolution: {integrity: sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] - '@babel/plugin-transform-unicode-sets-regex@7.29.7': - resolution: {integrity: sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@rolldown/binding-linux-s390x-gnu@1.2.11': + resolution: {integrity: sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] - '@babel/preset-env@7.29.7': - resolution: {integrity: sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-modules@0.1.6-no-external-plugins': - resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - - '@babel/preset-react@7.29.7': - resolution: {integrity: sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-linux-x64-gnu@1.2.11': + resolution: {integrity: sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] - '@babel/preset-typescript@7.29.7': - resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@rolldown/binding-linux-x64-musl@1.2.11': + resolution: {integrity: sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] - '@babel/runtime-corejs3@7.29.7': - resolution: {integrity: sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==} - engines: {node: '>=6.9.0'} + '@rolldown/binding-openharmony-arm64@1.2.11': + resolution: {integrity: sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] - '@babel/runtime@7.29.7': - resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} - engines: {node: '>=6.9.0'} + '@rolldown/binding-win32-arm64-msvc@1.2.11': + resolution: {integrity: sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] - '@babel/template@7.29.7': - resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} - engines: {node: '>=6.9.0'} + '@rolldown/binding-win32-x64-msvc@1.2.11': + resolution: {integrity: sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} - engines: {node: '>=6.9.0'} + '@rolldown/plugin-babel@0.2.4': + resolution: {integrity: sha512-ygmm2j/mN+aCvstHo0x3IOu4uc6LqXT9SJ3q4kpfG/+Yhfz0B+E47FITcC0FljZADwUFARP6E2kiE+Lc26D8wg==} + engines: {node: '>=22.12.0 || ^24.0.0'} + peerDependencies: + '@babel/core': ^7.29.0 || ^8.0.0-rc.1 + '@babel/plugin-transform-runtime': ^7.29.0 || ^8.0.0-rc.1 + '@babel/runtime': ^7.27.0 || ^8.0.0-rc.1 + rolldown: ^1.0.0-rc.5 + vite: ^8.0.0 + peerDependenciesMeta: + '@babel/plugin-transform-runtime': + optional: true + '@babel/runtime': + optional: true + vite: + optional: true - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} - engines: {node: '>=6.9.0'} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - '@biomejs/biome@2.4.16': - resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==} - engines: {node: '>=14.21.3'} - hasBin: true + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] - '@biomejs/cli-darwin-arm64@2.4.16': - resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==} - engines: {node: '>=14.21.3'} + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.4.16': - resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==} - engines: {node: '>=14.21.3'} + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.4.16': - resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==} - engines: {node: '>=14.21.3'} - cpu: [arm64] + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} + cpu: [arm] os: [linux] - libc: [musl] - '@biomejs/cli-linux-arm64@2.4.16': - resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==} - engines: {node: '>=14.21.3'} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@biomejs/cli-linux-x64-musl@2.4.16': - resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==} - engines: {node: '>=14.21.3'} - cpu: [x64] + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} + cpu: [arm64] os: [linux] libc: [musl] - '@biomejs/cli-linux-x64@2.4.16': - resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==} - engines: {node: '>=14.21.3'} + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@biomejs/cli-win32-arm64@2.4.16': - resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==} - engines: {node: '>=14.21.3'} + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.4.16': - resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==} - engines: {node: '>=14.21.3'} + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@cornerstonejs/codec-charls@1.2.3': - resolution: {integrity: sha512-qKUe6DN0dnGzhhfZLYhH9UZacMcudjxcaLXCrpxJImT/M/PQvZCT2rllu6VGJbWKJWG+dMVV2zmmleZcdJ7/cA==, tarball: https://registry.npmjs.org/@cornerstonejs/codec-charls/-/codec-charls-1.2.3.tgz} - engines: {node: '>=0.14'} - - '@cornerstonejs/codec-libjpeg-turbo-8bit@1.2.2': - resolution: {integrity: sha512-aAUMK2958YNpOb/7G6e2/aG7hExTiFTASlMt/v90XA0pRHdWiNg5ny4S5SAju0FbIw4zcMnR0qfY+yW3VG2ivg==, tarball: https://registry.npmjs.org/@cornerstonejs/codec-libjpeg-turbo-8bit/-/codec-libjpeg-turbo-8bit-1.2.2.tgz} - engines: {node: '>=0.14'} - - '@cornerstonejs/codec-openjpeg@1.3.0': - resolution: {integrity: sha512-hP8WAZ63AcaDYmHbBVTY04x424AglXsRHrI6VBdW4eTiJ76f0heWrEodT9Sb3sNwazzYuyMOIZNndJPeVSeHcw==, tarball: https://registry.npmjs.org/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-1.3.0.tgz} - engines: {node: '>=0.14'} + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} - '@cornerstonejs/codec-openjph@2.4.7': - resolution: {integrity: sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w==, tarball: https://registry.npmjs.org/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.7.tgz} - engines: {node: '>=0.14'} + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 - '@craco/craco@6.4.5': - resolution: {integrity: sha512-8F2rIAao8sEh0FPP52ViEvDM9GjJ7acq0knu1c8UgI+EuZMD5/ZB270ol6jV4iNY7it9Umg/RoGBvNRUNr8U8w==} - engines: {node: '>=6'} - hasBin: true + '@tanstack/react-table@8.20.5': + resolution: {integrity: sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==} + engines: {node: '>=12'} peerDependencies: - react-scripts: ^4.0.0 + react: '>=16.8' + react-dom: '>=16.8' - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + '@tanstack/table-core@8.20.5': + resolution: {integrity: sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==} engines: {node: '>=12'} - '@csstools/normalize.css@12.1.1': - resolution: {integrity: sha512-YAYeJ+Xqh7fUou1d1j9XHl44BmsuThiTr4iNrgCQ3J27IbhXsxXDGZ1cXv8Qvs99d4rBbLiSKy3+WZiet32PcQ==} + '@testing-library/dom@10.4.2': + resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==} + engines: {node: '>=18'} - '@csstools/postcss-cascade-layers@1.1.1': - resolution: {integrity: sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==} - engines: {node: ^12 || ^14 || >=16} + '@testing-library/jest-dom@7.0.1': + resolution: {integrity: sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw==} + engines: {node: '>=22', npm: '>=6', yarn: '>=1'} peerDependencies: - postcss: 8.5.25 + '@testing-library/dom': '>=10 <11' + vitest: '>= 0.32' + peerDependenciesMeta: + vitest: + optional: true - '@csstools/postcss-color-function@1.1.1': - resolution: {integrity: sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==} - engines: {node: ^12 || ^14 || >=16} + '@testing-library/react@16.3.3': + resolution: {integrity: sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==} + engines: {node: '>=18'} peerDependencies: - postcss: 8.5.25 + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@csstools/postcss-font-format-keywords@1.0.1': - resolution: {integrity: sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - '@csstools/postcss-hwb-function@1.0.2': - resolution: {integrity: sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@csstools/postcss-ic-unit@1.0.1': - resolution: {integrity: sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - '@csstools/postcss-is-pseudo-class@2.0.7': - resolution: {integrity: sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@csstools/postcss-nested-calc@1.0.0': - resolution: {integrity: sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - '@csstools/postcss-normalize-display-values@1.0.1': - resolution: {integrity: sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@csstools/postcss-oklab-function@1.1.1': - resolution: {integrity: sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/d3-dispatch@3.0.6': + resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==} - '@csstools/postcss-progressive-custom-properties@1.3.0': - resolution: {integrity: sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@csstools/postcss-stepped-value-functions@1.0.1': - resolution: {integrity: sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@csstools/postcss-text-decoration-shorthand@1.0.0': - resolution: {integrity: sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} - '@csstools/postcss-trigonometric-functions@1.0.2': - resolution: {integrity: sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==} - engines: {node: ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/node@24.19.0': + resolution: {integrity: sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==} - '@csstools/postcss-unset-value@1.0.2': - resolution: {integrity: sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 + '@types/rbush@4.0.0': + resolution: {integrity: sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ==} - '@csstools/selector-specificity@2.2.0': - resolution: {integrity: sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==} - engines: {node: ^14 || ^16 || >=18} + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} peerDependencies: - postcss-selector-parser: ^6.0.10 + '@types/react': ^19.3.0 - '@ctrl/tinycolor@3.6.1': - resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} - engines: {node: '>=10'} + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@types/retry@0.12.5': + resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==} - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@types/uuid@8.3.4': + resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] - '@imagingdatacommons/dicomicc@0.2.3': - resolution: {integrity: sha512-gHRHIct+5kyTqONASiuGgjlZVEaawpwqSxfBbltLwhTVYedcA7hs0TPYJk3uR1P5RvvC2fC1lPJ0c8jhkGxtNA==} - engines: {node: '>=0.14'} + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] - '@istanbuljs/schema@0.1.6': - resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} - engines: {node: '>=8'} + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] - '@jest/console@27.5.1': - resolution: {integrity: sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] - '@jest/console@28.1.3': - resolution: {integrity: sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] - '@jest/core@27.5.1': - resolution: {integrity: sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] - '@jest/environment@27.5.1': - resolution: {integrity: sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] - '@jest/expect-utils@28.1.3': - resolution: {integrity: sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] - '@jest/fake-timers@27.5.1': - resolution: {integrity: sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] - '@jest/globals@27.5.1': - resolution: {integrity: sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] - '@jest/reporters@27.5.1': - resolution: {integrity: sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] - '@jest/schemas@28.1.3': - resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] - '@jest/source-map@27.5.1': - resolution: {integrity: sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] - '@jest/test-result@27.5.1': - resolution: {integrity: sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] - '@jest/test-result@28.1.3': - resolution: {integrity: sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] - '@jest/test-sequencer@27.5.1': - resolution: {integrity: sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@vitejs/plugin-react@6.1.1': + resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.145.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + oxc-transform-react: + optional: true - '@jest/transform@27.5.1': - resolution: {integrity: sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true - '@jest/types@27.5.1': - resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} - '@jest/types@28.1.3': - resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + '@zarrita/storage@0.2.0': + resolution: {integrity: sha512-855ZXqtnds7spnT8vNvD+MXa3QExP1m2GqShe8yt7uZXHnQLgJHgkpVwFjE1B0KDDRO0ki09hmk6OboTaIfPsQ==} - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + adm-zip@0.6.1: + resolution: {integrity: sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==} + engines: {node: '>=14.0'} - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - '@jsonjoy.com/base64@1.1.2': - resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} - '@jsonjoy.com/base64@17.67.0': - resolution: {integrity: sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + array-union@1.0.2: + resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} + engines: {node: '>=0.10.0'} - '@jsonjoy.com/buffers@1.2.1': - resolution: {integrity: sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + array-uniq@1.0.3: + resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==} + engines: {node: '>=0.10.0'} - '@jsonjoy.com/buffers@17.67.0': - resolution: {integrity: sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} - '@jsonjoy.com/codegen@1.0.0': - resolution: {integrity: sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - '@jsonjoy.com/codegen@17.67.0': - resolution: {integrity: sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==} - engines: {node: '>=10.0'} + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} peerDependencies: - tslib: '2' + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@jsonjoy.com/fs-core@4.79.0': - resolution: {integrity: sha512-bxSwMrDY71OjEdj2mAYBxSe4BnHtAOjOxqUoFJc0WsllcGgA8iYl6wRF+4aVdFUjjEyHfBwxVv0QqW4AweIUZw==} - engines: {node: '>=10.0'} + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} peerDependencies: - tslib: '2' + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@jsonjoy.com/fs-fsa@4.79.0': - resolution: {integrity: sha512-EdadmStAvZw4jYg4iW3mDxBmzMFSI8jcHQkSl+rIQIO7dWbPdXaB83PBQhNCCtMBeX7zwcOFjpp0ecXqbYbjUA==} - engines: {node: '>=10.0'} + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} peerDependencies: - tslib: '2' + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@jsonjoy.com/fs-node-builtins@4.79.0': - resolution: {integrity: sha512-LoBHewVHp/bZQ8Om1Iivl6NEHg7Mmby9kXJZysfnHYW4256rzXFSEcEpto1qgSAka+sq2v4N/Cnl562Z15MByA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + babel-plugin-react-compiler@1.0.0: + resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} - '@jsonjoy.com/fs-node-to-fsa@4.79.0': - resolution: {integrity: sha512-xFza/t81sVL4rik7h3SlcymC6X7pvNllI88LW1GESEANDXinf5lZoqju59i3AsGLyy4i0eBQvPiizWSMwCunQA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - '@jsonjoy.com/fs-node-utils@4.79.0': - resolution: {integrity: sha512-Rj/WevHB8DV8VyEvFGSvDD3m3JHOVk3fO1biH26iworL9/rqdUMKQQeyiKTLbdf3Z9s9vVZnn8ZC0FWZgtO2mg==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - '@jsonjoy.com/fs-node@4.79.0': - resolution: {integrity: sha512-/k18aT1X+A2yLdlTe3WKpL5y2qrvbcUY1BXA4A+s8fBfTeXfi+xdvEB85JVXVf2i9kzASQZFx8dHw9OchgMUhQ==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + baseline-browser-mapping@2.11.26: + resolution: {integrity: sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==} + engines: {node: '>=6.0.0'} + hasBin: true - '@jsonjoy.com/fs-print@4.79.0': - resolution: {integrity: sha512-Bp5Q+OV1Dnl8oKf8hVhmboeI+NWbWRk1GxtJzuRvopDeNBDBh0RLevEsfnOVtwBwgRZaptSXn3e/mm3kbOzfDw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + bidi-js@1.1.0: + resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==} - '@jsonjoy.com/fs-snapshot@4.79.0': - resolution: {integrity: sha512-/GY+eMvOl4ZQHUnrKYjC/t3N0BImBg7zv/cAGm3kiwUfqSQuo87jtbjmD3mzgQMv16K8zttkHE2wT9GaVsehvA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} - '@jsonjoy.com/json-pack@1.21.0': - resolution: {integrity: sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + brace-expansion@1.1.21: + resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} - '@jsonjoy.com/json-pack@17.67.0': - resolution: {integrity: sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} - '@jsonjoy.com/json-pointer@1.0.2': - resolution: {integrity: sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + browserslist@4.28.7: + resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true - '@jsonjoy.com/json-pointer@17.67.0': - resolution: {integrity: sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - '@jsonjoy.com/util@1.9.0': - resolution: {integrity: sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + caniuse-lite@1.0.30001812: + resolution: {integrity: sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==} - '@jsonjoy.com/util@17.67.0': - resolution: {integrity: sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} - '@leichtgewicht/ip-codec@2.0.5': - resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': - resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + colormap@2.3.2: + resolution: {integrity: sha512-jDOjaoEEmA9AgA11B/jCSAvYE95r3wRoAyTf3LEHGiUVlNHJaL1mRkf5AyLSpQBVGfTEPwGEqCIzL+kgr2WgNA==} - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - '@petamoriken/float16@3.9.3': - resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==} + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - '@pmmmwh/react-refresh-webpack-plugin@0.5.17': - resolution: {integrity: sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ==} - engines: {node: '>= 10.13'} - peerDependencies: - '@types/webpack': 4.x || 5.x - react-refresh: '>=0.10.0 <1.0.0' - sockjs-client: ^1.4.0 - type-fest: '>=0.17.0 <5.0.0' - webpack: '>=4.43.0 <6.0.0' - webpack-dev-server: 3.x || 4.x || 5.x - webpack-hot-middleware: 2.x - webpack-plugin-serve: 0.x || 1.x - peerDependenciesMeta: - '@types/webpack': - optional: true - sockjs-client: - optional: true - type-fest: - optional: true - webpack-dev-server: - optional: true - webpack-hot-middleware: - optional: true - webpack-plugin-serve: - optional: true + complex.js@2.4.3: + resolution: {integrity: sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ==} - '@rc-component/portal@1.1.2': - resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - '@remix-run/router@1.23.4': - resolution: {integrity: sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==} - engines: {node: '>=14.0.0'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - '@rollup/plugin-babel@5.3.1': - resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} - engines: {node: '>= 10.0.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@types/babel__core': ^7.1.9 - rollup: 3.30.0 - peerDependenciesMeta: - '@types/babel__core': - optional: true + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} - '@rollup/plugin-node-resolve@11.2.1': - resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==} - engines: {node: '>= 10.0.0'} - peerDependencies: - rollup: 3.30.0 + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} - '@rollup/plugin-replace@2.4.2': - resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} - peerDependencies: - rollup: 3.30.0 + core-js-pure@3.49.0: + resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} - '@rollup/pluginutils@3.1.0': - resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} - engines: {node: '>= 8.0.0'} - peerDependencies: - rollup: 3.30.0 + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - '@rtsao/scc@1.1.0': - resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - '@rushstack/eslint-patch@1.16.1': - resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - '@sinclair/typebox@0.24.51': - resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@sinonjs/commons@1.8.6': - resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} + dcmjs@0.35.0: + resolution: {integrity: sha512-kKOX4y0XoSQsDx7xYh9HJLfAWizrvYNJPU/d+DGOehdGz1frvimBZDEL0iaCK46Lhy7amAmtv4g5hkjcxVF+kg==} - '@sinonjs/fake-timers@8.1.0': - resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} + dcmjs@0.41.0: + resolution: {integrity: sha512-kr46REomItFeWz+0ck4Wif4uS5VVDWVlwdh5GGaCtTYHWfNQmrcCSiQOkrShc7Dc5zP8vNKrHEdORlZXenlg3w==} - '@surma/rollup-plugin-off-main-thread@2.2.3': - resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true - '@svgr/babel-plugin-add-jsx-attribute@5.4.0': - resolution: {integrity: sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==} - engines: {node: '>=10'} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - '@svgr/babel-plugin-remove-jsx-attribute@5.4.0': - resolution: {integrity: sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==} - engines: {node: '>=10'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} - '@svgr/babel-plugin-remove-jsx-empty-expression@5.0.1': - resolution: {integrity: sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==} - engines: {node: '>=10'} + detect-browser@5.3.0: + resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} - '@svgr/babel-plugin-replace-jsx-attribute-value@5.0.1': - resolution: {integrity: sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==} - engines: {node: '>=10'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} - '@svgr/babel-plugin-svg-dynamic-title@5.4.0': - resolution: {integrity: sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==} - engines: {node: '>=10'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - '@svgr/babel-plugin-svg-em-dimensions@5.4.0': - resolution: {integrity: sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==} - engines: {node: '>=10'} + dicom-microscopy-viewer@0.48.27: + resolution: {integrity: sha512-Y/5q/AOgqYirT9mzfdAUQwi2bPdY/TOG5ouLbP1KGgtpEsfWelAVlagtovB15Gh7585kZ9XWR6fGnBAmwNE9Cg==} - '@svgr/babel-plugin-transform-react-native-svg@5.4.0': - resolution: {integrity: sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==} - engines: {node: '>=10'} + dicomweb-client@0.10.3: + resolution: {integrity: sha512-/fHNEAYiz8j+9TNOrNJ0k+hYqirbOT85B7vM7I4VkY8DeDQb4BDUeL3RX6huDVtn6ZQlR91dI+2tejLc5c99wA==} - '@svgr/babel-plugin-transform-svg-component@5.5.0': - resolution: {integrity: sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==} - engines: {node: '>=10'} + dicomweb-client@0.11.3: + resolution: {integrity: sha512-7t1Rc/01fyFnKlQeNPW9Pvxvmsx8R/BgB2n7BxTuWirRFwlTC5upEeLeS3vO72P+n6/SQZ3+5ij0bgaWDHQG/A==} - '@svgr/babel-preset@5.5.0': - resolution: {integrity: sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==} - engines: {node: '>=10'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - '@svgr/core@5.5.0': - resolution: {integrity: sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==} - engines: {node: '>=10'} + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - '@svgr/hast-util-to-babel-ast@5.5.0': - resolution: {integrity: sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==} - engines: {node: '>=10'} + earcut@3.0.2: + resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} - '@svgr/plugin-jsx@5.5.0': - resolution: {integrity: sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==} - engines: {node: '>=10'} + electron-to-chromium@1.5.439: + resolution: {integrity: sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==} - '@svgr/plugin-svgo@5.5.0': - resolution: {integrity: sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==} - engines: {node: '>=10'} + email-addresses@5.0.0: + resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} - '@svgr/webpack@5.5.0': - resolution: {integrity: sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==} - engines: {node: '>=10'} + enhanced-resolve@5.26.0: + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} + engines: {node: '>=10.13.0'} - '@testing-library/dom@9.3.4': - resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} - engines: {node: '>=14'} + entities@8.1.0: + resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==} + engines: {node: '>=20.19.0'} - '@testing-library/jest-dom@5.17.0': - resolution: {integrity: sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==} - engines: {node: '>=8', npm: '>=6', yarn: '>=1'} + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} - '@testing-library/react@14.3.1': - resolution: {integrity: sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==} - engines: {node: '>=14'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} - '@testing-library/user-event@7.2.1': - resolution: {integrity: sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==} - peerDependencies: - '@testing-library/dom': '>=5' + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} - '@tootallnate/once@1.1.2': - resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} - engines: {node: '>= 6'} + escape-latex@1.2.0: + resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==} - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true - '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + file-type@10.11.0: + resolution: {integrity: sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==} + engines: {node: '>=6'} - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + filename-reserved-regex@2.0.0: + resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} + engines: {node: '>=4'} - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + filenamify@4.3.0: + resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==} + engines: {node: '>=8'} - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} - '@types/body-parser@1.19.6': - resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} - '@types/bonjour@3.5.13': - resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} - '@types/connect-history-api-fallback@1.5.4': - resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} + fraction.js@4.3.4: + resolution: {integrity: sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==} - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} - '@types/d3-dispatch@3.0.6': - resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - '@types/eslint@8.56.12': - resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] - '@types/estree@0.0.39': - resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} - '@types/express-serve-static-core@4.19.8': - resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + geotiff@3.0.5: + resolution: {integrity: sha512-OWcL9S9+yDZ6iAlXMt32T1iwUApJM8UiD47xbm6ZP1h33d10fqkPs14EG/ttT5EnefpZSx3G15iDFC5FxUNUwA==} + engines: {node: '>=10.19'} - '@types/express-serve-static-core@5.1.1': - resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} - '@types/express@4.17.25': - resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + gh-pages@5.0.0: + resolution: {integrity: sha512-Nqp1SjkPIB94Xw/3yYNTUL+G2dxlhjvv1zeN/4kMC1jfViTEqhtVz/Ba1zSXHuvXCN9ADNS1dN4r5/J/nZWEQQ==} + engines: {node: '>=10'} + hasBin: true - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} - '@types/history@4.7.11': - resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} - '@types/html-minifier-terser@6.1.0': - resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - '@types/http-errors@2.0.5': - resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + globby@6.1.0: + resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==} + engines: {node: '>=0.10.0'} - '@types/http-proxy@1.17.17': - resolution: {integrity: sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + html-encoding-sniffer@7.0.0: + resolution: {integrity: sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==} + engines: {node: ^22.13.0 || >=24.0.0} - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true - '@types/jest@28.1.8': - resolution: {integrity: sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==} + image-type@4.1.0: + resolution: {integrity: sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg==} + engines: {node: '>=6'} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - '@types/lodash@4.17.24': - resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + iota-array@1.0.0: + resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} - '@types/node-forge@1.3.14': - resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} - '@types/node@14.18.63': - resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} - '@types/prettier@2.7.3': - resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} - '@types/qs@6.15.1': - resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - '@types/rbush@4.0.0': - resolution: {integrity: sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ==} + javascript-natural-sort@0.7.1: + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} - '@types/react-dom@18.3.7': - resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} - peerDependencies: - '@types/react': ^18.0.0 + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true - '@types/react-router-dom@5.3.3': - resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - '@types/react-router@5.1.20': - resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + jsdom@30.1.1: + resolution: {integrity: sha512-FahmoPK5vbPc+jxV1iErMHmAZypCZ942NHF4+qqaWAuvaKKTBZxawnmAtrbGWLU7MtlxfqIP0qw6aSI+aWGtLg==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true - '@types/react@18.3.30': - resolution: {integrity: sha512-3ek6mwJL5/VBewBcY4S66cqlCtK3qi4WIq37Z0m/NHw1hjhI7274Mx1qz/+ggSzyBCOEf7eHjBN6INjPAWYfYw==} + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true - '@types/resolve@1.17.1': - resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true - '@types/retry@0.12.0': - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - '@types/retry@0.12.5': - resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==} + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} - '@types/semver@7.7.1': - resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + lerc@3.0.0: + resolution: {integrity: sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==} - '@types/send@0.17.6': - resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + lerp@1.0.3: + resolution: {integrity: sha512-70Rh4rCkJDvwWiTsyZ1HmJGvnyfFah4m6iTux29XmasRiZPDBpT9Cfa4ai73+uLZxnlKruUS62jj2lb11wURiA==} - '@types/send@1.2.1': - resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] - '@types/serve-index@1.9.4': - resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] - '@types/serve-static@1.15.10': - resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - '@types/sockjs@0.3.36': - resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] - '@types/testing-library__jest-dom@5.14.9': - resolution: {integrity: sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==} + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] - '@types/trusted-types@2.0.7': - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] - '@types/uuid@8.3.4': - resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] - '@types/yargs@16.0.11': - resolution: {integrity: sha512-sbtvk8wDN+JvEdabmZExoW/HNr1cB7D/j4LT08rMiuikfA7m/JNJg7ATQcgzs34zHnoScDkY0ZRSl29Fkmk36g==} + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] - '@types/yargs@17.0.35': - resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] - '@typescript-eslint/eslint-plugin@5.62.0': - resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] - '@typescript-eslint/experimental-utils@5.62.0': - resolution: {integrity: sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] - '@typescript-eslint/parser@5.62.0': - resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] - '@typescript-eslint/scope-manager@5.62.0': - resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] - '@typescript-eslint/type-utils@5.62.0': - resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '*' - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] - '@typescript-eslint/types@5.62.0': - resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] - '@typescript-eslint/typescript-estree@5.62.0': - resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] - '@typescript-eslint/utils@5.62.0': - resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] - '@typescript-eslint/visitor-keys@5.62.0': - resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] - '@ungap/structured-clone@1.3.1': - resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] - '@webassemblyjs/ast@1.14.1': - resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} - '@webassemblyjs/floating-point-hex-parser@1.13.2': - resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} - '@webassemblyjs/helper-api-error@1.13.2': - resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} - '@webassemblyjs/helper-buffer@1.14.1': - resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - '@webassemblyjs/helper-numbers@1.13.2': - resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - '@webassemblyjs/helper-wasm-bytecode@1.13.2': - resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} - '@webassemblyjs/helper-wasm-section@1.14.1': - resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} - '@webassemblyjs/ieee754@1.13.2': - resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - '@webassemblyjs/leb128@1.13.2': - resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} - '@webassemblyjs/utf8@1.13.2': - resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + lucide-react@1.49.0: + resolution: {integrity: sha512-ApfaNM4/7jBrVopqLJ84Jz8TMQ1Q1JeZ1PmKh25aeS3E1FTVcMkSh1bEQB7dfzdIrwXRP8p3z4AJjTIHHb5IIA==} + peerDependencies: + '@types/react': '*' + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - '@webassemblyjs/wasm-edit@1.14.1': - resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true - '@webassemblyjs/wasm-gen@1.14.1': - resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - '@webassemblyjs/wasm-opt@1.14.1': - resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + magic-string@1.4.2: + resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==} - '@webassemblyjs/wasm-parser@1.14.1': - resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} - '@webassemblyjs/wast-printer@1.14.1': - resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + mathjs@11.12.0: + resolution: {integrity: sha512-UGhVw8rS1AyedyI55DGz9q1qZ0p98kyKPyc9vherBkoueLntPfKtPBh14x+V4cdUWK0NZV2TBwqRFlvadscSuw==} + engines: {node: '>= 14'} + hasBin: true - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} - '@zarrita/storage@0.2.0': - resolution: {integrity: sha512-855ZXqtnds7spnT8vNvD+MXa3QExP1m2GqShe8yt7uZXHnQLgJHgkpVwFjE1B0KDDRO0ki09hmk6OboTaIfPsQ==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - abab@2.0.6: - resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} - deprecated: Use your platform's native atob() and btoa() methods instead + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true - acorn-globals@6.0.0: - resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} + ndarray@1.0.19: + resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==} - acorn-import-phases@1.0.4: - resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} - engines: {node: '>=10.13.0'} - peerDependencies: - acorn: ^8.14.0 + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} + engines: {node: '>=18'} - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} - acorn-walk@7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} + numcodecs@0.3.2: + resolution: {integrity: sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==} - acorn-walk@8.3.5: - resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} - engines: {node: '>=0.4.0'} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} - acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true + oidc-client-ts@3.5.0: + resolution: {integrity: sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A==} + engines: {node: '>=18'} - address@1.2.2: - resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} - engines: {node: '>= 10.0.0'} + ol@10.9.0: + resolution: {integrity: sha512-svbbgVQUmEHaKpLQ8kRySojs59Brvgl2zYIrqG9eQNXGfsbi55rQasZIDpwpQzDL6OlzrUb0H4hQaiX9wDoGmA==} - adjust-sourcemap-loader@4.0.0: - resolution: {integrity: sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==} - engines: {node: '>=8.9'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - adm-zip@0.6.1: - resolution: {integrity: sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==} - engines: {node: '>=14.0'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true + p-map@7.0.8: + resolution: {integrity: sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==} + engines: {node: '>=18'} - ajv-keywords@3.5.2: - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} - ajv-keywords@5.1.0: - resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} - peerDependencies: - ajv: ^8.8.2 + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + parse-headers@2.0.6: + resolution: {integrity: sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==} - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - ansi-html-community@0.0.8: - resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} - engines: {'0': node >= 0.8.0} - hasBin: true + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - ansi-html@0.0.9: - resolution: {integrity: sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==} - engines: {'0': node >= 0.8.0} + pbf@4.0.1: + resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==} hasBin: true - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} - antd@4.24.16: - resolution: {integrity: sha512-zZrK4UYxHtU6tGOOf0uG/kBRx1kTvypfuSB3GqE/SBQxFhZ/TZ+yj7Z1qwI8vGfMtUUJdLeuoCAqGDa1zPsXnQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' + pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + protocol-buffers-schema@3.6.1: + resolution: {integrity: sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} - aria-query@5.1.3: - resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + quick-lru@6.1.2: + resolution: {integrity: sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==} + engines: {node: '>=12'} - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} + quickselect@3.0.0: + resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} + rbush@4.0.1: + resolution: {integrity: sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==} - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + react-dom@19.3.0: + resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} + peerDependencies: + react: ^19.3.0 - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} + react-error-boundary@6.1.6: + resolution: {integrity: sha512-CDXPnXDGyFIbkwaaJ6u+xgsRmJhSi6YdgUDW1vnyKHfXp1a9pfAlM+ZET2CDu80/A+8iRcmXN0NSY9BCCoWP5A==} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - array-tree-filter@2.1.0: - resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true - array-union@1.0.2: - resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-router@8.4.0: + resolution: {integrity: sha512-JtydAkBvU9UTEe5qNC+POhu+ZBNM7rlKY2IegwXc7Idj7xfRG16x5RZrw3mju+XlqBxfvb2ky9P3jUp9WyWC1g==} + engines: {node: '>=22.22.0'} + peerDependencies: + react: '>=19.2.7' + react-dom: '>=19.2.7' + peerDependenciesMeta: + react-dom: + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.3.0: + resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} engines: {node: '>=0.10.0'} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - array-uniq@1.0.3: - resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==} + reference-spec-reader@0.2.0: + resolution: {integrity: sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} + resolve-protobuf-schema@2.1.0: + resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} - array.prototype.findlastindex@1.2.6: - resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} + hasBin: true - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} + rolldown@1.2.11: + resolution: {integrity: sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} - asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + seedrandom@3.0.5: + resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} - ast-types-flow@0.0.8: - resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + semver@7.5.2: + resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} + engines: {node: '>=10'} + hasBin: true - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} - async-validator@4.2.5: - resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} - at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} - autoprefixer@10.5.0: - resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: 8.5.25 + strip-outer@1.0.1: + resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} + engines: {node: '>=0.10.0'} - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - axe-core@4.12.0: - resolution: {integrity: sha512-FTavr/7Ba0IptwGOPxnQvdyW2tAsdLBMTBXz7rKH6xJ2skpyxpBxyHkDdBs4lf69yRqYpkqCdfhnwS8YULGOmg==} - engines: {node: '>=4'} + tailwind-merge@3.7.0: + resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==} - axios@1.18.0: - resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} - b4a@1.8.1: - resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} - peerDependencies: - react-native-b4a: '*' - peerDependenciesMeta: - react-native-b4a: - optional: true + terser@5.48.0: + resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} + engines: {node: '>=10'} + hasBin: true - babel-jest@27.5.1: - resolution: {integrity: sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - peerDependencies: - '@babel/core': ^7.8.0 + tiny-emitter@2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} - babel-loader@8.4.1: - resolution: {integrity: sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==} - engines: {node: '>= 8.9'} - peerDependencies: - '@babel/core': ^7.0.0 - webpack: '>=2' + tinybench@6.2.0: + resolution: {integrity: sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==} + engines: {node: '>=20.0.0'} - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} - babel-plugin-jest-hoist@27.5.1: - resolution: {integrity: sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} - babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} + tldts-core@7.4.16: + resolution: {integrity: sha512-MDolfaSJtlSK5Y0A1xl3277ekubZwobpBjugknDizI9O5Rm60a1m8k4ICK+MRsCDzPygT81mp3BBf5RKDlFRfA==} - babel-plugin-named-asset-import@0.3.8: - resolution: {integrity: sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==} - peerDependencies: - '@babel/core': ^7.1.0 + tldts@7.4.16: + resolution: {integrity: sha512-QwBER5KMR86IIjpIiO7H/Z3IMJPsZ1A6RKPAqzTTgOyUQUSt9FdnKcqhTaJmkY6HVrgouZHZR0ncK5QxvmnQeg==} + hasBin: true - babel-plugin-polyfill-corejs2@0.4.17: - resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} - babel-plugin-polyfill-corejs3@0.13.0: - resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} - babel-plugin-polyfill-corejs3@0.14.2: - resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} - babel-plugin-polyfill-regenerator@0.6.8: - resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + trim-repeated@1.0.0: + resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} + engines: {node: '>=0.10.0'} - babel-plugin-transform-react-remove-prop-types@0.4.24: - resolution: {integrity: sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - babel-preset-current-node-syntax@1.2.0: - resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} - peerDependencies: - '@babel/core': ^7.0.0 || ^8.0.0-0 + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} - babel-preset-jest@27.5.1: - resolution: {integrity: sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - peerDependencies: - '@babel/core': ^7.0.0 + typed-function@4.2.2: + resolution: {integrity: sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==} + engines: {node: '>= 18'} + + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true - babel-preset-react-app@10.1.0: - resolution: {integrity: sha512-f9B1xMdnkCIqe+2dHrJsoQFRz7reChaAHE/65SdaykPklQqhme2WaC08oD3is77x9ff98/9EazAKFDZv5rFEQg==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + undici@8.11.2: + resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==} + engines: {node: '>=22.19.0'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + unzipit@2.0.0: + resolution: {integrity: sha512-DVeVIWUZCAQPNzm5sB0hpsG1GygTTdBnzNtYYEpInkttx5evkyqRgZi6rTczoySqp8hO5jHVKzrH0f23X8FZLg==} + engines: {node: '>=18'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: 4.28.7 - bare-events@2.9.1: - resolution: {integrity: sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} peerDependencies: - bare-abort-controller: '*' + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - bare-abort-controller: + '@types/react': optional: true - bare-fs@4.7.2: - resolution: {integrity: sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==} - engines: {bare: '>=1.16.0'} + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} peerDependencies: - bare-buffer: '*' + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - bare-buffer: + '@types/react': optional: true - bare-os@3.9.1: - resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} - engines: {bare: '>=1.14.0'} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true - bare-path@3.0.1: - resolution: {integrity: sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==} + vite-plugin-static-copy@4.1.1: + resolution: {integrity: sha512-GrlA8YklrAfSyxJ4M3fdQLOo9oNkp56IM9FYgX/WtEgeIFkPwhu4wzpufBCIuNKCa6Fn77FkRdYxkHqV0FwjAw==} + engines: {node: ^22.0.0 || >=24.0.0} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - bare-stream@2.13.1: - resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} + vite@8.3.1: + resolution: {integrity: sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true peerDependencies: - bare-abort-controller: '*' - bare-buffer: '*' - bare-events: '*' + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: - bare-abort-controller: + '@types/node': optional: true - bare-buffer: + '@vitejs/devtools': optional: true - bare-events: + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: optional: true - bare-url@2.4.5: - resolution: {integrity: sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.11.26: - resolution: {integrity: sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==} - engines: {node: '>=6.0.0'} + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true - batch@0.6.1: - resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + w3c-xmlserializer@6.0.0: + resolution: {integrity: sha512-4Nsy8K5Tr6SPDH9jhKJOHf7ChDrc1zufZTVSF7x72hwuEXBqxqk9G6cK+K2NRUtB3iELRJqjXb4JPDMBjMTl2Q==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - bfj@7.1.0: - resolution: {integrity: sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==} - engines: {node: '>= 8.0.0'} + web-worker@1.5.0: + resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} - big.js@5.2.2: - resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + whatwg-url@17.1.2: + resolution: {integrity: sha512-TEZA+Zqxin7Jjsm2cjRohCmen5awh+hT6Zi3VZdqZlNRk7zvOI/9WpBFg/DWlA56bWnzwm6DuB8NS0EsxQH9uQ==} + engines: {node: ^22.14.0 || >=24.0.0} - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true - bonjour-service@1.4.0: - resolution: {integrity: sha512-fGQtj1qdR9vIKjFiWPQd52qIqwjaYqhcI40JEiDuvlZ86E7ZBPBwY9fPgHy9r2rYGIjiRfctNPYz6OQU73ww2w==} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} - brace-expansion@1.1.21: - resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} + xml-utils@1.10.2: + resolution: {integrity: sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==} - brace-expansion@2.1.7: - resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - browser-process-hrtime@1.0.0: - resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - browserslist@4.28.7: - resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} hasBin: true - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + zarrita@0.7.3: + resolution: {integrity: sha512-wChTQ1Ox75INoQCzKAfLWAfB70JJ4KjdW8Sz5x4ZWrFB4Dw+YZdnxHTL0xSdsrB9EmKSeK7fS1Y+I2ibhfGbkw==} - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + zstddec@0.2.0: + resolution: {integrity: sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==} - builtin-modules@3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} +snapshots: - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} + '@adobe/css-tools@4.5.0': {} - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camel-case@4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - - camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - - caniuse-lite@1.0.30001793: - resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} - - caniuse-lite@1.0.30001812: - resolution: {integrity: sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==} - - case-sensitive-paths-webpack-plugin@2.4.0: - resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} - engines: {node: '>=4'} - - chalk@3.0.0: - resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} - engines: {node: '>=8'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - char-regex@2.0.2: - resolution: {integrity: sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==} - engines: {node: '>=12.20'} - - check-types@11.2.3: - resolution: {integrity: sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - cjs-module-lexer@1.4.3: - resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - - classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - - clean-css@5.3.3: - resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} - engines: {node: '>= 10.0'} - - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - collect-v8-coverage@1.0.3: - resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - colormap@2.3.2: - resolution: {integrity: sha512-jDOjaoEEmA9AgA11B/jCSAvYE95r3wRoAyTf3LEHGiUVlNHJaL1mRkf5AyLSpQBVGfTEPwGEqCIzL+kgr2WgNA==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - - common-tags@1.8.2: - resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} - engines: {node: '>=4.0.0'} - - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - - complex.js@2.4.3: - resolution: {integrity: sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ==} - - compressible@2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} - engines: {node: '>= 0.6'} - - compression@1.8.1: - resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} - engines: {node: '>= 0.8.0'} - - compute-scroll-into-view@1.0.20: - resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - confusing-browser-globals@1.0.11: - resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} - - connect-history-api-fallback@2.0.0: - resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} - engines: {node: '>=0.8'} - - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} - - copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - - copy-to-clipboard@3.3.3: - resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} - - copy-webpack-plugin@9.1.0: - resolution: {integrity: sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.1.0 - - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} - - core-js-pure@3.49.0: - resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} - - core-js@3.49.0: - resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cosmiconfig-typescript-loader@1.0.9: - resolution: {integrity: sha512-tRuMRhxN4m1Y8hP9SNYfz7jRwt8lZdWxdjg/ohg5esKmsndJIn4yT96oJVcf5x0eA11taXl+sIp+ielu529k6g==} - engines: {node: '>=12', npm: '>=6'} - peerDependencies: - '@types/node': '*' - cosmiconfig: '>=7' - typescript: '>=3' - - cosmiconfig@6.0.0: - resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} - engines: {node: '>=8'} - - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - - craco-less@2.0.0: - resolution: {integrity: sha512-980mQaZVrC4ZsvOwvud6/AgvW7fLY3mW5m5+gR4sw4krxszgHb+qoRyOjqsYPD0F4oUmQoSiZSrlYY/bFGD9kQ==} - peerDependencies: - '@craco/craco': ^6.0.0 - react-scripts: ^5.0.0 - - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - crypto-js@4.2.0: - resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} - deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained. - - crypto-random-string@2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} - engines: {node: '>=8'} - - css-blank-pseudo@3.0.3: - resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==} - engines: {node: ^12 || ^14 || >=16} - hasBin: true - peerDependencies: - postcss: 8.5.25 - - css-declaration-sorter@6.4.1: - resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} - engines: {node: ^10 || ^12 || >=14} - peerDependencies: - postcss: 8.5.25 - - css-has-pseudo@3.0.4: - resolution: {integrity: sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==} - engines: {node: ^12 || ^14 || >=16} - hasBin: true - peerDependencies: - postcss: 8.5.25 - - css-loader@6.11.0: - resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} - engines: {node: '>= 12.13.0'} - peerDependencies: - '@rspack/core': 0.x || 1.x - webpack: ^5.0.0 - peerDependenciesMeta: - '@rspack/core': - optional: true - webpack: - optional: true - - css-minimizer-webpack-plugin@3.4.1: - resolution: {integrity: sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==} - engines: {node: '>= 12.13.0'} - peerDependencies: - '@parcel/css': '*' - clean-css: '*' - csso: '*' - esbuild: '*' - webpack: ^5.0.0 - peerDependenciesMeta: - '@parcel/css': - optional: true - clean-css: - optional: true - csso: - optional: true - esbuild: - optional: true - - css-prefers-color-scheme@6.0.3: - resolution: {integrity: sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==} - engines: {node: ^12 || ^14 || >=16} - hasBin: true - peerDependencies: - postcss: 8.5.25 - - css-select@4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - - css-tree@1.1.3: - resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} - engines: {node: '>=8.0.0'} - - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} - - css.escape@1.5.1: - resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - - cssdb@7.11.2: - resolution: {integrity: sha512-lhQ32TFkc1X4eTefGfYPvgovRSzIMofHkigfH8nWtyRL4XJLsRhJFreRvEgKzept7x1rjBuy3J/MurXLaFxW/A==} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssnano-preset-default@5.2.14: - resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - cssnano-utils@3.1.0: - resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - cssnano@5.1.15: - resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - csso@4.2.0: - resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} - engines: {node: '>=8.0.0'} - - cssom@0.3.8: - resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} - - cssom@0.4.4: - resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==} - - cssstyle@2.3.0: - resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} - engines: {node: '>=8'} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - damerau-levenshtein@1.0.8: - resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - - data-urls@2.0.0: - resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} - engines: {node: '>=10'} - - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - - dayjs@1.11.21: - resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} - - dcmjs@0.35.0: - resolution: {integrity: sha512-kKOX4y0XoSQsDx7xYh9HJLfAWizrvYNJPU/d+DGOehdGz1frvimBZDEL0iaCK46Lhy7amAmtv4g5hkjcxVF+kg==} - - dcmjs@0.41.0: - resolution: {integrity: sha512-kr46REomItFeWz+0ck4Wif4uS5VVDWVlwdh5GGaCtTYHWfNQmrcCSiQOkrShc7Dc5zP8vNKrHEdORlZXenlg3w==} - - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - - dedent@0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - - deep-equal@2.2.3: - resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} - engines: {node: '>= 0.4'} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - default-gateway@6.0.3: - resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} - engines: {node: '>= 10'} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - depd@1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} - engines: {node: '>= 0.6'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - detect-browser@5.3.0: - resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} - - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - - detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - - detect-port-alt@1.1.6: - resolution: {integrity: sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==} - engines: {node: '>= 4.2.1'} - hasBin: true - - dicom-microscopy-viewer@0.48.27: - resolution: {integrity: sha512-Y/5q/AOgqYirT9mzfdAUQwi2bPdY/TOG5ouLbP1KGgtpEsfWelAVlagtovB15Gh7585kZ9XWR6fGnBAmwNE9Cg==} - - dicomweb-client@0.10.3: - resolution: {integrity: sha512-/fHNEAYiz8j+9TNOrNJ0k+hYqirbOT85B7vM7I4VkY8DeDQb4BDUeL3RX6huDVtn6ZQlR91dI+2tejLc5c99wA==} - - dicomweb-client@0.11.3: - resolution: {integrity: sha512-7t1Rc/01fyFnKlQeNPW9Pvxvmsx8R/BgB2n7BxTuWirRFwlTC5upEeLeS3vO72P+n6/SQZ3+5ij0bgaWDHQG/A==} - - didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - - diff-sequences@27.5.1: - resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - diff-sequences@28.1.1: - resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - diff@4.0.4: - resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} - engines: {node: '>=0.3.1'} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - - dns-packet@5.6.1: - resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} - engines: {node: '>=6'} - - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - - dom-align@1.12.4: - resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==} - - dom-converter@0.2.0: - resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} - - dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domexception@2.0.1: - resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==} - engines: {node: '>=8'} - deprecated: Use your platform's native DOMException instead - - domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} - - domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - - dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - - dotenv-expand@5.1.0: - resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} - - dotenv@10.0.0: - resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} - engines: {node: '>=10'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - - earcut@3.0.2: - resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} - hasBin: true - - electron-to-chromium@1.5.439: - resolution: {integrity: sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==} - - email-addresses@5.0.0: - resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} - - emittery@0.10.2: - resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} - engines: {node: '>=12'} - - emittery@0.8.1: - resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==} - engines: {node: '>=10'} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - emojis-list@3.0.0: - resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} - engines: {node: '>= 4'} - - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - enhanced-resolve@5.22.2: - resolution: {integrity: sha512-0rxICaFZ7NQho/sHely2bvOPRP0Eu2B0NZ9zM54YvRvWMn7jfz3DmnOZDR9LlXDdDcqntAVc6Hfy4gr/tdH/Ag==} - engines: {node: '>=10.13.0'} - - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - - errno@0.1.8: - resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} - hasBin: true - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - error-stack-parser@2.1.4: - resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} - - es-abstract@1.24.2: - resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-get-iterator@1.1.3: - resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - - es-iterator-helpers@1.3.2: - resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} - engines: {node: '>= 0.4'} - - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-latex@1.2.0: - resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - - eslint-config-react-app@7.0.1: - resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==} - engines: {node: '>=14.0.0'} - peerDependencies: - eslint: ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - eslint-import-resolver-node@0.3.10: - resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} - - eslint-module-utils@2.13.0: - resolution: {integrity: sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-plugin-flowtype@8.0.3: - resolution: {integrity: sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@babel/plugin-syntax-flow': ^7.14.5 - '@babel/plugin-transform-react-jsx': ^7.14.9 - eslint: ^8.1.0 - - eslint-plugin-import@2.32.0: - resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-plugin-jest@25.7.0: - resolution: {integrity: sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/eslint-plugin': ^4.0.0 || ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - jest: '*' - peerDependenciesMeta: - '@typescript-eslint/eslint-plugin': - optional: true - jest: - optional: true - - eslint-plugin-jsx-a11y@6.10.2: - resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - - eslint-plugin-react-hooks@4.6.2: - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - - eslint-plugin-react@7.37.5: - resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - - eslint-plugin-sonarjs@0.25.1: - resolution: {integrity: sha512-5IOKvj/GMBNqjxBdItfotfRHo7w48496GOu1hxdeXuD0mB1JBlDCViiLHETDTfA8pDAVSBimBEQoetRXYceQEw==} - engines: {node: '>=16'} - peerDependencies: - eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - - eslint-plugin-testing-library@5.11.1: - resolution: {integrity: sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} - peerDependencies: - eslint: ^7.5.0 || ^8.0.0 - - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-webpack-plugin@3.2.0: - resolution: {integrity: sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==} - engines: {node: '>= 12.13.0'} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - webpack: ^5.0.0 - - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esprima@1.2.5: - resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==} - engines: {node: '>=0.4.0'} - hasBin: true - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@1.0.1: - resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - - events-universal@1.0.1: - resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - expect@27.5.1: - resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - expect@28.1.3: - resolution: {integrity: sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - express@4.21.0: - resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==} - engines: {node: '>= 0.10.0'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fast-uri@3.1.8: - resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - faye-websocket@0.11.4: - resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} - engines: {node: '>=0.8.0'} - - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fflate@0.8.3: - resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - file-loader@6.2.0: - resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - webpack: ^4.0.0 || ^5.0.0 - - file-type@10.11.0: - resolution: {integrity: sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==} - engines: {node: '>=6'} - - filelist@1.0.6: - resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} - - filename-reserved-regex@2.0.0: - resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} - engines: {node: '>=4'} - - filenamify@4.3.0: - resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==} - engines: {node: '>=8'} - - filesize@8.0.7: - resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==} - engines: {node: '>= 0.4.0'} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} - engines: {node: '>= 0.8'} - - find-cache-dir@3.3.2: - resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} - engines: {node: '>=8'} - - find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - - follow-redirects@1.16.0: - resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - fork-ts-checker-webpack-plugin@6.5.3: - resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} - engines: {node: '>=10', yarn: '>=1.0.0'} - peerDependencies: - eslint: '>= 6' - typescript: '>= 2.7' - vue-template-compiler: '*' - webpack: '>= 4' - peerDependenciesMeta: - eslint: - optional: true - vue-template-compiler: - optional: true - - form-data@3.0.5: - resolution: {integrity: sha512-j23EibVLnp4zNXGW7LjryXYa2X6U/M96yoOX+ybZxwkYajdxRNEqYY3zhh7y0i6kfISKS2jr+EJq1YTUDEv5+w==} - engines: {node: '>= 6'} - - form-data@4.0.6: - resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} - engines: {node: '>= 6'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fraction.js@4.3.4: - resolution: {integrity: sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==} - - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - - fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - - fs-monkey@1.1.0: - resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - geotiff@3.0.5: - resolution: {integrity: sha512-OWcL9S9+yDZ6iAlXMt32T1iwUApJM8UiD47xbm6ZP1h33d10fqkPs14EG/ttT5EnefpZSx3G15iDFC5FxUNUwA==} - engines: {node: '>=10.19'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-own-enumerable-property-symbols@3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - - gh-pages@5.0.0: - resolution: {integrity: sha512-Nqp1SjkPIB94Xw/3yYNTUL+G2dxlhjvv1zeN/4kMC1jfViTEqhtVz/Ba1zSXHuvXCN9ADNS1dN4r5/J/nZWEQQ==} - engines: {node: '>=10'} - hasBin: true - - gl-matrix@3.4.4: - resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob-to-regex.js@1.3.1: - resolution: {integrity: sha512-zhWhMRsgnOym+1txhUmYJGD6eUsbUdrZv55gCYKuJX8t2p3+M8My23PD09bEIsreGUs5pnSa/idmcfp2/IlqGg==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - global-modules@2.0.0: - resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} - engines: {node: '>=6'} - - global-prefix@3.0.0: - resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} - engines: {node: '>=6'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globby@6.1.0: - resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==} - engines: {node: '>=0.10.0'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - gzip-size@6.0.0: - resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} - engines: {node: '>=10'} - - handle-thing@2.0.1: - resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} - - harmony-reflect@1.6.2: - resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} - - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hasown@2.0.4: - resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} - engines: {node: '>= 0.4'} - - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - - hoopy@0.1.4: - resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==} - engines: {node: '>= 6.0.0'} - - hpack.js@2.1.6: - resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} - - hpagent@1.2.0: - resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} - engines: {node: '>=14'} - - html-encoding-sniffer@2.0.1: - resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==} - engines: {node: '>=10'} - - html-entities@2.6.0: - resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - html-minifier-terser@6.1.0: - resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} - engines: {node: '>=12'} - hasBin: true - - html-webpack-plugin@5.6.7: - resolution: {integrity: sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==} - engines: {node: '>=10.13.0'} - peerDependencies: - '@rspack/core': 0.x || 1.x - webpack: ^5.20.0 - peerDependenciesMeta: - '@rspack/core': - optional: true - webpack: - optional: true - - htmlparser2@6.1.0: - resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} - - http-deceiver@1.2.7: - resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} - - http-errors@1.8.1: - resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} - engines: {node: '>= 0.6'} - - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - - http-parser-js@0.5.10: - resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} - - http-proxy-agent@4.0.1: - resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} - engines: {node: '>= 6'} - - http-proxy-middleware@2.0.9: - resolution: {integrity: sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/express': ^4.17.13 - peerDependenciesMeta: - '@types/express': - optional: true - - http-proxy@1.18.1: - resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} - engines: {node: '>=8.0.0'} - - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - husky@9.1.7: - resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} - engines: {node: '>=18'} - hasBin: true - - hyperdyperid@1.2.0: - resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} - engines: {node: '>=10.18'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - icss-utils@5.1.0: - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: 8.5.25 - - idb@7.1.1: - resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} - - identity-obj-proxy@3.0.0: - resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} - engines: {node: '>=4'} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - image-size@0.5.5: - resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} - engines: {node: '>=0.10.0'} - hasBin: true - - image-type@4.1.0: - resolution: {integrity: sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg==} - engines: {node: '>=6'} - - immer@9.0.21: - resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} - hasBin: true - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - iota-array@1.0.0: - resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - ipaddr.js@2.4.0: - resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} - engines: {node: '>= 10'} - - is-arguments@1.2.0: - resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} - engines: {node: '>= 0.4'} - - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - - is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-obj@1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} - engines: {node: '>=0.10.0'} - - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-plain-obj@3.0.0: - resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} - engines: {node: '>=10'} - - 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'} - - is-regexp@1.0.0: - resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} - engines: {node: '>=0.10.0'} - - is-root@2.1.0: - resolution: {integrity: sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==} - engines: {node: '>=6'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} - - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} - engines: {node: '>=12.13'} - - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} - - iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} - - jake@10.9.4: - resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} - engines: {node: '>=10'} - hasBin: true - - javascript-natural-sort@0.7.1: - resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} - - jest-changed-files@27.5.1: - resolution: {integrity: sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-circus@27.5.1: - resolution: {integrity: sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-cli@27.5.1: - resolution: {integrity: sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jest-config@27.5.1: - resolution: {integrity: sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - peerDependencies: - ts-node: '>=9.0.0' - peerDependenciesMeta: - ts-node: - optional: true - - jest-diff@27.5.1: - resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-diff@28.1.3: - resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - jest-docblock@27.5.1: - resolution: {integrity: sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-each@27.5.1: - resolution: {integrity: sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-environment-jsdom@27.5.1: - resolution: {integrity: sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-environment-node@27.5.1: - resolution: {integrity: sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-get-type@27.5.1: - resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-get-type@28.0.2: - resolution: {integrity: sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - jest-haste-map@27.5.1: - resolution: {integrity: sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-jasmine2@27.5.1: - resolution: {integrity: sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-leak-detector@27.5.1: - resolution: {integrity: sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-matcher-utils@27.5.1: - resolution: {integrity: sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-matcher-utils@28.1.3: - resolution: {integrity: sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - jest-message-util@27.5.1: - resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-message-util@28.1.3: - resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - jest-mock@27.5.1: - resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - - jest-regex-util@27.5.1: - resolution: {integrity: sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-regex-util@28.0.2: - resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - jest-resolve-dependencies@27.5.1: - resolution: {integrity: sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-resolve@27.5.1: - resolution: {integrity: sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-runner@27.5.1: - resolution: {integrity: sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-runtime@27.5.1: - resolution: {integrity: sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-serializer@27.5.1: - resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-snapshot@27.5.1: - resolution: {integrity: sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-util@27.5.1: - resolution: {integrity: sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-util@28.1.3: - resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - jest-validate@27.5.1: - resolution: {integrity: sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-watch-typeahead@1.1.0: - resolution: {integrity: sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - jest: ^27.0.0 || ^28.0.0 - - jest-watcher@27.5.1: - resolution: {integrity: sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - jest-watcher@28.1.3: - resolution: {integrity: sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - jest-worker@26.6.2: - resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} - engines: {node: '>= 10.13.0'} - - jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} - - jest-worker@28.1.3: - resolution: {integrity: sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - jest@27.5.1: - resolution: {integrity: sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} - hasBin: true - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@3.15.2: - resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==} - hasBin: true - - js-yaml@4.3.2: - resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} - hasBin: true - - jsdom@16.7.0: - resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==} - engines: {node: '>=10'} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json2mq@0.2.0: - resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} - - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - - jsonfile@6.2.1: - resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - - jsonpath@1.3.0: - resolution: {integrity: sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==} - - jsonpointer@5.0.1: - resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} - engines: {node: '>=0.10.0'} - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - klona@2.0.6: - resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} - engines: {node: '>= 8'} - - language-subtag-registry@0.3.23: - resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - - language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} - - launch-editor@2.14.1: - resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} - - lerc@3.0.0: - resolution: {integrity: sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==} - - lerp@1.0.3: - resolution: {integrity: sha512-70Rh4rCkJDvwWiTsyZ1HmJGvnyfFah4m6iTux29XmasRiZPDBpT9Cfa4ai73+uLZxnlKruUS62jj2lb11wURiA==} - - less-loader@7.3.0: - resolution: {integrity: sha512-Mi8915g7NMaLlgi77mgTTQvK022xKRQBIVDSyfl3ErTuBhmZBQab0mjeJjNNqGbdR+qrfTleKXqbGI4uEFavxg==} - engines: {node: '>= 10.13.0'} - peerDependencies: - less: ^3.5.0 || ^4.0.0 - webpack: ^4.0.0 || ^5.0.0 - - less@4.6.4: - resolution: {integrity: sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==} - engines: {node: '>=18'} - hasBin: true - - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - loader-runner@4.3.2: - resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} - engines: {node: '>=6.11.5'} - - loader-utils@2.0.4: - resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} - engines: {node: '>=8.9.0'} - - loader-utils@3.3.1: - resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} - engines: {node: '>= 12.13.0'} - - locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.clonedeep@4.5.0: - resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - - loglevel@1.9.2: - resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} - engines: {node: '>= 0.6.0'} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - - magic-string@0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - mathjs@11.12.0: - resolution: {integrity: sha512-UGhVw8rS1AyedyI55DGz9q1qZ0p98kyKPyc9vherBkoueLntPfKtPBh14x+V4cdUWK0NZV2TBwqRFlvadscSuw==} - engines: {node: '>= 14'} - hasBin: true - - mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - memfs@3.5.3: - resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} - engines: {node: '>= 4.0.0'} - - memfs@4.79.0: - resolution: {integrity: sha512-vDFqA9WSa//xa3XIKwMmVqxgWWft4PMKW0k69gBM2rYOzZ3VqL9BrK3Kyl45jSxS7qImDgwejZzEb/xDWhP37g==} - - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - mini-css-extract-plugin@2.10.2: - resolution: {integrity: sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 - - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} - engines: {node: '>=10'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - multicast-dns@7.2.5: - resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} - hasBin: true - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - nanoid@3.3.19: - resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - ndarray@1.0.19: - resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==} - - needle@3.5.0: - resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==} - engines: {node: '>= 4.4.x'} - hasBin: true - - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - - negotiator@0.6.4: - resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} - engines: {node: '>= 0.6'} - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - - node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} - engines: {node: '>= 0.4'} - - node-forge@1.4.0: - resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} - engines: {node: '>= 6.13.0'} - - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - node-releases@2.0.57: - resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} - engines: {node: '>=18'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - nth-check@2.0.1: - resolution: {integrity: sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==} - - numcodecs@0.3.2: - resolution: {integrity: sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==} - - nwsapi@2.2.23: - resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} - - obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - - oidc-client@1.11.5: - resolution: {integrity: sha512-LcKrKC8Av0m/KD/4EFmo9Sg8fSQ+WFJWBrmtWd+tZkNn3WT/sQG3REmPANE9tzzhbjW6VkTNy4xhAXCfPApAOg==} - - ol@10.9.0: - resolution: {integrity: sha512-svbbgVQUmEHaKpLQ8kRySojs59Brvgl2zYIrqG9eQNXGfsbi55rQasZIDpwpQzDL6OlzrUb0H4hQaiX9wDoGmA==} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - on-headers@1.1.0: - resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} - engines: {node: '>=8'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - pako@2.1.0: - resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - - param-case@3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-headers@2.0.6: - resolution: {integrity: sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse-node-version@1.0.1: - resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} - engines: {node: '>= 0.10'} - - parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-to-regexp@0.1.13: - resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pbf@4.0.1: - resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==} - hasBin: true - - performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - pinkie-promise@2.0.1: - resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} - engines: {node: '>=0.10.0'} - - pinkie@2.0.4: - resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} - engines: {node: '>=0.10.0'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - - pkg-up@3.1.0: - resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} - engines: {node: '>=8'} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss-attribute-case-insensitive@5.0.2: - resolution: {integrity: sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-browser-comments@4.0.0: - resolution: {integrity: sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==} - engines: {node: '>=8'} - peerDependencies: - browserslist: 4.28.7 - postcss: 8.5.25 - - postcss-calc@8.2.4: - resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} - peerDependencies: - postcss: 8.5.25 - - postcss-clamp@4.1.0: - resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==} - engines: {node: '>=7.6.0'} - peerDependencies: - postcss: 8.5.25 - - postcss-color-functional-notation@4.2.4: - resolution: {integrity: sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-color-hex-alpha@8.0.4: - resolution: {integrity: sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-color-rebeccapurple@7.1.1: - resolution: {integrity: sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-colormin@5.3.1: - resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-convert-values@5.1.3: - resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-custom-media@8.0.2: - resolution: {integrity: sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-custom-properties@12.1.11: - resolution: {integrity: sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-custom-selectors@6.0.3: - resolution: {integrity: sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-dir-pseudo-class@6.0.5: - resolution: {integrity: sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-discard-comments@5.1.2: - resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-discard-duplicates@5.1.0: - resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-discard-empty@5.1.1: - resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-discard-overridden@5.1.0: - resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-double-position-gradients@3.1.2: - resolution: {integrity: sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-env-function@4.0.6: - resolution: {integrity: sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-flexbugs-fixes@5.0.2: - resolution: {integrity: sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==} - peerDependencies: - postcss: 8.5.25 - - postcss-focus-visible@6.0.4: - resolution: {integrity: sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-focus-within@5.0.4: - resolution: {integrity: sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-font-variant@5.0.0: - resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==} - peerDependencies: - postcss: 8.5.25 - - postcss-gap-properties@3.0.5: - resolution: {integrity: sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-image-set-function@4.0.7: - resolution: {integrity: sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-import@15.1.0: - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: 8.5.25 - - postcss-initial@4.0.1: - resolution: {integrity: sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==} - peerDependencies: - postcss: 8.5.25 - - postcss-js@4.1.0: - resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: 8.5.25 - - postcss-lab-function@4.2.1: - resolution: {integrity: sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: 8.5.25 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - - postcss-loader@6.2.1: - resolution: {integrity: sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==} - engines: {node: '>= 12.13.0'} - peerDependencies: - postcss: 8.5.25 - webpack: ^5.0.0 - - postcss-logical@5.0.4: - resolution: {integrity: sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-media-minmax@5.0.0: - resolution: {integrity: sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - postcss: 8.5.25 - - postcss-merge-longhand@5.1.7: - resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-merge-rules@5.1.4: - resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-minify-font-values@5.1.0: - resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-minify-gradients@5.1.1: - resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-minify-params@5.1.4: - resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-minify-selectors@5.2.1: - resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-modules-extract-imports@3.1.0: - resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: 8.5.25 - - postcss-modules-local-by-default@4.2.0: - resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: 8.5.25 - - postcss-modules-scope@3.2.1: - resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: 8.5.25 - - postcss-modules-values@4.0.0: - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: 8.5.25 - - postcss-nested@6.2.0: - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: 8.5.25 - - postcss-nesting@10.2.0: - resolution: {integrity: sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-charset@5.1.0: - resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-display-values@5.1.0: - resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-positions@5.1.1: - resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-repeat-style@5.1.1: - resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-string@5.1.0: - resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-timing-functions@5.1.0: - resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-unicode@5.1.1: - resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-url@5.1.0: - resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize-whitespace@5.1.1: - resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-normalize@10.0.1: - resolution: {integrity: sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==} - engines: {node: '>= 12'} - peerDependencies: - browserslist: 4.28.7 - postcss: 8.5.25 - - postcss-opacity-percentage@1.1.3: - resolution: {integrity: sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-ordered-values@5.1.3: - resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-overflow-shorthand@3.0.4: - resolution: {integrity: sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-page-break@3.0.4: - resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==} - peerDependencies: - postcss: 8.5.25 - - postcss-place@7.0.5: - resolution: {integrity: sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-preset-env@7.8.3: - resolution: {integrity: sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-pseudo-class-any-link@7.1.6: - resolution: {integrity: sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-reduce-initial@5.1.2: - resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-reduce-transforms@5.1.0: - resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-replace-overflow-wrap@4.0.0: - resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==} - peerDependencies: - postcss: 8.5.25 - - postcss-selector-not@6.0.1: - resolution: {integrity: sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==} - engines: {node: ^12 || ^14 || >=16} - peerDependencies: - postcss: 8.5.25 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-selector-parser@7.1.1: - resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} - engines: {node: '>=4'} - - postcss-svgo@5.1.0: - resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-unique-selectors@5.1.1: - resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.25: - resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} - engines: {node: ^10 || ^12 || >=14} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - pretty-bytes@5.6.0: - resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} - engines: {node: '>=6'} - - pretty-error@4.0.0: - resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} - - pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - pretty-format@28.1.3: - resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - promise@8.3.0: - resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - properties-file@4.0.0: - resolution: {integrity: sha512-VWnuWVVQIeYPdJwWfBVu6/SQExpAliGkY8Bq58pHHPJHgD4THXlJfsc+qC41+62iV4WNwQhmrxbqpEXIvIMOmw==} - - protocol-buffers-schema@3.6.1: - resolution: {integrity: sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - proxy-from-env@2.1.0: - resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} - engines: {node: '>=10'} - - prr@1.0.1: - resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - - psl@1.15.0: - resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - quick-lru@6.1.2: - resolution: {integrity: sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==} - engines: {node: '>=12'} - - quickselect@3.0.0: - resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} - - raf@3.4.1: - resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} - - rbush@4.0.1: - resolution: {integrity: sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==} - - rc-align@4.0.15: - resolution: {integrity: sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-cascader@3.7.3: - resolution: {integrity: sha512-KBpT+kzhxDW+hxPiNk4zaKa99+Lie2/8nnI11XF+FIOPl4Bj9VlFZi61GrnWzhLGA7VEN+dTxAkNOjkySDa0dA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-checkbox@3.0.1: - resolution: {integrity: sha512-k7nxDWxYF+jDI0ZcCvuvj71xONmWRVe5+1MKcERRR9MRyP3tZ69b+yUCSXXh+sik4/Hc9P5wHr2nnUoGS2zBjA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-collapse@3.4.2: - resolution: {integrity: sha512-jpTwLgJzkhAgp2Wpi3xmbTbbYExg6fkptL67Uu5LCRVEj6wqmy0DHTjjeynsjOLsppHGHu41t1ELntZ0lEvS/Q==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-dialog@9.0.4: - resolution: {integrity: sha512-pmnPRZKd9CGzGgf4a1ysBvMhxm8Afx5fF6M7AzLtJ0qh8X1bshurDlqnK4MBNAB4hAeAMMbz6Ytb1rkGMvKFbQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-drawer@6.3.0: - resolution: {integrity: sha512-uBZVb3xTAR+dBV53d/bUhTctCw3pwcwJoM7g5aX+7vgwt2zzVzoJ6aqFjYJpBlZ9zp0dVYN8fV+hykFE7c4lig==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-dropdown@4.0.1: - resolution: {integrity: sha512-OdpXuOcme1rm45cR0Jzgfl1otzmU4vuBVb+etXM8vcaULGokAKVpKlw8p6xzspG7jGd/XxShvq+N3VNEfk/l5g==} - peerDependencies: - react: '>=16.11.0' - react-dom: '>=16.11.0' - - rc-field-form@1.38.2: - resolution: {integrity: sha512-O83Oi1qPyEv31Sg+Jwvsj6pXc8uQI2BtIAkURr5lvEYHVggXJhdU/nynK8wY1gbw0qR48k731sN5ON4egRCROA==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-image@5.13.0: - resolution: {integrity: sha512-iZTOmw5eWo2+gcrJMMcnd7SsxVHl3w5xlyCgsULUdJhJbnuI8i/AL0tVOsE7aLn9VfOh1qgDT3mC2G75/c7mqg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-input-number@7.3.11: - resolution: {integrity: sha512-aMWPEjFeles6PQnMqP5eWpxzsvHm9rh1jQOWXExUEIxhX62Fyl/ptifLHOn17+waDG1T/YUb6flfJbvwRhHrbA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-input@0.1.4: - resolution: {integrity: sha512-FqDdNz+fV2dKNgfXzcSLKvC+jEs1709t7nD+WdfjrdSaOcefpgc7BUJYadc3usaING+b7ediMTfKxuJBsEFbXA==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - rc-mentions@1.13.1: - resolution: {integrity: sha512-FCkaWw6JQygtOz0+Vxz/M/NWqrWHB9LwqlY2RtcuFqWJNFK9njijOOzTSsBGANliGufVUzx/xuPHmZPBV0+Hgw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-menu@9.8.4: - resolution: {integrity: sha512-lmw2j8I2fhdIzHmC9ajfImfckt0WDb2KVJJBBRIsxPEw2kGkEfjLMUoB1NgiNT/Q5cC8PdjGOGQjHJIJMwyNMw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-motion@2.9.5: - resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-notification@4.6.1: - resolution: {integrity: sha512-NSmFYwrrdY3+un1GvDAJQw62Xi9LNMSsoQyo95tuaYrcad5Bn9gJUL8AREufRxSQAQnr64u3LtP3EUyLYT6bhw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-overflow@1.5.0: - resolution: {integrity: sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-pagination@3.2.0: - resolution: {integrity: sha512-5tIXjB670WwwcAJzAqp2J+cOBS9W3cH/WU1EiYwXljuZ4vtZXKlY2Idq8FZrnYBz8KhN3vwPo9CoV/SJS6SL1w==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-picker@2.7.6: - resolution: {integrity: sha512-H9if/BUJUZBOhPfWcPeT15JUI3/ntrG9muzERrXDkSoWmDj4yzmBvumozpxYrHwjcKnjyDGAke68d+whWwvhHA==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-progress@3.4.2: - resolution: {integrity: sha512-iAGhwWU+tsayP+Jkl9T4+6rHeQTG9kDz8JAHZk4XtQOcYN5fj9H34NXNEdRdZx94VUDHMqCb1yOIvi8eJRh67w==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-rate@2.9.3: - resolution: {integrity: sha512-2THssUSnRhtqIouQIIXqsZGzRczvp4WsH4WvGuhiwm+LG2fVpDUJliP9O1zeDOZvYfBE/Bup4SgHun/eCkbjgQ==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-resize-observer@1.4.3: - resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-segmented@2.3.0: - resolution: {integrity: sha512-I3FtM5Smua/ESXutFfb8gJ8ZPcvFR+qUgeeGFQHBOvRiRKyAk4aBE5nfqrxXx+h8/vn60DQjOt6i4RNtrbOobg==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - rc-select@14.1.18: - resolution: {integrity: sha512-4JgY3oG2Yz68ECMUSCON7mtxuJvCSj+LJpHEg/AONaaVBxIIrmI/ZTuMJkyojall/X50YdBe5oMKqHHPNiPzEg==} - engines: {node: '>=8.x'} - peerDependencies: - react: '*' - react-dom: '*' - - rc-slider@10.0.1: - resolution: {integrity: sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-steps@5.0.0: - resolution: {integrity: sha512-9TgRvnVYirdhbV0C3syJFj9EhCRqoJAsxt4i1rED5o8/ZcSv5TLIYyo4H8MCjLPvbe2R+oBAm/IYBEtC+OS1Rw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-switch@3.2.2: - resolution: {integrity: sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-table@7.26.0: - resolution: {integrity: sha512-0cD8e6S+DTGAt5nBZQIPFYEaIukn17sfa5uFL98faHlH/whZzD8ii3dbFL4wmUDEL4BLybhYop+QUfZJ4CPvNQ==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-tabs@12.5.10: - resolution: {integrity: sha512-Ay0l0jtd4eXepFH9vWBvinBjqOpqzcsJTerBGwJy435P2S90Uu38q8U/mvc1sxUEVOXX5ZCFbxcWPnfG3dH+tQ==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-textarea@0.4.7: - resolution: {integrity: sha512-IQPd1CDI3mnMlkFyzt2O4gQ2lxUsnBAeJEoZGJnkkXgORNqyM9qovdrCj9NzcRfpHgLdzaEbU3AmobNFGUznwQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-tooltip@5.2.2: - resolution: {integrity: sha512-jtQzU/18S6EI3lhSGoDYhPqNpWajMtS5VV/ld1LwyfrDByQpYmw/LW6U7oFXXLukjfDHQ7Ju705A82PRNFWYhg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-tree-select@5.5.5: - resolution: {integrity: sha512-k2av7jF6tW9bIO4mQhaVdV4kJ1c54oxV3/hHVU+oD251Gb5JN+m1RbJFTMf1o0rAFqkvto33rxMdpafaGKQRJw==} - peerDependencies: - react: '*' - react-dom: '*' - - rc-tree@5.7.12: - resolution: {integrity: sha512-LXA5nY2hG5koIAlHW5sgXgLpOMz+bFRbnZZ+cCg0tQs4Wv1AmY7EDi1SK7iFXhslYockbqUerQan82jljoaItg==} - engines: {node: '>=10.x'} - peerDependencies: - react: '*' - react-dom: '*' - - rc-trigger@5.3.4: - resolution: {integrity: sha512-mQv+vas0TwKcjAO2izNPkqR4j86OemLRmvL2nOzdP9OWNWA1ivoTt5hzFqYNW9zACwmTezRiN8bttrC7cZzYSw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-upload@4.3.6: - resolution: {integrity: sha512-Bt7ESeG5tT3IY82fZcP+s0tQU2xmo1W6P3S8NboUUliquJLQYLkUcsaExi3IlBVr43GQMCjo30RA2o0i70+NjA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-util@5.44.4: - resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-virtual-list@3.19.2: - resolution: {integrity: sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - react-app-polyfill@3.0.0: - resolution: {integrity: sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==} - engines: {node: '>=14'} - - react-dev-utils@12.0.1: - resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=2.7' - webpack: '>=4' - peerDependenciesMeta: - typescript: - optional: true - - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} - peerDependencies: - react: ^18.3.1 - - react-error-boundary@3.1.4: - resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} - engines: {node: '>=10', npm: '>=6'} - peerDependencies: - react: '>=16.13.1' - - react-error-overlay@6.1.0: - resolution: {integrity: sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==} - - react-icons@3.11.0: - resolution: {integrity: sha512-JRgiI/vdF6uyBgyZhVyYJUZAop95Sy4XDe/jmT3R/bKliFWpO/uZBwvSjWEdxwzec7SYbEPNPck0Kff2tUGM2Q==} - peerDependencies: - react: '*' - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - react-refresh@0.11.0: - resolution: {integrity: sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==} - engines: {node: '>=0.10.0'} - - react-router-dom@6.30.6: - resolution: {integrity: sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.30.6: - resolution: {integrity: sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - - react-scripts@5.0.0: - resolution: {integrity: sha512-3i0L2CyIlROz7mxETEdfif6Sfhh9Lfpzi10CtcGs1emDQStmZfWjJbAIMtRD0opVUjQuFWqHZyRZ9PPzKCFxWg==} - engines: {node: '>=14.0.0'} - hasBin: true - peerDependencies: - eslint: '*' - react: '>= 16' - typescript: ^3.2.1 || ^4 - peerDependenciesMeta: - typescript: - optional: true - - react-shallow-renderer@16.15.0: - resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==} - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - - react-test-renderer@18.3.1: - resolution: {integrity: sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==} - peerDependencies: - react: ^18.3.1 - - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} - - read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - recursive-readdir@2.2.3: - resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==} - engines: {node: '>=6.0.0'} - - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - - reference-spec-reader@0.2.0: - resolution: {integrity: sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ==} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regenerate-unicode-properties@10.2.2: - resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} - engines: {node: '>=4'} - - regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - - regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - - regex-parser@2.3.1: - resolution: {integrity: sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - regexpu-core@6.4.0: - resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} - engines: {node: '>=4'} - - regjsgen@0.8.0: - resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - - regjsparser@0.13.1: - resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} - hasBin: true - - relateurl@0.2.7: - resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} - engines: {node: '>= 0.10'} - - renderkid@3.0.0: - resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - - resize-observer-polyfill@1.5.1: - resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve-protobuf-schema@2.1.0: - resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} - - resolve-url-loader@4.0.0: - resolution: {integrity: sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==} - engines: {node: '>=8.9'} - peerDependencies: - rework: 1.0.1 - rework-visit: 1.0.0 - peerDependenciesMeta: - rework: - optional: true - rework-visit: - optional: true - - resolve.exports@1.1.1: - resolution: {integrity: sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==} - engines: {node: '>=10'} - - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} - hasBin: true - - resolve@2.0.0-next.7: - resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} - engines: {node: '>= 0.4'} - hasBin: true - - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rollup-plugin-terser@7.0.2: - resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} - deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser - peerDependencies: - rollup: 3.30.0 - - rollup@3.30.0: - resolution: {integrity: sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - safe-array-concat@1.1.4: - resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} - engines: {node: '>=0.4'} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sanitize.css@13.0.0: - resolution: {integrity: sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==} - - sass-loader@12.6.0: - resolution: {integrity: sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==} - engines: {node: '>= 12.13.0'} - peerDependencies: - fibers: '>= 3.1.0' - node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - sass: ^1.3.0 - sass-embedded: '*' - webpack: ^5.0.0 - peerDependenciesMeta: - fibers: - optional: true - node-sass: - optional: true - sass: - optional: true - sass-embedded: - optional: true - - sax@1.6.0: - resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} - engines: {node: '>=11.0.0'} - - saxes@5.0.1: - resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} - engines: {node: '>=10'} - - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - - schema-utils@2.7.0: - resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==} - engines: {node: '>= 8.9.0'} - - schema-utils@2.7.1: - resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==} - engines: {node: '>= 8.9.0'} - - schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} - - schema-utils@4.3.3: - resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} - engines: {node: '>= 10.13.0'} - - scroll-into-view-if-needed@2.2.31: - resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} - - seedrandom@3.0.5: - resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} - - select-hose@2.0.0: - resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} - - selfsigned@2.4.1: - resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} - engines: {node: '>=10'} - - semver@7.5.2: - resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} - engines: {node: '>=10'} - hasBin: true - - send@0.19.0: - resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} - engines: {node: '>= 0.8.0'} - - serialize-javascript@7.0.5: - resolution: {integrity: sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==} - engines: {node: '>=20.0.0'} - - serve-index@1.9.2: - resolution: {integrity: sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==} - engines: {node: '>= 0.8.0'} - - serve-static@1.16.2: - resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} - engines: {node: '>= 0.8.0'} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shallowequal@1.1.0: - resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shell-quote@1.9.0: - resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} - engines: {node: '>= 0.4'} - - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} - - slugify@1.6.9: - resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} - engines: {node: '>=8.0.0'} - - sockjs@0.3.24: - resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} - - sonarqube-scanner@4.3.6: - resolution: {integrity: sha512-gRtTf11UXl5SqXrRKzXagdRh06v8n2qXAypEhmlTOFCdAUlR4TboPzYClMFFT7NCZq5jKZSyN2mk5jj01RgVVQ==} - engines: {node: '>= 18'} - hasBin: true - - source-list-map@2.0.1: - resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map-loader@3.0.2: - resolution: {integrity: sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} - - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - deprecated: The work that was done in this beta branch won't be included in future versions - - sourcemap-codec@1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - deprecated: Please use @jridgewell/sourcemap-codec instead - - spdy-transport@3.0.0: - resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} - - spdy@4.0.2: - resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} - engines: {node: '>=6.0.0'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - stable@0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - - stackframe@1.3.4: - resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} - - static-eval@2.1.1: - resolution: {integrity: sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==} - - statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} - engines: {node: '>= 0.6'} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - - streamx@2.26.0: - resolution: {integrity: sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==} - - string-convert@0.2.1: - resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} - - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - - string-length@5.0.1: - resolution: {integrity: sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==} - engines: {node: '>=12.20'} - - string-natural-compare@3.0.1: - resolution: {integrity: sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string.prototype.includes@2.0.1: - resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} - engines: {node: '>= 0.4'} - - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} - - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - stringify-object@3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} - engines: {node: '>=4'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - - strip-comments@2.0.1: - resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} - engines: {node: '>=10'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strip-outer@1.0.1: - resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} - engines: {node: '>=0.10.0'} - - style-loader@3.3.4: - resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==} - engines: {node: '>= 12.13.0'} - peerDependencies: - webpack: ^5.0.0 - - stylehacks@5.1.1: - resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: 8.5.25 - - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - supports-hyperlinks@2.3.0: - resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - svg-parser@2.0.4: - resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - - svgo@2.8.4: - resolution: {integrity: sha512-2GJ4h3rl13qYTdwllaK6QlL8tG+UrM8626V2Ylcd/yBUv2Y/EwLsYisVV6UDCRmlk+C74G8nMpxJCk0RWPdDCw==} - engines: {node: '>=10.13.0'} - hasBin: true - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - tailwindcss@3.4.19: - resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} - engines: {node: '>=14.0.0'} - hasBin: true - - tapable@1.1.3: - resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} - engines: {node: '>=6'} - - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - tar-stream@3.1.8: - resolution: {integrity: sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==} - - teex@1.0.1: - resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - - temp-dir@2.0.0: - resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} - engines: {node: '>=8'} - - tempy@0.6.0: - resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} - engines: {node: '>=10'} - - terminal-link@2.1.1: - resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} - engines: {node: '>=8'} - - terser-webpack-plugin@5.6.1: - resolution: {integrity: sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@minify-html/node': '*' - '@swc/core': '*' - '@swc/css': '*' - '@swc/html': '*' - clean-css: '*' - cssnano: '*' - csso: '*' - esbuild: '*' - html-minifier-terser: '*' - lightningcss: '*' - postcss: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@minify-html/node': - optional: true - '@swc/core': - optional: true - '@swc/css': - optional: true - '@swc/html': - optional: true - clean-css: - optional: true - cssnano: - optional: true - csso: - optional: true - esbuild: - optional: true - html-minifier-terser: - optional: true - lightningcss: - optional: true - postcss: - optional: true - uglify-js: - optional: true - - terser@5.48.0: - resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} - engines: {node: '>=10'} - hasBin: true - - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - - text-decoder@1.2.7: - resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - thingies@2.6.1: - resolution: {integrity: sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==} - engines: {node: '>=10.18'} - peerDependencies: - tslib: ^2 - - throat@6.0.2: - resolution: {integrity: sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==} - - throttle-debounce@5.0.2: - resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} - engines: {node: '>=12.22'} - - thunky@1.1.0: - resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} - - tiny-emitter@2.1.0: - resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toggle-selection@1.0.6: - resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} - - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - - tr46@2.1.0: - resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} - engines: {node: '>=8'} - - tree-dump@1.1.0: - resolution: {integrity: sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - trim-repeated@1.0.0: - resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} - engines: {node: '>=0.10.0'} - - tryer@1.0.1: - resolution: {integrity: sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsutils@3.21.0: - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - type-fest@0.16.0: - resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} - engines: {node: '>=10'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.8: - resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} - engines: {node: '>= 0.4'} - - typed-function@4.2.2: - resolution: {integrity: sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==} - engines: {node: '>= 18'} - - typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - - typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - underscore@1.13.8: - resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} - - unicode-canonical-property-names-ecmascript@2.0.1: - resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} - engines: {node: '>=4'} - - unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} - - unicode-match-property-value-ecmascript@2.2.1: - resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} - engines: {node: '>=4'} - - unicode-property-aliases-ecmascript@2.2.0: - resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} - engines: {node: '>=4'} - - unique-string@2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} - engines: {node: '>=8'} - - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - unzipit@2.0.0: - resolution: {integrity: sha512-DVeVIWUZCAQPNzm5sB0hpsG1GygTTdBnzNtYYEpInkttx5evkyqRgZi6rTczoySqp8hO5jHVKzrH0f23X8FZLg==} - engines: {node: '>=18'} - - upath@1.2.0: - resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} - engines: {node: '>=4'} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: 4.28.7 - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - utila@0.4.0: - resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} - - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). - hasBin: true - - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). - hasBin: true - - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - v8-to-istanbul@8.1.1: - resolution: {integrity: sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==} - engines: {node: '>=10.12.0'} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - w3c-hr-time@1.0.2: - resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} - deprecated: Use your platform's native performance.now() and performance.timeOrigin. - - w3c-xmlserializer@2.0.0: - resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==} - engines: {node: '>=10'} - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - - watchpack@2.5.1: - resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} - engines: {node: '>=10.13.0'} - - wbuf@1.7.3: - resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==} - - web-worker@1.5.0: - resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} - - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - - webidl-conversions@5.0.0: - resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} - engines: {node: '>=8'} - - webidl-conversions@6.1.0: - resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==} - engines: {node: '>=10.4'} - - webpack-dev-middleware@7.4.6: - resolution: {integrity: sha512-yBWCMvIfUmuhAE8vdqUKzH0vg9kuWN0KeG4vBnqRplUFHRU7lMQjkiJWxVQzvo2BTewqhPhDlMB41rAt2jVA9A==} - engines: {node: '>= 18.12.0'} - peerDependencies: - webpack: ^5.0.0 - peerDependenciesMeta: - webpack: - optional: true - - webpack-dev-server@4.15.2: - resolution: {integrity: sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==} - engines: {node: '>= 12.13.0'} - hasBin: true - peerDependencies: - webpack: ^4.37.0 || ^5.0.0 - webpack-cli: '*' - peerDependenciesMeta: - webpack: - optional: true - webpack-cli: - optional: true - - webpack-manifest-plugin@4.1.1: - resolution: {integrity: sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==} - engines: {node: '>=12.22.0'} - peerDependencies: - webpack: ^4.44.2 || ^5.47.0 - - webpack-merge@4.2.2: - resolution: {integrity: sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==} - - webpack-sources@1.4.3: - resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==} - - webpack-sources@2.3.1: - resolution: {integrity: sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==} - engines: {node: '>=10.13.0'} - - webpack-sources@3.5.0: - resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==} - engines: {node: '>=10.13.0'} - - webpack@5.107.2: - resolution: {integrity: sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - - websocket-driver@0.7.5: - resolution: {integrity: sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==} - engines: {node: '>=0.8.0'} - - websocket-extensions@0.1.4: - resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} - engines: {node: '>=0.8.0'} - - whatwg-encoding@1.0.5: - resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} - deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation - - whatwg-fetch@3.6.20: - resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} - - whatwg-mimetype@2.3.0: - resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==} - - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - - whatwg-url@8.7.0: - resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} - engines: {node: '>=10'} - - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.21: - resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==} - engines: {node: '>= 0.4'} - - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - workbox-background-sync@6.6.0: - resolution: {integrity: sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==} - - workbox-broadcast-update@6.6.0: - resolution: {integrity: sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==} - - workbox-build@6.6.0: - resolution: {integrity: sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==} - engines: {node: '>=10.0.0'} - - workbox-cacheable-response@6.6.0: - resolution: {integrity: sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==} - deprecated: workbox-background-sync@6.6.0 - - workbox-core@6.6.0: - resolution: {integrity: sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==} - - workbox-expiration@6.6.0: - resolution: {integrity: sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==} - - workbox-google-analytics@6.6.0: - resolution: {integrity: sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==} - deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained - - workbox-navigation-preload@6.6.0: - resolution: {integrity: sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==} - - workbox-precaching@6.6.0: - resolution: {integrity: sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==} - - workbox-range-requests@6.6.0: - resolution: {integrity: sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==} - - workbox-recipes@6.6.0: - resolution: {integrity: sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==} - - workbox-routing@6.6.0: - resolution: {integrity: sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==} - - workbox-strategies@6.6.0: - resolution: {integrity: sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==} - - workbox-streams@6.6.0: - resolution: {integrity: sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==} - - workbox-sw@6.6.0: - resolution: {integrity: sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==} - - workbox-webpack-plugin@6.6.0: - resolution: {integrity: sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==} - engines: {node: '>=10.0.0'} - peerDependencies: - webpack: ^4.4.0 || ^5.9.0 - - workbox-window@6.6.0: - resolution: {integrity: sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@3.0.3: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} - - ws@7.5.11: - resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} - 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@3.0.0: - resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} - - xml-utils@1.10.2: - resolution: {integrity: sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==} - - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yaml@1.10.3: - resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} - engines: {node: '>= 6'} - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - zarrita@0.7.3: - resolution: {integrity: sha512-wChTQ1Ox75INoQCzKAfLWAfB70JJ4KjdW8Sz5x4ZWrFB4Dw+YZdnxHTL0xSdsrB9EmKSeK7fS1Y+I2ibhfGbkw==} - - zstddec@0.2.0: - resolution: {integrity: sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==} - -snapshots: - - '@adobe/css-tools@4.5.0': {} - - '@alloc/quick-lru@5.2.0': {} - - '@ant-design/colors@6.0.0': - dependencies: - '@ctrl/tinycolor': 3.6.1 - - '@ant-design/icons-svg@4.4.2': {} - - '@ant-design/icons@4.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@ant-design/colors': 6.0.0 - '@ant-design/icons-svg': 4.4.2 - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - lodash: 4.18.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@ant-design/react-slick@1.0.2(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - json2mq: 0.2.0 - react: 18.3.1 - resize-observer-polyfill: 1.5.1 - throttle-debounce: 5.0.2 - - '@apideck/better-ajv-errors@0.3.7(ajv@8.20.0)': - dependencies: - ajv: 8.20.0 - jsonpointer: 5.0.1 - leven: 3.1.0 - - '@babel/code-frame@7.29.7': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.7': {} - - '@babel/core@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 7.5.2 - transitivePeerDependencies: - - supports-color - - '@babel/eslint-parser@7.29.7(@babel/core@7.29.7)(eslint@8.57.1)': - dependencies: - '@babel/core': 7.29.7 - '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 - eslint: 8.57.1 - eslint-visitor-keys: 2.1.0 - semver: 7.5.2 - - '@babel/generator@7.29.7': - dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-annotate-as-pure@7.29.7': - dependencies: - '@babel/types': 7.29.7 - - '@babel/helper-compilation-targets@7.29.7': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.7 - lru-cache: 5.1.1 - semver: 7.5.2 - - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.7 - semver: 7.5.2 - transitivePeerDependencies: - - supports-color - - '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - regexpu-core: 6.4.0 - semver: 7.5.2 - - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - debug: 4.4.3 - lodash.debounce: 4.0.8 - resolve: 1.22.12 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.29.7': {} - - '@babel/helper-member-expression-to-functions@7.29.7': - dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.29.7': - dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-optimise-call-expression@7.29.7': - dependencies: - '@babel/types': 7.29.7 - - '@babel/helper-plugin-utils@7.29.7': {} - - '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-wrap-function': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': - dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/helper-validator-option@7.29.7': {} - - '@babel/helper-wrap-function@7.29.7': - dependencies: - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helpers@7.29.7': - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - - '@babel/parser@7.29.7': - dependencies: - '@babel/types': 7.29.7 - - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - - '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) - - '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-block-scoped-functions@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-computed-properties@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/template': 7.29.7 - - '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-dynamic-import@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) - - '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-function-name@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-json-strings@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-member-expression-literals@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-modules-amd@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-systemjs@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-modules-umd@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-object-super@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-property-literals@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-react-constant-elements@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-react-jsx-development@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/types': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-react-pure-annotations@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-regenerator@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-regexp-modifiers@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) - semver: 7.5.2 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-spread@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-property-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-unicode-sets-regex@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/preset-env@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/core': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7) - '@babel/plugin-syntax-import-assertions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7) - '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-block-scoped-functions': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-duplicate-keys': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-dynamic-import': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-explicit-resource-management': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-exponentiation-operator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-json-strings': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-member-expression-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-amd': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-systemjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-umd': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-new-target': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-object-super': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-property-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-regexp-modifiers': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-reserved-words': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-spread': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typeof-symbol': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-escapes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-property-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-unicode-sets-regex': 7.29.7(@babel/core@7.29.7) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7) - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) - babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.7) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) - core-js-compat: 3.49.0 - semver: 7.5.2 - transitivePeerDependencies: - - supports-color - - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/types': 7.29.7 - esutils: 2.0.3 - - '@babel/preset-react@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-pure-annotations': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/runtime-corejs3@7.29.7': - dependencies: - core-js-pure: 3.49.0 - - '@babel/runtime@7.29.7': {} - - '@babel/template@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - - '@babel/traverse@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.7': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@bcoe/v8-coverage@0.2.3': {} - - '@biomejs/biome@2.4.16': - optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.4.16 - '@biomejs/cli-darwin-x64': 2.4.16 - '@biomejs/cli-linux-arm64': 2.4.16 - '@biomejs/cli-linux-arm64-musl': 2.4.16 - '@biomejs/cli-linux-x64': 2.4.16 - '@biomejs/cli-linux-x64-musl': 2.4.16 - '@biomejs/cli-win32-arm64': 2.4.16 - '@biomejs/cli-win32-x64': 2.4.16 - - '@biomejs/cli-darwin-arm64@2.4.16': - optional: true - - '@biomejs/cli-darwin-x64@2.4.16': - optional: true - - '@biomejs/cli-linux-arm64-musl@2.4.16': - optional: true - - '@biomejs/cli-linux-arm64@2.4.16': - optional: true - - '@biomejs/cli-linux-x64-musl@2.4.16': - optional: true - - '@biomejs/cli-linux-x64@2.4.16': - optional: true - - '@biomejs/cli-win32-arm64@2.4.16': - optional: true - - '@biomejs/cli-win32-x64@2.4.16': - optional: true - - '@cornerstonejs/codec-charls@1.2.3': {} - - '@cornerstonejs/codec-libjpeg-turbo-8bit@1.2.2': {} - - '@cornerstonejs/codec-openjpeg@1.3.0': {} - - '@cornerstonejs/codec-openjph@2.4.7': {} - - '@craco/craco@6.4.5(@types/node@14.18.63)(react-scripts@5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5))(typescript@4.9.5)': - dependencies: - cosmiconfig: 7.1.0 - cosmiconfig-typescript-loader: 1.0.9(@types/node@14.18.63)(cosmiconfig@7.1.0)(typescript@4.9.5) - cross-spawn: 7.0.6 - lodash: 4.18.1 - react-scripts: 5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5) - semver: 7.5.2 - webpack-merge: 4.2.2 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - - '@types/node' - - typescript - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - - '@csstools/normalize.css@12.1.1': {} - - '@csstools/postcss-cascade-layers@1.1.1(postcss@8.5.25)': - dependencies: - '@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.1.2) - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 - - '@csstools/postcss-color-function@1.1.1(postcss@8.5.25)': - dependencies: - '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-font-format-keywords@1.0.1(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-hwb-function@1.0.2(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-ic-unit@1.0.1(postcss@8.5.25)': - dependencies: - '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-is-pseudo-class@2.0.7(postcss@8.5.25)': - dependencies: - '@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.1.2) - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 - - '@csstools/postcss-nested-calc@1.0.0(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-normalize-display-values@1.0.1(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-oklab-function@1.1.1(postcss@8.5.25)': - dependencies: - '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-progressive-custom-properties@1.3.0(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-stepped-value-functions@1.0.1(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-text-decoration-shorthand@1.0.0(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-trigonometric-functions@1.0.2(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - '@csstools/postcss-unset-value@1.0.2(postcss@8.5.25)': - dependencies: - postcss: 8.5.25 - - '@csstools/selector-specificity@2.2.0(postcss-selector-parser@6.1.2)': - dependencies: - postcss-selector-parser: 6.1.2 - - '@ctrl/tinycolor@3.6.1': {} - - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.12.6 - debug: 4.4.3 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.3.2 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.1': {} - - '@humanwhocodes/config-array@0.13.0': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@imagingdatacommons/dicomicc@0.2.3': {} - - '@istanbuljs/load-nyc-config@1.1.0': - dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.15.2 - resolve-from: 5.0.0 - - '@istanbuljs/schema@0.1.6': {} - - '@jest/console@27.5.1': - dependencies: - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - chalk: 4.1.2 - jest-message-util: 27.5.1 - jest-util: 27.5.1 - slash: 3.0.0 - - '@jest/console@28.1.3': - dependencies: - '@jest/types': 28.1.3 - '@types/node': 14.18.63 - chalk: 4.1.2 - jest-message-util: 28.1.3 - jest-util: 28.1.3 - slash: 3.0.0 - - '@jest/core@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))': - dependencies: - '@jest/console': 27.5.1 - '@jest/reporters': 27.5.1 - '@jest/test-result': 27.5.1 - '@jest/transform': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.8.1 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 27.5.1 - jest-config: 27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)) - jest-haste-map: 27.5.1 - jest-message-util: 27.5.1 - jest-regex-util: 27.5.1 - jest-resolve: 27.5.1 - jest-resolve-dependencies: 27.5.1 - jest-runner: 27.5.1 - jest-runtime: 27.5.1 - jest-snapshot: 27.5.1 - jest-util: 27.5.1 - jest-validate: 27.5.1 - jest-watcher: 27.5.1 - micromatch: 4.0.8 - rimraf: 3.0.2 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - ts-node - - utf-8-validate - - '@jest/environment@27.5.1': - dependencies: - '@jest/fake-timers': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - jest-mock: 27.5.1 - - '@jest/expect-utils@28.1.3': - dependencies: - jest-get-type: 28.0.2 - - '@jest/fake-timers@27.5.1': - dependencies: - '@jest/types': 27.5.1 - '@sinonjs/fake-timers': 8.1.0 - '@types/node': 14.18.63 - jest-message-util: 27.5.1 - jest-mock: 27.5.1 - jest-util: 27.5.1 - - '@jest/globals@27.5.1': - dependencies: - '@jest/environment': 27.5.1 - '@jest/types': 27.5.1 - expect: 27.5.1 - - '@jest/reporters@27.5.1': - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 27.5.1 - '@jest/test-result': 27.5.1 - '@jest/transform': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - chalk: 4.1.2 - collect-v8-coverage: 1.0.3 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 5.2.1 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.2.0 - jest-haste-map: 27.5.1 - jest-resolve: 27.5.1 - jest-util: 27.5.1 - jest-worker: 27.5.1 - slash: 3.0.0 - source-map: 0.6.1 - string-length: 4.0.2 - terminal-link: 2.1.1 - v8-to-istanbul: 8.1.1 - transitivePeerDependencies: - - supports-color - - '@jest/schemas@28.1.3': - dependencies: - '@sinclair/typebox': 0.24.51 - - '@jest/source-map@27.5.1': - dependencies: - callsites: 3.1.0 - graceful-fs: 4.2.11 - source-map: 0.6.1 - - '@jest/test-result@27.5.1': - dependencies: - '@jest/console': 27.5.1 - '@jest/types': 27.5.1 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.3 - - '@jest/test-result@28.1.3': - dependencies: - '@jest/console': 28.1.3 - '@jest/types': 28.1.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.3 - - '@jest/test-sequencer@27.5.1': - dependencies: - '@jest/test-result': 27.5.1 - graceful-fs: 4.2.11 - jest-haste-map: 27.5.1 - jest-runtime: 27.5.1 - transitivePeerDependencies: - - supports-color - - '@jest/transform@27.5.1': - dependencies: - '@babel/core': 7.29.7 - '@jest/types': 27.5.1 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 1.9.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 27.5.1 - jest-regex-util: 27.5.1 - jest-util: 27.5.1 - micromatch: 4.0.8 - pirates: 4.0.7 - slash: 3.0.0 - source-map: 0.6.1 - write-file-atomic: 3.0.3 - transitivePeerDependencies: - - supports-color - - '@jest/types@27.5.1': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 14.18.63 - '@types/yargs': 16.0.11 - chalk: 4.1.2 - - '@jest/types@28.1.3': - dependencies: - '@jest/schemas': 28.1.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 14.18.63 - '@types/yargs': 17.0.35 - chalk: 4.1.2 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/source-map@0.3.11': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@jsonjoy.com/base64@17.67.0(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@jsonjoy.com/buffers@1.2.1(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@jsonjoy.com/buffers@17.67.0(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@jsonjoy.com/codegen@1.0.0(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@jsonjoy.com/codegen@17.67.0(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@jsonjoy.com/fs-core@4.79.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-node-builtins': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.79.0(tslib@2.8.1) - thingies: 2.6.1(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/fs-fsa@4.79.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-core': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.79.0(tslib@2.8.1) - thingies: 2.6.1(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/fs-node-builtins@4.79.0(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@jsonjoy.com/fs-node-to-fsa@4.79.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-fsa': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.79.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/fs-node-utils@4.79.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-node-builtins': 4.79.0(tslib@2.8.1) - glob-to-regex.js: 1.3.1(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/fs-node@4.79.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-core': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.79.0(tslib@2.8.1) - glob-to-regex.js: 1.3.1(tslib@2.8.1) - thingies: 2.6.1(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/fs-print@4.79.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-node-utils': 4.79.0(tslib@2.8.1) - tree-dump: 1.1.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/fs-snapshot@4.79.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/json-pack': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) - '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) - '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) - '@jsonjoy.com/json-pointer': 1.0.2(tslib@2.8.1) - '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) - hyperdyperid: 1.2.0 - thingies: 2.6.1(tslib@2.8.1) - tree-dump: 1.1.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/json-pack@17.67.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/base64': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/codegen': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/json-pointer': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) - hyperdyperid: 1.2.0 - thingies: 2.6.1(tslib@2.8.1) - tree-dump: 1.1.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/json-pointer@1.0.2(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) - '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/json-pointer@17.67.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/util@1.9.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) - '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/util@17.67.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/codegen': 17.67.0(tslib@2.8.1) - tslib: 2.8.1 - - '@leichtgewicht/ip-codec@2.0.5': {} - - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': - dependencies: - eslint-scope: 5.1.1 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@petamoriken/float16@3.9.3': {} - - '@pmmmwh/react-refresh-webpack-plugin@0.5.17(react-refresh@0.11.0)(type-fest@4.41.0)(webpack-dev-server@4.15.2(webpack@5.107.2(postcss@8.5.25)))(webpack@5.107.2(postcss@8.5.25))': - dependencies: - ansi-html: 0.0.9 - core-js-pure: 3.49.0 - error-stack-parser: 2.1.4 - html-entities: 2.6.0 - loader-utils: 2.0.4 - react-refresh: 0.11.0 - schema-utils: 4.3.3 - source-map: 0.7.6 - webpack: 5.107.2(postcss@8.5.25) - optionalDependencies: - type-fest: 4.41.0 - webpack-dev-server: 4.15.2(webpack@5.107.2(postcss@8.5.25)) - - '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@remix-run/router@1.23.4': {} - - '@rollup/plugin-babel@5.3.1(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@3.30.0)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@rollup/pluginutils': 3.1.0(rollup@3.30.0) - rollup: 3.30.0 - optionalDependencies: - '@types/babel__core': 7.20.5 - transitivePeerDependencies: - - supports-color - - '@rollup/plugin-node-resolve@11.2.1(rollup@3.30.0)': - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@3.30.0) - '@types/resolve': 1.17.1 - builtin-modules: 3.3.0 - deepmerge: 4.3.1 - is-module: 1.0.0 - resolve: 1.22.12 - rollup: 3.30.0 - - '@rollup/plugin-replace@2.4.2(rollup@3.30.0)': - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@3.30.0) - magic-string: 0.25.9 - rollup: 3.30.0 - - '@rollup/pluginutils@3.1.0(rollup@3.30.0)': - dependencies: - '@types/estree': 0.0.39 - estree-walker: 1.0.1 - picomatch: 2.3.2 - rollup: 3.30.0 - - '@rtsao/scc@1.1.0': {} - - '@rushstack/eslint-patch@1.16.1': {} - - '@sinclair/typebox@0.24.51': {} - - '@sinonjs/commons@1.8.6': - dependencies: - type-detect: 4.0.8 - - '@sinonjs/fake-timers@8.1.0': - dependencies: - '@sinonjs/commons': 1.8.6 - - '@surma/rollup-plugin-off-main-thread@2.2.3': - dependencies: - ejs: 3.1.10 - json5: 2.2.3 - magic-string: 0.25.9 - string.prototype.matchall: 4.0.12 - - '@svgr/babel-plugin-add-jsx-attribute@5.4.0': {} - - '@svgr/babel-plugin-remove-jsx-attribute@5.4.0': {} - - '@svgr/babel-plugin-remove-jsx-empty-expression@5.0.1': {} - - '@svgr/babel-plugin-replace-jsx-attribute-value@5.0.1': {} - - '@svgr/babel-plugin-svg-dynamic-title@5.4.0': {} - - '@svgr/babel-plugin-svg-em-dimensions@5.4.0': {} - - '@svgr/babel-plugin-transform-react-native-svg@5.4.0': {} - - '@svgr/babel-plugin-transform-svg-component@5.5.0': {} - - '@svgr/babel-preset@5.5.0': - dependencies: - '@svgr/babel-plugin-add-jsx-attribute': 5.4.0 - '@svgr/babel-plugin-remove-jsx-attribute': 5.4.0 - '@svgr/babel-plugin-remove-jsx-empty-expression': 5.0.1 - '@svgr/babel-plugin-replace-jsx-attribute-value': 5.0.1 - '@svgr/babel-plugin-svg-dynamic-title': 5.4.0 - '@svgr/babel-plugin-svg-em-dimensions': 5.4.0 - '@svgr/babel-plugin-transform-react-native-svg': 5.4.0 - '@svgr/babel-plugin-transform-svg-component': 5.5.0 - - '@svgr/core@5.5.0': - dependencies: - '@svgr/plugin-jsx': 5.5.0 - camelcase: 6.3.0 - cosmiconfig: 7.1.0 - transitivePeerDependencies: - - supports-color - - '@svgr/hast-util-to-babel-ast@5.5.0': - dependencies: - '@babel/types': 7.29.7 - - '@svgr/plugin-jsx@5.5.0': - dependencies: - '@babel/core': 7.29.7 - '@svgr/babel-preset': 5.5.0 - '@svgr/hast-util-to-babel-ast': 5.5.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - - '@svgr/plugin-svgo@5.5.0': - dependencies: - cosmiconfig: 7.1.0 - deepmerge: 4.3.1 - svgo: 2.8.4 - - '@svgr/webpack@5.5.0': - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-transform-react-constant-elements': 7.29.7(@babel/core@7.29.7) - '@babel/preset-env': 7.29.7(@babel/core@7.29.7) - '@babel/preset-react': 7.29.7(@babel/core@7.29.7) - '@svgr/core': 5.5.0 - '@svgr/plugin-jsx': 5.5.0 - '@svgr/plugin-svgo': 5.5.0 - loader-utils: 2.0.4 - transitivePeerDependencies: - - supports-color - - '@testing-library/dom@9.3.4': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/runtime': 7.29.7 - '@types/aria-query': 5.0.4 - aria-query: 5.1.3 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - - '@testing-library/jest-dom@5.17.0': - dependencies: - '@adobe/css-tools': 4.5.0 - '@babel/runtime': 7.29.7 - '@types/testing-library__jest-dom': 5.14.9 - aria-query: 5.3.2 - chalk: 3.0.0 - css.escape: 1.5.1 - dom-accessibility-api: 0.5.16 - lodash: 4.18.1 - redent: 3.0.0 - - '@testing-library/react@14.3.1(@types/react@18.3.30)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.7 - '@testing-library/dom': 9.3.4 - '@types/react-dom': 18.3.7(@types/react@18.3.30) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - '@testing-library/user-event@7.2.1(@testing-library/dom@9.3.4)': - dependencies: - '@testing-library/dom': 9.3.4 - - '@tootallnate/once@1.1.2': {} - - '@tsconfig/node10@1.0.12': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - - '@types/aria-query@5.0.4': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.29.7 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.29.7 - - '@types/body-parser@1.19.6': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 14.18.63 - - '@types/bonjour@3.5.13': - dependencies: - '@types/node': 14.18.63 - - '@types/connect-history-api-fallback@1.5.4': - dependencies: - '@types/express-serve-static-core': 5.1.1 - '@types/node': 14.18.63 - - '@types/connect@3.4.38': - dependencies: - '@types/node': 14.18.63 - - '@types/d3-dispatch@3.0.6': {} - - '@types/eslint@8.56.12': - dependencies: - '@types/estree': 1.0.9 - '@types/json-schema': 7.0.15 - - '@types/estree@0.0.39': {} - - '@types/estree@1.0.9': {} - - '@types/express-serve-static-core@4.19.8': - dependencies: - '@types/node': 14.18.63 - '@types/qs': 6.15.1 - '@types/range-parser': 1.2.7 - '@types/send': 1.2.1 - - '@types/express-serve-static-core@5.1.1': - dependencies: - '@types/node': 14.18.63 - '@types/qs': 6.15.1 - '@types/range-parser': 1.2.7 - '@types/send': 1.2.1 - - '@types/express@4.17.25': - dependencies: - '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.8 - '@types/qs': 6.15.1 - '@types/serve-static': 1.15.10 - - '@types/graceful-fs@4.1.9': - dependencies: - '@types/node': 14.18.63 - - '@types/history@4.7.11': {} - - '@types/html-minifier-terser@6.1.0': {} - - '@types/http-errors@2.0.5': {} - - '@types/http-proxy@1.17.17': - dependencies: - '@types/node': 14.18.63 - - '@types/istanbul-lib-coverage@2.0.6': {} - - '@types/istanbul-lib-report@3.0.3': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - - '@types/istanbul-reports@3.0.4': - dependencies: - '@types/istanbul-lib-report': 3.0.3 - - '@types/jest@28.1.8': - dependencies: - expect: 28.1.3 - pretty-format: 28.1.3 - - '@types/json-schema@7.0.15': {} - - '@types/json5@0.0.29': {} - - '@types/lodash@4.17.24': {} - - '@types/mime@1.3.5': {} - - '@types/node-forge@1.3.14': - dependencies: - '@types/node': 14.18.63 - - '@types/node@14.18.63': {} - - '@types/parse-json@4.0.2': {} - - '@types/prettier@2.7.3': {} - - '@types/prop-types@15.7.15': {} - - '@types/qs@6.15.1': {} - - '@types/range-parser@1.2.7': {} - - '@types/rbush@4.0.0': {} - - '@types/react-dom@18.3.7(@types/react@18.3.30)': - dependencies: - '@types/react': 18.3.30 - - '@types/react-router-dom@5.3.3': - dependencies: - '@types/history': 4.7.11 - '@types/react': 18.3.30 - '@types/react-router': 5.1.20 - - '@types/react-router@5.1.20': - dependencies: - '@types/history': 4.7.11 - '@types/react': 18.3.30 - - '@types/react@18.3.30': - dependencies: - '@types/prop-types': 15.7.15 - csstype: 3.2.3 - - '@types/resolve@1.17.1': - dependencies: - '@types/node': 14.18.63 - - '@types/retry@0.12.0': {} - - '@types/retry@0.12.5': {} - - '@types/semver@7.7.1': {} - - '@types/send@0.17.6': - dependencies: - '@types/mime': 1.3.5 - '@types/node': 14.18.63 - - '@types/send@1.2.1': - dependencies: - '@types/node': 14.18.63 - - '@types/serve-index@1.9.4': - dependencies: - '@types/express': 4.17.25 - - '@types/serve-static@1.15.10': - dependencies: - '@types/http-errors': 2.0.5 - '@types/node': 14.18.63 - '@types/send': 0.17.6 - - '@types/sockjs@0.3.36': - dependencies: - '@types/node': 14.18.63 - - '@types/stack-utils@2.0.3': {} - - '@types/testing-library__jest-dom@5.14.9': - dependencies: - '@types/jest': 28.1.8 - - '@types/trusted-types@2.0.7': {} - - '@types/uuid@8.3.4': {} - - '@types/ws@8.18.1': - dependencies: - '@types/node': 14.18.63 - - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@16.0.11': - dependencies: - '@types/yargs-parser': 21.0.3 - - '@types/yargs@17.0.35': - dependencies: - '@types/yargs-parser': 21.0.3 - - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - debug: 4.4.3 - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare-lite: 1.4.0 - semver: 7.5.2 - tsutils: 3.21.0(typescript@4.9.5) - optionalDependencies: - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)': - dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - eslint: 8.57.1 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5)': - dependencies: - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) - debug: 4.4.3 - eslint: 8.57.1 - optionalDependencies: - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@5.62.0': - dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 - - '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)': - dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - debug: 4.4.3 - eslint: 8.57.1 - tsutils: 3.21.0(typescript@4.9.5) - optionalDependencies: - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@5.62.0': {} - - '@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5)': - dependencies: - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.3 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.2 - tsutils: 3.21.0(typescript@4.9.5) - optionalDependencies: - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.7.1 - '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) - eslint: 8.57.1 - eslint-scope: 5.1.1 - semver: 7.5.2 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@5.62.0': - dependencies: - '@typescript-eslint/types': 5.62.0 - eslint-visitor-keys: 3.4.3 - - '@ungap/structured-clone@1.3.1': {} - - '@webassemblyjs/ast@1.14.1': - dependencies: - '@webassemblyjs/helper-numbers': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - - '@webassemblyjs/floating-point-hex-parser@1.13.2': {} - - '@webassemblyjs/helper-api-error@1.13.2': {} - - '@webassemblyjs/helper-buffer@1.14.1': {} - - '@webassemblyjs/helper-numbers@1.13.2': - dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.13.2 - '@webassemblyjs/helper-api-error': 1.13.2 - '@xtuc/long': 4.2.2 - - '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} - - '@webassemblyjs/helper-wasm-section@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/wasm-gen': 1.14.1 - - '@webassemblyjs/ieee754@1.13.2': - dependencies: - '@xtuc/ieee754': 1.2.0 - - '@webassemblyjs/leb128@1.13.2': - dependencies: - '@xtuc/long': 4.2.2 - - '@webassemblyjs/utf8@1.13.2': {} - - '@webassemblyjs/wasm-edit@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/helper-wasm-section': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-opt': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - '@webassemblyjs/wast-printer': 1.14.1 - - '@webassemblyjs/wasm-gen@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - - '@webassemblyjs/wasm-opt@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - - '@webassemblyjs/wasm-parser@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-api-error': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - - '@webassemblyjs/wast-printer@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@xtuc/long': 4.2.2 - - '@xtuc/ieee754@1.2.0': {} - - '@xtuc/long@4.2.2': {} - - '@zarrita/storage@0.2.0': - dependencies: - reference-spec-reader: 0.2.0 - unzipit: 2.0.0 - - abab@2.0.6: {} - - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - - acorn-globals@6.0.0: - dependencies: - acorn: 7.4.1 - acorn-walk: 7.2.0 - - acorn-import-phases@1.0.4(acorn@8.16.0): - dependencies: - acorn: 8.16.0 - - acorn-jsx@5.3.2(acorn@8.16.0): - dependencies: - acorn: 8.16.0 - - acorn-walk@7.2.0: {} - - acorn-walk@8.3.5: - dependencies: - acorn: 8.16.0 - - acorn@7.4.1: {} - - acorn@8.16.0: {} - - address@1.2.2: {} - - adjust-sourcemap-loader@4.0.0: - dependencies: - loader-utils: 2.0.4 - regex-parser: 2.3.1 - - adm-zip@0.6.1: {} - - agent-base@6.0.2: - dependencies: - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - ajv-formats@2.1.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv-keywords@3.5.2(ajv@6.12.6): - dependencies: - ajv: 6.12.6 - - ajv-keywords@5.1.0(ajv@8.20.0): - dependencies: - ajv: 8.20.0 - fast-deep-equal: 3.1.3 - - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ajv@8.20.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.8 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - - ansi-html-community@0.0.8: {} - - ansi-html@0.0.9: {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - antd@4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@ant-design/colors': 6.0.0 - '@ant-design/icons': 4.8.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@ant-design/react-slick': 1.0.2(react@18.3.1) - '@babel/runtime': 7.29.7 - '@ctrl/tinycolor': 3.6.1 - classnames: 2.5.1 - copy-to-clipboard: 3.3.3 - lodash: 4.18.1 - moment: 2.30.1 - rc-cascader: 3.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-checkbox: 3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-collapse: 3.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-dialog: 9.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-drawer: 6.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-dropdown: 4.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-field-form: 1.38.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-image: 5.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-input: 0.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-input-number: 7.3.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-mentions: 1.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-menu: 9.8.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-notification: 4.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-pagination: 3.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-picker: 2.7.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-progress: 3.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-rate: 2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-segmented: 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-select: 14.1.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-slider: 10.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-steps: 5.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-switch: 3.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-table: 7.26.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tabs: 12.5.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-textarea: 0.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tooltip: 5.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tree: 5.7.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tree-select: 5.5.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-trigger: 5.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-upload: 4.3.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - scroll-into-view-if-needed: 2.2.31 - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.2 - - arg@4.1.3: {} - - arg@5.0.2: {} - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - argparse@2.0.1: {} - - aria-query@5.1.3: - dependencies: - deep-equal: 2.2.3 - - aria-query@5.3.2: {} - - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 - - array-flatten@1.1.1: {} - - array-includes@3.1.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 - is-string: 1.1.1 - math-intrinsics: 1.1.0 - - array-tree-filter@2.1.0: {} - - array-union@1.0.2: - dependencies: - array-uniq: 1.0.3 - - array-union@2.1.0: {} - - array-uniq@1.0.3: {} - - array.prototype.findlast@1.2.5: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - es-shim-unscopables: 1.1.0 - - array.prototype.findlastindex@1.2.6: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - es-shim-unscopables: 1.1.0 - - array.prototype.flat@1.3.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-shim-unscopables: 1.1.0 - - array.prototype.flatmap@1.3.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-shim-unscopables: 1.1.0 - - array.prototype.tosorted@1.1.4: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-shim-unscopables: 1.1.0 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 - - asap@2.0.6: {} - - ast-types-flow@0.0.8: {} - - async-function@1.0.0: {} - - async-validator@4.2.5: {} - - async@3.2.6: {} - - asynckit@0.4.0: {} - - at-least-node@1.0.0: {} - - autoprefixer@10.5.0(postcss@8.5.25): - dependencies: - browserslist: 4.28.7 - caniuse-lite: 1.0.30001793 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - axe-core@4.12.0: {} - - axios@1.18.0: - dependencies: - follow-redirects: 1.16.0 - form-data: 4.0.6 - https-proxy-agent: 5.0.1 - proxy-from-env: 2.1.0 - transitivePeerDependencies: - - debug - - supports-color - - axobject-query@4.1.0: {} - - b4a@1.8.1: {} - - babel-jest@27.5.1(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@jest/transform': 27.5.1 - '@jest/types': 27.5.1 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 27.5.1(@babel/core@7.29.7) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - - babel-loader@8.4.1(@babel/core@7.29.7)(webpack@5.107.2(postcss@8.5.25)): - dependencies: - '@babel/core': 7.29.7 - find-cache-dir: 3.3.2 - loader-utils: 2.0.4 - make-dir: 3.1.0 - schema-utils: 2.7.1 - webpack: 5.107.2(postcss@8.5.25) - - babel-plugin-istanbul@6.1.1: - dependencies: - '@babel/helper-plugin-utils': 7.29.7 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.6 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-jest-hoist@27.5.1: - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.28.0 - - babel-plugin-macros@3.1.0: - dependencies: - '@babel/runtime': 7.29.7 - cosmiconfig: 7.1.0 - resolve: 1.22.12 - - babel-plugin-named-asset-import@0.3.8(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) - semver: 7.5.2 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) - core-js-compat: 3.49.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) - core-js-compat: 3.49.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - babel-plugin-transform-react-remove-prop-types@0.4.24: {} - - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) - - babel-preset-jest@27.5.1(@babel/core@7.29.7): - dependencies: - '@babel/core': 7.29.7 - babel-plugin-jest-hoist: 27.5.1 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) - - babel-preset-react-app@10.1.0: - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.7) - '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.7) - '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.29.7) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.29.7) - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.29.7) - '@babel/plugin-proposal-private-property-in-object': 7.21.11(@babel/core@7.29.7) - '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) - '@babel/preset-env': 7.29.7(@babel/core@7.29.7) - '@babel/preset-react': 7.29.7(@babel/core@7.29.7) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) - '@babel/runtime': 7.29.7 - babel-plugin-macros: 3.1.0 - babel-plugin-transform-react-remove-prop-types: 0.4.24 - transitivePeerDependencies: - - supports-color - - balanced-match@1.0.2: {} - - bare-events@2.9.1: {} - - bare-fs@4.7.2: - dependencies: - bare-events: 2.9.1 - bare-path: 3.0.1 - bare-stream: 2.13.1(bare-events@2.9.1) - bare-url: 2.4.5 - fast-fifo: 1.3.2 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - bare-os@3.9.1: {} - - bare-path@3.0.1: - dependencies: - bare-os: 3.9.1 - - bare-stream@2.13.1(bare-events@2.9.1): - dependencies: - streamx: 2.26.0 - teex: 1.0.1 - optionalDependencies: - bare-events: 2.9.1 - transitivePeerDependencies: - - react-native-b4a - - bare-url@2.4.5: - dependencies: - bare-path: 3.0.1 - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.11.26: {} - - batch@0.6.1: {} - - bfj@7.1.0: - dependencies: - bluebird: 3.7.2 - check-types: 11.2.3 - hoopy: 0.1.4 - jsonpath: 1.3.0 - tryer: 1.0.1 - - big.js@5.2.2: {} - - binary-extensions@2.3.0: {} - - bluebird@3.7.2: {} - - body-parser@1.20.3: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - bonjour-service@1.4.0: - dependencies: - fast-deep-equal: 3.1.3 - multicast-dns: 7.2.5 - - boolbase@1.0.0: {} - - brace-expansion@1.1.21: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.1.7: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browser-process-hrtime@1.0.0: {} - - browserslist@4.28.7: - dependencies: - baseline-browser-mapping: 2.11.26 - caniuse-lite: 1.0.30001812 - electron-to-chromium: 1.5.439 - node-releases: 2.0.57 - update-browserslist-db: 1.2.3(browserslist@4.28.7) - - bser@2.1.1: - dependencies: - node-int64: 0.4.0 - - buffer-from@1.1.2: {} - - builtin-modules@3.3.0: {} - - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.9: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - callsites@3.1.0: {} - - camel-case@4.1.2: - dependencies: - pascal-case: 3.1.2 - tslib: 2.8.1 - - camelcase-css@2.0.1: {} - - camelcase@5.3.1: {} - - camelcase@6.3.0: {} - - caniuse-api@3.0.0: - dependencies: - browserslist: 4.28.7 - caniuse-lite: 1.0.30001793 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - - caniuse-lite@1.0.30001793: {} - - caniuse-lite@1.0.30001812: {} - - case-sensitive-paths-webpack-plugin@2.4.0: {} - - chalk@3.0.0: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - char-regex@1.0.2: {} - - char-regex@2.0.2: {} - - check-types@11.2.3: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chrome-trace-event@1.0.4: {} - - ci-info@3.9.0: {} - - cjs-module-lexer@1.4.3: {} - - classnames@2.5.1: {} - - clean-css@5.3.3: - dependencies: - source-map: 0.6.1 - - cliui@7.0.4: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - co@4.6.0: {} - - collect-v8-coverage@1.0.3: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colord@2.9.3: {} - - colorette@2.0.20: {} - - colormap@2.3.2: - dependencies: - lerp: 1.0.3 - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - commander@14.0.3: {} - - commander@2.20.3: {} - - commander@4.1.1: {} - - commander@7.2.0: {} - - commander@8.3.0: {} - - common-tags@1.8.2: {} - - commondir@1.0.1: {} - - complex.js@2.4.3: {} - - compressible@2.0.18: - dependencies: - mime-db: 1.54.0 - - compression@1.8.1: - dependencies: - bytes: 3.1.2 - compressible: 2.0.18 - debug: 2.6.9 - negotiator: 0.6.4 - on-headers: 1.1.0 - safe-buffer: 5.2.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - compute-scroll-into-view@1.0.20: {} - - concat-map@0.0.1: {} - - confusing-browser-globals@1.0.11: {} - - connect-history-api-fallback@2.0.0: {} - - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - - convert-source-map@1.9.0: {} - - convert-source-map@2.0.0: {} - - cookie-signature@1.0.6: {} - - cookie@0.6.0: {} - - copy-anything@3.0.5: - dependencies: - is-what: 4.1.16 - - copy-to-clipboard@3.3.3: - dependencies: - toggle-selection: 1.0.6 - - copy-webpack-plugin@9.1.0(webpack@5.107.2(postcss@8.5.25)): - dependencies: - fast-glob: 3.3.3 - glob-parent: 6.0.2 - globby: 11.1.0 - normalize-path: 3.0.0 - schema-utils: 3.3.0 - serialize-javascript: 7.0.5 - webpack: 5.107.2(postcss@8.5.25) - - core-js-compat@3.49.0: - dependencies: - browserslist: 4.28.7 - - core-js-pure@3.49.0: {} - - core-js@3.49.0: {} - - core-util-is@1.0.3: {} - - cosmiconfig-typescript-loader@1.0.9(@types/node@14.18.63)(cosmiconfig@7.1.0)(typescript@4.9.5): - dependencies: - '@types/node': 14.18.63 - cosmiconfig: 7.1.0 - ts-node: 10.9.2(@types/node@14.18.63)(typescript@4.9.5) - typescript: 4.9.5 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - - cosmiconfig@6.0.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.3 - - cosmiconfig@7.1.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.3 - - craco-less@2.0.0(@craco/craco@6.4.5(@types/node@14.18.63)(react-scripts@5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5))(typescript@4.9.5))(react-scripts@5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5))(webpack@5.107.2(postcss@8.5.25)): - dependencies: - '@craco/craco': 6.4.5(@types/node@14.18.63)(react-scripts@5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5))(typescript@4.9.5) - less: 4.6.4 - less-loader: 7.3.0(less@4.6.4)(webpack@5.107.2(postcss@8.5.25)) - react-scripts: 5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5) - transitivePeerDependencies: - - webpack - - create-require@1.1.1: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - crypto-js@4.2.0: {} - - crypto-random-string@2.0.0: {} - - css-blank-pseudo@3.0.3(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 - - css-declaration-sorter@6.4.1(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - - css-has-pseudo@3.0.4(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 - - css-loader@6.11.0(webpack@5.107.2(postcss@8.5.25)): - dependencies: - icss-utils: 5.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.25) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.25) - postcss-modules-scope: 3.2.1(postcss@8.5.25) - postcss-modules-values: 4.0.0(postcss@8.5.25) - postcss-value-parser: 4.2.0 - semver: 7.5.2 - optionalDependencies: - webpack: 5.107.2(postcss@8.5.25) - - css-minimizer-webpack-plugin@3.4.1(webpack@5.107.2(postcss@8.5.25)): - dependencies: - cssnano: 5.1.15(postcss@8.5.25) - jest-worker: 27.5.1 - postcss: 8.5.25 - schema-utils: 4.3.3 - serialize-javascript: 7.0.5 - source-map: 0.6.1 - webpack: 5.107.2(postcss@8.5.25) - - css-prefers-color-scheme@6.0.3(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - - css-select@4.3.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.0.1 - - css-tree@1.1.3: - dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 - - css-what@6.2.2: {} - - css.escape@1.5.1: {} - - cssdb@7.11.2: {} - - cssesc@3.0.0: {} - - cssnano-preset-default@5.2.14(postcss@8.5.25): - dependencies: - css-declaration-sorter: 6.4.1(postcss@8.5.25) - cssnano-utils: 3.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-calc: 8.2.4(postcss@8.5.25) - postcss-colormin: 5.3.1(postcss@8.5.25) - postcss-convert-values: 5.1.3(postcss@8.5.25) - postcss-discard-comments: 5.1.2(postcss@8.5.25) - postcss-discard-duplicates: 5.1.0(postcss@8.5.25) - postcss-discard-empty: 5.1.1(postcss@8.5.25) - postcss-discard-overridden: 5.1.0(postcss@8.5.25) - postcss-merge-longhand: 5.1.7(postcss@8.5.25) - postcss-merge-rules: 5.1.4(postcss@8.5.25) - postcss-minify-font-values: 5.1.0(postcss@8.5.25) - postcss-minify-gradients: 5.1.1(postcss@8.5.25) - postcss-minify-params: 5.1.4(postcss@8.5.25) - postcss-minify-selectors: 5.2.1(postcss@8.5.25) - postcss-normalize-charset: 5.1.0(postcss@8.5.25) - postcss-normalize-display-values: 5.1.0(postcss@8.5.25) - postcss-normalize-positions: 5.1.1(postcss@8.5.25) - postcss-normalize-repeat-style: 5.1.1(postcss@8.5.25) - postcss-normalize-string: 5.1.0(postcss@8.5.25) - postcss-normalize-timing-functions: 5.1.0(postcss@8.5.25) - postcss-normalize-unicode: 5.1.1(postcss@8.5.25) - postcss-normalize-url: 5.1.0(postcss@8.5.25) - postcss-normalize-whitespace: 5.1.1(postcss@8.5.25) - postcss-ordered-values: 5.1.3(postcss@8.5.25) - postcss-reduce-initial: 5.1.2(postcss@8.5.25) - postcss-reduce-transforms: 5.1.0(postcss@8.5.25) - postcss-svgo: 5.1.0(postcss@8.5.25) - postcss-unique-selectors: 5.1.1(postcss@8.5.25) - - cssnano-utils@3.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - - cssnano@5.1.15(postcss@8.5.25): - dependencies: - cssnano-preset-default: 5.2.14(postcss@8.5.25) - lilconfig: 2.1.0 - postcss: 8.5.25 - yaml: 1.10.3 - - csso@4.2.0: - dependencies: - css-tree: 1.1.3 - - cssom@0.3.8: {} - - cssom@0.4.4: {} - - cssstyle@2.3.0: - dependencies: - cssom: 0.3.8 - - csstype@3.2.3: {} - - damerau-levenshtein@1.0.8: {} - - data-urls@2.0.0: - dependencies: - abab: 2.0.6 - whatwg-mimetype: 2.3.0 - whatwg-url: 8.7.0 - - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-offset@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - date-fns@2.30.0: - dependencies: - '@babel/runtime': 7.29.7 - - dayjs@1.11.21: {} - - dcmjs@0.35.0: - dependencies: - '@babel/runtime-corejs3': 7.29.7 - adm-zip: 0.6.1 - gl-matrix: 3.4.4 - lodash.clonedeep: 4.5.0 - loglevel: 1.9.2 - ndarray: 1.0.19 - pako: 2.1.0 - - dcmjs@0.41.0: - dependencies: - '@babel/runtime-corejs3': 7.29.7 - adm-zip: 0.6.1 - gl-matrix: 3.4.4 - lodash.clonedeep: 4.5.0 - loglevel: 1.9.2 - ndarray: 1.0.19 - pako: 2.1.0 - - debug@2.6.9: - dependencies: - ms: 2.0.0 - - debug@3.2.7: - dependencies: - ms: 2.1.3 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decimal.js@10.6.0: {} - - dedent@0.7.0: {} - - deep-equal@2.2.3: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.9 - es-get-iterator: 1.1.3 - get-intrinsic: 1.3.0 - is-arguments: 1.2.0 - is-array-buffer: 3.0.5 - is-date-object: 1.1.0 - is-regex: 1.2.1 - is-shared-array-buffer: 1.0.4 - isarray: 2.0.5 - object-is: 1.1.6 - object-keys: 1.1.1 - object.assign: 4.1.7 - regexp.prototype.flags: 1.5.4 - side-channel: 1.1.0 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.21 - - deep-is@0.1.4: {} - - deepmerge@4.3.1: {} - - default-gateway@6.0.3: - dependencies: - execa: 5.1.1 - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-lazy-prop@2.0.0: {} - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - delayed-stream@1.0.0: {} - - depd@1.1.2: {} - - depd@2.0.0: {} - - destroy@1.2.0: {} - - detect-browser@5.3.0: {} - - detect-newline@3.1.0: {} - - detect-node@2.1.0: {} - - detect-port-alt@1.1.6: - dependencies: - address: 1.2.2 - debug: 2.6.9 - transitivePeerDependencies: - - supports-color - - dicom-microscopy-viewer@0.48.27: - dependencies: - '@cornerstonejs/codec-charls': 1.2.3 - '@cornerstonejs/codec-libjpeg-turbo-8bit': 1.2.2 - '@cornerstonejs/codec-openjpeg': 1.3.0 - '@cornerstonejs/codec-openjph': 2.4.7 - '@imagingdatacommons/dicomicc': 0.2.3 - colormap: 2.3.2 - dcmjs: 0.41.0 - dicomweb-client: 0.11.3 - image-type: 4.1.0 - mathjs: 11.12.0 - ol: 10.9.0 - uuid: 9.0.1 - - dicomweb-client@0.10.3: {} - - dicomweb-client@0.11.3: {} - - didyoumean@1.2.2: {} - - diff-sequences@27.5.1: {} - - diff-sequences@28.1.1: {} - - diff@4.0.4: {} - - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - - dlv@1.1.3: {} - - dns-packet@5.6.1: - dependencies: - '@leichtgewicht/ip-codec': 2.0.5 - - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 - - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - - dom-accessibility-api@0.5.16: {} - - dom-align@1.12.4: {} - - dom-converter@0.2.0: - dependencies: - utila: 0.4.0 - - dom-serializer@1.4.1: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - - domelementtype@2.3.0: {} - - domexception@2.0.1: - dependencies: - webidl-conversions: 5.0.0 - - domhandler@4.3.1: - dependencies: - domelementtype: 2.3.0 - - domutils@2.8.0: - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - - dot-case@3.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - - dotenv-expand@5.1.0: {} - - dotenv@10.0.0: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - duplexer@0.1.2: {} - - earcut@3.0.2: {} - - ee-first@1.1.1: {} - - ejs@3.1.10: - dependencies: - jake: 10.9.4 - - electron-to-chromium@1.5.439: {} - - email-addresses@5.0.0: {} - - emittery@0.10.2: {} - - emittery@0.8.1: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - emojis-list@3.0.0: {} - - encodeurl@1.0.2: {} - - encodeurl@2.0.0: {} - - enhanced-resolve@5.22.2: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - entities@2.2.0: {} - - errno@0.1.8: - dependencies: - prr: 1.0.1 - optional: true - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - error-stack-parser@2.1.4: - dependencies: - stackframe: 1.3.4 - - es-abstract@1.24.2: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.4 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.4 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.8 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.21 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-get-iterator@1.1.3: - dependencies: - call-bind: 1.0.9 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - is-arguments: 1.2.0 - is-map: 2.0.3 - is-set: 2.0.3 - is-string: 1.1.1 - isarray: 2.0.5 - stop-iteration-iterator: 1.1.0 - - es-iterator-helpers@1.3.2: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-set-tostringtag: 2.1.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - iterator.prototype: 1.1.5 - math-intrinsics: 1.1.0 - - es-module-lexer@2.1.0: {} - - es-object-atoms@1.1.2: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.4 - - es-shim-unscopables@1.1.0: - dependencies: - hasown: 2.0.4 - - es-to-primitive@1.3.0: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - - escalade@3.2.0: {} - - escape-html@1.0.3: {} - - escape-latex@1.2.0: {} - - escape-string-regexp@1.0.5: {} - - escape-string-regexp@2.0.0: {} - - escape-string-regexp@4.0.0: {} - - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)))(typescript@4.9.5): - dependencies: - '@babel/core': 7.29.7 - '@babel/eslint-parser': 7.29.7(@babel/core@7.29.7)(eslint@8.57.1) - '@rushstack/eslint-patch': 1.16.1 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5) - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - babel-preset-react-app: 10.1.0 - confusing-browser-globals: 1.0.11 - eslint: 8.57.1 - eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1) - eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)))(typescript@4.9.5) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-react: 7.37.5(eslint@8.57.1) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) - eslint-plugin-testing-library: 5.11.1(eslint@8.57.1)(typescript@4.9.5) - optionalDependencies: - typescript: 4.9.5 - transitivePeerDependencies: - - '@babel/plugin-syntax-flow' - - '@babel/plugin-transform-react-jsx' - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - jest - - supports-color - - eslint-import-resolver-node@0.3.10: - dependencies: - debug: 3.2.7 - is-core-module: 2.16.2 - resolve: 2.0.0-next.7 - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.13.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.10)(eslint@8.57.1): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.10 - transitivePeerDependencies: - - supports-color - - eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(eslint@8.57.1): - dependencies: - '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) - eslint: 8.57.1 - lodash: 4.18.1 - string-natural-compare: 3.0.1 - - eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.10)(eslint@8.57.1) - hasown: 2.0.4 - is-core-module: 2.16.2 - is-glob: 4.0.3 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 7.5.2 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)))(typescript@4.9.5): - dependencies: - '@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - eslint: 8.57.1 - optionalDependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5) - jest: 27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)) - transitivePeerDependencies: - - supports-color - - typescript - - eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): - dependencies: - aria-query: 5.3.2 - array-includes: 3.1.9 - array.prototype.flatmap: 1.3.3 - ast-types-flow: 0.0.8 - axe-core: 4.12.0 - axobject-query: 4.1.0 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 8.57.1 - hasown: 2.0.4 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - safe-regex-test: 1.1.0 - string.prototype.includes: 2.0.1 - - eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - - eslint-plugin-react@7.37.5(eslint@8.57.1): - dependencies: - array-includes: 3.1.9 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.3 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.3.2 - eslint: 8.57.1 - estraverse: 5.3.0 - hasown: 2.0.4 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.5 - object.entries: 1.1.9 - object.fromentries: 2.0.8 - object.values: 1.2.1 - prop-types: 15.8.1 - resolve: 2.0.0-next.7 - semver: 7.5.2 - string.prototype.matchall: 4.0.12 - string.prototype.repeat: 1.0.0 - - eslint-plugin-sonarjs@0.25.1(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - - eslint-plugin-testing-library@5.11.1(eslint@8.57.1)(typescript@4.9.5): - dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5) - eslint: 8.57.1 - transitivePeerDependencies: - - supports-color - - typescript - - eslint-scope@5.1.1: - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@2.1.0: {} - - eslint-visitor-keys@3.4.3: {} - - eslint-webpack-plugin@3.2.0(eslint@8.57.1)(webpack@5.107.2(postcss@8.5.25)): - dependencies: - '@types/eslint': 8.56.12 - eslint: 8.57.1 - jest-worker: 28.1.3 - micromatch: 4.0.8 - normalize-path: 3.0.0 - schema-utils: 4.3.3 - webpack: 5.107.2(postcss@8.5.25) - - eslint@8.57.1: - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.1 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.3.2 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - espree@9.6.1: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 3.4.3 - - esprima@1.2.5: {} - - esprima@4.0.1: {} - - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@4.3.0: {} - - estraverse@5.3.0: {} - - estree-walker@1.0.1: {} - - esutils@2.0.3: {} - - etag@1.8.1: {} - - eventemitter3@4.0.7: {} - - events-universal@1.0.1: - dependencies: - bare-events: 2.9.1 - transitivePeerDependencies: - - bare-abort-controller - - events@3.3.0: {} - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - exit@0.1.2: {} - - expect@27.5.1: - dependencies: - '@jest/types': 27.5.1 - jest-get-type: 27.5.1 - jest-matcher-utils: 27.5.1 - jest-message-util: 27.5.1 - - expect@28.1.3: - dependencies: - '@jest/expect-utils': 28.1.3 - jest-get-type: 28.0.2 - jest-matcher-utils: 28.1.3 - jest-message-util: 28.1.3 - jest-util: 28.1.3 - - express@4.21.0: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.6.0 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.13 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - fast-deep-equal@3.1.3: {} - - fast-fifo@1.3.2: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fast-uri@3.1.8: {} - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - faye-websocket@0.11.4: - dependencies: - websocket-driver: 0.7.5 - - fb-watchman@2.0.2: - dependencies: - bser: 2.1.1 - - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - fflate@0.8.3: {} - - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - - file-loader@6.2.0(webpack@5.107.2(postcss@8.5.25)): - dependencies: - loader-utils: 2.0.4 - schema-utils: 3.3.0 - webpack: 5.107.2(postcss@8.5.25) - - file-type@10.11.0: {} - - filelist@1.0.6: - dependencies: - minimatch: 5.1.9 - - filename-reserved-regex@2.0.0: {} - - filenamify@4.3.0: - dependencies: - filename-reserved-regex: 2.0.0 - strip-outer: 1.0.1 - trim-repeated: 1.0.0 - - filesize@8.0.7: {} - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - finalhandler@1.3.1: - dependencies: - debug: 2.6.9 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - find-cache-dir@3.3.2: - dependencies: - commondir: 1.0.1 - make-dir: 3.1.0 - pkg-dir: 4.2.0 - - find-up@3.0.0: - dependencies: - locate-path: 3.0.0 - - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@3.2.0: - dependencies: - flatted: 3.4.2 - keyv: 4.5.4 - rimraf: 3.0.2 - - flatted@3.4.2: {} - - follow-redirects@1.16.0: {} - - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - - fork-ts-checker-webpack-plugin@6.5.3(eslint@8.57.1)(typescript@4.9.5)(webpack@5.107.2(postcss@8.5.25)): - dependencies: - '@babel/code-frame': 7.29.7 - '@types/json-schema': 7.0.15 - chalk: 4.1.2 - chokidar: 3.6.0 - cosmiconfig: 6.0.0 - deepmerge: 4.3.1 - fs-extra: 9.1.0 - glob: 7.2.3 - memfs: 3.5.3 - minimatch: 3.1.5 - schema-utils: 2.7.0 - semver: 7.5.2 - tapable: 1.1.3 - typescript: 4.9.5 - webpack: 5.107.2(postcss@8.5.25) - optionalDependencies: - eslint: 8.57.1 - - form-data@3.0.5: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.4 - mime-types: 2.1.35 - - form-data@4.0.6: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.4 - mime-types: 2.1.35 - - forwarded@0.2.0: {} - - fraction.js@4.3.4: {} - - fraction.js@4.3.7: {} - - fraction.js@5.3.4: {} - - fresh@0.5.2: {} - - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - - fs-extra@8.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs-extra@9.1.0: - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - - fs-monkey@1.1.0: {} - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - function.prototype.name@1.1.8: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.4 - is-callable: 1.2.7 - - functions-have-names@1.2.3: {} - - generator-function@2.0.1: {} - - gensync@1.0.0-beta.2: {} - - geotiff@3.0.5: - dependencies: - '@petamoriken/float16': 3.9.3 - lerc: 3.0.0 - pako: 2.1.0 - parse-headers: 2.0.6 - quick-lru: 6.1.2 - web-worker: 1.5.0 - xml-utils: 1.10.2 - zstddec: 0.2.0 - - get-caller-file@2.0.5: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.4 - math-intrinsics: 1.1.0 - - get-own-enumerable-property-symbols@3.0.2: {} - - get-package-type@0.1.0: {} - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 - - get-stream@6.0.1: {} - - get-symbol-description@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - - gh-pages@5.0.0: - dependencies: - async: 3.2.6 - commander: 2.20.3 - email-addresses: 5.0.0 - filenamify: 4.3.0 - find-cache-dir: 3.3.2 - fs-extra: 8.1.0 - globby: 6.1.0 - - gl-matrix@3.4.4: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob-to-regex.js@1.3.1(tslib@2.8.1): - dependencies: - tslib: 2.8.1 - - glob-to-regexp@0.4.1: {} - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - - global-modules@2.0.0: - dependencies: - global-prefix: 3.0.0 - - global-prefix@3.0.0: - dependencies: - ini: 1.3.8 - kind-of: 6.0.3 - which: 1.3.1 - - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - - globby@6.1.0: - dependencies: - array-union: 1.0.2 - glob: 7.2.3 - object-assign: 4.1.1 - pify: 2.3.0 - pinkie-promise: 2.0.1 - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - - gzip-size@6.0.0: - dependencies: - duplexer: 0.1.2 - - handle-thing@2.0.1: {} - - harmony-reflect@1.6.2: {} - - has-bigints@1.1.0: {} - - has-flag@4.0.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.4: - dependencies: - function-bind: 1.1.2 - - he@1.2.0: {} - - hoopy@0.1.4: {} - - hpack.js@2.1.6: - dependencies: - inherits: 2.0.4 - obuf: 1.1.2 - readable-stream: 2.3.8 - wbuf: 1.7.3 - - hpagent@1.2.0: {} - - html-encoding-sniffer@2.0.1: - dependencies: - whatwg-encoding: 1.0.5 - - html-entities@2.6.0: {} - - html-escaper@2.0.2: {} - - html-minifier-terser@6.1.0: - dependencies: - camel-case: 4.1.2 - clean-css: 5.3.3 - commander: 8.3.0 - he: 1.2.0 - param-case: 3.0.4 - relateurl: 0.2.7 - terser: 5.48.0 - - html-webpack-plugin@5.6.7(webpack@5.107.2(postcss@8.5.25)): - dependencies: - '@types/html-minifier-terser': 6.1.0 - html-minifier-terser: 6.1.0 - lodash: 4.18.1 - pretty-error: 4.0.0 - tapable: 2.3.3 - optionalDependencies: - webpack: 5.107.2(postcss@8.5.25) - - htmlparser2@6.1.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - domutils: 2.8.0 - entities: 2.2.0 - - http-deceiver@1.2.7: {} - - http-errors@1.8.1: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 1.5.0 - toidentifier: 1.0.1 - - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - - http-parser-js@0.5.10: {} - - http-proxy-agent@4.0.1: - dependencies: - '@tootallnate/once': 1.1.2 - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - http-proxy-middleware@2.0.9(@types/express@4.17.25): - dependencies: - '@types/http-proxy': 1.17.17 - http-proxy: 1.18.1 - is-glob: 4.0.3 - is-plain-obj: 3.0.0 - micromatch: 4.0.8 - optionalDependencies: - '@types/express': 4.17.25 - transitivePeerDependencies: - - debug - - http-proxy@1.18.1: - dependencies: - eventemitter3: 4.0.7 - follow-redirects: 1.16.0 - requires-port: 1.0.0 - transitivePeerDependencies: - - debug - - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - human-signals@2.1.0: {} - - husky@9.1.7: {} - - hyperdyperid@1.2.0: {} - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - icss-utils@5.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - - idb@7.1.1: {} - - identity-obj-proxy@3.0.0: - dependencies: - harmony-reflect: 1.6.2 - - ignore@5.3.2: {} - - image-size@0.5.5: - optional: true - - image-type@4.1.0: - dependencies: - file-type: 10.11.0 - - immer@9.0.21: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - import-local@3.2.0: - dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 - - imurmurhash@0.1.4: {} - - indent-string@4.0.0: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - ini@1.3.8: {} - - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.4 - side-channel: 1.1.0 - - iota-array@1.0.0: {} - - ipaddr.js@1.9.1: {} - - ipaddr.js@2.4.0: {} - - is-arguments@1.2.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-arrayish@0.2.1: {} - - is-async-function@2.1.1: - dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-boolean-object@1.2.2: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-buffer@1.1.6: {} - - is-callable@1.2.7: {} - - is-core-module@2.16.2: - dependencies: - hasown: 2.0.4 - - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 - - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-docker@2.2.1: {} - - is-extglob@2.1.1: {} - - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-fullwidth-code-point@3.0.0: {} - - is-generator-fn@2.1.0: {} - - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-map@2.0.3: {} - - is-module@1.0.0: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-number@7.0.0: {} - - is-obj@1.0.1: {} - - is-path-inside@3.0.3: {} - - is-plain-obj@3.0.0: {} - - is-potential-custom-element-name@1.0.1: {} - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.4 - - is-regexp@1.0.0: {} - - is-root@2.1.0: {} - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 - - is-stream@2.0.1: {} - - is-string@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.21 - - is-typedarray@1.0.0: {} - - is-weakmap@2.0.2: {} - - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-what@4.1.16: {} - - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isexe@2.0.0: {} - - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-instrument@5.2.1: - dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 - '@istanbuljs/schema': 0.1.6 - istanbul-lib-coverage: 3.2.2 - semver: 7.5.2 - transitivePeerDependencies: - - supports-color - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 3.1.0 - supports-color: 7.2.0 - - istanbul-lib-source-maps@4.0.1: - dependencies: - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.2.0: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - - iterator.prototype@1.1.5: - dependencies: - define-data-property: 1.1.4 - es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - has-symbols: 1.1.0 - set-function-name: 2.0.2 - - jake@10.9.4: - dependencies: - async: 3.2.6 - filelist: 1.0.6 - picocolors: 1.1.1 - - javascript-natural-sort@0.7.1: {} - - jest-changed-files@27.5.1: - dependencies: - '@jest/types': 27.5.1 - execa: 5.1.1 - throat: 6.0.2 - - jest-circus@27.5.1: - dependencies: - '@jest/environment': 27.5.1 - '@jest/test-result': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - chalk: 4.1.2 - co: 4.6.0 - dedent: 0.7.0 - expect: 27.5.1 - is-generator-fn: 2.1.0 - jest-each: 27.5.1 - jest-matcher-utils: 27.5.1 - jest-message-util: 27.5.1 - jest-runtime: 27.5.1 - jest-snapshot: 27.5.1 - jest-util: 27.5.1 - pretty-format: 27.5.1 - slash: 3.0.0 - stack-utils: 2.0.6 - throat: 6.0.2 - transitivePeerDependencies: - - supports-color - - jest-cli@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)): - dependencies: - '@jest/core': 27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)) - '@jest/test-result': 27.5.1 - '@jest/types': 27.5.1 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - import-local: 3.2.0 - jest-config: 27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)) - jest-util: 27.5.1 - jest-validate: 27.5.1 - prompts: 2.4.2 - yargs: 16.2.0 - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - ts-node - - utf-8-validate - - jest-config@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)): - dependencies: - '@babel/core': 7.29.7 - '@jest/test-sequencer': 27.5.1 - '@jest/types': 27.5.1 - babel-jest: 27.5.1(@babel/core@7.29.7) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 27.5.1 - jest-environment-jsdom: 27.5.1 - jest-environment-node: 27.5.1 - jest-get-type: 27.5.1 - jest-jasmine2: 27.5.1 - jest-regex-util: 27.5.1 - jest-resolve: 27.5.1 - jest-runner: 27.5.1 - jest-util: 27.5.1 - jest-validate: 27.5.1 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 27.5.1 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - ts-node: 10.9.2(@types/node@14.18.63)(typescript@4.9.5) - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - utf-8-validate - - jest-diff@27.5.1: - dependencies: - chalk: 4.1.2 - diff-sequences: 27.5.1 - jest-get-type: 27.5.1 - pretty-format: 27.5.1 - - jest-diff@28.1.3: - dependencies: - chalk: 4.1.2 - diff-sequences: 28.1.1 - jest-get-type: 28.0.2 - pretty-format: 28.1.3 - - jest-docblock@27.5.1: - dependencies: - detect-newline: 3.1.0 - - jest-each@27.5.1: - dependencies: - '@jest/types': 27.5.1 - chalk: 4.1.2 - jest-get-type: 27.5.1 - jest-util: 27.5.1 - pretty-format: 27.5.1 - - jest-environment-jsdom@27.5.1: - dependencies: - '@jest/environment': 27.5.1 - '@jest/fake-timers': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - jest-mock: 27.5.1 - jest-util: 27.5.1 - jsdom: 16.7.0 - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - utf-8-validate - - jest-environment-node@27.5.1: - dependencies: - '@jest/environment': 27.5.1 - '@jest/fake-timers': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - jest-mock: 27.5.1 - jest-util: 27.5.1 - - jest-get-type@27.5.1: {} - - jest-get-type@28.0.2: {} - - jest-haste-map@27.5.1: - dependencies: - '@jest/types': 27.5.1 - '@types/graceful-fs': 4.1.9 - '@types/node': 14.18.63 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 27.5.1 - jest-serializer: 27.5.1 - jest-util: 27.5.1 - jest-worker: 27.5.1 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 - - jest-jasmine2@27.5.1: - dependencies: - '@jest/environment': 27.5.1 - '@jest/source-map': 27.5.1 - '@jest/test-result': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - chalk: 4.1.2 - co: 4.6.0 - expect: 27.5.1 - is-generator-fn: 2.1.0 - jest-each: 27.5.1 - jest-matcher-utils: 27.5.1 - jest-message-util: 27.5.1 - jest-runtime: 27.5.1 - jest-snapshot: 27.5.1 - jest-util: 27.5.1 - pretty-format: 27.5.1 - throat: 6.0.2 - transitivePeerDependencies: - - supports-color - - jest-leak-detector@27.5.1: - dependencies: - jest-get-type: 27.5.1 - pretty-format: 27.5.1 - - jest-matcher-utils@27.5.1: - dependencies: - chalk: 4.1.2 - jest-diff: 27.5.1 - jest-get-type: 27.5.1 - pretty-format: 27.5.1 - - jest-matcher-utils@28.1.3: - dependencies: - chalk: 4.1.2 - jest-diff: 28.1.3 - jest-get-type: 28.0.2 - pretty-format: 28.1.3 - - jest-message-util@27.5.1: - dependencies: - '@babel/code-frame': 7.29.7 - '@jest/types': 27.5.1 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 27.5.1 - slash: 3.0.0 - stack-utils: 2.0.6 - - jest-message-util@28.1.3: - dependencies: - '@babel/code-frame': 7.29.7 - '@jest/types': 28.1.3 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 28.1.3 - slash: 3.0.0 - stack-utils: 2.0.6 - - jest-mock@27.5.1: - dependencies: - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - - jest-pnp-resolver@1.2.3(jest-resolve@27.5.1): - optionalDependencies: - jest-resolve: 27.5.1 - - jest-regex-util@27.5.1: {} - - jest-regex-util@28.0.2: {} - - jest-resolve-dependencies@27.5.1: - dependencies: - '@jest/types': 27.5.1 - jest-regex-util: 27.5.1 - jest-snapshot: 27.5.1 - transitivePeerDependencies: - - supports-color - - jest-resolve@27.5.1: - dependencies: - '@jest/types': 27.5.1 - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 27.5.1 - jest-pnp-resolver: 1.2.3(jest-resolve@27.5.1) - jest-util: 27.5.1 - jest-validate: 27.5.1 - resolve: 1.22.12 - resolve.exports: 1.1.1 - slash: 3.0.0 - - jest-runner@27.5.1: - dependencies: - '@jest/console': 27.5.1 - '@jest/environment': 27.5.1 - '@jest/test-result': 27.5.1 - '@jest/transform': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - chalk: 4.1.2 - emittery: 0.8.1 - graceful-fs: 4.2.11 - jest-docblock: 27.5.1 - jest-environment-jsdom: 27.5.1 - jest-environment-node: 27.5.1 - jest-haste-map: 27.5.1 - jest-leak-detector: 27.5.1 - jest-message-util: 27.5.1 - jest-resolve: 27.5.1 - jest-runtime: 27.5.1 - jest-util: 27.5.1 - jest-worker: 27.5.1 - source-map-support: 0.5.21 - throat: 6.0.2 - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - utf-8-validate - - jest-runtime@27.5.1: - dependencies: - '@jest/environment': 27.5.1 - '@jest/fake-timers': 27.5.1 - '@jest/globals': 27.5.1 - '@jest/source-map': 27.5.1 - '@jest/test-result': 27.5.1 - '@jest/transform': 27.5.1 - '@jest/types': 27.5.1 - chalk: 4.1.2 - cjs-module-lexer: 1.4.3 - collect-v8-coverage: 1.0.3 - execa: 5.1.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-haste-map: 27.5.1 - jest-message-util: 27.5.1 - jest-mock: 27.5.1 - jest-regex-util: 27.5.1 - jest-resolve: 27.5.1 - jest-snapshot: 27.5.1 - jest-util: 27.5.1 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - - jest-serializer@27.5.1: - dependencies: - '@types/node': 14.18.63 - graceful-fs: 4.2.11 - - jest-snapshot@27.5.1: - dependencies: - '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - '@jest/transform': 27.5.1 - '@jest/types': 27.5.1 - '@types/babel__traverse': 7.28.0 - '@types/prettier': 2.7.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) - chalk: 4.1.2 - expect: 27.5.1 - graceful-fs: 4.2.11 - jest-diff: 27.5.1 - jest-get-type: 27.5.1 - jest-haste-map: 27.5.1 - jest-matcher-utils: 27.5.1 - jest-message-util: 27.5.1 - jest-util: 27.5.1 - natural-compare: 1.4.0 - pretty-format: 27.5.1 - semver: 7.5.2 - transitivePeerDependencies: - - supports-color - - jest-util@27.5.1: - dependencies: - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - chalk: 4.1.2 - ci-info: 3.9.0 - graceful-fs: 4.2.11 - picomatch: 2.3.2 - - jest-util@28.1.3: - dependencies: - '@jest/types': 28.1.3 - '@types/node': 14.18.63 - chalk: 4.1.2 - ci-info: 3.9.0 - graceful-fs: 4.2.11 - picomatch: 2.3.2 - - jest-validate@27.5.1: - dependencies: - '@jest/types': 27.5.1 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 27.5.1 - leven: 3.1.0 - pretty-format: 27.5.1 - - jest-watch-typeahead@1.1.0(jest@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))): - dependencies: - ansi-escapes: 4.3.2 - chalk: 4.1.2 - jest: 27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)) - jest-regex-util: 28.0.2 - jest-watcher: 28.1.3 - slash: 4.0.0 - string-length: 5.0.1 - strip-ansi: 7.2.0 - - jest-watcher@27.5.1: - dependencies: - '@jest/test-result': 27.5.1 - '@jest/types': 27.5.1 - '@types/node': 14.18.63 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - jest-util: 27.5.1 - string-length: 4.0.2 - - jest-watcher@28.1.3: - dependencies: - '@jest/test-result': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 14.18.63 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.10.2 - jest-util: 28.1.3 - string-length: 4.0.2 - - jest-worker@26.6.2: - dependencies: - '@types/node': 14.18.63 - merge-stream: 2.0.0 - supports-color: 7.2.0 - - jest-worker@27.5.1: - dependencies: - '@types/node': 14.18.63 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - jest-worker@28.1.3: - dependencies: - '@types/node': 14.18.63 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - jest@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)): - dependencies: - '@jest/core': 27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)) - import-local: 3.2.0 - jest-cli: 27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)) - transitivePeerDependencies: - - bufferutil - - canvas - - supports-color - - ts-node - - utf-8-validate - - jiti@1.21.7: {} - - js-tokens@4.0.0: {} - - js-yaml@3.15.2: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - js-yaml@4.3.2: - dependencies: - argparse: 2.0.1 - - jsdom@16.7.0: - dependencies: - abab: 2.0.6 - acorn: 8.16.0 - acorn-globals: 6.0.0 - cssom: 0.4.4 - cssstyle: 2.3.0 - data-urls: 2.0.0 - decimal.js: 10.6.0 - domexception: 2.0.1 - escodegen: 2.1.0 - form-data: 3.0.5 - html-encoding-sniffer: 2.0.1 - http-proxy-agent: 4.0.1 - https-proxy-agent: 5.0.1 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.23 - parse5: 6.0.1 - saxes: 5.0.1 - symbol-tree: 3.2.4 - tough-cookie: 4.1.4 - w3c-hr-time: 1.0.2 - w3c-xmlserializer: 2.0.0 - webidl-conversions: 6.1.0 - whatwg-encoding: 1.0.5 - whatwg-mimetype: 2.3.0 - whatwg-url: 8.7.0 - ws: 7.5.11 - xml-name-validator: 3.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jsesc@3.1.0: {} - - json-buffer@3.0.1: {} - - json-parse-even-better-errors@2.3.1: {} - - json-schema-traverse@0.4.1: {} - - json-schema-traverse@1.0.0: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - json2mq@0.2.0: - dependencies: - string-convert: 0.2.1 - - json5@1.0.2: - dependencies: - minimist: 1.2.8 - - json5@2.2.3: {} - - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - - jsonfile@6.2.1: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - jsonpath@1.3.0: - dependencies: - esprima: 1.2.5 - static-eval: 2.1.1 - underscore: 1.13.8 - - jsonpointer@5.0.1: {} - - jsx-ast-utils@3.3.5: - dependencies: - array-includes: 3.1.9 - array.prototype.flat: 1.3.3 - object.assign: 4.1.7 - object.values: 1.2.1 - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - kind-of@6.0.3: {} - - kleur@3.0.3: {} - - klona@2.0.6: {} - - language-subtag-registry@0.3.23: {} - - language-tags@1.0.9: - dependencies: - language-subtag-registry: 0.3.23 - - launch-editor@2.14.1: - dependencies: - picocolors: 1.1.1 - shell-quote: 1.9.0 - - lerc@3.0.0: {} - - lerp@1.0.3: {} - - less-loader@7.3.0(less@4.6.4)(webpack@5.107.2(postcss@8.5.25)): - dependencies: - klona: 2.0.6 - less: 4.6.4 - loader-utils: 2.0.4 - schema-utils: 3.3.0 - webpack: 5.107.2(postcss@8.5.25) - - less@4.6.4: - dependencies: - copy-anything: 3.0.5 - parse-node-version: 1.0.1 - optionalDependencies: - errno: 0.1.8 - graceful-fs: 4.2.11 - image-size: 0.5.5 - make-dir: 3.1.0 - mime: 1.6.0 - needle: 3.5.0 - source-map: 0.6.1 - - leven@3.1.0: {} - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lilconfig@2.1.0: {} - - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - loader-runner@4.3.2: {} - - loader-utils@2.0.4: - dependencies: - big.js: 5.2.2 - emojis-list: 3.0.0 - json5: 2.2.3 - - loader-utils@3.3.1: {} - - locate-path@3.0.0: - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.clonedeep@4.5.0: {} - - lodash.debounce@4.0.8: {} - - lodash.memoize@4.1.2: {} - - lodash.merge@4.6.2: {} - - lodash.sortby@4.7.0: {} - - lodash.uniq@4.5.0: {} - - lodash@4.18.1: {} - - loglevel@1.9.2: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lower-case@2.0.2: - dependencies: - tslib: 2.8.1 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - - lz-string@1.5.0: {} - - magic-string@0.25.9: - dependencies: - sourcemap-codec: 1.4.8 - - make-dir@3.1.0: - dependencies: - semver: 7.5.2 - - make-error@1.3.6: {} - - makeerror@1.0.12: - dependencies: - tmpl: 1.0.5 - - math-intrinsics@1.1.0: {} - - mathjs@11.12.0: - dependencies: - '@babel/runtime': 7.29.7 - complex.js: 2.4.3 - decimal.js: 10.6.0 - escape-latex: 1.2.0 - fraction.js: 4.3.4 - javascript-natural-sort: 0.7.1 - seedrandom: 3.0.5 - tiny-emitter: 2.1.0 - typed-function: 4.2.2 - - mdn-data@2.0.14: {} - - media-typer@0.3.0: {} - - memfs@3.5.3: - dependencies: - fs-monkey: 1.1.0 - - memfs@4.79.0: - dependencies: - '@jsonjoy.com/fs-core': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-fsa': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-to-fsa': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.79.0(tslib@2.8.1) - '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) - '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) - glob-to-regex.js: 1.3.1(tslib@2.8.1) - thingies: 2.6.1(tslib@2.8.1) - tree-dump: 1.1.0(tslib@2.8.1) - tslib: 2.8.1 - - merge-descriptors@1.0.3: {} - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - methods@1.1.2: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.2 - - mime-db@1.52.0: {} - - mime-db@1.54.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - - mime@1.6.0: {} - - mimic-fn@2.1.0: {} - - min-indent@1.0.1: {} - - mini-css-extract-plugin@2.10.2(webpack@5.107.2(postcss@8.5.25)): - dependencies: - schema-utils: 4.3.3 - tapable: 2.3.3 - webpack: 5.107.2(postcss@8.5.25) - - minimalistic-assert@1.0.1: {} - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.21 - - minimatch@5.1.9: - dependencies: - brace-expansion: 2.1.7 - - minimist@1.2.8: {} - - moment@2.30.1: {} - - ms@2.0.0: {} - - ms@2.1.3: {} - - multicast-dns@7.2.5: - dependencies: - dns-packet: 5.6.1 - thunky: 1.1.0 - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - nanoid@3.3.19: {} - - natural-compare-lite@1.4.0: {} - - natural-compare@1.4.0: {} - - ndarray@1.0.19: - dependencies: - iota-array: 1.0.0 - is-buffer: 1.1.6 - - needle@3.5.0: - dependencies: - iconv-lite: 0.6.3 - sax: 1.6.0 - optional: true - - negotiator@0.6.3: {} - - negotiator@0.6.4: {} - - neo-async@2.6.2: {} - - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.8.1 - - node-exports-info@1.6.0: - dependencies: - array.prototype.flatmap: 1.3.3 - es-errors: 1.3.0 - object.entries: 1.1.9 - semver: 7.5.2 - - node-forge@1.4.0: {} - - node-int64@0.4.0: {} - - node-releases@2.0.57: {} - - normalize-path@3.0.0: {} - - normalize-url@6.1.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - nth-check@2.0.1: - dependencies: - boolbase: 1.0.0 - - numcodecs@0.3.2: - dependencies: - fflate: 0.8.3 - - nwsapi@2.2.23: {} - - object-assign@4.1.1: {} - - object-hash@3.0.0: {} - - object-inspect@1.13.4: {} - - object-is@1.1.6: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - - object-keys@1.1.1: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - object.entries@1.1.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 - - object.groupby@1.0.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - - object.values@1.2.1: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 - - obuf@1.1.2: {} - - oidc-client@1.11.5: - dependencies: - acorn: 7.4.1 - base64-js: 1.5.1 - core-js: 3.49.0 - crypto-js: 4.2.0 - serialize-javascript: 7.0.5 - - ol@10.9.0: - dependencies: - '@types/rbush': 4.0.0 - earcut: 3.0.2 - geotiff: 3.0.5 - pbf: 4.0.1 - rbush: 4.0.1 - zarrita: 0.7.3 - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - on-headers@1.1.0: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - open@8.4.2: - dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - own-keys@1.0.1: - dependencies: - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@3.0.0: + '@asamuzakjp/css-color@7.1.2': dependencies: - p-limit: 2.3.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-retry@4.6.2: - dependencies: - '@types/retry': 0.12.0 - retry: 0.13.1 - - p-try@2.2.0: {} - - pako@2.1.0: {} + '@csstools/css-calc': 3.4.1(@csstools/css-parser-algorithms@4.0.1(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-color-parser': 4.2.4(@csstools/css-parser-algorithms@4.0.1(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.1(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + lru-cache: 11.5.3 - param-case@3.0.4: + '@asamuzakjp/dom-selector@9.2.2': dependencies: - dot-case: 3.0.4 - tslib: 2.8.1 + bidi-js: 1.1.0 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 - parent-module@1.0.1: + '@babel/code-frame@7.29.7': dependencies: - callsites: 3.1.0 + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 - parse-headers@2.0.6: {} + '@babel/compat-data@7.29.7': {} - parse-json@5.2.0: + '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - parse-node-version@1.0.1: {} - - parse5@6.0.1: {} - - parseurl@1.3.3: {} + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 7.5.2 + transitivePeerDependencies: + - supports-color - pascal-case@3.1.2: + '@babel/generator@7.29.7': dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - - path-exists@3.0.0: {} - - path-exists@4.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - path-to-regexp@0.1.13: {} - - path-type@4.0.0: {} + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 - pbf@4.0.1: + '@babel/helper-compilation-targets@7.29.7': dependencies: - resolve-protobuf-schema: 2.1.0 - - performance-now@2.1.0: {} - - picocolors@1.1.1: {} - - picomatch@2.3.2: {} - - picomatch@4.0.4: {} - - pify@2.3.0: {} + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.7 + lru-cache: 5.1.1 + semver: 7.5.2 - pinkie-promise@2.0.1: + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': dependencies: - pinkie: 2.0.4 - - pinkie@2.0.4: {} + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + optional: true - pirates@4.0.7: {} + '@babel/helper-globals@7.29.7': {} - pkg-dir@4.2.0: + '@babel/helper-module-imports@7.29.7': dependencies: - find-up: 4.1.0 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color - pkg-up@3.1.0: + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - find-up: 3.0.0 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color - possible-typed-array-names@1.1.0: {} + '@babel/helper-plugin-utils@7.29.7': + optional: true - postcss-attribute-case-insensitive@5.0.2(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@babel/helper-string-parser@7.29.7': {} - postcss-browser-comments@4.0.0(browserslist@4.28.7)(postcss@8.5.25): - dependencies: - browserslist: 4.28.7 - postcss: 8.5.25 + '@babel/helper-validator-identifier@7.29.7': {} - postcss-calc@8.2.4(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 - postcss-value-parser: 4.2.0 + '@babel/helper-validator-option@7.29.7': {} - postcss-clamp@4.1.0(postcss@8.5.25): + '@babel/helpers@7.29.7': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 - postcss-color-functional-notation@4.2.4(postcss@8.5.25): + '@babel/parser@7.29.7': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@babel/types': 7.29.7 - postcss-color-hex-alpha@8.0.4(postcss@8.5.25): + '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7)': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) + semver: 7.5.2 + transitivePeerDependencies: + - supports-color + optional: true - postcss-color-rebeccapurple@7.1.1(postcss@8.5.25): + '@babel/runtime-corejs3@7.29.7': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + core-js-pure: 3.49.0 - postcss-colormin@5.3.1(postcss@8.5.25): - dependencies: - browserslist: 4.28.7 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@babel/runtime@7.29.7': {} - postcss-convert-values@5.1.3(postcss@8.5.25): + '@babel/template@7.29.7': dependencies: - browserslist: 4.28.7 - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 - postcss-custom-media@8.0.2(postcss@8.5.25): + '@babel/traverse@7.29.7': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color - postcss-custom-properties@12.1.11(postcss@8.5.25): + '@babel/types@7.29.7': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 - postcss-custom-selectors@6.0.3(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@biomejs/biome@2.4.16': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.4.16 + '@biomejs/cli-darwin-x64': 2.4.16 + '@biomejs/cli-linux-arm64': 2.4.16 + '@biomejs/cli-linux-arm64-musl': 2.4.16 + '@biomejs/cli-linux-x64': 2.4.16 + '@biomejs/cli-linux-x64-musl': 2.4.16 + '@biomejs/cli-win32-arm64': 2.4.16 + '@biomejs/cli-win32-x64': 2.4.16 - postcss-dir-pseudo-class@6.0.5(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@biomejs/cli-darwin-arm64@2.4.16': + optional: true - postcss-discard-comments@5.1.2(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@biomejs/cli-darwin-x64@2.4.16': + optional: true - postcss-discard-duplicates@5.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@biomejs/cli-linux-arm64-musl@2.4.16': + optional: true - postcss-discard-empty@5.1.1(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@biomejs/cli-linux-arm64@2.4.16': + optional: true - postcss-discard-overridden@5.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@biomejs/cli-linux-x64-musl@2.4.16': + optional: true - postcss-double-position-gradients@3.1.2(postcss@8.5.25): - dependencies: - '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@biomejs/cli-linux-x64@2.4.16': + optional: true - postcss-env-function@4.0.6(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@biomejs/cli-win32-arm64@2.4.16': + optional: true - postcss-flexbugs-fixes@5.0.2(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@biomejs/cli-win32-x64@2.4.16': + optional: true - postcss-focus-visible@6.0.4(postcss@8.5.25): + '@bramus/specificity@2.4.2': dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + css-tree: 3.2.1 - postcss-focus-within@5.0.4(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@cornerstonejs/codec-charls@1.2.7': {} - postcss-font-variant@5.0.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@cornerstonejs/codec-libjpeg-turbo-8bit@1.2.8': {} - postcss-gap-properties@3.0.5(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@cornerstonejs/codec-openjpeg@1.3.6': {} - postcss-image-set-function@4.0.7(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@cornerstonejs/codec-openjph@2.4.11': {} - postcss-import@15.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.12 + '@csstools/color-helpers@6.1.2': {} - postcss-initial@4.0.1(postcss@8.5.25): + '@csstools/css-calc@3.4.1(@csstools/css-parser-algorithms@4.0.1(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': dependencies: - postcss: 8.5.25 + '@csstools/css-parser-algorithms': 4.0.1(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 - postcss-js@4.1.0(postcss@8.5.25): + '@csstools/css-color-parser@4.2.4(@csstools/css-parser-algorithms@4.0.1(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': dependencies: - camelcase-css: 2.0.1 - postcss: 8.5.25 + '@csstools/color-helpers': 6.1.2 + '@csstools/css-calc': 3.4.1(@csstools/css-parser-algorithms@4.0.1(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.1(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 - postcss-lab-function@4.2.1(postcss@8.5.25): + '@csstools/css-parser-algorithms@4.0.1(@csstools/css-tokenizer@4.0.2)': dependencies: - '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@csstools/css-tokenizer': 4.0.2 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.25): - dependencies: - lilconfig: 3.1.3 + '@csstools/css-syntax-patches-for-csstree@1.1.14(css-tree@3.2.1)': optionalDependencies: - jiti: 1.21.7 - postcss: 8.5.25 - - postcss-loader@6.2.1(postcss@8.5.25)(webpack@5.107.2(postcss@8.5.25)): - dependencies: - cosmiconfig: 7.1.0 - klona: 2.0.6 - postcss: 8.5.25 - semver: 7.5.2 - webpack: 5.107.2(postcss@8.5.25) + css-tree: 3.2.1 - postcss-logical@5.0.4(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - - postcss-media-minmax@5.0.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@csstools/css-tokenizer@4.0.2': {} - postcss-merge-longhand@5.1.7(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.5.25) + '@exodus/bytes@1.16.0(@noble/hashes@2.4.0)': + optionalDependencies: + '@noble/hashes': 2.4.0 - postcss-merge-rules@5.1.4(postcss@8.5.25): + '@floating-ui/core@1.8.0': dependencies: - browserslist: 4.28.7 - caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@floating-ui/utils': 0.2.12 - postcss-minify-font-values@5.1.0(postcss@8.5.25): + '@floating-ui/dom@1.8.0': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 - postcss-minify-gradients@5.1.1(postcss@8.5.25): + '@floating-ui/react-dom@2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@floating-ui/dom': 1.8.0 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) - postcss-minify-params@5.1.4(postcss@8.5.25): - dependencies: - browserslist: 4.28.7 - cssnano-utils: 3.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@floating-ui/utils@0.2.12': {} - postcss-minify-selectors@5.2.1(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@fontsource/ibm-plex-mono@5.3.0': {} - postcss-modules-extract-imports@3.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@fontsource/ibm-plex-sans@5.3.0': {} - postcss-modules-local-by-default@4.2.0(postcss@8.5.25): - dependencies: - icss-utils: 5.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-selector-parser: 7.1.1 - postcss-value-parser: 4.2.0 + '@imagingdatacommons/dicomicc@0.2.3': {} - postcss-modules-scope@3.2.1(postcss@8.5.25): + '@jridgewell/gen-mapping@0.3.13': dependencies: - postcss: 8.5.25 - postcss-selector-parser: 7.1.1 + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 - postcss-modules-values@4.0.0(postcss@8.5.25): + '@jridgewell/remapping@2.3.5': dependencies: - icss-utils: 5.1.0(postcss@8.5.25) - postcss: 8.5.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - postcss-nested@6.2.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@jridgewell/resolve-uri@3.1.2': {} - postcss-nesting@10.2.0(postcss@8.5.25): + '@jridgewell/source-map@0.3.11': dependencies: - '@csstools/selector-specificity': 2.2.0(postcss-selector-parser@6.1.2) - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + optional: true - postcss-normalize-charset@5.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 + '@jridgewell/sourcemap-codec@1.6.0': {} - postcss-normalize-display-values@5.1.0(postcss@8.5.25): + '@jridgewell/trace-mapping@0.3.31': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 - postcss-normalize-positions@5.1.1(postcss@8.5.25): + '@noble/curves@2.4.0': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@noble/hashes': 2.4.0 - postcss-normalize-repeat-style@5.1.1(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@noble/hashes@2.4.0': {} - postcss-normalize-string@5.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@oxc-project/types@0.151.0': {} - postcss-normalize-timing-functions@5.1.0(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@petamoriken/float16@3.9.3': {} - postcss-normalize-unicode@5.1.1(postcss@8.5.25): - dependencies: - browserslist: 4.28.7 - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@radix-ui/number@1.1.3': {} - postcss-normalize-url@5.1.0(postcss@8.5.25): - dependencies: - normalize-url: 6.1.0 - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@radix-ui/primitive@1.1.7': {} - postcss-normalize-whitespace@5.1.1(postcss@8.5.25): + '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-collapsible@1.1.20(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-normalize@10.0.1(browserslist@4.28.7)(postcss@8.5.25): + '@radix-ui/react-collection@1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@csstools/normalize.css': 12.1.1 - browserslist: 4.28.7 - postcss: 8.5.25 - postcss-browser-comments: 4.0.0(browserslist@4.28.7)(postcss@8.5.25) - sanitize.css: 13.0.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.3.3(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-opacity-percentage@1.1.3(postcss@8.5.25): + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.3.0)(react@19.3.0)': dependencies: - postcss: 8.5.25 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - postcss-ordered-values@5.1.3(postcss@8.5.25): + '@radix-ui/react-context@1.2.2(@types/react@19.3.0)(react@19.3.0)': dependencies: - cssnano-utils: 3.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.3.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + aria-hidden: 1.2.6 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-overflow-shorthand@3.0.4(postcss@8.5.25): + '@radix-ui/react-direction@1.1.4(@types/react@19.3.0)(react@19.3.0)': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - postcss-page-break@3.0.4(postcss@8.5.25): + '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - postcss: 8.5.25 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-place@7.0.5(postcss@8.5.25): + '@radix-ui/react-focus-guards@1.1.6(@types/react@19.3.0)(react@19.3.0)': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - postcss-preset-env@7.8.3(postcss@8.5.25): - dependencies: - '@csstools/postcss-cascade-layers': 1.1.1(postcss@8.5.25) - '@csstools/postcss-color-function': 1.1.1(postcss@8.5.25) - '@csstools/postcss-font-format-keywords': 1.0.1(postcss@8.5.25) - '@csstools/postcss-hwb-function': 1.0.2(postcss@8.5.25) - '@csstools/postcss-ic-unit': 1.0.1(postcss@8.5.25) - '@csstools/postcss-is-pseudo-class': 2.0.7(postcss@8.5.25) - '@csstools/postcss-nested-calc': 1.0.0(postcss@8.5.25) - '@csstools/postcss-normalize-display-values': 1.0.1(postcss@8.5.25) - '@csstools/postcss-oklab-function': 1.1.1(postcss@8.5.25) - '@csstools/postcss-progressive-custom-properties': 1.3.0(postcss@8.5.25) - '@csstools/postcss-stepped-value-functions': 1.0.1(postcss@8.5.25) - '@csstools/postcss-text-decoration-shorthand': 1.0.0(postcss@8.5.25) - '@csstools/postcss-trigonometric-functions': 1.0.2(postcss@8.5.25) - '@csstools/postcss-unset-value': 1.0.2(postcss@8.5.25) - autoprefixer: 10.5.0(postcss@8.5.25) - browserslist: 4.28.7 - css-blank-pseudo: 3.0.3(postcss@8.5.25) - css-has-pseudo: 3.0.4(postcss@8.5.25) - css-prefers-color-scheme: 6.0.3(postcss@8.5.25) - cssdb: 7.11.2 - postcss: 8.5.25 - postcss-attribute-case-insensitive: 5.0.2(postcss@8.5.25) - postcss-clamp: 4.1.0(postcss@8.5.25) - postcss-color-functional-notation: 4.2.4(postcss@8.5.25) - postcss-color-hex-alpha: 8.0.4(postcss@8.5.25) - postcss-color-rebeccapurple: 7.1.1(postcss@8.5.25) - postcss-custom-media: 8.0.2(postcss@8.5.25) - postcss-custom-properties: 12.1.11(postcss@8.5.25) - postcss-custom-selectors: 6.0.3(postcss@8.5.25) - postcss-dir-pseudo-class: 6.0.5(postcss@8.5.25) - postcss-double-position-gradients: 3.1.2(postcss@8.5.25) - postcss-env-function: 4.0.6(postcss@8.5.25) - postcss-focus-visible: 6.0.4(postcss@8.5.25) - postcss-focus-within: 5.0.4(postcss@8.5.25) - postcss-font-variant: 5.0.0(postcss@8.5.25) - postcss-gap-properties: 3.0.5(postcss@8.5.25) - postcss-image-set-function: 4.0.7(postcss@8.5.25) - postcss-initial: 4.0.1(postcss@8.5.25) - postcss-lab-function: 4.2.1(postcss@8.5.25) - postcss-logical: 5.0.4(postcss@8.5.25) - postcss-media-minmax: 5.0.0(postcss@8.5.25) - postcss-nesting: 10.2.0(postcss@8.5.25) - postcss-opacity-percentage: 1.1.3(postcss@8.5.25) - postcss-overflow-shorthand: 3.0.4(postcss@8.5.25) - postcss-page-break: 3.0.4(postcss@8.5.25) - postcss-place: 7.0.5(postcss@8.5.25) - postcss-pseudo-class-any-link: 7.1.6(postcss@8.5.25) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.25) - postcss-selector-not: 6.0.1(postcss@8.5.25) - postcss-value-parser: 4.2.0 - - postcss-pseudo-class-any-link@7.1.6(postcss@8.5.25): - dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 - - postcss-reduce-initial@5.1.2(postcss@8.5.25): + '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - browserslist: 4.28.7 - caniuse-api: 3.0.0 - postcss: 8.5.25 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-reduce-transforms@5.1.0(postcss@8.5.25): + '@radix-ui/react-id@1.1.4(@types/react@19.3.0)(react@19.3.0)': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-menu@2.1.24(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.3.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.3.0)(react@19.3.0) + aria-hidden: 1.2.6 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-popover@1.1.23(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.3.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + aria-hidden: 1.2.6 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-popper@1.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@floating-ui/react-dom': 2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/rect': 1.1.3 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-replace-overflow-wrap@4.0.0(postcss@8.5.25): + '@radix-ui/react-portal@1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - postcss: 8.5.25 + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-selector-not@6.0.1(postcss@8.5.25): + '@radix-ui/react-presence@1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-selector-parser@6.1.2: + '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 + '@radix-ui/react-slot': 1.3.3(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-select@2.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.3.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + aria-hidden: 1.2.6 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-slider@1.4.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-selector-parser@7.1.1: + '@radix-ui/react-slot@1.3.3(@types/react@19.3.0)(react@19.3.0)': dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-switch@1.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.3.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - postcss-svgo@5.1.0(postcss@8.5.25): + '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.3.0)(react@19.3.0)': dependencies: - postcss: 8.5.25 - postcss-value-parser: 4.2.0 - svgo: 2.8.4 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - postcss-unique-selectors@5.1.1(postcss@8.5.25): + '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.3.0)(react@19.3.0)': dependencies: - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 - - postcss-value-parser@4.2.0: {} + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - postcss@8.5.25: + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.3.0)(react@19.3.0)': dependencies: - nanoid: 3.3.19 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prelude-ls@1.2.1: {} - - pretty-bytes@5.6.0: {} + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - pretty-error@4.0.0: + '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.3.0)(react@19.3.0)': dependencies: - lodash: 4.18.1 - renderkid: 3.0.0 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - pretty-format@27.5.1: + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.3.0)(react@19.3.0)': dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - pretty-format@28.1.3: + '@radix-ui/react-use-previous@1.1.4(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@jest/schemas': 28.1.3 - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 18.3.1 - - process-nextick-args@2.0.1: {} + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - promise@8.3.0: + '@radix-ui/react-use-rect@1.1.4(@types/react@19.3.0)(react@19.3.0)': dependencies: - asap: 2.0.6 + '@radix-ui/rect': 1.1.3 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - prompts@2.4.2: + '@radix-ui/react-use-size@1.1.4(@types/react@19.3.0)(react@19.3.0)': dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - prop-types@15.8.1: + '@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - properties-file@4.0.0: {} - - protocol-buffers-schema@3.6.1: {} + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 + '@radix-ui/rect@1.1.3': {} - proxy-from-env@2.1.0: {} + '@remix-run/route-pattern@0.22.1': {} - prr@1.0.1: + '@rolldown/binding-android-arm-eabi@1.2.11': optional: true - psl@1.15.0: - dependencies: - punycode: 2.3.1 - - punycode@2.3.1: {} + '@rolldown/binding-android-arm64@1.2.11': + optional: true - qs@6.13.0: - dependencies: - side-channel: 1.1.0 + '@rolldown/binding-darwin-arm64@1.2.11': + optional: true - querystringify@2.2.0: {} + '@rolldown/binding-darwin-x64@1.2.11': + optional: true - queue-microtask@1.2.3: {} + '@rolldown/binding-freebsd-x64@1.2.11': + optional: true - quick-lru@6.1.2: {} + '@rolldown/binding-linux-arm-gnueabihf@1.2.11': + optional: true - quickselect@3.0.0: {} + '@rolldown/binding-linux-arm64-gnu@1.2.11': + optional: true - raf@3.4.1: - dependencies: - performance-now: 2.1.0 + '@rolldown/binding-linux-arm64-musl@1.2.11': + optional: true - range-parser@1.2.1: {} + '@rolldown/binding-linux-ppc64-gnu@1.2.11': + optional: true - raw-body@2.5.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 + '@rolldown/binding-linux-s390x-gnu@1.2.11': + optional: true - rbush@4.0.1: - dependencies: - quickselect: 3.0.0 + '@rolldown/binding-linux-x64-gnu@1.2.11': + optional: true - rc-align@4.0.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - dom-align: 1.12.4 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - resize-observer-polyfill: 1.5.1 + '@rolldown/binding-linux-x64-musl@1.2.11': + optional: true - rc-cascader@3.7.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - array-tree-filter: 2.1.0 - classnames: 2.5.1 - rc-select: 14.1.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tree: 5.7.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@rolldown/binding-openharmony-arm64@1.2.11': + optional: true - rc-checkbox@3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@rolldown/binding-win32-arm64-msvc@1.2.11': + optional: true - rc-collapse@3.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - shallowequal: 1.1.0 + '@rolldown/binding-win32-x64-msvc@1.2.11': + optional: true - rc-dialog@9.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@rolldown/plugin-babel@0.2.4(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(rolldown@1.2.11)(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1))': dependencies: + '@babel/core': 7.29.7 + picomatch: 4.0.7 + rolldown: 1.2.11 + optionalDependencies: + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) '@babel/runtime': 7.29.7 - '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + vite: 8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1) - rc-drawer@6.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@rolldown/pluginutils@1.0.1': {} - rc-dropdown@4.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@tailwindcss/node@4.3.3': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-trigger: 5.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.26.0 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.3 - rc-field-form@1.38.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - async-validator: 4.2.5 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-android-arm64@4.3.3': + optional: true - rc-image@5.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - classnames: 2.5.1 - rc-dialog: 9.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-darwin-arm64@4.3.3': + optional: true - rc-input-number@7.3.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-darwin-x64@4.3.3': + optional: true - rc-input@0.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-freebsd-x64@4.3.3': + optional: true - rc-mentions@1.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-menu: 9.8.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-textarea: 0.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-trigger: 5.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + optional: true - rc-menu@9.8.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-trigger: 5.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + optional: true - rc-motion@2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + optional: true - rc-notification@4.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + optional: true - rc-overflow@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + optional: true - rc-pagination@3.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + optional: true - rc-picker@2.7.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - date-fns: 2.30.0 - dayjs: 1.11.21 - moment: 2.30.1 - rc-trigger: 5.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - shallowequal: 1.1.0 - - rc-progress@3.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + optional: true - rc-rate@2.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + optional: true - rc-resize-observer@1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - resize-observer-polyfill: 1.5.1 + '@tailwindcss/oxide@4.3.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - rc-segmented@2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@tailwindcss/vite@4.3.3(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1))': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + tailwindcss: 4.3.3 + vite: 8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1) - rc-select@14.1.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@tanstack/react-table@8.20.5(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-trigger: 5.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@tanstack/table-core': 8.20.5 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) - rc-slider@10.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - shallowequal: 1.1.0 + '@tanstack/table-core@8.20.5': {} - rc-steps@5.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@testing-library/dom@10.4.2': dependencies: + '@babel/code-frame': 7.29.7 '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 - rc-switch@3.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@testing-library/jest-dom@7.0.1(@testing-library/dom@10.4.2)(vitest@5.0.3(@types/node@24.19.0)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)))': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@adobe/css-tools': 4.5.0 + '@testing-library/dom': 10.4.2 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + optionalDependencies: + vitest: 5.0.3(@types/node@24.19.0)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)) - rc-table@7.26.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@testing-library/react@16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - shallowequal: 1.1.0 + '@testing-library/dom': 10.4.2 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - rc-tabs@12.5.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-dropdown: 4.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-menu: 9.8.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@types/aria-query@5.0.4': {} - rc-textarea@0.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@types/babel__core@7.20.5': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - shallowequal: 1.1.0 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 - rc-tooltip@5.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@types/babel__generator@7.27.0': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-trigger: 5.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@babel/types': 7.29.7 - rc-tree-select@5.5.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@types/babel__template@7.4.4': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-select: 14.1.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-tree: 5.7.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 - rc-tree@5.7.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@types/babel__traverse@7.28.0': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@babel/types': 7.29.7 - rc-trigger@5.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + '@types/chai@5.2.3': dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-align: 4.0.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 - rc-upload@4.3.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@types/d3-dispatch@3.0.6': {} - rc-util@5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 18.3.1 + '@types/deep-eql@4.0.2': {} - rc-virtual-list@3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@types/estree@1.0.9': {} - react-app-polyfill@3.0.0: - dependencies: - core-js: 3.49.0 - object-assign: 4.1.1 - promise: 8.3.0 - raf: 3.4.1 - regenerator-runtime: 0.13.11 - whatwg-fetch: 3.6.20 + '@types/lodash@4.17.24': {} - react-dev-utils@12.0.1(eslint@8.57.1)(typescript@4.9.5)(webpack@5.107.2(postcss@8.5.25)): + '@types/node@24.19.0': dependencies: - '@babel/code-frame': 7.29.7 - address: 1.2.2 - browserslist: 4.28.7 - chalk: 4.1.2 - cross-spawn: 7.0.6 - detect-port-alt: 1.1.6 - escape-string-regexp: 4.0.0 - filesize: 8.0.7 - find-up: 5.0.0 - fork-ts-checker-webpack-plugin: 6.5.3(eslint@8.57.1)(typescript@4.9.5)(webpack@5.107.2(postcss@8.5.25)) - global-modules: 2.0.0 - globby: 11.1.0 - gzip-size: 6.0.0 - immer: 9.0.21 - is-root: 2.1.0 - loader-utils: 3.3.1 - open: 8.4.2 - pkg-up: 3.1.0 - prompts: 2.4.2 - react-error-overlay: 6.1.0 - recursive-readdir: 2.2.3 - shell-quote: 1.9.0 - strip-ansi: 6.0.1 - text-table: 0.2.0 - webpack: 5.107.2(postcss@8.5.25) - optionalDependencies: - typescript: 4.9.5 - transitivePeerDependencies: - - eslint - - supports-color - - vue-template-compiler + undici-types: 7.24.6 - react-dom@18.3.1(react@18.3.1): - dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + '@types/rbush@4.0.0': {} - react-error-boundary@3.1.4(react@18.3.1): + '@types/react-dom@19.3.0(@types/react@19.3.0)': dependencies: - '@babel/runtime': 7.29.7 - react: 18.3.1 + '@types/react': 19.3.0 - react-error-overlay@6.1.0: {} - - react-icons@3.11.0(react@18.3.1): + '@types/react@19.3.0': dependencies: - camelcase: 5.3.1 - react: 18.3.1 + csstype: 3.2.3 - react-is@16.13.1: {} + '@types/retry@0.12.5': {} - react-is@17.0.2: {} + '@types/uuid@8.3.4': {} - react-is@18.3.1: {} + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true - react-refresh@0.11.0: {} + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true - react-router-dom@6.30.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@remix-run/router': 1.23.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 6.30.6(react@18.3.1) + '@typescript/typescript-darwin-x64@7.0.2': + optional: true - react-router@6.30.6(react@18.3.1): - dependencies: - '@remix-run/router': 1.23.4 - react: 18.3.1 + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true - react-scripts@5.0.0(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))(type-fest@4.41.0)(typescript@4.9.5): - dependencies: - '@babel/core': 7.29.7 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.17(react-refresh@0.11.0)(type-fest@4.41.0)(webpack-dev-server@4.15.2(webpack@5.107.2(postcss@8.5.25)))(webpack@5.107.2(postcss@8.5.25)) - '@svgr/webpack': 5.5.0 - babel-jest: 27.5.1(@babel/core@7.29.7) - babel-loader: 8.4.1(@babel/core@7.29.7)(webpack@5.107.2(postcss@8.5.25)) - babel-plugin-named-asset-import: 0.3.8(@babel/core@7.29.7) - babel-preset-react-app: 10.1.0 - bfj: 7.1.0 - browserslist: 4.28.7 - camelcase: 6.3.0 - case-sensitive-paths-webpack-plugin: 2.4.0 - css-loader: 6.11.0(webpack@5.107.2(postcss@8.5.25)) - css-minimizer-webpack-plugin: 3.4.1(webpack@5.107.2(postcss@8.5.25)) - dotenv: 10.0.0 - dotenv-expand: 5.1.0 - eslint: 8.57.1 - eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7))(@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)))(typescript@4.9.5) - eslint-webpack-plugin: 3.2.0(eslint@8.57.1)(webpack@5.107.2(postcss@8.5.25)) - file-loader: 6.2.0(webpack@5.107.2(postcss@8.5.25)) - fs-extra: 10.1.0 - html-webpack-plugin: 5.6.7(webpack@5.107.2(postcss@8.5.25)) - identity-obj-proxy: 3.0.0 - jest: 27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5)) - jest-resolve: 27.5.1 - jest-watch-typeahead: 1.1.0(jest@27.5.1(ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5))) - mini-css-extract-plugin: 2.10.2(webpack@5.107.2(postcss@8.5.25)) - postcss: 8.5.25 - postcss-flexbugs-fixes: 5.0.2(postcss@8.5.25) - postcss-loader: 6.2.1(postcss@8.5.25)(webpack@5.107.2(postcss@8.5.25)) - postcss-normalize: 10.0.1(browserslist@4.28.7)(postcss@8.5.25) - postcss-preset-env: 7.8.3(postcss@8.5.25) - prompts: 2.4.2 - react: 18.3.1 - react-app-polyfill: 3.0.0 - react-dev-utils: 12.0.1(eslint@8.57.1)(typescript@4.9.5)(webpack@5.107.2(postcss@8.5.25)) - react-refresh: 0.11.0 - resolve: 1.22.12 - resolve-url-loader: 4.0.0 - sass-loader: 12.6.0(webpack@5.107.2(postcss@8.5.25)) - semver: 7.5.2 - source-map-loader: 3.0.2(webpack@5.107.2(postcss@8.5.25)) - style-loader: 3.3.4(webpack@5.107.2(postcss@8.5.25)) - tailwindcss: 3.4.19 - terser-webpack-plugin: 5.6.1(postcss@8.5.25)(webpack@5.107.2(postcss@8.5.25)) - webpack: 5.107.2(postcss@8.5.25) - webpack-dev-server: 4.15.2(webpack@5.107.2(postcss@8.5.25)) - webpack-manifest-plugin: 4.1.1(webpack@5.107.2(postcss@8.5.25)) - workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.107.2(postcss@8.5.25)) - optionalDependencies: - fsevents: 2.3.3 - typescript: 4.9.5 - transitivePeerDependencies: - - '@babel/plugin-syntax-flow' - - '@babel/plugin-transform-react-jsx' - - '@minify-html/node' - - '@parcel/css' - - '@rspack/core' - - '@swc/core' - - '@swc/css' - - '@swc/html' - - '@types/babel__core' - - '@types/webpack' - - bufferutil - - canvas - - clean-css - - cssnano - - csso - - debug - - esbuild - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - fibers - - html-minifier-terser - - lightningcss - - node-notifier - - node-sass - - rework - - rework-visit - - sass - - sass-embedded - - sockjs-client - - supports-color - - ts-node - - tsx - - type-fest - - uglify-js - - utf-8-validate - - vue-template-compiler - - webpack-cli - - webpack-hot-middleware - - webpack-plugin-serve - - yaml - - react-shallow-renderer@16.15.0(react@18.3.1): - dependencies: - object-assign: 4.1.1 - react: 18.3.1 - react-is: 18.3.1 + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true - react-test-renderer@18.3.1(react@18.3.1): - dependencies: - react: 18.3.1 - react-is: 18.3.1 - react-shallow-renderer: 16.15.0(react@18.3.1) - scheduler: 0.23.2 + '@typescript/typescript-linux-arm64@7.0.2': + optional: true - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + '@typescript/typescript-linux-arm@7.0.2': + optional: true - read-cache@1.0.0: - dependencies: - pify: 2.3.0 + '@typescript/typescript-linux-loong64@7.0.2': + optional: true - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true - readdirp@3.6.0: - dependencies: - picomatch: 2.3.2 + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true - recursive-readdir@2.2.3: - dependencies: - minimatch: 3.1.5 + '@typescript/typescript-linux-s390x@7.0.2': + optional: true - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 + '@typescript/typescript-linux-x64@7.0.2': + optional: true - reference-spec-reader@0.2.0: {} + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true - regenerate-unicode-properties@10.2.2: - dependencies: - regenerate: 1.4.2 + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true - regenerate@1.4.2: {} + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true - regenerator-runtime@0.13.11: {} + '@typescript/typescript-sunos-x64@7.0.2': + optional: true - regex-parser@2.3.1: {} + '@typescript/typescript-win32-arm64@7.0.2': + optional: true - regexp.prototype.flags@1.5.4: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 + '@typescript/typescript-win32-x64@7.0.2': + optional: true - regexpu-core@6.4.0: + '@vitejs/plugin-react@6.1.1(@rolldown/plugin-babel@0.2.4(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(rolldown@1.2.11)(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)))(babel-plugin-react-compiler@1.0.0)(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1))': dependencies: - regenerate: 1.4.2 - regenerate-unicode-properties: 10.2.2 - regjsgen: 0.8.0 - regjsparser: 0.13.1 - unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.2.1 - - regjsgen@0.8.0: {} + '@rolldown/pluginutils': 1.0.1 + vite: 8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1) + optionalDependencies: + '@rolldown/plugin-babel': 0.2.4(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(rolldown@1.2.11)(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)) + babel-plugin-react-compiler: 1.0.0 - regjsparser@0.13.1: + '@vitest/mocker@5.0.3(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1))': dependencies: - jsesc: 3.1.0 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.2 + optionalDependencies: + vite: 8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1) - relateurl@0.2.7: {} + '@vitest/spy@5.0.3': {} - renderkid@3.0.0: + '@zarrita/storage@0.2.0': dependencies: - css-select: 4.3.0 - dom-converter: 0.2.0 - htmlparser2: 6.1.0 - lodash: 4.18.1 - strip-ansi: 6.0.1 + reference-spec-reader: 0.2.0 + unzipit: 2.0.0 - require-directory@2.1.1: {} + acorn@8.16.0: + optional: true - require-from-string@2.0.2: {} + adm-zip@0.6.1: {} - requires-port@1.0.0: {} + ansi-regex@5.0.1: {} - resize-observer-polyfill@1.5.1: {} + ansi-styles@5.2.0: {} - resolve-cwd@3.0.0: + anymatch@3.1.3: dependencies: - resolve-from: 5.0.0 - - resolve-from@4.0.0: {} - - resolve-from@5.0.0: {} + normalize-path: 3.0.0 + picomatch: 2.3.2 - resolve-protobuf-schema@2.1.0: + aria-hidden@1.2.6: dependencies: - protocol-buffers-schema: 3.6.1 + tslib: 2.8.1 - resolve-url-loader@4.0.0: + aria-query@5.3.0: dependencies: - adjust-sourcemap-loader: 4.0.0 - convert-source-map: 1.9.0 - loader-utils: 2.0.4 - postcss: 8.5.25 - source-map: 0.6.1 + dequal: 2.0.3 - resolve.exports@1.1.1: {} + aria-query@5.3.2: {} - resolve@1.22.12: + array-union@1.0.2: dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 + array-uniq: 1.0.3 - resolve@2.0.0-next.7: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 - node-exports-info: 1.6.0 - object-keys: 1.1.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 + array-uniq@1.0.3: {} - retry@0.13.1: {} + assertion-error@2.0.1: {} - reusify@1.1.0: {} + async@3.2.6: {} - rimraf@3.0.2: + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): dependencies: - glob: 7.2.3 + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + semver: 7.5.2 + transitivePeerDependencies: + - supports-color + optional: true - rollup-plugin-terser@7.0.2(rollup@3.30.0): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): dependencies: - '@babel/code-frame': 7.29.7 - jest-worker: 26.6.2 - rollup: 3.30.0 - serialize-javascript: 7.0.5 - terser: 5.48.0 - - rollup@3.30.0: - optionalDependencies: - fsevents: 2.3.3 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + core-js-compat: 3.49.0 + transitivePeerDependencies: + - supports-color + optional: true - run-parallel@1.2.0: + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): dependencies: - queue-microtask: 1.2.3 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + optional: true - safe-array-concat@1.1.4: + babel-plugin-react-compiler@1.0.0: dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 + '@babel/types': 7.29.7 - safe-buffer@5.1.2: {} + balanced-match@1.0.2: {} - safe-buffer@5.2.1: {} + baseline-browser-mapping@2.11.26: {} - safe-push-apply@1.0.0: + bidi-js@1.1.0: dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 + require-from-string: 2.0.2 - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 + binary-extensions@2.3.0: {} - safer-buffer@2.1.2: {} + brace-expansion@1.1.21: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 - sanitize.css@13.0.0: {} + braces@3.0.3: + dependencies: + fill-range: 7.1.1 - sass-loader@12.6.0(webpack@5.107.2(postcss@8.5.25)): + browserslist@4.28.7: dependencies: - klona: 2.0.6 - neo-async: 2.6.2 - webpack: 5.107.2(postcss@8.5.25) + baseline-browser-mapping: 2.11.26 + caniuse-lite: 1.0.30001812 + electron-to-chromium: 1.5.439 + node-releases: 2.0.57 + update-browserslist-db: 1.2.3(browserslist@4.28.7) + + buffer-from@1.1.2: + optional: true - sax@1.6.0: {} + caniuse-lite@1.0.30001812: {} - saxes@5.0.1: - dependencies: - xmlchars: 2.2.0 + chai@6.2.2: {} - scheduler@0.23.2: + chokidar@3.6.0: dependencies: - loose-envify: 1.4.0 + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 - schema-utils@2.7.0: + class-variance-authority@0.7.1: dependencies: - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) + clsx: 2.1.1 - schema-utils@2.7.1: - dependencies: - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) + clsx@2.1.1: {} - schema-utils@3.3.0: + colormap@2.3.2: dependencies: - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) + lerp: 1.0.3 - schema-utils@4.3.3: - dependencies: - '@types/json-schema': 7.0.15 - ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) - ajv-keywords: 5.1.0(ajv@8.20.0) + commander@2.20.3: {} - scroll-into-view-if-needed@2.2.31: - dependencies: - compute-scroll-into-view: 1.0.20 + commondir@1.0.1: {} - seedrandom@3.0.5: {} + complex.js@2.4.3: {} - select-hose@2.0.0: {} + concat-map@0.0.1: {} - selfsigned@2.4.1: - dependencies: - '@types/node-forge': 1.3.14 - node-forge: 1.4.0 + convert-source-map@2.0.0: {} - semver@7.5.2: - dependencies: - lru-cache: 6.0.0 + cookie-es@3.1.1: {} - send@0.19.0: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color + core-js-compat@3.49.0: + dependencies: + browserslist: 4.28.7 + optional: true - serialize-javascript@7.0.5: {} + core-js-pure@3.49.0: {} - serve-index@1.9.2: + css-tree@3.2.1: dependencies: - accepts: 1.3.8 - batch: 0.6.1 - debug: 2.6.9 - escape-html: 1.0.3 - http-errors: 1.8.1 - mime-types: 2.1.35 - parseurl: 1.3.3 - transitivePeerDependencies: - - supports-color + mdn-data: 2.27.1 + source-map-js: 1.2.1 - serve-static@1.16.2: + css.escape@1.5.1: {} + + csstype@3.2.3: {} + + data-urls@7.0.0(@noble/hashes@2.4.0): dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.0 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1(@noble/hashes@2.4.0) transitivePeerDependencies: - - supports-color + - '@noble/hashes' - set-function-length@1.2.2: + dcmjs@0.35.0: dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 + '@babel/runtime-corejs3': 7.29.7 + adm-zip: 0.6.1 + gl-matrix: 3.4.4 + lodash.clonedeep: 4.5.0 + loglevel: 1.9.2 + ndarray: 1.0.19 + pako: 2.1.0 - set-function-name@2.0.2: + dcmjs@0.41.0: dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 + '@babel/runtime-corejs3': 7.29.7 + adm-zip: 0.6.1 + gl-matrix: 3.4.4 + lodash.clonedeep: 4.5.0 + loglevel: 1.9.2 + ndarray: 1.0.19 + pako: 2.1.0 - set-proto@1.0.0: + debug@4.4.3: dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - - setprototypeof@1.2.0: {} - - shallowequal@1.1.0: {} + ms: 2.1.3 - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 + decimal.js@10.6.0: {} - shebang-regex@3.0.0: {} + dequal@2.0.3: {} - shell-quote@1.9.0: {} + detect-browser@5.3.0: {} - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 + detect-libc@2.1.2: {} - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 + detect-node-es@1.1.0: {} - side-channel-weakmap@1.0.2: + dicom-microscopy-viewer@0.48.27: dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 + '@cornerstonejs/codec-charls': 1.2.7 + '@cornerstonejs/codec-libjpeg-turbo-8bit': 1.2.8 + '@cornerstonejs/codec-openjpeg': 1.3.6 + '@cornerstonejs/codec-openjph': 2.4.11 + '@imagingdatacommons/dicomicc': 0.2.3 + colormap: 2.3.2 + dcmjs: 0.41.0 + dicomweb-client: 0.11.3 + image-type: 4.1.0 + mathjs: 11.12.0 + ol: 10.9.0 + uuid: 9.0.1 - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 + dicomweb-client@0.10.3: {} - signal-exit@3.0.7: {} + dicomweb-client@0.11.3: {} - sisteransi@1.0.5: {} + dom-accessibility-api@0.5.16: {} - slash@3.0.0: {} + dom-accessibility-api@0.6.3: {} - slash@4.0.0: {} + earcut@3.0.2: {} - slugify@1.6.9: {} + electron-to-chromium@1.5.439: {} - sockjs@0.3.24: - dependencies: - faye-websocket: 0.11.4 - uuid: 8.3.2 - websocket-driver: 0.7.5 + email-addresses@5.0.0: {} - sonarqube-scanner@4.3.6: + enhanced-resolve@5.26.0: dependencies: - adm-zip: 0.6.1 - axios: 1.18.0 - commander: 14.0.3 - hpagent: 1.2.0 - node-forge: 1.4.0 - properties-file: 4.0.0 - proxy-from-env: 2.1.0 - semver: 7.5.2 - slugify: 1.6.9 - tar-stream: 3.1.8 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - debug - - react-native-b4a - - supports-color + graceful-fs: 4.2.11 + tapable: 2.3.3 - source-list-map@2.0.1: {} + entities@8.1.0: {} - source-map-js@1.2.1: {} + es-errors@1.3.0: + optional: true - source-map-loader@3.0.2(webpack@5.107.2(postcss@8.5.25)): - dependencies: - abab: 2.0.6 - iconv-lite: 0.6.3 - source-map-js: 1.2.1 - webpack: 5.107.2(postcss@8.5.25) + es-module-lexer@2.3.2: {} - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 + escalade@3.2.0: {} - source-map@0.6.1: {} + escape-latex@1.2.0: {} - source-map@0.7.6: {} + escape-string-regexp@1.0.5: {} - source-map@0.8.0-beta.0: + estree-walker@3.0.3: dependencies: - whatwg-url: 7.1.0 + '@types/estree': 1.0.9 - sourcemap-codec@1.4.8: {} + expect-type@1.4.0: {} - spdy-transport@3.0.0: - dependencies: - debug: 4.4.3 - detect-node: 2.1.0 - hpack.js: 2.1.6 - obuf: 1.1.2 - readable-stream: 3.6.2 - wbuf: 1.7.3 - transitivePeerDependencies: - - supports-color + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 - spdy@4.0.2: - dependencies: - debug: 4.4.3 - handle-thing: 2.0.1 - http-deceiver: 1.2.7 - select-hose: 2.0.0 - spdy-transport: 3.0.0 - transitivePeerDependencies: - - supports-color + fflate@0.8.3: {} - sprintf-js@1.0.3: {} + file-type@10.11.0: {} - stable@0.1.8: {} + filename-reserved-regex@2.0.0: {} - stack-utils@2.0.6: + filenamify@4.3.0: dependencies: - escape-string-regexp: 2.0.0 + filename-reserved-regex: 2.0.0 + strip-outer: 1.0.1 + trim-repeated: 1.0.0 - stackframe@1.3.4: {} + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 - static-eval@2.1.1: + find-cache-dir@3.3.2: dependencies: - escodegen: 2.1.0 + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 - statuses@1.5.0: {} + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 - statuses@2.0.1: {} + fraction.js@4.3.4: {} - stop-iteration-iterator@1.1.0: + fs-extra@8.1.0: dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 - streamx@2.26.0: - dependencies: - events-universal: 1.0.1 - fast-fifo: 1.3.2 - text-decoder: 1.2.7 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a + fs.realpath@1.0.0: {} - string-convert@0.2.1: {} + fsevents@2.3.3: + optional: true - string-length@4.0.2: - dependencies: - char-regex: 1.0.2 - strip-ansi: 6.0.1 + function-bind@1.1.2: + optional: true + + gensync@1.0.0-beta.2: {} - string-length@5.0.1: + geotiff@3.0.5: dependencies: - char-regex: 2.0.2 - strip-ansi: 7.2.0 + '@petamoriken/float16': 3.9.3 + lerc: 3.0.0 + pako: 2.1.0 + parse-headers: 2.0.6 + quick-lru: 6.1.2 + web-worker: 1.5.0 + xml-utils: 1.10.2 + zstddec: 0.2.0 - string-natural-compare@3.0.1: {} + get-nonce@1.0.1: {} - string-width@4.2.3: + gh-pages@5.0.0: dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 + async: 3.2.6 + commander: 2.20.3 + email-addresses: 5.0.0 + filenamify: 4.3.0 + find-cache-dir: 3.3.2 + fs-extra: 8.1.0 + globby: 6.1.0 - string.prototype.includes@2.0.1: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 + gl-matrix@3.4.4: {} - string.prototype.matchall@4.0.12: + glob-parent@5.1.2: dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - regexp.prototype.flags: 1.5.4 - set-function-name: 2.0.2 - side-channel: 1.1.0 + is-glob: 4.0.3 - string.prototype.repeat@1.0.0: + glob@7.2.3: dependencies: - define-properties: 1.2.1 - es-abstract: 1.24.2 + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 - string.prototype.trim@1.2.10: + globby@6.1.0: dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.2 - has-property-descriptors: 1.0.2 + array-union: 1.0.2 + glob: 7.2.3 + object-assign: 4.1.1 + pify: 2.3.0 + pinkie-promise: 2.0.1 - string.prototype.trimend@1.0.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 + graceful-fs@4.2.11: {} - string.prototype.trimstart@1.0.8: + hasown@2.0.4: dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-object-atoms: 1.1.2 + function-bind: 1.1.2 + optional: true - string_decoder@1.1.1: + html-encoding-sniffer@7.0.0(@noble/hashes@2.4.0): dependencies: - safe-buffer: 5.1.2 + '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) + transitivePeerDependencies: + - '@noble/hashes' - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 + husky@9.1.7: {} - stringify-object@3.3.0: + image-type@4.1.0: dependencies: - get-own-enumerable-property-symbols: 3.0.2 - is-obj: 1.0.1 - is-regexp: 1.0.0 + file-type: 10.11.0 - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 + indent-string@4.0.0: {} - strip-ansi@7.2.0: + inflight@1.0.6: dependencies: - ansi-regex: 6.2.2 + once: 1.4.0 + wrappy: 1.0.2 - strip-bom@3.0.0: {} + inherits@2.0.4: {} - strip-bom@4.0.0: {} + iota-array@1.0.0: {} - strip-comments@2.0.1: {} + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 - strip-final-newline@2.0.0: {} + is-buffer@1.1.6: {} - strip-indent@3.0.0: + is-core-module@2.16.2: dependencies: - min-indent: 1.0.1 + hasown: 2.0.4 + optional: true - strip-json-comments@3.1.1: {} + is-extglob@2.1.1: {} - strip-outer@1.0.1: + is-glob@4.0.3: dependencies: - escape-string-regexp: 1.0.5 + is-extglob: 2.1.1 - style-loader@3.3.4(webpack@5.107.2(postcss@8.5.25)): - dependencies: - webpack: 5.107.2(postcss@8.5.25) + is-number@7.0.0: {} - stylehacks@5.1.1(postcss@8.5.25): - dependencies: - browserslist: 4.28.7 - postcss: 8.5.25 - postcss-selector-parser: 6.1.2 + is-potential-custom-element-name@1.0.1: {} - sucrase@3.35.1: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.17 - ts-interface-checker: 0.1.13 + javascript-natural-sort@0.7.1: {} - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 + jiti@2.7.0: {} - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 + js-tokens@4.0.0: {} - supports-hyperlinks@2.3.0: + jsdom@30.1.1(@noble/hashes@2.4.0): dependencies: - has-flag: 4.0.0 - supports-color: 7.2.0 + '@asamuzakjp/css-color': 7.1.2 + '@asamuzakjp/dom-selector': 9.2.2 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.14(css-tree@3.2.1) + '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) + css-tree: 3.2.1 + data-urls: 7.0.0(@noble/hashes@2.4.0) + decimal.js: 10.6.0 + html-encoding-sniffer: 7.0.0(@noble/hashes@2.4.0) + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 + parse5: 8.0.1 + saxes: 6.0.0 + tough-cookie: 6.0.2 + undici: 8.11.2 + w3c-xmlserializer: 6.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.1.2(@noble/hashes@2.4.0) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' - supports-preserve-symlinks-flag@1.0.0: {} + jsesc@3.1.0: {} - svg-parser@2.0.4: {} + json5@2.2.3: {} - svgo@2.8.4: - dependencies: - commander: 7.2.0 - css-select: 4.3.0 - css-tree: 1.1.3 - csso: 4.2.0 - picocolors: 1.1.1 - sax: 1.6.0 - stable: 0.1.8 + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 - symbol-tree@3.2.4: {} + jwt-decode@4.0.0: {} - tailwindcss@3.4.19: - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.3 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.7 - lilconfig: 3.1.3 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.1.1 - postcss: 8.5.25 - postcss-import: 15.1.0(postcss@8.5.25) - postcss-js: 4.1.0(postcss@8.5.25) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.25) - postcss-nested: 6.2.0(postcss@8.5.25) - postcss-selector-parser: 6.1.2 - resolve: 1.22.12 - sucrase: 3.35.1 - transitivePeerDependencies: - - tsx - - yaml + lerc@3.0.0: {} - tapable@1.1.3: {} + lerp@1.0.3: {} - tapable@2.3.3: {} + lightningcss-android-arm64@1.32.0: + optional: true - tar-stream@3.1.8: - dependencies: - b4a: 1.8.1 - bare-fs: 4.7.2 - fast-fifo: 1.3.2 - streamx: 2.26.0 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a + lightningcss-android-arm64@1.33.0: + optional: true - teex@1.0.1: - dependencies: - streamx: 2.26.0 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a + lightningcss-darwin-arm64@1.32.0: + optional: true - temp-dir@2.0.0: {} + lightningcss-darwin-arm64@1.33.0: + optional: true - tempy@0.6.0: - dependencies: - is-stream: 2.0.1 - temp-dir: 2.0.0 - type-fest: 0.16.0 - unique-string: 2.0.0 + lightningcss-darwin-x64@1.32.0: + optional: true - terminal-link@2.1.1: - dependencies: - ansi-escapes: 4.3.2 - supports-hyperlinks: 2.3.0 + lightningcss-darwin-x64@1.33.0: + optional: true - terser-webpack-plugin@5.6.1(postcss@8.5.25)(webpack@5.107.2(postcss@8.5.25)): - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.48.0 - webpack: 5.107.2(postcss@8.5.25) - optionalDependencies: - postcss: 8.5.25 + lightningcss-freebsd-x64@1.32.0: + optional: true - terser@5.48.0: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 - commander: 2.20.3 - source-map-support: 0.5.21 + lightningcss-freebsd-x64@1.33.0: + optional: true - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.6 - glob: 7.2.3 - minimatch: 3.1.5 + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true - text-decoder@1.2.7: - dependencies: - b4a: 1.8.1 - transitivePeerDependencies: - - react-native-b4a + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true - text-table@0.2.0: {} + lightningcss-linux-arm64-gnu@1.32.0: + optional: true - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 + lightningcss-linux-arm64-gnu@1.33.0: + optional: true - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 + lightningcss-linux-arm64-musl@1.32.0: + optional: true - thingies@2.6.1(tslib@2.8.1): - dependencies: - tslib: 2.8.1 + lightningcss-linux-arm64-musl@1.33.0: + optional: true - throat@6.0.2: {} + lightningcss-linux-x64-gnu@1.32.0: + optional: true - throttle-debounce@5.0.2: {} + lightningcss-linux-x64-gnu@1.33.0: + optional: true - thunky@1.1.0: {} + lightningcss-linux-x64-musl@1.32.0: + optional: true - tiny-emitter@2.1.0: {} + lightningcss-linux-x64-musl@1.33.0: + optional: true - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true - tmpl@1.0.5: {} + lightningcss-win32-x64-msvc@1.32.0: + optional: true - to-regex-range@5.0.1: + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.32.0: dependencies: - is-number: 7.0.0 + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 - toggle-selection@1.0.6: {} + lodash.clonedeep@4.5.0: {} - toidentifier@1.0.1: {} + lodash.debounce@4.0.8: + optional: true - tough-cookie@4.1.4: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 + loglevel@1.9.2: {} - tr46@1.0.1: - dependencies: - punycode: 2.3.1 + lru-cache@11.5.3: {} - tr46@2.1.0: + lru-cache@5.1.1: dependencies: - punycode: 2.3.1 + yallist: 3.1.1 - tree-dump@1.1.0(tslib@2.8.1): + lru-cache@6.0.0: dependencies: - tslib: 2.8.1 + yallist: 4.0.0 - trim-repeated@1.0.0: + lucide-react@1.49.0(@types/react@19.3.0)(react@19.3.0): dependencies: - escape-string-regexp: 1.0.5 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - tryer@1.0.1: {} + lz-string@1.5.0: {} - ts-interface-checker@0.1.13: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 - ts-node@10.9.2(@types/node@14.18.63)(typescript@4.9.5): + magic-string@1.4.2: dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 14.18.63 - acorn: 8.16.0 - acorn-walk: 8.3.5 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 4.9.5 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 + '@jridgewell/sourcemap-codec': 1.6.0 - tsconfig-paths@3.15.0: + make-dir@3.1.0: dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 + semver: 7.5.2 - tslib@1.14.1: {} + mathjs@11.12.0: + dependencies: + '@babel/runtime': 7.29.7 + complex.js: 2.4.3 + decimal.js: 10.6.0 + escape-latex: 1.2.0 + fraction.js: 4.3.4 + javascript-natural-sort: 0.7.1 + seedrandom: 3.0.5 + tiny-emitter: 2.1.0 + typed-function: 4.2.2 - tslib@2.8.1: {} + mdn-data@2.27.1: {} - tsutils@3.21.0(typescript@4.9.5): - dependencies: - tslib: 1.14.1 - typescript: 4.9.5 + min-indent@1.0.1: {} - type-check@0.4.0: + minimatch@3.1.5: dependencies: - prelude-ls: 1.2.1 + brace-expansion: 1.1.21 - type-detect@4.0.8: {} + ms@2.1.3: {} - type-fest@0.16.0: {} + nanoid@3.3.19: {} - type-fest@0.20.2: {} + ndarray@1.0.19: + dependencies: + iota-array: 1.0.0 + is-buffer: 1.1.6 - type-fest@0.21.3: {} + node-releases@2.0.57: {} - type-fest@4.41.0: - optional: true + normalize-path@3.0.0: {} - type-is@1.6.18: + numcodecs@0.3.2: dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 + fflate: 0.8.3 - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 + object-assign@4.1.1: {} - typed-array-byte-length@1.0.3: + obug@2.2.1: {} + + oidc-client-ts@3.5.0: dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 + jwt-decode: 4.0.0 - typed-array-byte-offset@1.0.4: + ol@10.9.0: dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 + '@types/rbush': 4.0.0 + earcut: 3.0.2 + geotiff: 3.0.5 + pbf: 4.0.1 + rbush: 4.0.1 + zarrita: 0.7.3 - typed-array-length@1.0.8: + once@1.4.0: dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 + wrappy: 1.0.2 - typed-function@4.2.2: {} + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 - typedarray-to-buffer@3.1.5: + p-locate@4.1.0: dependencies: - is-typedarray: 1.0.0 + p-limit: 2.3.0 - typescript@4.9.5: {} + p-map@7.0.8: {} - unbox-primitive@1.1.0: - dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 + p-try@2.2.0: {} - underscore@1.13.8: {} + pako@2.1.0: {} - unicode-canonical-property-names-ecmascript@2.0.1: {} + parse-headers@2.0.6: {} - unicode-match-property-ecmascript@2.0.0: + parse5@8.0.1: dependencies: - unicode-canonical-property-names-ecmascript: 2.0.1 - unicode-property-aliases-ecmascript: 2.2.0 + entities: 8.1.0 - unicode-match-property-value-ecmascript@2.2.1: {} + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} - unicode-property-aliases-ecmascript@2.2.0: {} + path-parse@1.0.7: + optional: true - unique-string@2.0.0: + pbf@4.0.1: dependencies: - crypto-random-string: 2.0.0 + resolve-protobuf-schema: 2.1.0 - universalify@0.1.2: {} + picocolors@1.1.1: {} - universalify@0.2.0: {} + picomatch@2.3.2: {} - universalify@2.0.1: {} + picomatch@4.0.4: {} - unpipe@1.0.0: {} + picomatch@4.0.7: {} - unzipit@2.0.0: {} + pify@2.3.0: {} + + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 - upath@1.2.0: {} + pinkie@2.0.4: {} - update-browserslist-db@1.2.3(browserslist@4.28.7): + pkg-dir@4.2.0: dependencies: - browserslist: 4.28.7 - escalade: 3.2.0 - picocolors: 1.1.1 + find-up: 4.1.0 - uri-js@4.4.1: + postcss@8.5.28: dependencies: - punycode: 2.3.1 + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.1 - url-parse@1.5.10: + pretty-format@27.5.1: dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 - util-deprecate@1.0.2: {} + protocol-buffers-schema@3.6.1: {} - utila@0.4.0: {} + punycode@2.3.1: {} - utils-merge@1.0.1: {} + quick-lru@6.1.2: {} - uuid@8.3.2: {} + quickselect@3.0.0: {} - uuid@9.0.1: {} + rbush@4.0.1: + dependencies: + quickselect: 3.0.0 - v8-compile-cache-lib@3.0.1: {} + react-dom@19.3.0(react@19.3.0): + dependencies: + react: 19.3.0 + scheduler: 0.28.0 - v8-to-istanbul@8.1.1: + react-error-boundary@6.1.6(@types/react@19.3.0)(react@19.3.0): dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - convert-source-map: 1.9.0 - source-map: 0.7.6 + react: 19.3.0 + optionalDependencies: + '@types/react': 19.3.0 - vary@1.1.2: {} + react-is@17.0.2: {} - w3c-hr-time@1.0.2: + react-remove-scroll-bar@2.3.8(@types/react@19.3.0)(react@19.3.0): dependencies: - browser-process-hrtime: 1.0.0 + react: 19.3.0 + react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.3.0 - w3c-xmlserializer@2.0.0: + react-remove-scroll@2.7.2(@types/react@19.3.0)(react@19.3.0): dependencies: - xml-name-validator: 3.0.0 + react: 19.3.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.3.0)(react@19.3.0) + react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.3.0)(react@19.3.0) + use-sidecar: 1.1.3(@types/react@19.3.0)(react@19.3.0) + optionalDependencies: + '@types/react': 19.3.0 - walker@1.0.8: + react-router@8.4.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: - makeerror: 1.0.12 + '@remix-run/route-pattern': 0.22.1 + cookie-es: 3.1.1 + react: 19.3.0 + optionalDependencies: + react-dom: 19.3.0(react@19.3.0) - watchpack@2.5.1: + react-style-singleton@2.2.3(@types/react@19.3.0)(react@19.3.0): dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 + get-nonce: 1.0.1 + react: 19.3.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.3.0 - wbuf@1.7.3: - dependencies: - minimalistic-assert: 1.0.1 + react@19.3.0: {} - web-worker@1.5.0: {} + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 - webidl-conversions@4.0.2: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 - webidl-conversions@5.0.0: {} + reference-spec-reader@0.2.0: {} - webidl-conversions@6.1.0: {} + require-from-string@2.0.2: {} - webpack-dev-middleware@7.4.6(webpack@5.107.2(postcss@8.5.25)): + resolve-protobuf-schema@2.1.0: dependencies: - colorette: 2.0.20 - memfs: 4.79.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - range-parser: 1.2.1 - schema-utils: 4.3.3 - optionalDependencies: - webpack: 5.107.2(postcss@8.5.25) - - webpack-dev-server@4.15.2(webpack@5.107.2(postcss@8.5.25)): - dependencies: - '@types/bonjour': 3.5.13 - '@types/connect-history-api-fallback': 1.5.4 - '@types/express': 4.17.25 - '@types/serve-index': 1.9.4 - '@types/serve-static': 1.15.10 - '@types/sockjs': 0.3.36 - '@types/ws': 8.18.1 - ansi-html-community: 0.0.8 - bonjour-service: 1.4.0 - chokidar: 3.6.0 - colorette: 2.0.20 - compression: 1.8.1 - connect-history-api-fallback: 2.0.0 - default-gateway: 6.0.3 - express: 4.21.0 - graceful-fs: 4.2.11 - html-entities: 2.6.0 - http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.4.0 - launch-editor: 2.14.1 - open: 8.4.2 - p-retry: 4.6.2 - rimraf: 3.0.2 - schema-utils: 4.3.3 - selfsigned: 2.4.1 - serve-index: 1.9.2 - sockjs: 0.3.24 - spdy: 4.0.2 - webpack-dev-middleware: 7.4.6(webpack@5.107.2(postcss@8.5.25)) - ws: 8.21.0 - optionalDependencies: - webpack: 5.107.2(postcss@8.5.25) - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate + protocol-buffers-schema: 3.6.1 - webpack-manifest-plugin@4.1.1(webpack@5.107.2(postcss@8.5.25)): + resolve@1.22.12: dependencies: - tapable: 2.3.3 - webpack: 5.107.2(postcss@8.5.25) - webpack-sources: 2.3.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + optional: true + + retry@0.13.1: {} - webpack-merge@4.2.2: + rolldown@1.2.11: dependencies: - lodash: 4.18.1 + '@oxc-project/types': 0.151.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.11 + '@rolldown/binding-android-arm64': 1.2.11 + '@rolldown/binding-darwin-arm64': 1.2.11 + '@rolldown/binding-darwin-x64': 1.2.11 + '@rolldown/binding-freebsd-x64': 1.2.11 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.11 + '@rolldown/binding-linux-arm64-gnu': 1.2.11 + '@rolldown/binding-linux-arm64-musl': 1.2.11 + '@rolldown/binding-linux-ppc64-gnu': 1.2.11 + '@rolldown/binding-linux-s390x-gnu': 1.2.11 + '@rolldown/binding-linux-x64-gnu': 1.2.11 + '@rolldown/binding-linux-x64-musl': 1.2.11 + '@rolldown/binding-openharmony-arm64': 1.2.11 + '@rolldown/binding-win32-arm64-msvc': 1.2.11 + '@rolldown/binding-win32-x64-msvc': 1.2.11 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.28.0: {} - webpack-sources@1.4.3: + seedrandom@3.0.5: {} + + semver@7.5.2: dependencies: - source-list-map: 2.0.1 - source-map: 0.6.1 + lru-cache: 6.0.0 + + source-map-js@1.2.1: {} - webpack-sources@2.3.1: + source-map-support@0.5.21: dependencies: - source-list-map: 2.0.1 + buffer-from: 1.1.2 source-map: 0.6.1 + optional: true + + source-map@0.6.1: + optional: true - webpack-sources@3.5.0: {} + std-env@4.3.0: {} - webpack@5.107.2(postcss@8.5.25): + strip-indent@3.0.0: dependencies: - '@types/estree': 1.0.9 - '@types/json-schema': 7.0.15 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.16.0 - acorn-import-phases: 1.0.4(acorn@8.16.0) - browserslist: 4.28.7 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.22.2 - es-module-lexer: 2.1.0 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - loader-runner: 4.3.2 - mime-db: 1.54.0 - neo-async: 2.6.2 - schema-utils: 4.3.3 - tapable: 2.3.3 - terser-webpack-plugin: 5.6.1(postcss@8.5.25)(webpack@5.107.2(postcss@8.5.25)) - watchpack: 2.5.1 - webpack-sources: 3.5.0 - transitivePeerDependencies: - - '@minify-html/node' - - '@swc/core' - - '@swc/css' - - '@swc/html' - - clean-css - - cssnano - - csso - - esbuild - - html-minifier-terser - - lightningcss - - postcss - - uglify-js + min-indent: 1.0.1 - websocket-driver@0.7.5: + strip-outer@1.0.1: dependencies: - http-parser-js: 0.5.10 - safe-buffer: 5.2.1 - websocket-extensions: 0.1.4 + escape-string-regexp: 1.0.5 - websocket-extensions@0.1.4: {} + supports-preserve-symlinks-flag@1.0.0: + optional: true - whatwg-encoding@1.0.5: - dependencies: - iconv-lite: 0.4.24 + tailwind-merge@3.7.0: {} - whatwg-fetch@3.6.20: {} + tailwindcss@4.3.3: {} - whatwg-mimetype@2.3.0: {} + tapable@2.3.3: {} - whatwg-url@7.1.0: + terser@5.48.0: dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + optional: true - whatwg-url@8.7.0: - dependencies: - lodash: 4.18.1 - tr46: 2.1.0 - webidl-conversions: 6.1.0 + tiny-emitter@2.1.0: {} + + tinybench@6.2.0: {} - which-boxed-primitive@1.1.1: + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 - which-builtin-type@1.2.1: + tldts-core@7.4.16: {} + + tldts@7.4.16: dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.1.8 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.2 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.21 + tldts-core: 7.4.16 - which-collection@1.0.2: + to-regex-range@5.0.1: dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 + is-number: 7.0.0 - which-typed-array@1.1.21: + tough-cookie@6.0.2: dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 + tldts: 7.4.16 - which@1.3.1: + tr46@6.0.0: dependencies: - isexe: 2.0.0 + punycode: 2.3.1 - which@2.0.2: + trim-repeated@1.0.0: dependencies: - isexe: 2.0.0 + escape-string-regexp: 1.0.5 - word-wrap@1.2.5: {} + tslib@2.8.1: {} - workbox-background-sync@6.6.0: - dependencies: - idb: 7.1.1 - workbox-core: 6.6.0 + tw-animate-css@1.4.0: {} - workbox-broadcast-update@6.6.0: - dependencies: - workbox-core: 6.6.0 + typed-function@4.2.2: {} - workbox-build@6.6.0(@types/babel__core@7.20.5): - dependencies: - '@apideck/better-ajv-errors': 0.3.7(ajv@8.20.0) - '@babel/core': 7.29.7 - '@babel/preset-env': 7.29.7(@babel/core@7.29.7) - '@babel/runtime': 7.29.7 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@3.30.0) - '@rollup/plugin-node-resolve': 11.2.1(rollup@3.30.0) - '@rollup/plugin-replace': 2.4.2(rollup@3.30.0) - '@surma/rollup-plugin-off-main-thread': 2.2.3 - ajv: 8.20.0 - common-tags: 1.8.2 - fast-json-stable-stringify: 2.1.0 - fs-extra: 9.1.0 - glob: 7.2.3 - lodash: 4.18.1 - pretty-bytes: 5.6.0 - rollup: 3.30.0 - rollup-plugin-terser: 7.0.2(rollup@3.30.0) - source-map: 0.8.0-beta.0 - stringify-object: 3.3.0 - strip-comments: 2.0.1 - tempy: 0.6.0 - upath: 1.2.0 - workbox-background-sync: 6.6.0 - workbox-broadcast-update: 6.6.0 - workbox-cacheable-response: 6.6.0 - workbox-core: 6.6.0 - workbox-expiration: 6.6.0 - workbox-google-analytics: 6.6.0 - workbox-navigation-preload: 6.6.0 - workbox-precaching: 6.6.0 - workbox-range-requests: 6.6.0 - workbox-recipes: 6.6.0 - workbox-routing: 6.6.0 - workbox-strategies: 6.6.0 - workbox-streams: 6.6.0 - workbox-sw: 6.6.0 - workbox-window: 6.6.0 - transitivePeerDependencies: - - '@types/babel__core' - - supports-color + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + undici-types@7.24.6: {} + + undici@8.11.2: {} - workbox-cacheable-response@6.6.0: - dependencies: - workbox-core: 6.6.0 + universalify@0.1.2: {} - workbox-core@6.6.0: {} + unzipit@2.0.0: {} - workbox-expiration@6.6.0: + update-browserslist-db@1.2.3(browserslist@4.28.7): dependencies: - idb: 7.1.1 - workbox-core: 6.6.0 + browserslist: 4.28.7 + escalade: 3.2.0 + picocolors: 1.1.1 - workbox-google-analytics@6.6.0: + use-callback-ref@1.3.3(@types/react@19.3.0)(react@19.3.0): dependencies: - workbox-background-sync: 6.6.0 - workbox-core: 6.6.0 - workbox-routing: 6.6.0 - workbox-strategies: 6.6.0 + react: 19.3.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.3.0 - workbox-navigation-preload@6.6.0: + use-sidecar@1.1.3(@types/react@19.3.0)(react@19.3.0): dependencies: - workbox-core: 6.6.0 + detect-node-es: 1.1.0 + react: 19.3.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.3.0 - workbox-precaching@6.6.0: - dependencies: - workbox-core: 6.6.0 - workbox-routing: 6.6.0 - workbox-strategies: 6.6.0 + uuid@8.3.2: {} - workbox-range-requests@6.6.0: - dependencies: - workbox-core: 6.6.0 + uuid@9.0.1: {} - workbox-recipes@6.6.0: + vite-plugin-static-copy@4.1.1(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)): dependencies: - workbox-cacheable-response: 6.6.0 - workbox-core: 6.6.0 - workbox-expiration: 6.6.0 - workbox-precaching: 6.6.0 - workbox-routing: 6.6.0 - workbox-strategies: 6.6.0 + chokidar: 3.6.0 + p-map: 7.0.8 + picocolors: 1.1.1 + tinyglobby: 0.2.17 + vite: 8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1) - workbox-routing@6.6.0: + vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1): dependencies: - workbox-core: 6.6.0 + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.11 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.19.0 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.48.0 + yaml: 2.9.1 + + vitest@5.0.3(@types/node@24.19.0)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1)) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.2 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.1(@types/node@24.19.0)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.1) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 24.19.0 + jsdom: 30.1.1(@noble/hashes@2.4.0) + transitivePeerDependencies: + - msw - workbox-strategies@6.6.0: + w3c-xmlserializer@6.0.0: dependencies: - workbox-core: 6.6.0 + xml-name-validator: 5.0.0 - workbox-streams@6.6.0: - dependencies: - workbox-core: 6.6.0 - workbox-routing: 6.6.0 + web-worker@1.5.0: {} - workbox-sw@6.6.0: {} + webidl-conversions@8.0.1: {} - workbox-webpack-plugin@6.6.0(@types/babel__core@7.20.5)(webpack@5.107.2(postcss@8.5.25)): + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1(@noble/hashes@2.4.0): dependencies: - fast-json-stable-stringify: 2.1.0 - pretty-bytes: 5.6.0 - upath: 1.2.0 - webpack: 5.107.2(postcss@8.5.25) - webpack-sources: 1.4.3 - workbox-build: 6.6.0(@types/babel__core@7.20.5) + '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) + tr46: 6.0.0 + webidl-conversions: 8.0.1 transitivePeerDependencies: - - '@types/babel__core' - - supports-color + - '@noble/hashes' - workbox-window@6.6.0: + whatwg-url@17.1.2(@noble/hashes@2.4.0): dependencies: - '@types/trusted-types': 2.0.7 - workbox-core: 6.6.0 + '@exodus/bytes': 1.16.0(@noble/hashes@2.4.0) + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + why-is-node-running@3.2.1: {} wrappy@1.0.2: {} - write-file-atomic@3.0.3: - dependencies: - imurmurhash: 0.1.4 - is-typedarray: 1.0.0 - signal-exit: 3.0.7 - typedarray-to-buffer: 3.1.5 - - ws@7.5.11: {} - - ws@8.21.0: {} - - xml-name-validator@3.0.0: {} + xml-name-validator@5.0.0: {} xml-utils@1.10.2: {} xmlchars@2.2.0: {} - y18n@5.0.8: {} - yallist@3.1.1: {} yallist@4.0.0: {} - yaml@1.10.3: {} - - yargs-parser@20.2.9: {} - - yargs@16.2.0: - dependencies: - cliui: 7.0.4 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.9 - - yn@3.1.1: {} - - yocto-queue@0.1.0: {} + yaml@2.9.1: + optional: true zarrita@0.7.3: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fb5cf220..c6b11857 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,49 +2,21 @@ shamefullyHoist: true strictPeerDependencies: false allowBuilds: - '@parcel/watcher': true - core-js: true core-js-pure: true dicom-microscopy-viewer: true overrides: '@types/d3-dispatch': 3.0.6 - # Dependabot high: axios inherited-proxy after interceptor cloning - axios: 1.18.0 # Dependabot high: adm-zip unbounded memory on crafted ZIP adm-zip: 0.6.1 - # Dependabot high: brace-expansion DoS (v1 and v2 lines) + # Dependabot high: brace-expansion DoS brace-expansion@1: 1.1.21 - brace-expansion@2: 2.1.7 - # Dependabot high: js-yaml quadratic merge-key chains (v3 and v4 lines) - js-yaml@3: 3.15.2 - js-yaml@4: 4.3.2 - # Dependabot high: postcss sourceMappingURL path traversal (no 7.x patch; force 8.x) - postcss: 8.5.25 # Dependabot high: nanoid generators can loop indefinitely (GHSA-28wg-ghj8-5hjv, GHSA-2v37-7h3g-55p8) nanoid@3: 3.3.19 - # Dependabot high: shell-quote parse() quadratic DoS - shell-quote: 1.9.0 - # Dependabot high: fast-uri authority injection & host confusion (CVE-2026-84292, CVE-2026-84394) - fast-uri: 3.1.8 - # Dependabot high: SVGO removeScripts (CRA still pulls 1.x via @svgr; no SVG imports in app) - svgo: 2.8.4 # Dependabot high: browserslist RegExp ReDoS browserslist: 4.28.7 - # Dependabot high: webpack-dev-middleware path traversal (GHSA-g84c-rxfj-3j2c; no 5.x patch, CRA's dev server 4.x runs on 7.x) - webpack-dev-middleware: 7.4.6 - nth-check: 2.0.1 - wrap-ansi: 7.0.0 make-dir: 3.1.0 - axios>form-data: 4.0.6 - express: 4.21.0 - jsdom>form-data: 3.0.5 - path-to-regexp: 0.1.13 - rollup: 3.30.0 semver: 7.5.2 - serialize-javascript: 7.0.5 - tar: 7.5.16 - underscore: 1.13.8 minimumReleaseAgeExclude: - dicom-microscopy-viewer@0.48.23 || 0.48.24 || 0.48.25 || 0.48.26 || 0.48.27 diff --git a/public/config/local.js b/public/config/local.js index 75484f93..34869073 100644 --- a/public/config/local.js +++ b/public/config/local.js @@ -11,7 +11,7 @@ window.config = { ], disableWorklist: false, disableAnnotationTools: false, - mode: 'light', + mode: 'dark', preload: true, annotations: [ { diff --git a/public/config/preview.js b/public/config/preview.js index abfb0c3b..4d0c85b5 100644 --- a/public/config/preview.js +++ b/public/config/preview.js @@ -10,7 +10,7 @@ window.config = { disableWorklist: false, disableAnnotationTools: false, enableServerSelection: true, - mode: 'light', + mode: 'dark', preload: true, annotations: [ { diff --git a/public/favicon.ico b/public/favicon.ico index 20be4358..ed8d8f8d 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 00000000..3f3ccb6a --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/logo192.png b/public/logo192.png index 2a07ffca..59f52404 100644 Binary files a/public/logo192.png and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png index 2ae138df..d5ea3d59 100644 Binary files a/public/logo512.png and b/public/logo512.png differ diff --git a/public/manifest.json b/public/manifest.json index c4e89d32..a2686af1 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -4,9 +4,14 @@ "icons": [ { "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", + "sizes": "64x64 48x48 32x32 16x16", "type": "image/x-icon" }, + { + "src": "favicon.svg", + "sizes": "any", + "type": "image/svg+xml" + }, { "src": "logo192.png", "type": "image/png", @@ -20,6 +25,6 @@ ], "start_url": ".", "display": "standalone", - "theme_color": "#000000", + "theme_color": "#3d4f6b", "background_color": "#ffffff" } diff --git a/scripts/check-react-compiler.mjs b/scripts/check-react-compiler.mjs new file mode 100644 index 00000000..f840a122 --- /dev/null +++ b/scripts/check-react-compiler.mjs @@ -0,0 +1,135 @@ +#!/usr/bin/env node +/** + * Runs babel-plugin-react-compiler over every component and hook in src/ and + * exits non-zero when the compiler bails out on any of them. A bailout means + * the function breaks a Rule of React (refs read during render, mutated + * props or state, conditional hooks, ...) and ships without memoization. + * + * Functions that opt out with a 'use no memo' directive are listed but do not + * fail the check; the directive must carry a comment explaining why. + */ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import babel from '@babel/core' + +const OPT_OUT_DIRECTIVES = new Set(['use no memo', 'use no forget']) + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const isTestFile = (file) => + /(^|\/)(__tests__|__mocks__|testing)\//.test(file) || + /\.test\.tsx?$/.test(file) + +const files = fs + .globSync(['src/**/*.tsx', 'src/**/use*.ts'], { cwd: root }) + .filter((file) => !isTestFile(file)) + .sort() + +const locKey = (loc) => `${loc.start.line}:${loc.start.column}` + +/** `line:column` of every function whose body opts out of the compiler */ +function findOptedOutFunctions(ast) { + const optedOut = new Set() + babel.traverse(ast, { + Function(fnPath) { + const { body, loc } = fnPath.node + if ( + loc != null && + body.type === 'BlockStatement' && + body.directives.some((d) => OPT_OUT_DIRECTIVES.has(d.value.value)) + ) { + optedOut.add(locKey(loc)) + } + }, + }) + return optedOut +} + +function describe(event) { + if (event.kind === 'PipelineError') { + return { loc: event.fnLoc, reason: event.data } + } + const { detail } = event + const loc = + (typeof detail.primaryLocation === 'function' + ? detail.primaryLocation() + : detail.loc) ?? event.fnLoc + const description = detail.description ? ` ${detail.description}` : '' + return { loc, reason: `${detail.reason}${description}` } +} + +const COMPILER_PROBLEMS = new Set([ + 'CompileError', + 'CompileDiagnostic', + 'PipelineError', +]) + +/** Compile one file and sort its compiler events into results */ +function checkFile(file) { + const result = { compiled: 0, bailouts: [], optOuts: [] } + const filename = path.join(root, file) + const code = fs.readFileSync(filename, 'utf8') + const ast = babel.parseSync(code, { + babelrc: false, + configFile: false, + filename, + parserOpts: { + plugins: file.endsWith('.tsx') ? ['typescript', 'jsx'] : ['typescript'], + }, + }) + const optedOut = findOptedOutFunctions(ast) + + const logger = { + logEvent(_filename, event) { + if (event.kind === 'CompileSuccess') { + result.compiled += 1 + return + } + if (!COMPILER_PROBLEMS.has(event.kind)) { + return + } + const { loc, reason } = describe(event) + const entry = { file, line: loc?.start.line, reason } + const isOptOut = event.fnLoc != null && optedOut.has(locKey(event.fnLoc)) + ;(isOptOut ? result.optOuts : result.bailouts).push(entry) + }, + } + + babel.transformFromAstSync(ast, code, { + babelrc: false, + configFile: false, + filename, + code: false, + plugins: [ + ['babel-plugin-react-compiler', { panicThreshold: 'none', logger }], + ], + }) + return result +} + +const results = files.map(checkFile) +const compiled = results.reduce((sum, result) => sum + result.compiled, 0) +const bailouts = results.flatMap((result) => result.bailouts) +const optOuts = results.flatMap((result) => result.optOuts) + +const print = ({ file, line, reason }) => ` ${file}:${line ?? '?'} ${reason}` +const writeLines = (stream, lines) => stream.write(`${lines.join('\n')}\n`) + +if (optOuts.length > 0) { + writeLines(process.stdout, [ + `Opted out with 'use no memo' (${optOuts.length}):`, + ...optOuts.map(print), + ]) +} + +if (bailouts.length > 0) { + writeLines(process.stderr, [ + `React Compiler bailed out (${bailouts.length}):`, + ...bailouts.map(print), + ]) + process.exit(1) +} + +writeLines(process.stdout, [ + `React Compiler: ${compiled} components/hooks compiled in ${files.length} files, no bailouts.`, +]) diff --git a/scripts/inject-slim-env.mjs b/scripts/inject-slim-env.mjs index 16c3cd7b..930e5f53 100644 --- a/scripts/inject-slim-env.mjs +++ b/scripts/inject-slim-env.mjs @@ -2,97 +2,27 @@ /** * Writes public/config/env.js from process env + .env. * Configs read window.slim.env.VAR_NAME. - * Only an allowlisted set of SLIM_* keys is exported (avoids leaking unrelated secrets). */ import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' +import { resolveSlimEnv, slimEnvScript } from './slimEnv.mjs' + const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const outPath = path.join(root, 'public/config/env.js') -const DEFAULT_LOCAL_DICOMWEB_URL = - 'http://localhost:8008/dcm4chee-arc/aets/DCM4CHEE/rs' - -const ALLOWED_SLIM_ENV_KEYS = [ - 'SLIM_LOCAL_DICOMWEB_URL', - 'SLIM_DEMO_DICOMWEB_URL', - 'SLIM_PREVIEW_DICOMWEB_URL', -] - -const requiredUrlByConfig = { - local: 'SLIM_LOCAL_DICOMWEB_URL', - demo: 'SLIM_DEMO_DICOMWEB_URL', - preview: 'SLIM_PREVIEW_DICOMWEB_URL', -} - -function loadDotEnvInto(file, target) { - if (!fs.existsSync(file)) { - return - } - for (const line of fs.readFileSync(file, 'utf8').split(/\r?\n/)) { - const trimmed = line.trim() - if (!trimmed || trimmed.startsWith('#')) { - continue - } - const eq = trimmed.indexOf('=') - if (eq === -1) { - continue - } - const key = trimmed.slice(0, eq).trim() - let val = trimmed.slice(eq + 1).trim() - if ( - (val.startsWith("'") && val.endsWith("'")) || - (val.startsWith('"') && val.endsWith('"')) - ) { - val = val.slice(1, -1) - } - target[key] = val - } -} - -const fileValues = {} -loadDotEnvInto(path.join(root, '.env'), fileValues) - -for (const [key, value] of Object.entries(fileValues)) { - if (process.env[key] === undefined) { - process.env[key] = value - } -} - -// Safe public default for docker-compose / fresh clones / CI unit builds. -if (!process.env.SLIM_LOCAL_DICOMWEB_URL) { - process.env.SLIM_LOCAL_DICOMWEB_URL = DEFAULT_LOCAL_DICOMWEB_URL -} - -const slimEnv = {} -for (const key of ALLOWED_SLIM_ENV_KEYS) { - const value = process.env[key] - if (value !== undefined && value !== '') { - slimEnv[key] = value - } -} - -const configName = process.env.REACT_APP_CONFIG || 'local' -const requiredKey = requiredUrlByConfig[configName] -if (requiredKey && !slimEnv[requiredKey]) { - const ciHint = - requiredKey === 'SLIM_DEMO_DICOMWEB_URL' || - requiredKey === 'SLIM_PREVIEW_DICOMWEB_URL' - ? ` Set GitHub Actions secret or variable ${requiredKey}, or add it to .env.` - : ' Set it in .env (see .env.example).' - console.error( - `${requiredKey} is required when REACT_APP_CONFIG=${configName}.${ciHint}` +const { values, configName, missingKey } = resolveSlimEnv(root, process.env) +if (missingKey !== undefined) { + const hint = + missingKey === 'SLIM_LOCAL_DICOMWEB_URL' + ? ' Set it in .env (see .env.example).' + : ` Set GitHub Actions secret or variable ${missingKey}, or add it to .env.` + process.stderr.write( + `${missingKey} is required when REACT_APP_CONFIG=${configName}.${hint}\n`, ) process.exit(1) } fs.mkdirSync(path.dirname(outPath), { recursive: true }) -fs.writeFileSync( - outPath, - [ - 'window.slim = window.slim || {}', - `window.slim.env = ${JSON.stringify(slimEnv, null, 2)}`, - '', - ].join('\n') -) +fs.writeFileSync(outPath, slimEnvScript(values)) diff --git a/scripts/profile-bulk-annotations.mjs b/scripts/profile-bulk-annotations.mjs deleted file mode 100644 index d77b93c5..00000000 --- a/scripts/profile-bulk-annotations.mjs +++ /dev/null @@ -1,363 +0,0 @@ -/** - * Automated profiling script for bulk annotation rendering performance. - * - * Usage: - * node scripts/profile-bulk-annotations.mjs [studyUrl] - * - * Requires the slim dev server to be running on port 3000. - */ - -import { chromium } from 'playwright' - -const DEFAULT_STUDY_URL = 'http://localhost:3000/studies/2.25.68803095896966276583382138924964839274/series/1.3.6.1.4.1.5962.99.1.1139028448.995765201.1637521600992.2.0?profile=1' - -async function main() { - const studyUrl = process.argv[2] || DEFAULT_STUDY_URL - console.log('Starting profiling session...') - console.log('Study URL:', studyUrl) - - const browser = await chromium.launch({ - headless: true, - args: [ - '--use-gl=angle', - '--use-angle=swiftshader', - '--enable-unsafe-swiftshader', - '--ignore-gpu-blocklist', - '--disable-dev-shm-usage', - ], - }) - - const context = await browser.newContext({ - viewport: { width: 1600, height: 900 }, - }) - - const page = await context.newPage() - - // Capture all console logs for debugging - page.on('console', msg => { - const text = msg.text() - // Log all messages to see what's happening - console.log(`[${msg.type()}] ${text}`) - }) - - console.log('\n1. Loading study...') - await page.goto(studyUrl) - - // Wait for the viewer to load - await page.waitForSelector('.ol-viewport', { timeout: 120000 }) - console.log(' Viewer loaded') - - // Wait for initial render - await page.waitForTimeout(5000) - - // Take screenshot to see the UI - await page.screenshot({ path: '/tmp/profiler-ui.png' }) - console.log(' Screenshot saved to /tmp/profiler-ui.png') - - // First click the annotation group to trigger loading (profiler loads lazily) - console.log('\n2. Finding and clicking annotation group toggle...') - - // First, expand the Annotations submenu by clicking its title - console.log(' Looking for Annotations submenu...') - const annotationsSubmenu = await page.$('text=Annotations >> xpath=ancestor::li[contains(@class, "ant-menu-submenu")]') - if (annotationsSubmenu) { - // Click the submenu title to expand it - const submenuTitle = await annotationsSubmenu.$('.ant-menu-submenu-title') - if (submenuTitle) { - await submenuTitle.click() - console.log(' Clicked to expand Annotations submenu') - await page.waitForTimeout(1000) - } - } - - // Now find and click the first visible switch inside the annotation groups - const visibleSwitches = await page.$$('.ant-switch:visible') - console.log(` Found ${visibleSwitches.length} visible switches`) - - if (visibleSwitches.length > 0) { - // Find first unchecked switch - let clicked = false - for (const switchEl of visibleSwitches) { - const isChecked = await switchEl.evaluate(el => el.classList.contains('ant-switch-checked')) - if (!isChecked) { - await switchEl.click() - console.log(' Clicked an unchecked annotation group switch') - clicked = true - break - } - } - if (!clicked && visibleSwitches.length > 0) { - console.log(' All switches checked, clicking first one') - await visibleSwitches[0].click() - } - } else { - console.log(' WARNING: No visible switches found!') - // Take a debug screenshot - await page.screenshot({ path: '/tmp/profiler-no-switches.png' }) - } - - // Wait for annotation data to start loading (this triggers manager.js import) - console.log('\n3. Waiting for bulk annotation manager to load...') - await page.waitForTimeout(5000) - - // Now check for profiler and wait for it to become available - let profilerAvailable = false - for (let i = 0; i < 20; i++) { - profilerAvailable = await page.evaluate(() => { - return typeof window.__bulkAnnProfiler !== 'undefined' - }) - if (profilerAvailable) break - await page.waitForTimeout(1000) - process.stdout.write('.') - } - console.log('') - console.log(' Profiler available:', profilerAvailable) - - if (!profilerAvailable) { - console.log(' ERROR: Profiler not found on window object after waiting') - await page.screenshot({ path: '/tmp/profiler-debug.png' }) - console.log(' Screenshot saved to /tmp/profiler-debug.png') - await browser.close() - return - } - - // Profiler should be auto-enabled via URL parameter - console.log('\n4. Verifying profiler is enabled...') - const profilerStatus = await page.evaluate(() => { - return { - enabled: window.__bulkAnnProfiler?.isEnabled() ?? false, - metricsCount: window.__bulkAnnProfiler?._metrics?.size ?? 0, - } - }) - console.log(' Profiler status:', profilerStatus) - - if (!profilerStatus.enabled) { - console.log(' Profiler not auto-enabled, enabling manually...') - await page.evaluate(() => { - window.__bulkAnnProfiler.enable() - }) - } - - // Wait for annotations to load - console.log('\n5. Waiting for annotations to load...') - await page.waitForTimeout(10000) - - // Look for annotation group switches specifically in "Annotation Groups" submenu - console.log('\n5a. Finding annotation group switches in Annotation Groups submenu...') - - // Find and expand the "Annotation Groups" submenu (not "Annotations") - // The submenu has key="annotation-groups" and title="Annotation Groups" - const annotationGroupsTitle = page.locator('span:has-text("Annotation Groups")').first() - const annotationGroupsTitleCount = await annotationGroupsTitle.count() - console.log(` Found ${annotationGroupsTitleCount} "Annotation Groups" titles`) - - if (annotationGroupsTitleCount > 0) { - // Click the submenu title to expand it - const submenuTitle = annotationGroupsTitle.locator('xpath=ancestor::div[contains(@class, "ant-menu-submenu-title")]').first() - const titleCount = await submenuTitle.count() - - if (titleCount > 0) { - console.log(' Clicking to expand Annotation Groups submenu...') - await submenuTitle.click() - await page.waitForTimeout(2000) - } else { - // Try clicking the span directly if it's part of the title - console.log(' Clicking Annotation Groups span...') - await annotationGroupsTitle.click() - await page.waitForTimeout(2000) - } - } - - // Take debug screenshot - await page.screenshot({ path: '/tmp/profiler-before-toggle.png' }) - - // Now find switches inside the expanded Annotation Groups submenu - // The submenu should contain switches with eye icons for individual annotation groups - // Look for the master switch in AnnotationGroupList (it's the one that toggles all groups) - const annotationGroupSubmenu = page.locator('li.ant-menu-submenu').filter({ - has: page.locator('span:has-text("Annotation Groups")') - }).first() - - let switched = false - if (await annotationGroupSubmenu.count() > 0) { - // Find all switches within this submenu - const groupSwitches = annotationGroupSubmenu.locator('.ant-switch') - const switchCount = await groupSwitches.count() - console.log(` Found ${switchCount} switches in Annotation Groups submenu`) - - if (switchCount > 0) { - // Click the first switch (master toggle or first group) - console.log(' Clicking annotation group switch...') - const firstSwitch = groupSwitches.first() - const isChecked = await firstSwitch.evaluate(el => el.classList.contains('ant-switch-checked')) - console.log(` Switch is currently: ${isChecked ? 'ON' : 'OFF'}`) - - await firstSwitch.click({ force: true }) - console.log(' Clicked switch') - switched = true - - // Wait for annotation data to load - await page.waitForTimeout(20000) - - // Check profiler metrics - const metricsAfterShow = await page.evaluate(() => ({ - metricsCount: window.__bulkAnnProfiler?._metrics?.size ?? 0, - metricsKeys: window.__bulkAnnProfiler?._metrics ? Array.from(window.__bulkAnnProfiler._metrics.keys()) : [], - })) - console.log(' Metrics after toggle:', metricsAfterShow) - - // Toggle again to capture off/on cycle - if (metricsAfterShow.metricsCount === 0) { - console.log(' No metrics yet, toggling again...') - await firstSwitch.click({ force: true }) - await page.waitForTimeout(3000) - await firstSwitch.click({ force: true }) - await page.waitForTimeout(15000) - - const metricsAfterRetoggle = await page.evaluate(() => ({ - metricsCount: window.__bulkAnnProfiler?._metrics?.size ?? 0, - metricsKeys: window.__bulkAnnProfiler?._metrics ? Array.from(window.__bulkAnnProfiler._metrics.keys()) : [], - })) - console.log(' Metrics after re-toggle:', metricsAfterRetoggle) - } - } - } - - if (!switched) { - console.log(' Could not find Annotation Groups submenu, trying fallback...') - // Fallback: look for any submenu with "Annotation" that's not the ROI Annotations one - const allSubmenus = await page.locator('li.ant-menu-submenu').all() - console.log(` Found ${allSubmenus.length} submenus`) - - for (const submenu of allSubmenus) { - const title = await submenu.locator('.ant-menu-submenu-title').first().textContent() - console.log(` Submenu: "${title}"`) - if (title?.includes('Annotation Group')) { - const switches = submenu.locator('.ant-switch') - if (await switches.count() > 0) { - console.log(' Found switch in this submenu, clicking...') - await switches.first().click({ force: true }) - await page.waitForTimeout(20000) - switched = true - break - } - } - } - } - - await page.screenshot({ path: '/tmp/profiler-after-toggle.png' }) - console.log(' Screenshot after toggle saved') - - // Check metrics again - const metricsAfter = await page.evaluate(() => { - return { - profilerEnabled: window.__bulkAnnProfiler?.isEnabled() ?? false, - metricsSize: window.__bulkAnnProfiler?._metrics?.size ?? 0, - metricsKeys: window.__bulkAnnProfiler?._metrics ? Array.from(window.__bulkAnnProfiler._metrics.keys()) : [], - } - }) - console.log(' Metrics after waiting:', metricsAfter) - - // Check profiler status - const metricsCount = await page.evaluate(() => { - return window.__bulkAnnProfiler._metrics.size - }) - console.log(' Metrics recorded:', metricsCount) - - // Perform pan operations - console.log('\n6. Performing pan operations...') - const viewport = await page.$('.ol-viewport') - const box = await viewport?.boundingBox() - if (box) { - const centerX = box.x + box.width / 2 - const centerY = box.y + box.height / 2 - - for (let i = 0; i < 5; i++) { - await page.mouse.move(centerX, centerY) - await page.mouse.down() - await page.mouse.move(centerX + 100, centerY + 50, { steps: 10 }) - await page.mouse.up() - await page.waitForTimeout(1000) - } - console.log(' Completed 5 pan operations') - } - - // Perform zoom operations - console.log('\n7. Performing zoom operations...') - if (box) { - const centerX = box.x + box.width / 2 - const centerY = box.y + box.height / 2 - await page.mouse.move(centerX, centerY) - - // Zoom in - for (let i = 0; i < 3; i++) { - await page.mouse.wheel(0, -200) - await page.waitForTimeout(1500) - } - console.log(' Zoomed in 3 times') - - // Zoom out - for (let i = 0; i < 3; i++) { - await page.mouse.wheel(0, 200) - await page.waitForTimeout(1500) - } - console.log(' Zoomed out 3 times') - } - - // Get profiler report - console.log('\n8. Getting profiler report...') - const report = await page.evaluate(() => { - return window.__bulkAnnProfiler.report() - }) - - console.log('\n' + '='.repeat(80)) - console.log('PROFILER REPORT') - console.log('='.repeat(80)) - - if (report && Object.keys(report).length > 0) { - for (const [name, data] of Object.entries(report)) { - console.log(`\n${name}:`) - console.log(` Count: ${data.count}`) - if (data.duration) { - console.log(` Duration (ms):`) - console.log(` Min: ${data.duration.min}`) - console.log(` Max: ${data.duration.max}`) - console.log(` Avg: ${data.duration.avg}`) - console.log(` Total: ${data.duration.total}`) - } - if (data.memory) { - console.log(` Memory:`) - console.log(` Min: ${data.memory.min}`) - console.log(` Max: ${data.memory.max}`) - console.log(` Total: ${data.memory.total}`) - } - // Show extra metadata from samples - if (data.samples && data.samples.length > 0) { - const sample = data.samples[0] - const extraKeys = Object.keys(sample).filter(k => - !['duration', 'memoryDelta', 'timestamp', 'value'].includes(k) - ) - if (extraKeys.length > 0) { - console.log(` Sample metadata:`, Object.fromEntries( - extraKeys.map(k => [k, sample[k]]) - )) - } - } - } - } else { - console.log('No profiler data available') - } - - console.log('\n' + '='.repeat(80)) - - // Take final screenshot - await page.screenshot({ path: '/tmp/profiler-final.png' }) - console.log('\nFinal screenshot saved to /tmp/profiler-final.png') - - // Close browser - console.log('\nProfiling complete. Closing browser...') - - await browser.close() -} - -main().catch(console.error) diff --git a/scripts/set-git-env.sh b/scripts/set-git-env.sh index 1e3ca460..f7bb2152 100755 --- a/scripts/set-git-env.sh +++ b/scripts/set-git-env.sh @@ -24,8 +24,8 @@ if [ -z "${REACT_APP_DMV_GIT_SHA:-}" ]; then fi fi -# Default config name when .env is absent (fresh clone). -export REACT_APP_CONFIG="${REACT_APP_CONFIG:-local}" +# REACT_APP_CONFIG is read from the shell or .env; vite.config.ts and +# inject-slim-env.mjs default it to 'local'. # Expose SLIM_* env vars to public/config/*.js via window.slim.env node "$SCRIPT_DIR/inject-slim-env.mjs" || exit $? diff --git a/scripts/slimEnv.mjs b/scripts/slimEnv.mjs new file mode 100644 index 00000000..8dcaa12b --- /dev/null +++ b/scripts/slimEnv.mjs @@ -0,0 +1,93 @@ +/** + * Builds the `window.slim.env` script that public/config/*.js read their + * DICOMweb URLs from. Shared by inject-slim-env.mjs (writes public/config/env.js + * for builds) and the Vite dev server (serves it per process, so servers started + * with different configs do not overwrite each other's URLs). + */ +import fs from 'node:fs' +import path from 'node:path' + +export const DEFAULT_LOCAL_DICOMWEB_URL = + 'http://localhost:8008/dcm4chee-arc/aets/DCM4CHEE/rs' + +/** Only these keys are exported, so unrelated secrets never reach the page */ +const ALLOWED_SLIM_ENV_KEYS = [ + 'SLIM_LOCAL_DICOMWEB_URL', + 'SLIM_DEMO_DICOMWEB_URL', + 'SLIM_PREVIEW_DICOMWEB_URL', +] + +const REQUIRED_URL_BY_CONFIG = new Map([ + ['local', 'SLIM_LOCAL_DICOMWEB_URL'], + ['demo', 'SLIM_DEMO_DICOMWEB_URL'], + ['preview', 'SLIM_PREVIEW_DICOMWEB_URL'], +]) + +/** + * @param {string} file + * @returns {Record} + */ +function readDotEnv(file) { + /** @type {Record} */ + const values = {} + if (!fs.existsSync(file)) { + return values + } + for (const line of fs.readFileSync(file, 'utf8').split(/\r?\n/)) { + const trimmed = line.trim() + const eq = trimmed.indexOf('=') + if (trimmed === '' || trimmed.startsWith('#') || eq === -1) { + continue + } + let value = trimmed.slice(eq + 1).trim() + if ( + (value.startsWith("'") && value.endsWith("'")) || + (value.startsWith('"') && value.endsWith('"')) + ) { + value = value.slice(1, -1) + } + values[trimmed.slice(0, eq).trim()] = value + } + return values +} + +/** + * Process env wins over `.env`; the local URL falls back to docker-compose. + * + * @param {string} root - repository root holding `.env` + * @param {Record} env - usually `process.env` + * @returns {{ values: Record, configName: string, missingKey: string | undefined }} + */ +export function resolveSlimEnv(root, env) { + const merged = { ...readDotEnv(path.join(root, '.env')), ...env } + if (!merged.SLIM_LOCAL_DICOMWEB_URL) { + merged.SLIM_LOCAL_DICOMWEB_URL = DEFAULT_LOCAL_DICOMWEB_URL + } + /** @type {Record} */ + const values = {} + for (const key of ALLOWED_SLIM_ENV_KEYS) { + const value = merged[key] + if (value !== undefined && value !== '') { + values[key] = value + } + } + const configName = merged.REACT_APP_CONFIG || 'local' + const requiredKey = REQUIRED_URL_BY_CONFIG.get(configName) + const missingKey = + requiredKey !== undefined && values[requiredKey] === undefined + ? requiredKey + : undefined + return { values, configName, missingKey } +} + +/** + * @param {Record} values + * @returns {string} + */ +export function slimEnvScript(values) { + return [ + 'window.slim = window.slim || {}', + `window.slim.env = ${JSON.stringify(values, null, 2)}`, + '', + ].join('\n') +} diff --git a/src/App.dark.less b/src/App.dark.less deleted file mode 100644 index 586e39f1..00000000 --- a/src/App.dark.less +++ /dev/null @@ -1,190 +0,0 @@ -@import 'antd/lib/style/themes/default.less'; -@import 'antd/dist/antd.dark.less'; -@import 'antd/dist/antd.compact.less'; -@import 'ol/ol.css'; - -@card-background: transparent; - -.ant-layout-header { - color: white -} -.ant-layout-header a { - color: white -} - -.ant-layout-sider-children { - overflow-y: auto; -} - -.ant-menu-item { - height: 100%; -} - -.slim-multiline-menu-item.ant-menu-item, -.slim-multiline-menu-item.ant-menu-item .ant-menu-title-content { - height: auto; - white-space: normal; - line-height: 1.2; - overflow: visible; -} - -.ant-collapse-header { - font-weight: 600; - border-top: 1px solid rgba(0, 126, 163, 0.5); - border-bottom: 1px solid rgba(0, 126, 163, 0.5); -} - -.ant-collapse-item + .ant-collapse-item .ant-collapse-header { - margin-top: -1px; -} - -.ant-menu-submenu-title { - font-size: 'medium'; - background-color: rgba(0, 126, 163, 0.25); - font-weight: 600; - border-top: 1px solid rgba(0, 126, 163, 0.5); - border-bottom: 1px solid rgba(0, 126, 163, 0.5); -} - -.ant-menu-submenu + .ant-menu-submenu .ant-menu-submenu-title { - margin-top: -1px; -} - -/* First section in sidebar has no top border */ -.ant-layout-sider .ant-menu-inline > .ant-menu-submenu:first-child > .ant-menu-submenu-title { - border-top: none; -} - -/* Settings drawer header border - matches section dividers (darker for contrast) */ -.slim-settings-drawer .ant-drawer-header { - border-bottom: 1px solid rgba(0, 126, 163, 0.5); -} - -/* Remove gap between submenu header and list content */ -.ant-layout-sider .ant-list-item { - padding: 0; -} - -.ol-overviewmap-box { - border: 2px solid @primary-color !important; -} - -/** - * Mini-map / scale insets (px). Slim clamp may reinforce these as inline styles - * after DMV render; keep values aligned with OVERVIEW_EDGE_INSET_PX. - */ -.ol-overviewmap { - left: 8px; - bottom: 8px; - margin: 0; - padding: 0; -} - -.ol-overviewmap:not(.ol-collapsed) button { - position: absolute; - bottom: 0; - left: 0; - margin: 0; -} - -.ol-overviewmap .ol-overviewmap-map { - margin: 0; - padding: 0; -} - -.ol-scale-line { - right: 8px; - bottom: 8px; - left: auto; - margin: 0; -} - -img { - object-fit: contain; - max-height: 100%; -} - -/** - * Worklist fills the AppShell content pane. Only the table body scrolls; - * column headers and pagination stay pinned above the MemoryFooter. - */ -.slim-worklist { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - width: 100%; - overflow: hidden; -} - -.slim-worklist-table-area { - flex: 1; - min-height: 0; - position: relative; - overflow: hidden; -} - -/* Absolute fill so row content cannot inflate the flex height. */ -.slim-worklist-table-area-inner { - position: absolute; - inset: 0; - overflow: hidden; -} - -.slim-worklist-table-area-inner .ant-table-wrapper, -.slim-worklist-table-area-inner .ant-spin-nested-loading, -.slim-worklist-table-area-inner .ant-spin-container { - height: 100%; -} - -/* Ant Design only centers .ant-spin-dot; center our shared Slim spinner. */ -.slim-worklist .ant-spin-nested-loading > div > .ant-spin { - max-height: none; -} - -.slim-worklist .ant-spin-nested-loading > div > .ant-spin .slim-app-loading-spinner { - position: absolute; - top: 50%; - left: 50%; - margin-top: -20px; - margin-left: -20px; -} - -/* Inset first/last cells so text clears the viewport edge without a table gap. */ -.slim-worklist .ant-table.ant-table-small .ant-table-thead > tr > th:first-child, -.slim-worklist .ant-table.ant-table-small .ant-table-tbody > tr > td:first-child, -.slim-worklist .ant-table-thead > tr > th:first-child, -.slim-worklist .ant-table-tbody > tr > td:first-child { - padding-left: 16px; -} - -.slim-worklist .ant-table.ant-table-small .ant-table-thead > tr > th:last-child, -.slim-worklist .ant-table.ant-table-small .ant-table-tbody > tr > td:last-child, -.slim-worklist .ant-table-thead > tr > th:last-child, -.slim-worklist .ant-table-tbody > tr > td:last-child { - padding-right: 16px; -} - -.slim-worklist-pagination { - flex-shrink: 0; - padding: 8px 16px; - border-top: 1px solid rgba(255, 255, 255, 0.12); - background: #141414; -} - -.slim-worklist-pagination .ant-pagination { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: flex-end; - row-gap: 8px; - column-gap: 8px; - margin: 0; - width: 100%; -} - -/* Keep the "N-M of T studies" label on the left; controls stay on the right. */ -.slim-worklist-pagination .ant-pagination-total-text { - margin-right: auto; -} - diff --git a/src/App.light.less b/src/App.light.less deleted file mode 100644 index 3935461b..00000000 --- a/src/App.light.less +++ /dev/null @@ -1,190 +0,0 @@ -@import 'antd/lib/style/themes/default.less'; -@import 'antd/dist/antd.less'; -@import 'antd/dist/antd.compact.less'; -@import 'ol/ol.css'; - -@card-background: transparent; - -.ant-layout-header { - color: white -} -.ant-layout-header a { - color: white -} - -.ant-layout-sider-children { - overflow-y: auto; -} - -.ant-menu-item { - height: 100%; -} - -.slim-multiline-menu-item.ant-menu-item, -.slim-multiline-menu-item.ant-menu-item .ant-menu-title-content { - height: auto; - white-space: normal; - line-height: 1.2; - overflow: visible; -} - -.ant-collapse-header { - font-weight: 600; - border-top: 1px solid rgba(0, 126, 163, 0.3); - border-bottom: 1px solid rgba(0, 126, 163, 0.3); -} - -.ant-collapse-item + .ant-collapse-item .ant-collapse-header { - margin-top: -1px; -} - -.ant-menu-submenu-title { - font-size: 'medium'; - background-color: #e0f2f7; - font-weight: 600; - border-top: 1px solid rgba(0, 126, 163, 0.3); - border-bottom: 1px solid rgba(0, 126, 163, 0.3); -} - -.ant-menu-submenu + .ant-menu-submenu .ant-menu-submenu-title { - margin-top: -1px; -} - -/* First section in sidebar has no top border */ -.ant-layout-sider .ant-menu-inline > .ant-menu-submenu:first-child > .ant-menu-submenu-title { - border-top: none; -} - -/* Settings drawer header border - matches section dividers */ -.slim-settings-drawer .ant-drawer-header { - border-bottom: 1px solid rgba(0, 126, 163, 0.3); -} - -/* Remove gap between submenu header and list content */ -.ant-layout-sider .ant-list-item { - padding: 0; -} - -.ol-overviewmap-box { - border: 2px solid @primary-color !important; -} - -/** - * Mini-map / scale insets (px). Slim clamp may reinforce these as inline styles - * after DMV render; keep values aligned with OVERVIEW_EDGE_INSET_PX. - */ -.ol-overviewmap { - left: 8px; - bottom: 8px; - margin: 0; - padding: 0; -} - -.ol-overviewmap:not(.ol-collapsed) button { - position: absolute; - bottom: 0; - left: 0; - margin: 0; -} - -.ol-overviewmap .ol-overviewmap-map { - margin: 0; - padding: 0; -} - -.ol-scale-line { - right: 8px; - bottom: 8px; - left: auto; - margin: 0; -} - -img { - object-fit: contain; - max-height: 100%; -} - -/** - * Worklist fills the AppShell content pane. Only the table body scrolls; - * column headers and pagination stay pinned above the MemoryFooter. - */ -.slim-worklist { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - width: 100%; - overflow: hidden; -} - -.slim-worklist-table-area { - flex: 1; - min-height: 0; - position: relative; - overflow: hidden; -} - -/* Absolute fill so row content cannot inflate the flex height. */ -.slim-worklist-table-area-inner { - position: absolute; - inset: 0; - overflow: hidden; -} - -.slim-worklist-table-area-inner .ant-table-wrapper, -.slim-worklist-table-area-inner .ant-spin-nested-loading, -.slim-worklist-table-area-inner .ant-spin-container { - height: 100%; -} - -/* Ant Design only centers .ant-spin-dot; center our shared Slim spinner. */ -.slim-worklist .ant-spin-nested-loading > div > .ant-spin { - max-height: none; -} - -.slim-worklist .ant-spin-nested-loading > div > .ant-spin .slim-app-loading-spinner { - position: absolute; - top: 50%; - left: 50%; - margin-top: -20px; - margin-left: -20px; -} - -/* Inset first/last cells so text clears the viewport edge without a table gap. */ -.slim-worklist .ant-table.ant-table-small .ant-table-thead > tr > th:first-child, -.slim-worklist .ant-table.ant-table-small .ant-table-tbody > tr > td:first-child, -.slim-worklist .ant-table-thead > tr > th:first-child, -.slim-worklist .ant-table-tbody > tr > td:first-child { - padding-left: 16px; -} - -.slim-worklist .ant-table.ant-table-small .ant-table-thead > tr > th:last-child, -.slim-worklist .ant-table.ant-table-small .ant-table-tbody > tr > td:last-child, -.slim-worklist .ant-table-thead > tr > th:last-child, -.slim-worklist .ant-table-tbody > tr > td:last-child { - padding-right: 16px; -} - -.slim-worklist-pagination { - flex-shrink: 0; - padding: 8px 16px; - border-top: 1px solid #f0f0f0; - background: #fff; -} - -.slim-worklist-pagination .ant-pagination { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: flex-end; - row-gap: 8px; - column-gap: 8px; - margin: 0; - width: 100%; -} - -/* Keep the "N-M of T studies" label on the left; controls stay on the right. */ -.slim-worklist-pagination .ant-pagination-total-text { - margin-right: auto; -} - diff --git a/src/App.tsx b/src/App.tsx index 34ffc3ff..015d20cf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,1022 +1,71 @@ -import { Layout, Modal, message } from 'antd' -// skipcq: JS-C1003 -import type * as dwc from 'dicomweb-client' -import React from 'react' -import { - BrowserRouter, - Navigate, - Route, - Routes, - useParams, -} from 'react-router-dom' - +import type { JSX } from 'react' +import { BrowserRouter } from 'react-router' import type AppConfig from './AppConfig' -import type { - ErrorMessageSettings, - OidcSettings, - ServerSettings, -} from './AppConfig' -import type { AuthManager, User } from './auth' -import OidcManager from './auth/OidcManager' -import { - clearCachedOidcConfig, - readCachedOidcConfig, - resetCachedOidcConfigFromUrl, -} from './auth/oidcConfig' +import { AppPage, type AppPageContext } from './app/AppPage' +import { AppRoutes } from './app/AppRoutes' +import { useAppSession } from './app/useAppSession' import AppLoading from './components/AppLoading' -import AppShell from './components/AppShell' -import CaseViewer from './components/CaseViewer' -import Header from './components/Header' import InfoPage from './components/InfoPage' -import Worklist from './components/Worklist' -import { SettingsProvider } from './contexts/SettingsContext' -import { ValidationProvider } from './contexts/ValidationContext' -import type { AuthorizationPolicy } from './DicomWebManager' -import DicomWebManager from './DicomWebManager' -import { StorageClasses } from './data/uids' -import NotificationMiddleware, { - NotificationMiddlewareContext, -} from './services/NotificationMiddleware' -import { - getOrigin, - isSecureOrigin, - readAuthorizationDecision, - writeAuthorizationDecision, -} from './utils/authPolicy' -import { CustomError, errorTypes } from './utils/CustomError' -import { getProjectStorePath, isProjectsPath, RoutePaths } from './utils/routes' -import { createSingleFlight } from './utils/singleFlight' -import { joinUrl, normalizeServerUrl } from './utils/url' - -function ParametrizedCaseViewer({ - clients, - user, - app, - config, -}: { - clients: { [key: string]: DicomWebManager } - user?: User - app: { - name: string - version: string - uid: string - organization?: string - } - config: AppConfig -}): JSX.Element { - const { studyInstanceUID } = useParams() - - if (studyInstanceUID === undefined) { - return - } - - const enableAnnotationTools = !(config.disableAnnotationTools ?? false) - const preload = config.preload ?? false - return ( - - - - ) -} - -function _createClientMapping({ - baseUri, - gcpBaseUrl, - settings, - onError, -}: { - baseUri: string - gcpBaseUrl: string - settings: ServerSettings[] - onError: ( - error: dwc.api.DICOMwebClientError, - serverSettings: ServerSettings, - ) => void -}): { [sopClassUID: string]: DicomWebManager } { - const storageClassMapping: { [key: string]: number } = { default: 0 } - const clientMapping: { [sopClassUID: string]: DicomWebManager } = {} - - const defaultServers: ServerSettings[] = [] - - settings.forEach((serverSettings) => { - if (serverSettings.storageClasses != null) { - serverSettings.storageClasses.forEach((sopClassUID) => { - if (Object.values(StorageClasses).includes(sopClassUID)) { - if (sopClassUID in storageClassMapping) { - storageClassMapping[sopClassUID] += 1 - } else { - storageClassMapping[sopClassUID] = 1 - } - } else { - console.warn( - `unknown storage class "${sopClassUID}" specified ` + - `for configured server "${serverSettings.id}"`, - ) - } - }) - } else { - if (isProjectsPath(window.location.pathname)) { - const pathname = getProjectStorePath(window.location.pathname) - const pathUrl = `${gcpBaseUrl}${pathname}/dicomWeb` - serverSettings.url = pathUrl - } - - storageClassMapping.default += 1 - defaultServers.push(serverSettings) - clientMapping.default = new DicomWebManager({ - baseUri, - settings: [serverSettings], - onError, - }) - } - }) - - if (storageClassMapping.default > 1) { - NotificationMiddleware.onError( - NotificationMiddlewareContext.SLIM, - new CustomError( - errorTypes.COMMUNICATION, - 'Only one default server can be configured without specification ' + - 'of storage classes.', - ), - ) - } - - /** - * For each storage class explicitly assigned to a non-default server, wrap - * BOTH the default server and the specialty server(s) in the same manager. - * - * This makes derived data (SR/SEG/ANN/PM/PR) load from the primary store - * AND the secondary `gcp=` URL store at the same time (GH-320). Without - * this, specifying `gcp=` previously caused the default store to be - * skipped for those classes and SLIM only saw the secondary's derived data. - */ - if (Object.keys(storageClassMapping).length > 1) { - const classToServers = new Map() - settings.forEach((server) => { - if (server.storageClasses != null) { - server.storageClasses.forEach((sopClassUID) => { - const list = classToServers.get(sopClassUID) ?? [] - list.push(server) - classToServers.set(sopClassUID, list) - }) - } - }) - - classToServers.forEach((specialtyServers, sopClassUID) => { - const combinedServers = [...defaultServers, ...specialtyServers] - clientMapping[sopClassUID] = new DicomWebManager({ - baseUri, - settings: combinedServers, - onError, - }) - }) - } +import { buildAppInfo } from './utils/appInfo' - Object.values(StorageClasses).forEach((sopClassUID) => { - if (!(sopClassUID in clientMapping)) { - clientMapping[sopClassUID] = clientMapping.default - } - }) - return clientMapping -} - -interface AppProps { +export interface AppProps { name: string homepage: string version: string config: AppConfig } -interface AppState { - clients: { [sopClassUID: string]: DicomWebManager } - defaultClients: { [sopClassUID: string]: DicomWebManager } - user?: User - isLoading: boolean - redirectTo?: string - wasAuthSuccessful: boolean - signInFailureMessage?: string - error?: ErrorMessageSettings - /** Bumped after mid-session auth recovery so views remount and refetch. */ - authRecoveryKey: number -} - -class App extends React.Component { - private readonly auth?: AuthManager - /** Whether `auth` was built from the OIDC config cached by server selection */ - private usesCachedOidcConfig = false - /** Issuer of the tokens `auth` hands out */ - private readonly oidcAuthority?: string - private reauthInProgress = false - private unsubscribeAuthorization?: () => void - - /** - * Origins that came from the deployed configuration file. Putting a server - * there is the operator stating they trust it, so a 401 from one of these - * escalates without troubling the user. Servers introduced at runtime — the - * "Select server" dialog, the `?gcp=` parameter — are not on this list and - * require explicit consent before the token is sent. - */ - private readonly configuredOrigins: Set - - /** - * Collapses consent negotiations by origin, so simultaneous challenges from - * different managers share a single prompt. - */ - private readonly disclosureGate = createSingleFlight() - - private readonly handleDICOMwebError = ( - error: dwc.api.DICOMwebClientError, - serverSettings: ServerSettings, - ): void => { - if (error.status === 401) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.ensureAuthorized() - } else if (error.status === 403) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - NotificationMiddleware.onError( - NotificationMiddlewareContext.DICOMWEB, - new CustomError( - errorTypes.COMMUNICATION, - 'User is not authorized to access DICOMweb resources.', - ), - ) - } - - const logServerError = (): void => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - NotificationMiddleware.onError( - NotificationMiddlewareContext.DICOMWEB, - new CustomError( - errorTypes.COMMUNICATION, - 'An unexpected server error occured.', - ), - ) - } - - if (serverSettings.errorMessages !== undefined) { - serverSettings.errorMessages.forEach((setting: ErrorMessageSettings) => { - if (error.status === setting.status) { - this.setState({ - error: { - status: error.status, - message: setting.message, - }, - }) - } else if (error.status === 500) { - logServerError() - } - }) - } else if (error.status === 500) { - logServerError() - } - } - - constructor(props: AppProps) { - super(props) - - // Only log in development environment - if (process.env.NODE_ENV === 'development') { - console.info('instatiate app') - console.info(`app is located at "${props.config.path}"`) - } - - const { protocol, host } = window.location - const baseUri = `${protocol}//${host}` - const appUri = joinUrl(props.config.path, baseUri) - - resetCachedOidcConfigFromUrl() - /** OIDC config entered in server selection overrides the deployment one */ - const cachedOidcSettings = readCachedOidcConfig() - this.usesCachedOidcConfig = cachedOidcSettings !== undefined - const oidcSettings = cachedOidcSettings ?? props.config.oidc - if (oidcSettings !== undefined) { - if (process.env.NODE_ENV === 'development') { - console.info( - 'app uses the following OIDC configuration: ', - oidcSettings, - ) - } - this.auth = new OidcManager(appUri, oidcSettings) - this.oidcAuthority = oidcSettings.authority - } - - if (props.config.servers.length === 0) { - NotificationMiddleware.onError( - NotificationMiddlewareContext.SLIM, - new CustomError( - errorTypes.COMMUNICATION, - 'One server needs to be configured.', - ), - ) - } - - if (process.env.NODE_ENV === 'development') { - console.info( - 'app uses the following DICOMweb server configuration: ', - props.config.servers, - ) - } - - message.config({ duration: 5 }) - - /** - * Hold the servers that came from the configuration file, before `?gcp=` - * appends a runtime, URL-supplied one. These are references, not copies: - * `_createClientMapping` rewrites `url` in place on `/projects/` routes, so - * the origins are read afterwards to capture the effective value. - */ - const configuredServers = [...props.config.servers] - - App.addGcpSecondaryAnnotationServer(props.config) - - const defaultClients = _createClientMapping({ - baseUri, - gcpBaseUrl: - props.config.gcpBaseUrl ?? 'https://healthcare.googleapis.com/v1', - settings: props.config.servers, - onError: this.handleDICOMwebError, - }) - - this.configuredOrigins = new Set( - configuredServers - .map((server) => (server.url != null ? getOrigin(server.url) : baseUri)) - .filter((origin): origin is string => origin !== undefined), +function App({ name, homepage, version, config }: AppProps): JSX.Element { + const { session, state } = useAppSession(config) + + const page: AppPageContext = { + app: buildAppInfo({ + name, + version, + homepage, + organization: config.organization, + }), + user: state.user, + clients: state.clients, + defaultClients: state.defaultClients, + onServerSelection: session.selectServer, + } + + if (state.isLoading) { + return ( + + + + + ) - this.applyAuthorizationPolicy(defaultClients) - - this.state = { - clients: defaultClients, - defaultClients, - isLoading: true, - wasAuthSuccessful: false, - authRecoveryKey: 0, - } } - - static addGcpSecondaryAnnotationServer(config: AppProps['config']): void { - const serverId = 'gcp_secondary_annotation_server' - const urlParams = new URLSearchParams(window.location.search) - const url = urlParams.get('gcp') - const gcpSecondaryAnnotationServer = config.servers.find( - (server) => server.id === serverId, + if (!state.wasAuthSuccessful) { + return ( + ) - if (gcpSecondaryAnnotationServer === undefined && typeof url === 'string') { - config.servers.push({ - id: serverId, - write: true, - url, - storageClasses: [ - StorageClasses.COMPREHENSIVE_SR, - StorageClasses.COMPREHENSIVE_3D_SR, - StorageClasses.SEGMENTATION, - StorageClasses.LABELMAP_SEGMENTATION, - StorageClasses.MICROSCOPY_BULK_SIMPLE_ANNOTATION, - StorageClasses.PARAMETRIC_MAP, - StorageClasses.ADVANCED_BLENDING_PRESENTATION_STATE, - StorageClasses.COLOR_SOFTCOPY_PRESENTATION_STATE, - StorageClasses.GRAYSCALE_SOFTCOPY_PRESENTATION_STATE, - StorageClasses.PSEUDOCOLOR_SOFTCOPY_PRESENTATION_STATE, - ], - }) - } - } - - /** - * Policy handed to every DicomWebManager: it decides which origins may - * receive the user's access token. - * - * Slim sends no token until a server answers 401/403. At that point an origin - * from the configuration file is credentialed silently, while any other - * origin needs the user to say yes — otherwise a server could obtain a live - * cloud credential just by claiming to want one. - */ - private readonly authorizationPolicy: AuthorizationPolicy = { - isPreAuthorized: (origin: string): boolean => - readAuthorizationDecision(origin) === 'granted', - - /** - * Collapsed per origin across the whole app. Each DicomWebManager already - * dedupes its own concurrent challenges, but a storage class gets its own - * manager, so a single page load can challenge one server from several of - * them at once. Without this the user is asked once per manager. - */ - requestAuthorization: async (origin: string): Promise => - await this.disclosureGate( - origin, - async () => await this.negotiateDisclosure(origin), - ), - } - - /** - * Decide whether the access token may be disclosed to an origin, prompting - * the user when the origin is not part of the deployed configuration, and - * return the token if so. - * - * Always call this through `authorizationPolicy.requestAuthorization`, which - * collapses concurrent callers onto one negotiation — this method itself will - * open a modal every time it is invoked. - * - * @param origin - Origin of the server that asked for credentials - * @returns The token to send, or undefined if it must be withheld - */ - private readonly negotiateDisclosure = async ( - origin: string, - ): Promise => { - try { - if (this.auth == null) { - return undefined - } - if (!isSecureOrigin(origin)) { - /** - * Refuse rather than warn. A bearer token sent over plain HTTP is - * readable by anything on the path, and no consent dialog makes that - * safe. An operator who has a reason to do it anyway can still say so - * explicitly with `sendAuthorization: true`, which never reaches here. - */ - console.warn( - `refusing to send access token to ${origin} over an insecure ` + - 'connection; set sendAuthorization on the server configuration ' + - 'to override', - ) - NotificationMiddleware.onError( - NotificationMiddlewareContext.AUTH, - new CustomError( - errorTypes.AUTHENTICATION, - `Not sending your access token to ${origin}: the connection is ` + - 'not secure.', - ), - ) - return undefined - } - const remembered = readAuthorizationDecision(origin) - if (remembered === 'denied') { - return undefined - } - if (remembered !== 'granted' && !this.configuredOrigins.has(origin)) { - const approved = await App.confirmAuthorizationDisclosure( - origin, - this.oidcAuthority, - ) - writeAuthorizationDecision(origin, approved ? 'granted' : 'denied') - console.info( - `${approved ? 'approved' : 'declined'} disclosure of access token ` + - `to ${origin} (user decision)`, - ) - if (!approved) { - return undefined - } - } else { - writeAuthorizationDecision(origin, 'granted') - console.info( - `approved disclosure of access token to ${origin} ` + - '(origin present in the deployed configuration)', - ) - } - - const authorization = await this.auth.getAuthorization() - if (authorization == null) { - return undefined - } - /** - * The grant is recorded per origin, but each storage class has its own - * manager. Push the token across all of them so stores on this origin in - * a sibling manager are credentialed now, rather than each having to be - * refused once before it asks. Every manager re-applies its own per-store - * filtering, so this cannot widen disclosure beyond the recorded grants. - */ - this.applyAuthorization(authorization) - return authorization - } catch (error) { - /** - * Never let a failed negotiation reject: callers are inside a DICOMweb - * error path already, and a rejection here would replace the underlying - * server error with a less useful one. - */ - console.error('could not negotiate token disclosure', error) - return undefined - } - } - - /** - * Ask the user before disclosing their access token to a server that is not - * part of the deployed configuration. - * - * Names the identity provider that issued the token, since "your access - * token" alone does not tell the user what is actually at stake — the answer - * differs a great deal between a hospital SSO and a personal Google account. - * - * @param origin - Origin of the server that asked for credentials - * @param authority - Issuer of the token, from the OIDC configuration - * @returns Whether the user agreed to disclose the token - */ - private static async confirmAuthorizationDisclosure( - origin: string, - authority?: string, - ): Promise { - return await new Promise((resolve) => { - Modal.confirm({ - title: 'Send your access token to this server?', - content: ( - <> -

- {origin} refused an anonymous request and is - asking you to sign in. -

-

- Slim can forward the access token issued to you by{' '} - {authority ?? 'your identity provider'} so this - server can identify you. Anyone holding that token can act as you - against that provider for as long as it remains valid. -

-

Only allow this if you trust {origin}.

- - ), - okText: 'Send token', - cancelText: "Don't send", - onOk: () => resolve(true), - onCancel: () => resolve(false), - }) - }) - } - - /** Install the authorization policy on every distinct manager in a mapping. */ - private applyAuthorizationPolicy(clients: { - [key: string]: DicomWebManager - }): void { - for (const client of new Set(Object.values(clients))) { - client.setAuthorizationPolicy(this.authorizationPolicy) - } - } - - handleServerSelection = async ({ - url, - oidc, - }: { - url: string - /** New settings, null to fall back to the deployment config */ - oidc?: OidcSettings | null - }): Promise => { - const trimmedUrl = url.trim() - console.info('select DICOMweb server: ', trimmedUrl) - - const resolvedUrl = - trimmedUrl === '' || - window.localStorage.getItem('slim_server_selection_mode') === 'default' - ? undefined - : normalizeServerUrl(trimmedUrl) - if (resolvedUrl !== undefined) { - window.localStorage.setItem('slim_selected_server', resolvedUrl) - } - - if (oidc !== undefined) { - /** - * Start over with the cached config, which the constructor reads and - * mount signs in with. Clients keep token grants and headers from the - * previous provider in memory, and the deployment may have no OIDC. - */ - console.info( - oidc === null - ? 'removing custom OIDC configuration' - : 'applying custom OIDC configuration', - ) - window.location.reload() - return - } - - if (resolvedUrl === undefined) { - this.setState({ clients: this.state.defaultClients }) - return - } - const tmpClient = new DicomWebManager({ - baseUri: '', - settings: [ - { - id: 'tmp', - url: resolvedUrl, - read: true, - write: false, - }, - ], - onError: this.handleDICOMwebError, - }) - tmpClient.setAuthorizationPolicy(this.authorizationPolicy) - /** - * Carry over non-credential headers only. The token is deliberately not - * forwarded here: this URL was typed by the user and has not been vetted by - * anyone. If the server actually needs credentials it will answer 401, and - * the authorization policy will ask before anything is disclosed. - */ - const { Authorization: _omitted, ...inheritedHeaders } = - this.state.clients.default.headers - tmpClient.updateHeaders(inheritedHeaders) - if (this.auth != null && this.state.user != null) { - const authorization = await this.auth.getAuthorization() - if (authorization != null) { - /** - * Offered, not forced: `updateHeaders` attaches it only if this origin - * has already been approved. - */ - tmpClient.updateHeaders({ Authorization: authorization }) - } - } - /** - * Use the newly created client for all storage classes. We may want to - * make this more sophisticated in the future to allow users to override - * the entire server configuration. - */ - this.setState((state) => { - const clients: { [key: string]: DicomWebManager } = {} - for (const key in state.clients) { - clients[key] = tmpClient - } - return { clients } - }) - } - - applyAuthorization = (authorization: string): void => { - for (const key of Object.keys(this.state.clients)) { - this.state.clients[key].updateHeaders({ Authorization: authorization }) - } - for (const key of Object.keys(this.state.defaultClients)) { - this.state.defaultClients[key].updateHeaders({ - Authorization: authorization, - }) - } - } - - /** - * Handle successful authentication event. - * - * Authorizes the DICOMweb client to access the DICOMweb server and directs - * the user back to the pre-login route (via OIDC state). - */ - handleSignIn = ({ - user, - authorization, - returnUrl, - }: { - user: User - authorization: string - returnUrl?: string - }): void => { - this.applyAuthorization(authorization) - this.setState({ user }) - - if (returnUrl != null && returnUrl !== '') { - const current = `${window.location.pathname}${window.location.search}` - if (returnUrl !== current) { - window.location.assign(returnUrl) - } - } - } - - /** - * Recover from an expired/missing access token without losing the route. - * Tries silent renew first; falls back to interactive redirect with returnUrl. - */ - ensureAuthorized = async (): Promise => { - if (this.auth == null || this.reauthInProgress) { - return - } - this.reauthInProgress = true - let redirectedToIdp = false - try { - const authorization = await this.auth.renewAuthorization() - if (authorization != null) { - this.applyAuthorization(authorization) - // Remount routed views so in-flight 401 failures refetch with the new token. - this.setState((state) => ({ - authRecoveryKey: state.authRecoveryKey + 1, - })) - return - } - console.info('silent renew unavailable; starting interactive sign-in') - const outcome = await this.auth.signIn({ - onSignIn: this.handleSignIn, - returnUrl: `${window.location.pathname}${window.location.search}`, - }) - redirectedToIdp = outcome === 'redirected' - if (outcome === 'completed') { - // Token was refreshed without leaving the page; remount views to refetch. - this.setState((state) => ({ - authRecoveryKey: state.authRecoveryKey + 1, - })) - } - } catch (error) { - console.error(error) - // eslint-disable-next-line @typescript-eslint/no-floating-promises - NotificationMiddleware.onError( - NotificationMiddlewareContext.AUTH, - new CustomError( - errorTypes.AUTHENTICATION, - 'Could not renew authorization.', - ), - ) - } finally { - // oidc-client resolves signinRedirect as soon as navigation is assigned. - // Keep the guard set until unload so concurrent 401s cannot start another redirect. - if (!redirectedToIdp) { - this.reauthInProgress = false - } - } - } - - signIn(): void { - if (this.auth !== undefined) { - console.info('try to sign in user') - this.auth - .signIn({ - onSignIn: this.handleSignIn, - returnUrl: `${window.location.pathname}${window.location.search}`, - }) - .then((outcome) => { - if (outcome === 'redirected') { - return - } - console.info('sign-in was successful') - this.setState({ - isLoading: false, - wasAuthSuccessful: true, - }) - }) - .catch((error) => { - console.error(error) - // eslint-disable-next-line @typescript-eslint/no-floating-promises - NotificationMiddleware.onError( - NotificationMiddlewareContext.AUTH, - new CustomError( - errorTypes.AUTHENTICATION, - 'Could not sign-in user.', - ), - ) - /** - * The failure page has no header, so a broken custom config could - * not be changed from the UI and would be reused on every load. - */ - let signInFailureMessage: string | undefined - if (this.usesCachedOidcConfig) { - clearCachedOidcConfig() - this.usesCachedOidcConfig = false - signInFailureMessage = - 'Sign-in with the custom OIDC configuration failed. It has been cleared; reload the page to use the default configuration.' - } - this.setState({ - isLoading: false, - redirectTo: undefined, - wasAuthSuccessful: false, - signInFailureMessage, - }) - }) - } else { - this.setState({ - isLoading: false, - redirectTo: undefined, - wasAuthSuccessful: true, - }) - } } - - componentDidMount(): void { - // Restore cached server selection if it exists - const cachedServerUrl = window.localStorage.getItem('slim_selected_server') - if ( - cachedServerUrl !== null && - cachedServerUrl !== undefined && - cachedServerUrl !== '' - ) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.handleServerSelection({ url: cachedServerUrl }) - } - - if (this.auth != null) { - this.unsubscribeAuthorization = this.auth.onAuthorizationChange( - (authorization) => { - this.applyAuthorization(authorization) - }, - ) - } - - this.signIn() - } - - componentWillUnmount(): void { - this.unsubscribeAuthorization?.() - } - - render(): React.ReactNode { - const appInfo = { - name: this.props.name, - version: this.props.version, - homepage: this.props.homepage, - uid: '1.2.826.0.1.3680043.9.7433.1.5', - organization: this.props.config.organization, - } - - const enableWorklist = !(this.props.config.disableWorklist ?? false) - const enableServerSelection = - this.props.config.enableServerSelection ?? false - const enableMemoryMonitoring = - this.props.config.enableMemoryMonitoring ?? true - - let worklist: React.ReactNode - if (enableWorklist) { - worklist = - } else { - worklist =
Worklist has been disabled.
- } - - let isLogoutPossible = false - let onLogout: () => void - if (this.auth != null) { - onLogout = (): void => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.auth?.signOut() - } - isLogoutPossible = true - } else { - onLogout = () => {} - isLogoutPossible = false - } - - /** - * Fill AppShell's main pane. flex + minHeight:0 keeps ant-layout from - * sizing to content and spilling into the in-flow MemoryFooter. - */ - const layoutStyle: React.CSSProperties = { - flex: '1 1 0%', - minHeight: 0, - overflow: 'hidden', - } - const layoutContentStyle: React.CSSProperties = { - flex: 1, - minHeight: 0, - overflow: 'hidden', - display: 'flex', - flexDirection: 'column', - } - - if (this.state.redirectTo !== undefined) { - return ( - - - - ) - } else if (this.state.isLoading) { - return ( - - - -
- - - - - - - ) - } else if (!this.state.wasAuthSuccessful) { - return ( - - ) - } else if (this.state.error != null) { - return - } else { - return ( - - - - -
- - {worklist} - - - - } - /> - - - -
- - - - - - - } - /> - - - -
- - - - - - - } - /> - - -
- - Logged out - - - - } - /> - - - ) - } + if (state.error != null) { + return } + return ( + + + + ) } export default App diff --git a/src/AppConfig.d.ts b/src/AppConfig.d.ts index 9e1d177c..ac014443 100644 --- a/src/AppConfig.d.ts +++ b/src/AppConfig.d.ts @@ -1,4 +1,4 @@ -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import * as dcmjs from 'dcmjs' export type DicomWebManagerErrorHandler = ( @@ -115,6 +115,7 @@ export default interface AppConfig { disableWorklist?: boolean disableAnnotationTools?: boolean enableServerSelection?: boolean + /** Default theme until the user picks one: 'light', else dark */ mode?: string preload?: boolean messages?: { diff --git a/src/AppDark.tsx b/src/AppDark.tsx deleted file mode 100644 index 96c58c9a..00000000 --- a/src/AppDark.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import './App.dark.less' -import App from './App' - -export default App diff --git a/src/AppLight.tsx b/src/AppLight.tsx deleted file mode 100644 index e705e60c..00000000 --- a/src/AppLight.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import './App.light.less' -import App from './App' - -export default App diff --git a/src/DicomWebManager.ts b/src/DicomWebManager.ts index 73149ac7..c219c710 100644 --- a/src/DicomWebManager.ts +++ b/src/DicomWebManager.ts @@ -1,8 +1,8 @@ -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import * as dcmjs from 'dcmjs' -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import * as dmv from 'dicom-microscopy-viewer' import * as dwc from 'dicomweb-client' @@ -80,6 +80,48 @@ const getErrorStatus = (error: unknown): number | undefined => { return typeof status === 'number' ? status : undefined } +/** + * Whether a server refused a request that we deliberately sent without + * credentials. + * + * This is distinct from a 401 against a credentialed store, which means the + * token expired. These must not be conflated: the application responds to the + * latter by renewing the session, up to an interactive redirect to the + * identity provider. Doing that here would drag the user through a sign-in + * they may have just declined, and could not help anyway — the request failed + * because the credential was withheld, not because it was stale. + */ +const isAnonymousChallenge = (store: Store, error: unknown): boolean => { + const status = getErrorStatus(error) + return ( + status !== undefined && + AUTH_CHALLENGE_STATUSES.has(status) && + store.authMode !== 'always' && + !store.authGranted + ) +} + +/** + * Tell the user their token was withheld from a server that wants it, once + * per store. Reported directly rather than through the DICOMweb error + * handler, which would treat the 401 as an expired session and start a + * pointless re-authentication. + */ +const reportWithheldAuthorization = (store: Store): void => { + if (store.authRefusalReported) { + return + } + store.authRefusalReported = true + NotificationMiddleware.onError( + NotificationMiddlewareContext.DICOMWEB, + new CustomError( + errorTypes.COMMUNICATION, + `The DICOMweb server at ${store.origin} requires sign-in, but your ` + + 'access token was not sent to it.', + ), + ) +} + /** DICOM JSON tag keys used for cross-store deduplication of search results. */ const STUDY_INSTANCE_UID_TAG = '0020000D' const SERIES_INSTANCE_UID_TAG = '0020000E' @@ -146,7 +188,7 @@ const searchAcrossStores = async ( } catch (error: unknown) { lastError = error failureCount += 1 - if (process.env.NODE_ENV === 'development') { + if (import.meta.env.MODE === 'development') { console.warn( `search against store "${store.id}" failed; ` + 'continuing with the remaining stores', @@ -200,7 +242,7 @@ const retrieveWithFallback = async ( return await call(store) } catch (error: unknown) { lastError = error - if (process.env.NODE_ENV === 'development') { + if (import.meta.env.MODE === 'development') { console.debug( `retrieve against store "${store.id}" failed; ` + 'falling back to the next configured store', @@ -215,10 +257,20 @@ const retrieveWithFallback = async ( } /** - * Cache mapping series UIDs to the store that successfully served them. - * Keyed by "studyInstanceUID/seriesInstanceUID". + * Per manager (keyed by its store list), maps + * "studyInstanceUID/seriesInstanceUID" to the store that served the series. + * Weakly held, so managers replaced by a server switch are not retained. */ -const seriesStoreCache = new Map() +const seriesStoreCaches = new WeakMap>() + +const getSeriesStoreCache = (stores: Store[]): Map => { + let cache = seriesStoreCaches.get(stores) + if (cache === undefined) { + cache = new Map() + seriesStoreCaches.set(stores, cache) + } + return cache +} /** * Build the cache key for a series. @@ -249,6 +301,7 @@ const retrieveWithCachedFallback = async ( } /** Reorder stores to try the cached one first if available. */ + const seriesStoreCache = getSeriesStoreCache(stores) let orderedStores = readable const cachedStore = cacheKey != null ? seriesStoreCache.get(cacheKey) : null if (cachedStore != null && readable.includes(cachedStore)) { @@ -266,7 +319,7 @@ const retrieveWithCachedFallback = async ( return result } catch (error: unknown) { lastError = error - if (process.env.NODE_ENV === 'development') { + if (import.meta.env.MODE === 'development') { console.debug( `retrieve against store "${store.id}" failed; ` + 'falling back to the next configured store', @@ -311,7 +364,7 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { this.handleError = onError } else { this.handleError = (error, serverSettings) => { - if (process.env.NODE_ENV === 'development') { + if (import.meta.env.MODE === 'development') { console.error(error, serverSettings) } } @@ -347,16 +400,9 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { } else if (serverSettings.path !== undefined) { serviceUrl = joinUrl(serverSettings.path, baseUri) } else { - NotificationMiddleware.onError( - NotificationMiddlewareContext.SLIM, - new CustomError( - errorTypes.COMMUNICATION, - 'Either path or full URL needs to be configured for server.', - ), - ) throw new CustomError( errorTypes.COMMUNICATION, - 'Either path or full URL needs to be configured for server.', + `The DICOMweb server "${serverSettings.id}" needs a "url" or a "path".`, ) } @@ -401,7 +447,7 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { error: dwc.api.DICOMwebClientError, ) => { const store = this.stores[storeIndex] - if (store !== undefined && this.isAnonymousChallenge(store, error)) { + if (store !== undefined && isAnonymousChallenge(store, error)) { /** * The store was queried anonymously and the server asked for * credentials. `callStore` owns this case: it escalates if it can and @@ -477,60 +523,15 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { } /** - * Whether a server refused a request that we deliberately sent without - * credentials. - * - * This is distinct from a 401 against a credentialed store, which means the - * token expired. These must not be conflated: the application responds to the - * latter by renewing the session, up to an interactive redirect to the - * identity provider. Doing that here would drag the user through a sign-in - * they may have just declined, and could not help anyway — the request failed - * because the credential was withheld, not because it was stale. - */ - private readonly isAnonymousChallenge = ( - store: Store, - error: unknown, - ): boolean => { - const status = getErrorStatus(error) - return ( - status !== undefined && - AUTH_CHALLENGE_STATUSES.has(status) && - store.authMode !== 'always' && - !store.authGranted - ) - } - - /** - * Whether such a challenge can still be escalated from, i.e. the store is - * negotiating, has not already been refused, and a policy exists to ask. + * Whether an anonymous challenge can still be escalated from, i.e. the store + * is negotiating, has not already been refused, and a policy exists to ask. */ private readonly isAuthChallenge = (store: Store, error: unknown): boolean => - this.isAnonymousChallenge(store, error) && + isAnonymousChallenge(store, error) && store.authMode === 'auto' && !store.authRefused && this.authorizationPolicy !== undefined - /** - * Tell the user their token was withheld from a server that wants it, once - * per store. Reported directly rather than through the DICOMweb error - * handler, which would treat the 401 as an expired session and start a - * pointless re-authentication. - */ - private readonly reportWithheldAuthorization = (store: Store): void => { - if (store.authRefusalReported) { - return - } - store.authRefusalReported = true - NotificationMiddleware.onError( - NotificationMiddlewareContext.DICOMWEB, - new CustomError( - errorTypes.COMMUNICATION, - `The DICOMweb server at ${store.origin} requires sign-in, but your ` + - 'access token was not sent to it.', - ), - ) - } - /** * Ask the policy for a token for this store's origin, collapsing concurrent * requests so the user is prompted once per server rather than once per @@ -565,13 +566,13 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { return await call(store.client) } catch (error: unknown) { if (!this.isAuthChallenge(store, error)) { - if (this.isAnonymousChallenge(store, error)) { + if (isAnonymousChallenge(store, error)) { /** * Already refused, or configured `sendAuthorization: false` against a * server that wants credentials. The interceptor stayed quiet, so say * so here. */ - this.reportWithheldAuthorization(store) + reportWithheldAuthorization(store) } throw error } @@ -579,7 +580,7 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { if (authorization === undefined) { /** Consent refused, or no token exists to send. */ store.authRefused = true - this.reportWithheldAuthorization(store) + reportWithheldAuthorization(store) throw error } store.authGranted = true @@ -605,16 +606,16 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { * anonymous requests. Non-credential headers propagate to every store. */ updateHeaders = (fields: { [name: string]: string }): void => { - for (const f in fields) { - const isAuthHeader = AUTH_HEADER_NAMES.has(f.toLowerCase()) + for (const [name, value] of Object.entries(fields)) { + const isAuthHeader = AUTH_HEADER_NAMES.has(name.toLowerCase()) if (isAuthHeader) { - this.currentAuthorization = fields[f] + this.currentAuthorization = value } for (const store of this.stores) { if (isAuthHeader && !this.mayAttachAuthorization(store)) { continue } - store.client.headers[f] = fields[f] + store.client.headers[name] = value } } } @@ -708,7 +709,7 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { ), ) const naturalized = naturalizeDataset(studySummaryMetadata) - DicomMetadataStore.addStudy(naturalized as Record) + DicomMetadataStore.addStudy(naturalized) return studySummaryMetadata } @@ -729,10 +730,7 @@ export default class DicomWebManager implements dwc.api.DICOMwebClient { cacheKey, ) const naturalized = seriesSummaryMetadata.map(naturalizeDataset) - DicomMetadataStore.addSeriesMetadata( - naturalized as Array>, - true, - ) + DicomMetadataStore.addSeriesMetadata(naturalized, true) return seriesSummaryMetadata } diff --git a/src/__mocks__/dicomMicroscopyViewerMock.js b/src/__mocks__/dicomMicroscopyViewerMock.js deleted file mode 100644 index b2b02afe..00000000 --- a/src/__mocks__/dicomMicroscopyViewerMock.js +++ /dev/null @@ -1,42 +0,0 @@ -// Mock for dicom-microscopy-viewer to resolve Jest test issues. -// Provides metadata.formatMetadata so Worklist and other components can run in tests. - -const TAG_TO_KEYWORD = { - '0020000D': 'StudyInstanceUID', - '00200010': 'StudyDescription', - '00080050': 'AccessionNumber', - '00080020': 'StudyDate', - '00080030': 'StudyTime', - '00100010': 'PatientName', - '00100020': 'PatientID', - '00100040': 'PatientSex', - '00100030': 'PatientBirthDate', - '00201206': 'NumberOfStudyRelatedSeries', - '00201208': 'NumberOfStudyRelatedInstances', - '00080061': 'ModalitiesInStudy', - '00080060': 'Modality', - '00080090': 'ReferringPhysicianName', - '0020000E': 'SeriesInstanceUID' -} - -function formatMetadata(metadata) { - if (!metadata || typeof metadata !== 'object') { - return { dataset: {}, bulkDataMapping: {} } - } - const dataset = {} - for (const [tag, keyword] of Object.entries(TAG_TO_KEYWORD)) { - if (metadata[tag] && metadata[tag].Value) { - const value = metadata[tag].Value - dataset[keyword] = value.length === 1 ? value[0] : value - } - } - return { dataset, bulkDataMapping: {} } -} - -const metadata = { - formatMetadata -} - -module.exports = { - metadata -} diff --git a/src/__mocks__/dicomMicroscopyViewerMock.ts b/src/__mocks__/dicomMicroscopyViewerMock.ts new file mode 100644 index 00000000..0a5e5ed1 --- /dev/null +++ b/src/__mocks__/dicomMicroscopyViewerMock.ts @@ -0,0 +1,47 @@ +/** + * Default stand-in for dicom-microscopy-viewer in unit tests (aliased in + * vite.config.ts). Provides metadata.formatMetadata so Worklist and other + * components can run; tests that need more call vi.mock themselves. + */ + +interface DicomJsonElement { + Value?: unknown[] +} + +const TAG_TO_KEYWORD: Record = { + '0020000D': 'StudyInstanceUID', + '00200010': 'StudyDescription', + '00080050': 'AccessionNumber', + '00080020': 'StudyDate', + '00080030': 'StudyTime', + '00100010': 'PatientName', + '00100020': 'PatientID', + '00100040': 'PatientSex', + '00100030': 'PatientBirthDate', + '00201206': 'NumberOfStudyRelatedSeries', + '00201208': 'NumberOfStudyRelatedInstances', + '00080061': 'ModalitiesInStudy', + '00080060': 'Modality', + '00080090': 'ReferringPhysicianName', + '0020000E': 'SeriesInstanceUID', +} + +function formatMetadata(metadata: unknown): { + dataset: Record + bulkDataMapping: Record +} { + const dataset: Record = {} + if (metadata == null || typeof metadata !== 'object') { + return { dataset, bulkDataMapping: {} } + } + const elements = metadata as Record + for (const [tag, keyword] of Object.entries(TAG_TO_KEYWORD)) { + const value = elements[tag]?.Value + if (value != null) { + dataset[keyword] = value.length === 1 ? value[0] : value + } + } + return { dataset, bulkDataMapping: {} } +} + +export const metadata = { formatMetadata } diff --git a/src/__mocks__/emscriptenMock.js b/src/__mocks__/emscriptenMock.js deleted file mode 100644 index 00b24697..00000000 --- a/src/__mocks__/emscriptenMock.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - process: () => { - return 'module.exports = () => {}' - } -} diff --git a/src/__mocks__/styleMock.js b/src/__mocks__/styleMock.js deleted file mode 100644 index e00e9596..00000000 --- a/src/__mocks__/styleMock.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - process: () => { - return '' - } -} diff --git a/src/__mocks__/wasmMock.js b/src/__mocks__/wasmMock.js deleted file mode 100644 index e00e9596..00000000 --- a/src/__mocks__/wasmMock.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - process: () => { - return '' - } -} diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx new file mode 100644 index 00000000..02cd3402 --- /dev/null +++ b/src/__tests__/App.test.tsx @@ -0,0 +1,103 @@ +import { render, screen } from '@testing-library/react' +import type { ReactNode } from 'react' + +import App from '../App' +import type AppConfig from '../AppConfig' + +vi.mock('../features/header', () => ({ + Header: ({ + showWorklistButton, + showServerSelectionButton, + }: { + showWorklistButton: boolean + showServerSelectionButton: boolean + }) => ( +
+ ), +})) + +vi.mock('../features/worklist', () => ({ + Worklist: () =>
Worklist
, +})) + +vi.mock('../contexts/ValidationContext', () => ({ + ValidationProvider: ({ children }: { children: ReactNode }) => children, +})) + +vi.mock('../components/CaseViewer', () => ({ + __esModule: true, + default: ({ studyInstanceUID }: { studyInstanceUID: string }) => ( +
Case {studyInstanceUID}
+ ), +})) + +const createConfig = (overrides: Partial = {}): AppConfig => ({ + servers: [ + { id: 'primary', url: 'https://dicom.example.com/dicomWeb', write: false }, + ], + path: '/', + annotations: [], + ...overrides, +}) + +const renderAt = (path: string, config: AppConfig = createConfig()): void => { + window.history.replaceState({}, '', path) + render() +} + +describe('App', () => { + beforeEach(() => { + vi.spyOn(console, 'info').mockImplementation(vi.fn()) + /** react-router v7 future-flag notices */ + vi.spyOn(console, 'warn').mockImplementation(vi.fn()) + }) + + afterEach(() => { + vi.restoreAllMocks() + window.history.replaceState({}, '', '/') + }) + + it('shows the worklist once signed in', async () => { + renderAt('/', createConfig({ enableServerSelection: true })) + + expect(await screen.findByText('Worklist')).toBeInTheDocument() + expect(screen.getByTestId('header')).toHaveAttribute( + 'data-server-selection', + 'true', + ) + }) + + it('explains a disabled worklist', async () => { + renderAt('/', createConfig({ disableWorklist: true })) + + expect( + await screen.findByText('Worklist has been disabled.'), + ).toBeInTheDocument() + }) + + it('opens the study from the route', async () => { + renderAt('/studies/1.2.3') + + expect(await screen.findByText('Case 1.2.3')).toBeInTheDocument() + expect(screen.getByTestId('header')).toHaveAttribute( + 'data-worklist-button', + 'true', + ) + }) + + it('opens a study addressed by a GCP store path', async () => { + renderAt('/projects/p/locations/l/datasets/d/dicomStores/s/study/4.5.6') + + expect(await screen.findByText('Case 4.5.6')).toBeInTheDocument() + }) + + it('shows the logout page', async () => { + renderAt('/logout') + + expect(await screen.findByText('Logged out')).toBeInTheDocument() + }) +}) diff --git a/src/__tests__/DicomWebManager.test.ts b/src/__tests__/DicomWebManager.test.ts index 96e9e4e0..7c40cf6d 100644 --- a/src/__tests__/DicomWebManager.test.ts +++ b/src/__tests__/DicomWebManager.test.ts @@ -1,23 +1,25 @@ -// skipcq: JS-C1003 -import * as dwc from 'dicomweb-client' +/** skipcq: JS-C1003 */ + +import type * as dwc from 'dicomweb-client' +import type { Mock } from 'vitest' import DicomWebManager from '../DicomWebManager' interface StubClient { baseURL: string headers: Record - searchForStudies: jest.Mock - searchForSeries: jest.Mock - searchForInstances: jest.Mock - retrieveStudyMetadata: jest.Mock - retrieveSeriesMetadata: jest.Mock - retrieveInstance: jest.Mock - retrieveInstanceMetadata: jest.Mock - retrieveInstanceFrames: jest.Mock - retrieveInstanceRendered: jest.Mock - retrieveInstanceFramesRendered: jest.Mock - retrieveBulkData: jest.Mock - storeInstances: jest.Mock + searchForStudies: Mock + searchForSeries: Mock + searchForInstances: Mock + retrieveStudyMetadata: Mock + retrieveSeriesMetadata: Mock + retrieveInstance: Mock + retrieveInstanceMetadata: Mock + retrieveInstanceFrames: Mock + retrieveInstanceRendered: Mock + retrieveInstanceFramesRendered: Mock + retrieveBulkData: Mock + storeInstances: Mock } interface ManagerInternals { @@ -32,18 +34,18 @@ interface ManagerInternals { const makeStubClient = (id: string): StubClient => ({ baseURL: `https://example.test/${id}/dicomWeb`, headers: {}, - searchForStudies: jest.fn(), - searchForSeries: jest.fn(), - searchForInstances: jest.fn(), - retrieveStudyMetadata: jest.fn(), - retrieveSeriesMetadata: jest.fn(), - retrieveInstance: jest.fn(), - retrieveInstanceMetadata: jest.fn(), - retrieveInstanceFrames: jest.fn(), - retrieveInstanceRendered: jest.fn(), - retrieveInstanceFramesRendered: jest.fn(), - retrieveBulkData: jest.fn(), - storeInstances: jest.fn(), + searchForStudies: vi.fn(), + searchForSeries: vi.fn(), + searchForInstances: vi.fn(), + retrieveStudyMetadata: vi.fn(), + retrieveSeriesMetadata: vi.fn(), + retrieveInstance: vi.fn(), + retrieveInstanceMetadata: vi.fn(), + retrieveInstanceFrames: vi.fn(), + retrieveInstanceRendered: vi.fn(), + retrieveInstanceFramesRendered: vi.fn(), + retrieveBulkData: vi.fn(), + storeInstances: vi.fn(), }) /** @@ -71,11 +73,11 @@ const httpError = (status: number): Error & { status: number } => const allowAllPolicy = ( token = 'Bearer abc', ): { - isPreAuthorized: jest.Mock - requestAuthorization: jest.Mock + isPreAuthorized: Mock + requestAuthorization: Mock } => ({ - isPreAuthorized: jest.fn().mockReturnValue(true), - requestAuthorization: jest.fn().mockResolvedValue(token), + isPreAuthorized: vi.fn().mockReturnValue(true), + requestAuthorization: vi.fn().mockResolvedValue(token), }) /** Policy that grants only when challenged, mimicking the consent prompt. */ @@ -83,13 +85,11 @@ const consentPolicy = ( approved: boolean, token = 'Bearer abc', ): { - isPreAuthorized: jest.Mock - requestAuthorization: jest.Mock + isPreAuthorized: Mock + requestAuthorization: Mock } => ({ - isPreAuthorized: jest.fn().mockReturnValue(false), - requestAuthorization: jest - .fn() - .mockResolvedValue(approved ? token : undefined), + isPreAuthorized: vi.fn().mockReturnValue(false), + requestAuthorization: vi.fn().mockResolvedValue(approved ? token : undefined), }) /** @@ -100,15 +100,15 @@ const consentPolicy = ( const recordingPolicy = ( token = 'Bearer abc', ): { - isPreAuthorized: jest.Mock - requestAuthorization: jest.Mock + isPreAuthorized: Mock + requestAuthorization: Mock granted: Set } => { const granted = new Set() return { granted, - isPreAuthorized: jest.fn((origin: string) => granted.has(origin)), - requestAuthorization: jest.fn(async (origin: string) => { + isPreAuthorized: vi.fn((origin: string) => granted.has(origin)), + requestAuthorization: vi.fn(async (origin: string) => { granted.add(origin) return await Promise.resolve(token) }), @@ -121,7 +121,11 @@ describe('DicomWebManager - multi-store search', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) @@ -135,7 +139,7 @@ describe('DicomWebManager - multi-store search', () => { '0020000E': { vr: 'UI', Value: ['1.2.3.A'] }, '00080060': { vr: 'CS', Value: ['ANN'] }, }, - // Same SeriesInstanceUID appears in both stores; should be deduped. + /** Same SeriesInstanceUID appears in both stores; should be deduped. */ { '0020000D': { vr: 'UI', Value: ['1.2.3'] }, '0020000E': { vr: 'UI', Value: ['1.2.3.SHARED'] }, @@ -175,7 +179,11 @@ describe('DicomWebManager - multi-store search', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) @@ -197,9 +205,8 @@ describe('DicomWebManager - multi-store search', () => { expect(merged.length).toBe(1) expect( - (merged[0] as unknown as Record)[ - '0020000E' - ]?.Value?.[0], + (merged[0] as unknown as Record)['0020000E'] + ?.Value?.[0], ).toBe('1.2.3.B') }) @@ -208,7 +215,11 @@ describe('DicomWebManager - multi-store search', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) @@ -236,7 +247,11 @@ describe('DicomWebManager - multi-store search', () => { write: false, read: false, }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) @@ -268,7 +283,11 @@ describe('DicomWebManager - multi-store retrieve fallback', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) @@ -295,7 +314,11 @@ describe('DicomWebManager - multi-store retrieve fallback', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) @@ -327,7 +350,11 @@ describe('DicomWebManager - multi-store retrieve fallback', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) @@ -352,7 +379,11 @@ describe('DicomWebManager - storeInstances and headers', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: true }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: true, + }, ], }) @@ -375,7 +406,11 @@ describe('DicomWebManager - storeInstances and headers', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) @@ -395,7 +430,11 @@ describe('DicomWebManager - storeInstances and headers', () => { baseUri, settings: [ { id: 'primary', url: 'https://primary.test/dicomWeb', write: false }, - { id: 'secondary', url: 'https://secondary.test/dicomWeb', write: false }, + { + id: 'secondary', + url: 'https://secondary.test/dicomWeb', + write: false, + }, ], }) manager.setAuthorizationPolicy(allowAllPolicy()) @@ -423,7 +462,7 @@ describe('DicomWebManager - storeInstances and headers', () => { }, ], }) - // The primary is in auto mode; approve its origin so it carries the token. + /** The primary is in auto mode; approve its origin so it carries the token. */ manager.setAuthorizationPolicy(allowAllPolicy()) const primaryStub = makeStubClient('primary') @@ -437,14 +476,16 @@ describe('DicomWebManager - storeInstances and headers', () => { expect(primaryStub.headers.Authorization).toBe('Bearer abc') expect(openStub.headers.Authorization).toBeUndefined() - // Non-credential headers are still propagated to the open store. + /** Non-credential headers are still propagated to the open store. */ expect(openStub.headers['X-Custom']).toBe('value') }) it('withholds the token from an unchallenged server until it asks', () => { const manager = new DicomWebManager({ baseUri, - settings: [{ id: 'open', url: 'https://open.test/dicomWeb', write: false }], + settings: [ + { id: 'open', url: 'https://open.test/dicomWeb', write: false }, + ], }) manager.setAuthorizationPolicy(consentPolicy(true)) @@ -453,7 +494,7 @@ describe('DicomWebManager - storeInstances and headers', () => { manager.updateHeaders({ Authorization: 'Bearer abc' }) - // Nothing has returned 401, so the server never sees the credential. + /** Nothing has returned 401, so the server never sees the credential. */ expect(openStub.headers.Authorization).toBeUndefined() }) @@ -526,7 +567,11 @@ describe('DicomWebManager - authorization escalation', () => { const manager = new DicomWebManager({ baseUri, settings: [ - { id: 'untrusted', url: 'https://untrusted.test/dicomWeb', write: false }, + { + id: 'untrusted', + url: 'https://untrusted.test/dicomWeb', + write: false, + }, ], }) manager.setAuthorizationPolicy(consentPolicy(false)) @@ -539,7 +584,7 @@ describe('DicomWebManager - authorization escalation', () => { await expect(manager.searchForStudies({})).rejects.toMatchObject({ status: 401, }) - // One attempt only: no retry, and the credential was never attached. + /** One attempt only: no retry, and the credential was never attached. */ expect(stub.searchForStudies).toHaveBeenCalledTimes(1) expect(stub.headers.Authorization).toBeUndefined() }) @@ -596,16 +641,20 @@ describe('DicomWebManager - authorization escalation', () => { manager.searchForInstances({}), ]) - // Three simultaneous challenges, one consent prompt. + /** Three simultaneous challenges, one consent prompt. */ expect(policy.requestAuthorization).toHaveBeenCalledTimes(1) }) it('does not route a refused challenge through the DICOMweb error handler', async () => { - const onError = jest.fn() + const onError = vi.fn() const manager = new DicomWebManager({ baseUri, settings: [ - { id: 'untrusted', url: 'https://untrusted.test/dicomWeb', write: false }, + { + id: 'untrusted', + url: 'https://untrusted.test/dicomWeb', + write: false, + }, ], onError, }) @@ -632,7 +681,11 @@ describe('DicomWebManager - authorization escalation', () => { const manager = new DicomWebManager({ baseUri, settings: [ - { id: 'untrusted', url: 'https://untrusted.test/dicomWeb', write: false }, + { + id: 'untrusted', + url: 'https://untrusted.test/dicomWeb', + write: false, + }, ], }) const policy = consentPolicy(false) @@ -649,12 +702,12 @@ describe('DicomWebManager - authorization escalation', () => { }) } - // Asked once; the answer stands for the rest of the session. + /** Asked once; the answer stands for the rest of the session. */ expect(policy.requestAuthorization).toHaveBeenCalledTimes(1) }) it('does not route an unauthorized sendAuthorization: false store through the error handler', async () => { - const onError = jest.fn() + const onError = vi.fn() const manager = new DicomWebManager({ baseUri, settings: [ @@ -729,7 +782,7 @@ describe('DicomWebManager - authorization escalation', () => { const secondaryStub = makeStubClient('secondary') stubManagerClients(manager, [primaryStub, secondaryStub]) - // Only the primary is challenged. + /** Only the primary is challenged. */ primaryStub.searchForStudies .mockRejectedValueOnce(httpError(401)) .mockResolvedValue([]) @@ -738,7 +791,7 @@ describe('DicomWebManager - authorization escalation', () => { await manager.searchForStudies({}) expect(primaryStub.headers.Authorization).toBe('Bearer abc') - // Same origin, same grant: no need to be refused once on its own account. + /** Same origin, same grant: no need to be refused once on its own account. */ expect(secondaryStub.headers.Authorization).toBe('Bearer abc') }) @@ -764,7 +817,7 @@ describe('DicomWebManager - authorization escalation', () => { await manager.searchForStudies({}) expect(gcpStub.headers.Authorization).toBe('Bearer abc') - // Consent is per origin; this one was never approved. + /** Consent is per origin; this one was never approved. */ expect(otherStub.headers.Authorization).toBeUndefined() }) @@ -795,7 +848,7 @@ describe('DicomWebManager - authorization escalation', () => { await manager.searchForStudies({}) expect(primaryStub.headers.Authorization).toBe('Bearer abc') - // An explicit operator override outranks a grant for the same origin. + /** An explicit operator override outranks a grant for the same origin. */ expect(openStub.headers.Authorization).toBeUndefined() }) @@ -843,7 +896,7 @@ describe('DicomWebManager - authorization escalation', () => { const gcpStub = makeStubClient('gcp') stubManagerClients(manager, [gcpStub]) - // Token arrives before the policy is installed. + /** Token arrives before the policy is installed. */ manager.updateHeaders({ Authorization: 'Bearer abc' }) expect(gcpStub.headers.Authorization).toBeUndefined() diff --git a/src/__tests__/authPolicy.test.ts b/src/__tests__/authPolicy.test.ts index a2ccb1dd..cb43310a 100644 --- a/src/__tests__/authPolicy.test.ts +++ b/src/__tests__/authPolicy.test.ts @@ -16,7 +16,7 @@ describe('authPolicy - origin resolution', () => { }) it('resolves a relative path against the Slim origin', () => { - // `servers[].path` configurations are same-origin by construction. + /** `servers[].path` configurations are same-origin by construction. */ expect(getOrigin('/dicomweb')).toBe(window.location.origin) }) @@ -79,8 +79,10 @@ describe('authPolicy - remembered decisions', () => { const now = 1_000_000_000_000 writeAuthorizationDecision('https://no.test', 'denied', now) - // Forgetting a denial would silently widen disclosure; forgetting a grant - // only costs a prompt. + /** + * Forgetting a denial would silently widen disclosure; forgetting a grant + * only costs a prompt. + */ expect( readAuthorizationDecision('https://no.test', now + 3650 * DAY_MS), ).toBe('denied') @@ -119,7 +121,7 @@ describe('authPolicy - remembered decisions', () => { }) it('never expires a pre-seeded grant that omits an expiry', () => { - // A long-lived browser profile must not start prompting mid-campaign. + /** A long-lived browser profile must not start prompting mid-campaign. */ window.localStorage.setItem( 'slim_authorization_policy', JSON.stringify({ 'https://archive.test': { decision: 'granted' } }), diff --git a/src/__tests__/singleFlight.test.ts b/src/__tests__/singleFlight.test.ts index b2be1556..5f412a06 100644 --- a/src/__tests__/singleFlight.test.ts +++ b/src/__tests__/singleFlight.test.ts @@ -18,7 +18,7 @@ const deferred = (): { describe('createSingleFlight', () => { it('runs the operation once for concurrent callers sharing a key', async () => { const gate = createSingleFlight() - const run = jest.fn(async () => await Promise.resolve('token')) + const run = vi.fn(async () => await Promise.resolve('token')) const results = await Promise.all([ gate('https://a.test', run), @@ -33,7 +33,7 @@ describe('createSingleFlight', () => { it('gives every concurrent caller the same answer', async () => { const gate = createSingleFlight() const control = deferred() - const run = jest.fn(async () => await control.promise) + const run = vi.fn(async () => await control.promise) const first = gate('https://a.test', run) const second = gate('https://a.test', run) @@ -46,7 +46,7 @@ describe('createSingleFlight', () => { it('keeps distinct keys independent', async () => { const gate = createSingleFlight() - const run = jest.fn(async (value: string) => await Promise.resolve(value)) + const run = vi.fn(async (value: string) => await Promise.resolve(value)) await Promise.all([ gate('https://a.test', async () => await run('a')), @@ -58,18 +58,18 @@ describe('createSingleFlight', () => { it('releases the key so a later call starts a fresh run', async () => { const gate = createSingleFlight() - const run = jest.fn(async () => await Promise.resolve('token')) + const run = vi.fn(async () => await Promise.resolve('token')) await gate('https://a.test', run) await gate('https://a.test', run) - // Sequential callers must not replay a stale answer. + /** Sequential callers must not replay a stale answer. */ expect(run).toHaveBeenCalledTimes(2) }) it('propagates a rejection to every joined caller and releases the key', async () => { const gate = createSingleFlight() - const failing = jest.fn(async () => await Promise.reject(new Error('nope'))) + const failing = vi.fn(async () => await Promise.reject(new Error('nope'))) const first = gate('https://a.test', failing) const second = gate('https://a.test', failing) @@ -78,8 +78,8 @@ describe('createSingleFlight', () => { await expect(second).rejects.toThrow('nope') expect(failing).toHaveBeenCalledTimes(1) - // A failed run must not poison the key. - const succeeding = jest.fn(async () => await Promise.resolve('token')) + /** A failed run must not poison the key. */ + const succeeding = vi.fn(async () => await Promise.resolve('token')) await expect(gate('https://a.test', succeeding)).resolves.toBe('token') }) }) diff --git a/src/app/AppPage.tsx b/src/app/AppPage.tsx new file mode 100644 index 00000000..40cb0ec2 --- /dev/null +++ b/src/app/AppPage.tsx @@ -0,0 +1,68 @@ +import type { JSX, ReactNode } from 'react' + +import type { User } from '../auth' +import AppShell from '../components/AppShell' +import { Header, type ServerSelectionParams } from '../features/header' +import type { AppInfo } from '../utils/appInfo' +import type { ClientMapping } from './clientMapping' + +const PAGE_CONTENT_CLASS_NAME = 'flex-1 min-h-0 overflow-hidden flex flex-col' + +export interface PageOptions { + showWorklistButton: boolean + showServerSelectionButton: boolean + onUserLogout?: () => void + /** Appended to the content container's base classes */ + contentClassName?: string +} + +/** Header inputs shared by every page */ +export interface AppPageContext { + app: AppInfo + user?: User + clients: ClientMapping + defaultClients: ClientMapping + onServerSelection: (params: ServerSelectionParams) => void +} + +export interface AppPageProps extends AppPageContext, PageOptions { + children: ReactNode +} + +/** App shell with the header above `children` */ +export function AppPage({ + app, + user, + clients, + defaultClients, + onServerSelection, + showWorklistButton, + showServerSelectionButton, + onUserLogout, + contentClassName, + children, +}: AppPageProps): JSX.Element { + return ( + +
+
+
className !== undefined) + .join(' ')} + > + {children} +
+
+
+ ) +} diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx new file mode 100644 index 00000000..801e5422 --- /dev/null +++ b/src/app/AppRoutes.tsx @@ -0,0 +1,84 @@ +import type { JSX } from 'react' +import { Route, Routes } from 'react-router' + +import type AppConfig from '../AppConfig' +import { Worklist } from '../features/worklist' +import { RoutePaths } from '../utils/routes' +import { AppPage, type AppPageContext } from './AppPage' +import { ParametrizedCaseViewer } from './ParametrizedCaseViewer' + +export interface AppRoutesProps { + config: AppConfig + page: AppPageContext + onUserLogout?: () => void + /** Changing it remounts every route */ + recoveryKey: number +} + +export function AppRoutes({ + config, + page, + onUserLogout, + recoveryKey, +}: AppRoutesProps): JSX.Element { + const enableWorklist = !(config.disableWorklist ?? false) + const enableServerSelection = config.enableServerSelection ?? false + + const worklist = enableWorklist ? ( + + ) : ( +
+ Worklist has been disabled. +
+ ) + + const caseViewerPage = ( + + + + ) + + return ( + + + {worklist} + + } + /> + + + + Logged out + + } + /> + + ) +} diff --git a/src/app/AppSession.ts b/src/app/AppSession.ts new file mode 100644 index 00000000..f7f7c1ce --- /dev/null +++ b/src/app/AppSession.ts @@ -0,0 +1,481 @@ +import type AppConfig from '../AppConfig' +import type { + DicomWebManagerErrorHandler, + ErrorMessageSettings, + OidcSettings, +} from '../AppConfig' +import type { AuthManager, SignInCallback, User } from '../auth' +import OidcManager from '../auth/OidcManager' +import { + clearCachedOidcConfig, + readCachedOidcConfig, + resetCachedOidcConfigFromUrl, +} from '../auth/oidcConfig' +import type { AuthorizationPolicy } from '../DicomWebManager' +import DicomWebManager from '../DicomWebManager' +import type { ServerSelectionParams } from '../features/header' +import NotificationMiddleware, { + NotificationMiddlewareContext, +} from '../services/NotificationMiddleware' +import { CustomError, errorTypes } from '../utils/CustomError' +import { + getLocalStorage, + readStorage, + writeStorage, +} from '../utils/safeStorage' +import { + loadServerSelection, + SERVER_MODE_STORAGE_KEY, + SERVER_URL_STORAGE_KEY, +} from '../utils/serverSelectionStorage' +import { joinUrl } from '../utils/url' +import { + addGcpSecondaryAnnotationServer, + applyAuthorizationPolicy, + type ClientMapping, + collectConfiguredOrigins, + createClientMapping, + DEFAULT_GCP_BASE_URL, + mapAllStorageClasses, + resolveCustomServerUrl, + updateAuthorization, +} from './clientMapping' +import { confirmAuthorizationDisclosure } from './confirmAuthorizationDisclosure' +import { + createTokenDisclosurePolicy, + type TokenDisclosureOptions, +} from './tokenDisclosure' + +export interface AppSessionState { + clients: ClientMapping + defaultClients: ClientMapping + user?: User + isLoading: boolean + wasAuthSuccessful: boolean + signInFailureMessage?: string + error?: ErrorMessageSettings + /** Bumped after mid-session auth recovery so views remount and refetch. */ + authRecoveryKey: number +} + +export interface AppSessionOptions { + config: AppConfig + createAuth?: (appUri: string, settings: OidcSettings) => AuthManager + confirmDisclosure?: TokenDisclosureOptions['confirm'] + reload?: () => void +} + +type StateUpdate = + | Partial + | ((state: AppSessionState) => Partial) + +const SIGN_IN_FAILED_WITH_CACHED_CONFIG = + 'Sign-in with the custom OIDC configuration failed. It has been cleared; reload the page to use the default configuration.' + +/** + * Sign-in, DICOMweb clients and server selection for the app, as an external + * store the React tree subscribes to. Constructing it has side effects (URL + * cleanup, an OIDC manager with its own timers, `?gcp=` appended to the + * configured servers), so it is created once per configuration rather than + * during render. + */ +export class AppSession { + readonly auth?: AuthManager + /** Whether `auth` was built from the OIDC config cached by server selection */ + private usesCachedOidcConfig = false + /** Issuer of the tokens `auth` hands out */ + private readonly oidcAuthority?: string + private reauthInProgress = false + private hasStarted = false + private unsubscribeAuthorization?: () => void + private readonly authorizationPolicy: AuthorizationPolicy + private readonly reload: () => void + private readonly listeners = new Set<() => void>() + private state: AppSessionState + + constructor({ + config, + createAuth = (appUri, settings) => new OidcManager(appUri, settings), + confirmDisclosure = confirmAuthorizationDisclosure, + reload = () => window.location.reload(), + }: AppSessionOptions) { + this.reload = reload + + if (import.meta.env.MODE === 'development') { + console.info('instatiate app') + console.info(`app is located at "${config.path}"`) + } + + const { protocol, host } = window.location + const baseUri = `${protocol}//${host}` + const appUri = joinUrl(config.path, baseUri) + + resetCachedOidcConfigFromUrl() + /** OIDC config entered in server selection overrides the deployment one */ + const cachedOidcSettings = readCachedOidcConfig() + this.usesCachedOidcConfig = cachedOidcSettings !== undefined + const oidcSettings = cachedOidcSettings ?? config.oidc + if (oidcSettings !== undefined) { + if (import.meta.env.MODE === 'development') { + console.info( + 'app uses the following OIDC configuration: ', + oidcSettings, + ) + } + this.auth = createAuth(appUri, oidcSettings) + this.oidcAuthority = oidcSettings.authority + } + + if (config.servers.length === 0) { + NotificationMiddleware.onError( + NotificationMiddlewareContext.SLIM, + new CustomError( + errorTypes.COMMUNICATION, + 'One server needs to be configured.', + ), + ) + } + + if (import.meta.env.MODE === 'development') { + console.info( + 'app uses the following DICOMweb server configuration: ', + config.servers, + ) + } + + /** + * Hold the servers that came from the configuration file, before `?gcp=` + * appends a runtime, URL-supplied one. These are references, not copies: + * `createClientMapping` rewrites `url` in place on `/projects/` routes, so + * the origins are read afterwards to capture the effective value. + */ + const configuredServers = [...config.servers] + addGcpSecondaryAnnotationServer(config, window.location.search) + + const defaultClients = createClientMapping({ + baseUri, + gcpBaseUrl: config.gcpBaseUrl ?? DEFAULT_GCP_BASE_URL, + settings: config.servers, + pathname: window.location.pathname, + onError: this.handleDicomWebError, + }) + + this.authorizationPolicy = createTokenDisclosurePolicy({ + auth: this.auth, + configuredOrigins: collectConfiguredOrigins(configuredServers, baseUri), + oidcAuthority: this.oidcAuthority, + confirm: confirmDisclosure, + onAuthorization: (authorization) => { + this.applyAuthorization(authorization) + }, + }) + applyAuthorizationPolicy(defaultClients, this.authorizationPolicy) + + this.state = { + clients: defaultClients, + defaultClients, + isLoading: true, + wasAuthSuccessful: false, + authRecoveryKey: 0, + } + } + + subscribe = (listener: () => void): (() => void) => { + this.listeners.add(listener) + return () => { + this.listeners.delete(listener) + } + } + + getSnapshot = (): AppSessionState => this.state + + private update(update: StateUpdate): void { + const patch = typeof update === 'function' ? update(this.state) : update + this.state = { ...this.state, ...patch } + for (const listener of this.listeners) { + listener() + } + } + + /** + * Connect to the identity provider once the app has mounted. Sign-in and + * the cached server selection run only on the first call, so a remount does + * not restart them; the returned cleanup only detaches token updates. + */ + start = (): (() => void) => { + const isFirstStart = !this.hasStarted + this.hasStarted = true + + if (isFirstStart) { + const cachedSelection = loadServerSelection(getLocalStorage()) + if (cachedSelection.mode === 'custom') { + void this.selectServer({ url: cachedSelection.url }) + } + } + + if (this.auth != null && this.unsubscribeAuthorization === undefined) { + this.unsubscribeAuthorization = this.auth.onAuthorizationChange( + (authorization) => { + this.applyAuthorization(authorization) + }, + ) + } + + if (isFirstStart) { + this.signIn() + } + + return () => { + this.unsubscribeAuthorization?.() + this.unsubscribeAuthorization = undefined + } + } + + readonly handleDicomWebError: DicomWebManagerErrorHandler = ( + error, + serverSettings, + ) => { + if (error.status === 401) { + void this.ensureAuthorized() + } else if (error.status === 403) { + NotificationMiddleware.onError( + NotificationMiddlewareContext.DICOMWEB, + new CustomError( + errorTypes.COMMUNICATION, + 'User is not authorized to access DICOMweb resources.', + ), + ) + } + + const logServerError = (): void => { + NotificationMiddleware.onError( + NotificationMiddlewareContext.DICOMWEB, + new CustomError( + errorTypes.COMMUNICATION, + 'An unexpected server error occured.', + ), + ) + } + + if (serverSettings.errorMessages !== undefined) { + serverSettings.errorMessages.forEach((setting: ErrorMessageSettings) => { + if (error.status === setting.status) { + this.update({ + error: { + status: error.status, + message: setting.message, + }, + }) + } else if (error.status === 500) { + logServerError() + } + }) + } else if (error.status === 500) { + logServerError() + } + } + + selectServer = async ({ + url, + oidc, + }: ServerSelectionParams): Promise => { + const trimmedUrl = url.trim() + console.info('select DICOMweb server: ', trimmedUrl) + + const resolvedUrl = resolveCustomServerUrl( + trimmedUrl, + readStorage(SERVER_MODE_STORAGE_KEY), + ) + if (resolvedUrl !== undefined) { + writeStorage(SERVER_URL_STORAGE_KEY, resolvedUrl) + } + + if (oidc !== undefined) { + /** + * Start over with the cached config, which the constructor reads and + * mount signs in with. Clients keep token grants and headers from the + * previous provider in memory, and the deployment may have no OIDC. + */ + console.info( + oidc === null + ? 'removing custom OIDC configuration' + : 'applying custom OIDC configuration', + ) + this.reload() + return + } + + if (resolvedUrl === undefined) { + this.update((state) => ({ clients: state.defaultClients })) + return + } + const tmpClient = new DicomWebManager({ + baseUri: '', + settings: [ + { + id: 'tmp', + url: resolvedUrl, + read: true, + write: false, + }, + ], + onError: this.handleDicomWebError, + }) + tmpClient.setAuthorizationPolicy(this.authorizationPolicy) + /** + * Carry over non-credential headers only. The token is deliberately not + * forwarded here: this URL was typed by the user and has not been vetted by + * anyone. If the server actually needs credentials it will answer 401, and + * the authorization policy will ask before anything is disclosed. + */ + const { Authorization: _omitted, ...inheritedHeaders } = + this.state.clients.default.headers + tmpClient.updateHeaders(inheritedHeaders) + if (this.auth != null && this.state.user != null) { + const authorization = await this.auth.getAuthorization() + if (authorization != null) { + /** + * Offered, not forced: `updateHeaders` attaches it only if this origin + * has already been approved. + */ + tmpClient.updateHeaders({ Authorization: authorization }) + } + } + this.update((state) => ({ + clients: mapAllStorageClasses(state.clients, tmpClient), + })) + } + + private applyAuthorization(authorization: string): void { + updateAuthorization(this.state.clients, authorization) + updateAuthorization(this.state.defaultClients, authorization) + } + + /** + * Handle successful authentication event. + * + * Authorizes the DICOMweb client to access the DICOMweb server and directs + * the user back to the pre-login route (via OIDC state). + */ + private readonly handleSignIn: SignInCallback = ({ + user, + authorization, + returnUrl, + }) => { + this.applyAuthorization(authorization) + this.update({ user }) + + if (returnUrl != null && returnUrl !== '') { + const current = `${window.location.pathname}${window.location.search}` + if (returnUrl !== current) { + window.location.assign(returnUrl) + } + } + } + + /** + * Recover from an expired/missing access token without losing the route. + * Tries silent renew first; falls back to interactive redirect with returnUrl. + */ + ensureAuthorized = async (): Promise => { + if (this.auth == null || this.reauthInProgress) { + return + } + this.reauthInProgress = true + let redirectedToIdp = false + try { + const authorization = await this.auth.renewAuthorization() + if (authorization != null) { + this.applyAuthorization(authorization) + /** Remount routed views so in-flight 401 failures refetch with the new token. */ + this.update((state) => ({ + authRecoveryKey: state.authRecoveryKey + 1, + })) + return + } + console.info('silent renew unavailable; starting interactive sign-in') + const outcome = await this.auth.signIn({ + onSignIn: this.handleSignIn, + returnUrl: `${window.location.pathname}${window.location.search}`, + }) + redirectedToIdp = outcome === 'redirected' + if (outcome === 'completed') { + /** Token was refreshed without leaving the page; remount views to refetch. */ + this.update((state) => ({ + authRecoveryKey: state.authRecoveryKey + 1, + })) + } + } catch (error) { + console.error(error) + NotificationMiddleware.onError( + NotificationMiddlewareContext.AUTH, + new CustomError( + errorTypes.AUTHENTICATION, + 'Could not renew authorization.', + ), + ) + } finally { + /** + * Once the page is navigating to the IdP, signIn either resolves right + * away or (oidc-client-ts) stays pending until unload. Keep the guard set + * until unload so concurrent 401s cannot start another redirect. + */ + if (!redirectedToIdp) { + this.reauthInProgress = false + } + } + } + + private signIn(): void { + if (this.auth === undefined) { + this.update({ + isLoading: false, + wasAuthSuccessful: true, + }) + return + } + console.info('try to sign in user') + this.auth + .signIn({ + onSignIn: this.handleSignIn, + returnUrl: `${window.location.pathname}${window.location.search}`, + }) + .then((outcome) => { + if (outcome === 'redirected') { + return + } + console.info('sign-in was successful') + this.update({ + isLoading: false, + wasAuthSuccessful: true, + }) + }) + .catch((error: unknown) => { + console.error(error) + NotificationMiddleware.onError( + NotificationMiddlewareContext.AUTH, + new CustomError(errorTypes.AUTHENTICATION, 'Could not sign-in user.'), + ) + /** + * The failure page has no header, so a broken custom config could + * not be changed from the UI and would be reused on every load. + */ + let signInFailureMessage: string | undefined + if (this.usesCachedOidcConfig) { + clearCachedOidcConfig() + this.usesCachedOidcConfig = false + signInFailureMessage = SIGN_IN_FAILED_WITH_CACHED_CONFIG + } + this.update({ + isLoading: false, + wasAuthSuccessful: false, + signInFailureMessage, + }) + }) + } + + /** Sign out with the identity provider; a no-op without OIDC */ + signOut = (): void => { + void this.auth?.signOut() + } +} diff --git a/src/app/ConfigProblemsPage.tsx b/src/app/ConfigProblemsPage.tsx new file mode 100644 index 00000000..06a207e0 --- /dev/null +++ b/src/app/ConfigProblemsPage.tsx @@ -0,0 +1,30 @@ +import type { JSX } from 'react' + +import InfoPage from '../components/InfoPage' +import type { ConfigProblem } from './configProblems' + +export function ConfigProblemsPage({ + problems, +}: { + problems: readonly ConfigProblem[] +}): JSX.Element { + return ( + +
    + {problems.map((problem) => ( +
  • +

    {problem.message}

    +

    {problem.hint}

    +
  • + ))} +
+
+ ) +} diff --git a/src/app/ParametrizedCaseViewer.tsx b/src/app/ParametrizedCaseViewer.tsx new file mode 100644 index 00000000..fb8e456a --- /dev/null +++ b/src/app/ParametrizedCaseViewer.tsx @@ -0,0 +1,46 @@ +import type { JSX } from 'react' +import { Navigate, useParams } from 'react-router' + +import type AppConfig from '../AppConfig' +import type { User } from '../auth' +import CaseViewer from '../components/CaseViewer' +import { ValidationProvider } from '../contexts/ValidationContext' +import type { AppInfo } from '../utils/appInfo' +import type { ClientMapping } from './clientMapping' + +export interface ParametrizedCaseViewerProps { + clients: ClientMapping + user?: User + app: AppInfo + config: AppConfig +} + +export function ParametrizedCaseViewer({ + clients, + user, + app, + config, +}: ParametrizedCaseViewerProps): JSX.Element { + const { studyInstanceUID } = useParams() + + if (studyInstanceUID === undefined) { + return + } + + const enableAnnotationTools = !(config.disableAnnotationTools ?? false) + const preload = config.preload ?? false + return ( + + + + ) +} diff --git a/src/app/__tests__/AppSession.test.ts b/src/app/__tests__/AppSession.test.ts new file mode 100644 index 00000000..e49fd5ff --- /dev/null +++ b/src/app/__tests__/AppSession.test.ts @@ -0,0 +1,368 @@ +import type { Mock } from 'vitest' +import type AppConfig from '../../AppConfig' +import type { OidcSettings } from '../../AppConfig' +import type { + AuthManager, + AuthorizationCallback, + SignInOutcome, +} from '../../auth' +import { + OIDC_CONFIG_STORAGE_KEY, + readCachedOidcConfig, +} from '../../auth/oidcConfig' +import NotificationMiddleware from '../../services/NotificationMiddleware' +import { + SERVER_MODE_STORAGE_KEY, + SERVER_URL_STORAGE_KEY, +} from '../../utils/serverSelectionStorage' +import { AppSession } from '../AppSession' + +const PRIMARY_URL = 'https://dicom.example.com/dicomWeb' +const CUSTOM_URL = 'https://custom.example.com/dicomWeb' + +const USER = { name: 'Ada Lovelace', email: 'ada@example.com' } + +type SignInArgs = Parameters[0] + +interface FakeAuth extends AuthManager { + signIn: Mock<(...args: [SignInArgs]) => Promise> + renewAuthorization: Mock<(...args: []) => Promise> + onAuthorizationChange: Mock<(...args: [AuthorizationCallback]) => () => void> + /** Unsubscribe functions handed out by onAuthorizationChange */ + unsubscribes: Mock[] +} + +/** Signs in with `Bearer t` unless `signIn` is overridden */ +const createFakeAuth = (): FakeAuth => { + const unsubscribes: Mock[] = [] + return { + unsubscribes, + signIn: vi.fn(({ onSignIn }: SignInArgs) => { + onSignIn?.({ user: USER, authorization: 'Bearer t' }) + return Promise.resolve('completed') + }), + signOut: vi.fn(() => Promise.resolve()), + getAuthorization: vi.fn(() => Promise.resolve('Bearer t')), + getUser: vi.fn(() => Promise.resolve(USER)), + renewAuthorization: vi.fn(() => + Promise.resolve(undefined), + ), + onAuthorizationChange: vi.fn((_callback: AuthorizationCallback) => { + const unsubscribe = vi.fn() + unsubscribes.push(unsubscribe) + return unsubscribe + }), + } +} + +const createConfig = (overrides: Partial = {}): AppConfig => ({ + servers: [{ id: 'primary', url: PRIMARY_URL, write: false }], + path: '/', + annotations: [], + ...overrides, +}) + +const OIDC: OidcSettings = { + authority: 'https://idp.example.com', + clientId: 'slim', + scope: 'openid', +} + +interface Setup { + session: AppSession + auth: FakeAuth + createAuth: Mock<(...args: [string, OidcSettings]) => AuthManager> + reload: Mock<(...args: []) => void> +} + +const setup = (config: AppConfig = createConfig({ oidc: OIDC })): Setup => { + const auth = createFakeAuth() + const createAuth = vi.fn<(...args: [string, OidcSettings]) => AuthManager>( + () => auth, + ) + const reload = vi.fn<(...args: []) => void>() + const session = new AppSession({ + config, + createAuth, + reload, + confirmDisclosure: () => Promise.resolve(false), + }) + return { session, auth, createAuth, reload } +} + +const flush = (): Promise => + new Promise((resolve) => { + setTimeout(resolve, 0) + }) + +describe('AppSession', () => { + beforeEach(() => { + window.localStorage.clear() + vi.spyOn(console, 'info').mockImplementation(vi.fn()) + vi.spyOn(console, 'error').mockImplementation(vi.fn()) + vi.spyOn(NotificationMiddleware, 'onError').mockImplementation(vi.fn()) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('sign-in', () => { + it('is ready right away without OIDC', () => { + const { session, createAuth } = setup(createConfig()) + session.start() + + expect(createAuth).not.toHaveBeenCalled() + expect(session.getSnapshot()).toEqual( + expect.objectContaining({ isLoading: false, wasAuthSuccessful: true }), + ) + }) + + it('signs in and credentials the clients', async () => { + const { session, auth } = setup() + const updateHeaders = vi.spyOn( + session.getSnapshot().defaultClients.default, + 'updateHeaders', + ) + session.start() + await flush() + + expect(auth.signIn).toHaveBeenCalledTimes(1) + expect(updateHeaders).toHaveBeenCalledWith({ Authorization: 'Bearer t' }) + expect(session.getSnapshot()).toEqual( + expect.objectContaining({ + user: USER, + isLoading: false, + wasAuthSuccessful: true, + }), + ) + }) + + it('keeps loading while the page is redirected to the provider', async () => { + const { session, auth } = setup() + auth.signIn.mockResolvedValue('redirected') + session.start() + await flush() + + expect(session.getSnapshot().isLoading).toBe(true) + }) + + it('reports a failed sign-in', async () => { + const { session, auth } = setup() + auth.signIn.mockRejectedValue(new Error('offline')) + session.start() + await flush() + + expect(session.getSnapshot()).toEqual( + expect.objectContaining({ + isLoading: false, + wasAuthSuccessful: false, + signInFailureMessage: undefined, + }), + ) + }) + + it('discards a cached OIDC config that fails to sign in', async () => { + window.localStorage.setItem( + OIDC_CONFIG_STORAGE_KEY, + JSON.stringify({ + ...OIDC, + authority: 'https://custom-idp.example.com', + }), + ) + const { session, auth, createAuth } = setup() + auth.signIn.mockRejectedValue(new Error('unknown client')) + session.start() + await flush() + + expect(createAuth).toHaveBeenCalledWith( + 'http://localhost/', + expect.objectContaining({ + authority: 'https://custom-idp.example.com', + }), + ) + expect(readCachedOidcConfig()).toBeUndefined() + expect(session.getSnapshot().signInFailureMessage).toMatch( + /custom OIDC configuration failed/, + ) + }) + + it('signs in once across remounts and re-subscribes to token updates', async () => { + const { session, auth } = setup() + const cleanup = session.start() + cleanup() + session.start() + await flush() + + expect(auth.signIn).toHaveBeenCalledTimes(1) + expect(auth.onAuthorizationChange).toHaveBeenCalledTimes(2) + expect(auth.unsubscribes[0]).toHaveBeenCalled() + }) + + it('applies renewed tokens from the identity provider', () => { + const { session, auth } = setup() + session.start() + const updateHeaders = vi.spyOn( + session.getSnapshot().defaultClients.default, + 'updateHeaders', + ) + const [listener] = auth.onAuthorizationChange.mock.calls[0] + + listener('Bearer renewed') + + expect(updateHeaders).toHaveBeenCalledWith({ + Authorization: 'Bearer renewed', + }) + }) + + it('notifies subscribers on every change', async () => { + const { session } = setup() + const listener = vi.fn() + session.subscribe(listener) + session.start() + await flush() + + expect(listener).toHaveBeenCalled() + }) + }) + + describe('server selection', () => { + it('re-applies a legacy stored URL without a mode as custom', () => { + window.localStorage.setItem(SERVER_URL_STORAGE_KEY, CUSTOM_URL) + const { session } = setup(createConfig()) + session.start() + + const { clients, defaultClients } = session.getSnapshot() + expect(clients.default.baseURL).toBe(CUSTOM_URL) + expect(new Set(Object.values(clients)).size).toBe(1) + expect(defaultClients.default.baseURL).toBe(PRIMARY_URL) + }) + + it('ignores a stored URL in default mode', () => { + window.localStorage.setItem(SERVER_URL_STORAGE_KEY, CUSTOM_URL) + window.localStorage.setItem(SERVER_MODE_STORAGE_KEY, 'default') + const { session } = setup(createConfig()) + session.start() + + const { clients, defaultClients } = session.getSnapshot() + expect(clients).toBe(defaultClients) + }) + + it('switches to a read-only client without forwarding the token', async () => { + const { session } = setup( + createConfig({ + servers: [ + { + id: 'primary', + url: PRIMARY_URL, + write: false, + sendAuthorization: true, + }, + ], + }), + ) + session.getSnapshot().defaultClients.default.updateHeaders({ + Authorization: 'Bearer t', + 'X-Api-Key': 'k', + }) + + await session.selectServer({ url: ` ${CUSTOM_URL} ` }) + + const { clients } = session.getSnapshot() + expect(clients.default.baseURL).toBe(CUSTOM_URL) + expect(clients.default.headers).toEqual({ 'X-Api-Key': 'k' }) + expect(window.localStorage.getItem(SERVER_URL_STORAGE_KEY)).toBe( + CUSTOM_URL, + ) + }) + + it('returns to the configured servers for an empty selection', async () => { + const { session } = setup(createConfig()) + await session.selectServer({ url: CUSTOM_URL }) + await session.selectServer({ url: '' }) + + const { clients, defaultClients } = session.getSnapshot() + expect(clients).toBe(defaultClients) + }) + + it('reloads to apply a new OIDC configuration', async () => { + const { session, reload } = setup(createConfig()) + const { clients } = session.getSnapshot() + + await session.selectServer({ url: CUSTOM_URL, oidc: OIDC }) + + expect(reload).toHaveBeenCalledTimes(1) + expect(session.getSnapshot().clients).toBe(clients) + }) + }) + + describe('ensureAuthorized', () => { + it('renews silently and remounts the views', async () => { + const { session, auth } = setup() + auth.renewAuthorization.mockResolvedValue('Bearer renewed') + + await Promise.all([ + session.ensureAuthorized(), + session.ensureAuthorized(), + ]) + + expect(auth.renewAuthorization).toHaveBeenCalledTimes(1) + expect(session.getSnapshot().authRecoveryKey).toBe(1) + }) + + it('falls back to interactive sign-in and remounts when it completes', async () => { + const { session, auth } = setup() + + await session.ensureAuthorized() + await session.ensureAuthorized() + + expect(auth.signIn).toHaveBeenCalledTimes(2) + expect(session.getSnapshot().authRecoveryKey).toBe(2) + }) + + it('stays locked once the page is redirected to the provider', async () => { + const { session, auth } = setup() + auth.signIn.mockResolvedValue('redirected') + + await session.ensureAuthorized() + await session.ensureAuthorized() + + expect(auth.renewAuthorization).toHaveBeenCalledTimes(1) + expect(session.getSnapshot().authRecoveryKey).toBe(0) + }) + + it('does nothing without OIDC', async () => { + const { session, auth } = setup(createConfig()) + await session.ensureAuthorized() + expect(auth.renewAuthorization).not.toHaveBeenCalled() + }) + }) + + describe('DICOMweb errors', () => { + it('recovers authorization on 401', async () => { + const { session, auth } = setup() + session.handleDicomWebError( + { request: new XMLHttpRequest(), response: '', status: 401 }, + { id: 'primary', write: false }, + ) + await flush() + expect(auth.renewAuthorization).toHaveBeenCalled() + }) + + it('shows the configured message for a status', () => { + const { session } = setup(createConfig()) + session.handleDicomWebError( + { request: new XMLHttpRequest(), response: '', status: 503 }, + { + id: 'primary', + write: false, + errorMessages: [{ status: 503, message: 'Down for maintenance' }], + }, + ) + expect(session.getSnapshot().error).toEqual({ + status: 503, + message: 'Down for maintenance', + }) + }) + }) +}) diff --git a/src/app/__tests__/clientMapping.test.ts b/src/app/__tests__/clientMapping.test.ts new file mode 100644 index 00000000..446cbab9 --- /dev/null +++ b/src/app/__tests__/clientMapping.test.ts @@ -0,0 +1,187 @@ +import type { ServerSettings } from '../../AppConfig' +import DicomWebManager, { + type AuthorizationPolicy, +} from '../../DicomWebManager' +import { StorageClasses } from '../../data/uids' +import NotificationMiddleware from '../../services/NotificationMiddleware' +import { + addGcpSecondaryAnnotationServer, + applyAuthorizationPolicy, + collectConfiguredOrigins, + createClientMapping, + DEFAULT_GCP_BASE_URL, + GCP_SECONDARY_SERVER_ID, + mapAllStorageClasses, + resolveCustomServerUrl, + updateAuthorization, +} from '../clientMapping' + +const BASE_URI = 'http://localhost' +const PRIMARY_URL = 'https://dicom.example.com/dicomWeb' +const SECONDARY_URL = 'https://annotations.example.com/dicomWeb' + +const primary = (): ServerSettings => ({ + id: 'primary', + url: PRIMARY_URL, + write: false, +}) + +const createMapping = ( + settings: ServerSettings[], + pathname = '/', +): ReturnType => + createClientMapping({ + baseUri: BASE_URI, + gcpBaseUrl: DEFAULT_GCP_BASE_URL, + settings, + pathname, + onError: vi.fn(), + }) + +describe('addGcpSecondaryAnnotationServer', () => { + it('appends the ?gcp= store for derived data', () => { + const config = { servers: [primary()] } + addGcpSecondaryAnnotationServer(config, `?gcp=${SECONDARY_URL}`) + + expect(config.servers).toHaveLength(2) + expect(config.servers[1]).toEqual( + expect.objectContaining({ + id: GCP_SECONDARY_SERVER_ID, + url: SECONDARY_URL, + write: true, + }), + ) + expect(config.servers[1].storageClasses).toContain( + StorageClasses.COMPREHENSIVE_SR, + ) + expect(config.servers[1].storageClasses).not.toContain( + StorageClasses.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE, + ) + }) + + it('appends it only once', () => { + const config = { servers: [primary()] } + addGcpSecondaryAnnotationServer(config, `?gcp=${SECONDARY_URL}`) + addGcpSecondaryAnnotationServer(config, `?gcp=${SECONDARY_URL}`) + expect(config.servers).toHaveLength(2) + }) + + it('does nothing without the parameter', () => { + const config = { servers: [primary()] } + addGcpSecondaryAnnotationServer(config, '?state=1') + expect(config.servers).toHaveLength(1) + }) +}) + +describe('createClientMapping', () => { + beforeEach(() => { + vi.spyOn(NotificationMiddleware, 'onError').mockImplementation(vi.fn()) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('maps every storage class to the default server', () => { + const mapping = createMapping([primary()]) + + expect(mapping.default.baseURL).toBe(PRIMARY_URL) + for (const sopClassUID of Object.values(StorageClasses)) { + expect(mapping[sopClassUID]).toBe(mapping.default) + } + }) + + it('reads derived data from both the default and the specialty store', () => { + const config = { servers: [primary()] } + addGcpSecondaryAnnotationServer(config, `?gcp=${SECONDARY_URL}`) + const mapping = createMapping(config.servers) + + const srClient = mapping[StorageClasses.COMPREHENSIVE_SR] + expect(srClient).not.toBe(mapping.default) + expect(srClient.baseURL).toBe(PRIMARY_URL) + expect(mapping[StorageClasses.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE]).toBe( + mapping.default, + ) + }) + + it('points the default server at the store of a /projects/ route', () => { + const server = primary() + createMapping( + [server], + '/projects/p/locations/l/datasets/d/dicomStores/s/study/1.2.3', + ) + expect(server.url).toBe( + `${DEFAULT_GCP_BASE_URL}/projects/p/locations/l/datasets/d/dicomStores/s/dicomWeb`, + ) + }) + + it('reports more than one default server', () => { + createMapping([primary(), { ...primary(), id: 'other' }]) + expect(NotificationMiddleware.onError).toHaveBeenCalledTimes(1) + }) + + it('warns about unknown storage classes', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(vi.fn()) + createMapping([primary(), { ...primary(), storageClasses: ['1.2.3'] }]) + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('unknown storage class "1.2.3"'), + ) + }) +}) + +describe('collectConfiguredOrigins', () => { + it('uses the server origin, or the app origin for path-only servers', () => { + expect( + collectConfiguredOrigins( + [primary(), { id: 'local', path: '/dicomweb', write: false }], + BASE_URI, + ), + ).toEqual(new Set(['https://dicom.example.com', BASE_URI])) + }) +}) + +describe('resolveCustomServerUrl', () => { + it.each([ + ['', null, undefined], + [' ', 'custom', undefined], + [PRIMARY_URL, 'default', undefined], + [` ${PRIMARY_URL} `, 'custom', PRIMARY_URL], + [PRIMARY_URL, null, PRIMARY_URL], + ])('resolves %p in mode %p to %p', (url, mode, expected) => { + expect(resolveCustomServerUrl(url, mode)).toBe(expected) + }) +}) + +describe('client helpers', () => { + const createClient = (url: string): DicomWebManager => + new DicomWebManager({ + baseUri: BASE_URI, + settings: [{ id: url, url, write: false }], + }) + + it('maps every key to one client', () => { + const replacement = createClient(SECONDARY_URL) + const mapped = mapAllStorageClasses(createMapping([primary()]), replacement) + expect(new Set(Object.values(mapped))).toEqual(new Set([replacement])) + expect(Object.keys(mapped)).toContain('default') + }) + + it('installs the policy once per distinct client', () => { + const mapping = createMapping([primary()]) + const setPolicy = vi.spyOn(mapping.default, 'setAuthorizationPolicy') + const policy: AuthorizationPolicy = { + isPreAuthorized: () => false, + requestAuthorization: () => Promise.resolve(undefined), + } + applyAuthorizationPolicy(mapping, policy) + expect(setPolicy).toHaveBeenCalledTimes(1) + expect(setPolicy).toHaveBeenCalledWith(policy) + }) + + it('offers the token to every mapped client', () => { + const mapping = createMapping([primary()]) + const updateHeaders = vi.spyOn(mapping.default, 'updateHeaders') + updateAuthorization(mapping, 'Bearer t') + expect(updateHeaders).toHaveBeenCalledWith({ Authorization: 'Bearer t' }) + }) +}) diff --git a/src/app/__tests__/configProblems.test.ts b/src/app/__tests__/configProblems.test.ts new file mode 100644 index 00000000..cfbcd5b2 --- /dev/null +++ b/src/app/__tests__/configProblems.test.ts @@ -0,0 +1,97 @@ +import type { ServerSettings } from '../../AppConfig' +import { findConfigProblems } from '../configProblems' + +const server = (overrides: Partial): ServerSettings => ({ + id: 'main', + write: false, + ...overrides, +}) + +describe('findConfigProblems', () => { + it('accepts servers with a URL or a path', () => { + expect( + findConfigProblems( + { + servers: [ + server({ url: 'https://dicom.example/rs' }), + server({ id: 'p', path: '/dicomweb' }), + ], + }, + 'local', + ), + ).toEqual([]) + }) + + it('reports a configuration file that did not load', () => { + expect(findConfigProblems(undefined, 'custom')).toEqual([ + { + message: 'The configuration file config/custom.js did not load.', + hint: 'Check that REACT_APP_CONFIG names a file in public/config.', + }, + ]) + }) + + it('reports a missing server list', () => { + const [problem] = findConfigProblems({ servers: [] }, 'custom') + expect(problem.message).toBe('No DICOMweb server is configured.') + expect(problem.hint).toContain('config/custom.js') + }) + + it('points committed configs at their environment variable', () => { + expect( + findConfigProblems({ servers: [server({ id: 'preview' })] }, 'preview'), + ).toEqual([ + { + message: 'The DICOMweb server "preview" has no URL.', + hint: 'Set SLIM_PREVIEW_DICOMWEB_URL in .env or in the environment that starts Slim, then restart it.', + }, + ]) + }) + + it('points custom configs at the server entry and ignores blank values', () => { + const problems = findConfigProblems( + { servers: [server({ url: ' ', path: '' })] }, + 'custom', + ) + expect(problems).toHaveLength(1) + expect(problems[0].hint).toBe( + 'Set "url" or "path" for this server in config/custom.js.', + ) + }) + + describe('on a Google Cloud store route', () => { + const projectsPath = + '/projects/p/locations/l/datasets/d/dicomStores/s/study/1.2.3' + + it('accepts a default server without a URL', () => { + expect( + findConfigProblems({ servers: [server({})] }, 'gcp', projectsPath), + ).toEqual([]) + }) + + it('still reports a storage-class server without a URL', () => { + const problems = findConfigProblems( + { + servers: [ + server({}), + server({ + id: 'ann', + storageClasses: ['1.2.840.10008.5.1.4.1.1.91.1'], + }), + ], + }, + 'gcp', + projectsPath, + ) + expect(problems.map((problem) => problem.message)).toEqual([ + 'The DICOMweb server "ann" has no URL.', + ]) + }) + + it('reports the default server on other routes', () => { + expect( + findConfigProblems({ servers: [server({})] }, 'gcp', '/studies/1.2.3'), + ).toHaveLength(1) + }) + }) +}) diff --git a/src/app/__tests__/tokenDisclosure.test.ts b/src/app/__tests__/tokenDisclosure.test.ts new file mode 100644 index 00000000..7495a3d0 --- /dev/null +++ b/src/app/__tests__/tokenDisclosure.test.ts @@ -0,0 +1,149 @@ +import type { Mock } from 'vitest' +import type { AuthManager } from '../../auth' +import NotificationMiddleware from '../../services/NotificationMiddleware' +import { + clearAuthorizationDecisions, + readAuthorizationDecision, + writeAuthorizationDecision, +} from '../../utils/authPolicy' +import { + createTokenDisclosurePolicy, + decideDisclosure, +} from '../tokenDisclosure' + +const CONFIGURED = 'https://dicom.example.com' +const UNKNOWN = 'https://unknown.example.com' +const AUTHORITY = 'https://idp.example.com' + +const createAuth = ( + getAuthorization: AuthManager['getAuthorization'] = () => + Promise.resolve('Bearer t'), +): AuthManager => ({ + signIn: vi.fn(() => Promise.resolve('completed' as const)), + signOut: vi.fn(() => Promise.resolve()), + getAuthorization: vi.fn(getAuthorization), + getUser: vi.fn(() => Promise.resolve({ name: 'A', email: 'a@b.c' })), + renewAuthorization: vi.fn(() => Promise.resolve(undefined)), + onAuthorizationChange: vi.fn(() => () => {}), +}) + +describe('decideDisclosure', () => { + it.each([ + [false, undefined, true, 'refuse-insecure'], + [false, 'granted', true, 'refuse-insecure'], + [true, 'denied', true, 'withhold'], + [true, undefined, false, 'ask'], + [true, undefined, true, 'grant'], + [true, 'granted', false, 'grant'], + ] as const)('secure=%p remembered=%p configured=%p -> %p', (isSecure, remembered, isConfigured, expected) => { + expect(decideDisclosure({ isSecure, remembered, isConfigured })).toBe( + expected, + ) + }) +}) + +describe('createTokenDisclosurePolicy', () => { + let confirm: Mock<(...args: [string, string | undefined]) => Promise> + let onAuthorization: Mock<(...args: [string]) => void> + + const createPolicy = ( + auth: AuthManager | null = createAuth(), + ): ReturnType => + createTokenDisclosurePolicy({ + auth: auth ?? undefined, + configuredOrigins: new Set([CONFIGURED, 'http://intranet.example']), + oidcAuthority: AUTHORITY, + confirm, + onAuthorization, + }) + + beforeEach(() => { + clearAuthorizationDecisions() + confirm = vi.fn((_origin: string, _authority: string | undefined) => + Promise.resolve(true), + ) + onAuthorization = vi.fn() + vi.spyOn(console, 'info').mockImplementation(vi.fn()) + vi.spyOn(console, 'warn').mockImplementation(vi.fn()) + vi.spyOn(NotificationMiddleware, 'onError').mockImplementation(vi.fn()) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('sends nothing without sign-in', async () => { + await expect( + createPolicy(null).requestAuthorization(CONFIGURED), + ).resolves.toBeUndefined() + }) + + it('credentials configured origins without asking', async () => { + await expect(createPolicy().requestAuthorization(CONFIGURED)).resolves.toBe( + 'Bearer t', + ) + + expect(confirm).not.toHaveBeenCalled() + expect(readAuthorizationDecision(CONFIGURED)).toBe('granted') + expect(onAuthorization).toHaveBeenCalledWith('Bearer t') + }) + + it('refuses insecure origins even when configured', async () => { + await expect( + createPolicy().requestAuthorization('http://intranet.example'), + ).resolves.toBeUndefined() + + expect(confirm).not.toHaveBeenCalled() + expect(NotificationMiddleware.onError).toHaveBeenCalled() + }) + + it('asks before sending the token to an unknown origin', async () => { + await expect(createPolicy().requestAuthorization(UNKNOWN)).resolves.toBe( + 'Bearer t', + ) + + expect(confirm).toHaveBeenCalledWith(UNKNOWN, AUTHORITY) + expect(readAuthorizationDecision(UNKNOWN)).toBe('granted') + }) + + it('remembers a refusal and does not ask again', async () => { + confirm.mockResolvedValue(false) + const policy = createPolicy() + + await expect(policy.requestAuthorization(UNKNOWN)).resolves.toBeUndefined() + await expect(policy.requestAuthorization(UNKNOWN)).resolves.toBeUndefined() + + expect(confirm).toHaveBeenCalledTimes(1) + expect(readAuthorizationDecision(UNKNOWN)).toBe('denied') + expect(onAuthorization).not.toHaveBeenCalled() + }) + + it('shares one prompt between simultaneous challenges', async () => { + const policy = createPolicy() + + await Promise.all([ + policy.requestAuthorization(UNKNOWN), + policy.requestAuthorization(UNKNOWN), + ]) + + expect(confirm).toHaveBeenCalledTimes(1) + }) + + it('never rejects when the token cannot be obtained', async () => { + vi.spyOn(console, 'error').mockImplementation(vi.fn()) + const policy = createPolicy( + createAuth(() => Promise.reject(new Error('expired'))), + ) + + await expect( + policy.requestAuthorization(CONFIGURED), + ).resolves.toBeUndefined() + }) + + it('reports remembered grants as pre-authorized', () => { + writeAuthorizationDecision(UNKNOWN, 'granted') + const policy = createPolicy() + expect(policy.isPreAuthorized(UNKNOWN)).toBe(true) + expect(policy.isPreAuthorized(CONFIGURED)).toBe(false) + }) +}) diff --git a/src/app/clientMapping.ts b/src/app/clientMapping.ts new file mode 100644 index 00000000..7dcd0b0d --- /dev/null +++ b/src/app/clientMapping.ts @@ -0,0 +1,214 @@ +import type AppConfig from '../AppConfig' +import type { DicomWebManagerErrorHandler, ServerSettings } from '../AppConfig' +import type { AuthorizationPolicy } from '../DicomWebManager' +import DicomWebManager from '../DicomWebManager' +import { StorageClasses } from '../data/uids' +import NotificationMiddleware, { + NotificationMiddlewareContext, +} from '../services/NotificationMiddleware' +import { getOrigin } from '../utils/authPolicy' +import { CustomError, errorTypes } from '../utils/CustomError' +import { getProjectStorePath, isProjectsPath } from '../utils/routes' +import { normalizeServerUrl } from '../utils/url' + +export type ClientMapping = { [sopClassUID: string]: DicomWebManager } + +export const DEFAULT_GCP_BASE_URL = 'https://healthcare.googleapis.com/v1' + +export const GCP_SECONDARY_SERVER_ID = 'gcp_secondary_annotation_server' + +/** Derived data classes routed to the `?gcp=` store */ +export const GCP_SECONDARY_STORAGE_CLASSES: readonly string[] = [ + StorageClasses.COMPREHENSIVE_SR, + StorageClasses.COMPREHENSIVE_3D_SR, + StorageClasses.SEGMENTATION, + StorageClasses.LABELMAP_SEGMENTATION, + StorageClasses.MICROSCOPY_BULK_SIMPLE_ANNOTATION, + StorageClasses.PARAMETRIC_MAP, + StorageClasses.ADVANCED_BLENDING_PRESENTATION_STATE, + StorageClasses.COLOR_SOFTCOPY_PRESENTATION_STATE, + StorageClasses.GRAYSCALE_SOFTCOPY_PRESENTATION_STATE, + StorageClasses.PSEUDOCOLOR_SOFTCOPY_PRESENTATION_STATE, +] + +/** Append the `?gcp=` store to the configured servers, once */ +export function addGcpSecondaryAnnotationServer( + config: Pick, + search: string, +): void { + const url = new URLSearchParams(search).get('gcp') + const existing = config.servers.find( + (server) => server.id === GCP_SECONDARY_SERVER_ID, + ) + if (existing === undefined && typeof url === 'string') { + config.servers.push({ + id: GCP_SECONDARY_SERVER_ID, + write: true, + url, + storageClasses: [...GCP_SECONDARY_STORAGE_CLASSES], + }) + } +} + +export function createClientMapping({ + baseUri, + gcpBaseUrl, + settings, + pathname, + onError, +}: { + baseUri: string + gcpBaseUrl: string + settings: ServerSettings[] + /** Current route; `/projects/` routes point the default server at that store */ + pathname: string + onError: DicomWebManagerErrorHandler +}): ClientMapping { + const storageClassMapping: { [key: string]: number } = { default: 0 } + const clientMapping: ClientMapping = {} + + const defaultServers: ServerSettings[] = [] + + settings.forEach((serverSettings) => { + if (serverSettings.storageClasses != null) { + serverSettings.storageClasses.forEach((sopClassUID) => { + if (Object.values(StorageClasses).includes(sopClassUID)) { + if (sopClassUID in storageClassMapping) { + storageClassMapping[sopClassUID] += 1 + } else { + storageClassMapping[sopClassUID] = 1 + } + } else { + console.warn( + `unknown storage class "${sopClassUID}" specified ` + + `for configured server "${serverSettings.id}"`, + ) + } + }) + } else { + if (isProjectsPath(pathname)) { + const storePath = getProjectStorePath(pathname) + serverSettings.url = `${gcpBaseUrl}${storePath}/dicomWeb` + } + + storageClassMapping.default += 1 + defaultServers.push(serverSettings) + clientMapping.default = new DicomWebManager({ + baseUri, + settings: [serverSettings], + onError, + }) + } + }) + + if (storageClassMapping.default > 1) { + NotificationMiddleware.onError( + NotificationMiddlewareContext.SLIM, + new CustomError( + errorTypes.COMMUNICATION, + 'Only one default server can be configured without specification ' + + 'of storage classes.', + ), + ) + } + + /** + * For each storage class explicitly assigned to a non-default server, wrap + * BOTH the default server and the specialty server(s) in the same manager. + * + * This makes derived data (SR/SEG/ANN/PM/PR) load from the primary store + * AND the secondary `gcp=` URL store at the same time (GH-320). Without + * this, specifying `gcp=` previously caused the default store to be + * skipped for those classes and SLIM only saw the secondary's derived data. + */ + if (Object.keys(storageClassMapping).length > 1) { + const classToServers = new Map() + settings.forEach((server) => { + if (server.storageClasses != null) { + server.storageClasses.forEach((sopClassUID) => { + const list = classToServers.get(sopClassUID) ?? [] + list.push(server) + classToServers.set(sopClassUID, list) + }) + } + }) + + classToServers.forEach((specialtyServers, sopClassUID) => { + const combinedServers = [...defaultServers, ...specialtyServers] + clientMapping[sopClassUID] = new DicomWebManager({ + baseUri, + settings: combinedServers, + onError, + }) + }) + } + + Object.values(StorageClasses).forEach((sopClassUID) => { + if (!(sopClassUID in clientMapping)) { + clientMapping[sopClassUID] = clientMapping.default + } + }) + return clientMapping +} + +/** + * Origins of the configured servers. Read after `createClientMapping`, which + * rewrites `url` in place on `/projects/` routes, to capture the effective value. + */ +export function collectConfiguredOrigins( + servers: ServerSettings[], + baseUri: string, +): Set { + return new Set( + servers + .map((server) => (server.url != null ? getOrigin(server.url) : baseUri)) + .filter((origin): origin is string => origin !== undefined), + ) +} + +/** Install the authorization policy on every distinct manager in a mapping. */ +export function applyAuthorizationPolicy( + clients: ClientMapping, + policy: AuthorizationPolicy, +): void { + for (const client of new Set(Object.values(clients))) { + client.setAuthorizationPolicy(policy) + } +} + +/** Offer a token to every manager; each one filters it per store. */ +export function updateAuthorization( + clients: ClientMapping, + authorization: string, +): void { + for (const key of Object.keys(clients)) { + clients[key].updateHeaders({ Authorization: authorization }) + } +} + +/** + * URL of the custom server to switch to, or undefined to use the configured + * servers: an empty entry, or default mode chosen in server selection. + */ +export function resolveCustomServerUrl( + url: string, + storedMode: string | null, +): string | undefined { + const trimmedUrl = url.trim() + if (trimmedUrl === '' || storedMode === 'default') { + return undefined + } + return normalizeServerUrl(trimmedUrl) +} + +/** + * Use one read-only client for every storage class. We may want to make this + * more sophisticated in the future to allow users to override the entire + * server configuration. + */ +export function mapAllStorageClasses( + clients: ClientMapping, + client: DicomWebManager, +): ClientMapping { + return Object.fromEntries(Object.keys(clients).map((key) => [key, client])) +} diff --git a/src/app/configProblems.ts b/src/app/configProblems.ts new file mode 100644 index 00000000..6576dc98 --- /dev/null +++ b/src/app/configProblems.ts @@ -0,0 +1,61 @@ +import type AppConfig from '../AppConfig' +import { isProjectsPath } from '../utils/routes' + +export interface ConfigProblem { + message: string + /** What the person deploying Slim should change */ + hint: string +} + +/** Variables the committed configs read their DICOMweb URL from */ +const ENV_URL_KEY_BY_CONFIG: ReadonlyMap = new Map([ + ['local', 'SLIM_LOCAL_DICOMWEB_URL'], + ['demo', 'SLIM_DEMO_DICOMWEB_URL'], + ['preview', 'SLIM_PREVIEW_DICOMWEB_URL'], +]) + +const isBlank = (value: string | undefined): boolean => + value === undefined || value.trim() === '' + +/** + * Problems that keep Slim from starting, phrased for the person deploying it. + * `config` is whatever `config/.js` assigned to `window.config`, + * so it is checked defensively. On `/projects/.../dicomStores/...` routes the + * default server (no `storageClasses`) gets its URL from the route, so it + * may leave `url` and `path` empty. + */ +export function findConfigProblems( + config: Pick | undefined, + configName: string, + pathname: string = '', +): ConfigProblem[] { + const file = `config/${configName}.js` + if (config === undefined) { + return [ + { + message: `The configuration file ${file} did not load.`, + hint: 'Check that REACT_APP_CONFIG names a file in public/config.', + }, + ] + } + if (!Array.isArray(config.servers) || config.servers.length === 0) { + return [ + { + message: 'No DICOMweb server is configured.', + hint: `Add at least one entry to "servers" in ${file}.`, + }, + ] + } + const envKey = ENV_URL_KEY_BY_CONFIG.get(configName) + const isUrlFromRoute = isProjectsPath(pathname) + return config.servers + .filter((server) => isBlank(server.url) && isBlank(server.path)) + .filter((server) => !(isUrlFromRoute && server.storageClasses == null)) + .map((server) => ({ + message: `The DICOMweb server "${server.id}" has no URL.`, + hint: + envKey !== undefined + ? `Set ${envKey} in .env or in the environment that starts Slim, then restart it.` + : `Set "url" or "path" for this server in ${file}.`, + })) +} diff --git a/src/app/confirmAuthorizationDisclosure.tsx b/src/app/confirmAuthorizationDisclosure.tsx new file mode 100644 index 00000000..6d86898f --- /dev/null +++ b/src/app/confirmAuthorizationDisclosure.tsx @@ -0,0 +1,41 @@ +import { showConfirmDialog } from '../components/ConfirmDialog' + +/** + * Ask the user before disclosing their access token to a server that is not + * part of the deployed configuration. + * + * Names the identity provider that issued the token, since "your access + * token" alone does not tell the user what is actually at stake — the answer + * differs a great deal between a hospital SSO and a personal Google account. + * + * @param origin - Origin of the server that asked for credentials + * @param authority - Issuer of the token, from the OIDC configuration + * @returns Whether the user agreed to disclose the token + */ +export async function confirmAuthorizationDisclosure( + origin: string, + authority?: string, +): Promise { + return await showConfirmDialog({ + title: 'Send your access token to this server?', + description: ( +
+

+ {origin} refused an + anonymous request and is asking you to sign in. +

+

+ Slim can forward the access token issued to you by{' '} + + {authority ?? 'your identity provider'} + {' '} + so this server can identify you. Anyone holding that token can act as + you against that provider for as long as it remains valid. +

+

Only allow this if you trust {origin}.

+
+ ), + confirmLabel: 'Send token', + cancelLabel: "Don't send", + }) +} diff --git a/src/app/tokenDisclosure.ts b/src/app/tokenDisclosure.ts new file mode 100644 index 00000000..f0930603 --- /dev/null +++ b/src/app/tokenDisclosure.ts @@ -0,0 +1,191 @@ +import type { AuthManager } from '../auth' +import type { AuthorizationPolicy } from '../DicomWebManager' +import NotificationMiddleware, { + NotificationMiddlewareContext, +} from '../services/NotificationMiddleware' +import { + type AuthorizationDecision, + isSecureOrigin, + readAuthorizationDecision, + writeAuthorizationDecision, +} from '../utils/authPolicy' +import { CustomError, errorTypes } from '../utils/CustomError' +import { createSingleFlight } from '../utils/singleFlight' + +/** + * - `refuse-insecure`: the connection would expose the token in transit + * - `withhold`: the user declined before + * - `ask`: unknown origin, the user must consent + * - `grant`: configured or previously approved origin + */ +export type DisclosureStep = 'refuse-insecure' | 'withhold' | 'ask' | 'grant' + +export function decideDisclosure({ + isSecure, + remembered, + isConfigured, +}: { + isSecure: boolean + remembered: AuthorizationDecision | undefined + isConfigured: boolean +}): DisclosureStep { + if (!isSecure) { + return 'refuse-insecure' + } + if (remembered === 'denied') { + return 'withhold' + } + if (remembered !== 'granted' && !isConfigured) { + return 'ask' + } + return 'grant' +} + +export interface TokenDisclosureOptions { + auth: AuthManager | undefined + /** + * Origins that came from the deployed configuration file. Putting a server + * there is the operator stating they trust it, so a 401 from one of these + * escalates without troubling the user. Servers introduced at runtime — the + * "Select server" dialog, the `?gcp=` parameter — are not on this list and + * require explicit consent before the token is sent. + */ + configuredOrigins: ReadonlySet + /** Issuer of the tokens `auth` hands out */ + oidcAuthority: string | undefined + /** Ask the user whether the token may be sent to `origin` */ + confirm: (origin: string, authority: string | undefined) => Promise + /** Receives every token that was cleared for disclosure */ + onAuthorization: (authorization: string) => void +} + +/** + * Policy handed to every DicomWebManager: it decides which origins may + * receive the user's access token. + * + * Slim sends no token until a server answers 401/403. At that point an origin + * from the configuration file is credentialed silently, while any other + * origin needs the user to say yes — otherwise a server could obtain a live + * cloud credential just by claiming to want one. + */ +export function createTokenDisclosurePolicy({ + auth, + configuredOrigins, + oidcAuthority, + confirm, + onAuthorization, +}: TokenDisclosureOptions): AuthorizationPolicy { + /** + * Collapses consent negotiations by origin, so simultaneous challenges from + * different managers share a single prompt. + */ + const disclosureGate = createSingleFlight() + + /** + * Decide whether the access token may be disclosed to an origin, prompting + * the user when the origin is not part of the deployed configuration, and + * return the token if so. + * + * Always call this through `requestAuthorization`, which collapses + * concurrent callers onto one negotiation — this function itself will open + * a dialog every time it is invoked. + * + * @param origin - Origin of the server that asked for credentials + * @returns The token to send, or undefined if it must be withheld + */ + const negotiateDisclosure = async ( + origin: string, + ): Promise => { + try { + if (auth == null) { + return undefined + } + const step = decideDisclosure({ + isSecure: isSecureOrigin(origin), + remembered: readAuthorizationDecision(origin), + isConfigured: configuredOrigins.has(origin), + }) + if (step === 'refuse-insecure') { + /** + * Refuse rather than warn. A bearer token sent over plain HTTP is + * readable by anything on the path, and no consent dialog makes that + * safe. An operator who has a reason to do it anyway can still say so + * explicitly with `sendAuthorization: true`, which never reaches here. + */ + console.warn( + `refusing to send access token to ${origin} over an insecure ` + + 'connection; set sendAuthorization on the server configuration ' + + 'to override', + ) + NotificationMiddleware.onError( + NotificationMiddlewareContext.AUTH, + new CustomError( + errorTypes.AUTHENTICATION, + `Not sending your access token to ${origin}: the connection is ` + + 'not secure.', + ), + ) + return undefined + } + if (step === 'withhold') { + return undefined + } + if (step === 'ask') { + const approved = await confirm(origin, oidcAuthority) + writeAuthorizationDecision(origin, approved ? 'granted' : 'denied') + console.info( + `${approved ? 'approved' : 'declined'} disclosure of access token ` + + `to ${origin} (user decision)`, + ) + if (!approved) { + return undefined + } + } else { + writeAuthorizationDecision(origin, 'granted') + console.info( + `approved disclosure of access token to ${origin} ` + + '(origin present in the deployed configuration)', + ) + } + + const authorization = await auth.getAuthorization() + if (authorization == null) { + return undefined + } + /** + * The grant is recorded per origin, but each storage class has its own + * manager. Push the token across all of them so stores on this origin in + * a sibling manager are credentialed now, rather than each having to be + * refused once before it asks. Every manager re-applies its own per-store + * filtering, so this cannot widen disclosure beyond the recorded grants. + */ + onAuthorization(authorization) + return authorization + } catch (error) { + /** + * Never let a failed negotiation reject: callers are inside a DICOMweb + * error path already, and a rejection here would replace the underlying + * server error with a less useful one. + */ + console.error('could not negotiate token disclosure', error) + return undefined + } + } + + return { + isPreAuthorized: (origin: string): boolean => + readAuthorizationDecision(origin) === 'granted', + + /** + * Collapsed per origin across the whole app. Each DicomWebManager already + * dedupes its own concurrent challenges, but a storage class gets its own + * manager, so a single page load can challenge one server from several of + * them at once. Without this the user is asked once per manager. + */ + requestAuthorization: async (origin: string): Promise => + await disclosureGate( + origin, + async () => await negotiateDisclosure(origin), + ), + } +} diff --git a/src/app/useAppSession.ts b/src/app/useAppSession.ts new file mode 100644 index 00000000..b88c9941 --- /dev/null +++ b/src/app/useAppSession.ts @@ -0,0 +1,32 @@ +import { useEffect, useSyncExternalStore } from 'react' + +import type AppConfig from '../AppConfig' +import { AppSession, type AppSessionState } from './AppSession' + +const sessions = new WeakMap() + +/** + * The session for a configuration, created on first use. Keyed by the config + * object so re-renders, remounts and StrictMode double rendering share one + * OIDC manager and one set of DICOMweb clients. + */ +export function getAppSession(config: AppConfig): AppSession { + let session = sessions.get(config) + if (session === undefined) { + session = new AppSession({ config }) + sessions.set(config, session) + } + return session +} + +export interface UseAppSessionResult { + session: AppSession + state: AppSessionState +} + +export function useAppSession(config: AppConfig): UseAppSessionResult { + const session = getAppSession(config) + const state = useSyncExternalStore(session.subscribe, session.getSnapshot) + useEffect(() => session.start(), [session]) + return { session, state } +} diff --git a/src/auth/OidcManager.tsx b/src/auth/OidcManager.tsx index 2aeb4c07..a6faca20 100644 --- a/src/auth/OidcManager.tsx +++ b/src/auth/OidcManager.tsx @@ -1,8 +1,12 @@ import { + type MetadataService, type OidcMetadata, + type SigninRedirectArgs, type User as UserData, UserManager, -} from 'oidc-client' + type UserManagerEvents, + type UserManagerSettings, +} from 'oidc-client-ts' import type { OidcSettings } from '../AppConfig' import NotificationMiddleware, { @@ -21,105 +25,110 @@ import type { SignInOutcome, User, } from '.' +import { ImplicitGrant, type ImplicitGrantHost } from './implicitGrant' +import { createOidcStores, type OidcStores } from './oidcStore' +import { + authorizationFromUser, + clearAuthParamsFromUrl, + createUser, + currentReturnUrl, + readReturnUrl, +} from './oidcUser' +import { installPkceFallback } from './pkce' interface ReturnUrlState { - returnUrl?: string + returnUrl: string } -const createUser = (userData: UserData | null): User => { - let profile: UserData['profile'] | undefined - if (userData !== null) { - profile = userData.profile - } - - if (profile !== undefined) { - if (profile.name === undefined || profile.email === undefined) { - NotificationMiddleware.onError( - NotificationMiddlewareContext.AUTH, - new CustomError( - errorTypes.AUTHENTICATION, - 'Failed to obtain user "name" and "email".', - ), - ) - } else { - return { - name: profile.name, - email: profile.email, - } - } - } else { - NotificationMiddleware.onError( - NotificationMiddlewareContext.AUTH, - new CustomError( - errorTypes.AUTHENTICATION, - 'Failed to obtain user profile.', - ), - ) - } - return { - name: undefined, - email: undefined, - } +/** The part of oidc-client-ts's `UserManager` this manager relies on */ +export interface OidcUserManager extends ImplicitGrantHost { + events: Pick< + UserManagerEvents, + 'load' | 'addUserLoaded' | 'addAccessTokenExpiring' + > + metadataService: ImplicitGrantHost['metadataService'] & + Pick + signinRedirect: (args?: SigninRedirectArgs) => Promise + signinRedirectCallback: (url?: string) => Promise + signinSilent: () => Promise + signoutRedirect: () => Promise + removeUser: () => Promise + revokeTokens: () => Promise } -const authorizationFromUser = (userData: UserData): string => { - const tokenType = userData.token_type || 'Bearer' - return `${tokenType} ${userData.access_token}` +export interface OidcManagerOptions { + stores?: OidcStores + createUserManager?: (settings: UserManagerSettings) => OidcUserManager } -const clearAuthParamsFromUrl = (): void => { - const url = new URL(window.location.href) - const authParams = [ - 'code', - 'state', - 'session_state', - 'iss', - 'id_token', - 'access_token', - 'token_type', - 'expires_in', - 'scope', - 'error', - 'error_description', - ] - for (const key of authParams) { - url.searchParams.delete(key) - } - // Implicit / hybrid responses put tokens in the hash fragment. - url.hash = '' - const cleaned = `${url.pathname}${url.search}` - window.history.replaceState({}, document.title, cleaned) -} +const isNonEmpty = (value: string | undefined): value is string => + value != null && value !== '' -const readReturnUrl = (userData: UserData): string | undefined => { - const state = userData.state as ReturnUrlState | string | null | undefined - if (state == null) { - return undefined +/** Endpoints from the configuration that take precedence over discovery */ +export const buildMetadataSeed = ( + settings: OidcSettings, +): Partial | undefined => { + const seed: Partial = {} + if (isNonEmpty(settings.authorizationEndpoint)) { + seed.authorization_endpoint = settings.authorizationEndpoint } - if (typeof state === 'string') { - return state || undefined + if (isNonEmpty(settings.endSessionEndpoint)) { + seed.end_session_endpoint = settings.endSessionEndpoint } - if (typeof state.returnUrl === 'string' && state.returnUrl !== '') { - return state.returnUrl - } - return undefined + return Object.keys(seed).length > 0 ? seed : undefined } -/** Only allow same-origin relative paths (block open redirects). */ -export const isSafeReturnUrl = (returnUrl: string): boolean => { - if (!returnUrl.startsWith('/') || returnUrl.startsWith('//')) { - return false - } - try { - const parsed = new URL(returnUrl, window.location.origin) - return parsed.origin === window.location.origin - } catch { - return false +const handleSignIn = ( + userData: UserData, + onSignIn: SignInCallback | undefined, + { includeReturnUrl }: { includeReturnUrl: boolean }, +): void => { + if (onSignIn == null) { + console.warn('no callback function was provided to handle sign-in') + return } + console.info('handling sign-in using provided callback function') + onSignIn({ + user: createUser(userData), + authorization: authorizationFromUser(userData), + returnUrl: includeReturnUrl ? readReturnUrl(userData) : undefined, + }) } -const currentReturnUrl = (): string => { - return `${window.location.pathname}${window.location.search}` +export const buildUserManagerSettings = ( + appUri: string, + settings: OidcSettings, + stores: OidcStores, +): UserManagerSettings => { + const isImplicit = settings.grantType === 'implicit' + return { + authority: settings.authority, + client_id: settings.clientId, + redirect_uri: appUri, + /** + * Reuse the main redirect_uri for silent renew so existing IdP client + * registrations (app root only) keep working. The iframe path is handled + * in index.tsx via completeSilentRenewIfFrame() before React mounts. + */ + silent_redirect_uri: appUri, + post_logout_redirect_uri: joinUrl('logout', appUri), + scope: settings.scope, + /** The implicit grant does not go through the library's request builder */ + response_type: 'code', + metadataSeed: buildMetadataSeed(settings), + loadUserInfo: true, + /** + * The library renews with the code flow, which an implicit-only client + * cannot complete; implicit sessions are renewed by this manager instead. + */ + automaticSilentRenew: !isImplicit, + monitorSession: true, + includeIdTokenInSilentRenew: true, + /** Revoked by signOut only when the provider supports revocation */ + revokeTokensOnSignout: false, + stateStore: stores.stateStore, + userStore: stores.userStore, + } } /** @@ -134,80 +143,54 @@ export const completeSilentRenewIfFrame = async (): Promise => { if (window.parent === window) { return false } - // Embedded Slim (non-OIDC iframe) should still mount; only OIDC callbacks skip it. + /** Embedded Slim (non-OIDC iframe) should still mount; only OIDC callbacks skip it. */ if (!isOidcAuthorizeCallbackUrl(window.location)) { return false } try { - await new UserManager({}).signinSilentCallback() + /** Only posts the callback URL to the parent, which validates it */ + await new UserManager({ + authority: '', + client_id: '', + redirect_uri: window.location.href, + automaticSilentRenew: false, + ...createOidcStores(), + }).signinSilentCallback() } catch (error) { console.error('silent renew callback failed', error) } - // Always skip SPA mount for OIDC iframe callbacks (success or error). + /** Always skip SPA mount for OIDC iframe callbacks (success or error). */ return true } export default class OidcManager implements AuthManager { - private _oidc: UserManager - private readonly _ready: Promise + private readonly _oidc: OidcUserManager + /** Set when the configuration asks for the implicit grant */ + private readonly _implicit?: ImplicitGrant private readonly _authorizationListeners = new Set() - constructor(appUri: string, settings: OidcSettings) { - const isImplicit = settings.grantType === 'implicit' - const responseType = isImplicit ? 'id_token token' : 'code' - const redirectUri = appUri - /* - * Reuse the main redirect_uri for silent renew so existing IdP client - * registrations (app root only) keep working. The iframe path is handled - * in index.tsx via completeSilentRenewIfFrame() before React mounts. - */ - const silentRedirectUri = redirectUri - const postLogoutRedirectUri = joinUrl('logout', appUri) - - const baseSettings = { - authority: settings.authority, - client_id: settings.clientId, - redirect_uri: redirectUri, - silent_redirect_uri: silentRedirectUri, - post_logout_redirect_uri: postLogoutRedirectUri, - scope: settings.scope, - response_type: responseType, - loadUserInfo: true, - automaticSilentRenew: true, - revokeAccessTokenOnSignout: true, - } - - const needsMetadataPatch = - (settings.endSessionEndpoint != null && - settings.endSessionEndpoint !== '') || - (settings.authorizationEndpoint != null && - settings.authorizationEndpoint !== '') - - /** - * With a metadata override, this first manager only fetches discovery - * metadata. oidc-client cannot stop a session monitor once started, so the - * manager that renews tokens and monitors the session is created only - * after the override is applied. - */ - this._oidc = new UserManager( - needsMetadataPatch - ? { - ...baseSettings, - automaticSilentRenew: false, - monitorSession: false, - } - : baseSettings, + constructor( + appUri: string, + settings: OidcSettings, + { + stores = createOidcStores(), + createUserManager = (managerSettings) => new UserManager(managerSettings), + }: OidcManagerOptions = {}, + ) { + this._oidc = createUserManager( + buildUserManagerSettings(appUri, settings, stores), ) - this._wireInternalEvents() - this._ready = needsMetadataPatch - ? this._applyMetadataOverride(baseSettings, settings) - : Promise.resolve() - } - - private _wireInternalEvents(): void { this._oidc.events.addUserLoaded((userData) => { this._notifyAuthorization(authorizationFromUser(userData)) }) + if (settings.grantType === 'implicit') { + this._implicit = new ImplicitGrant(this._oidc, stores.stateStore) + this._oidc.events.addAccessTokenExpiring(() => { + void this.renewAuthorization() + }) + } else { + installPkceFallback() + } } private _notifyAuthorization(authorization: string): void { @@ -216,39 +199,6 @@ export default class OidcManager implements AuthManager { } } - private async _applyMetadataOverride( - baseSettings: ConstructorParameters[0], - settings: OidcSettings, - ): Promise { - let metadata: OidcMetadata | undefined - try { - metadata = await this._oidc.metadataService.getMetadata() - if ( - settings.endSessionEndpoint != null && - settings.endSessionEndpoint !== '' - ) { - metadata.end_session_endpoint = settings.endSessionEndpoint - } - if ( - settings.authorizationEndpoint != null && - settings.authorizationEndpoint !== '' - ) { - metadata.authorization_endpoint = settings.authorizationEndpoint - } - } catch (error) { - console.error('failed to get metadata from authorization server: ', error) - } - this._oidc = new UserManager( - metadata == null ? baseSettings : { ...baseSettings, metadata }, - ) - this._wireInternalEvents() - } - - private async _ensureReady(): Promise { - await this._ready - return this._oidc - } - /** * Sign-in to authenticate the user and obtain authorization. */ @@ -259,65 +209,46 @@ export default class OidcManager implements AuthManager { onSignIn?: SignInCallback returnUrl?: string }): Promise => { - const oidc = await this._ensureReady() - - const handleSignIn = ( - userData: UserData, - { includeReturnUrl }: { includeReturnUrl: boolean }, - ): void => { - const user = createUser(userData) - const authorization = authorizationFromUser(userData) - let resolvedReturnUrl: string | undefined - if (includeReturnUrl) { - const candidate = readReturnUrl(userData) - if (candidate != null && isSafeReturnUrl(candidate)) { - resolvedReturnUrl = candidate - } - } - if (onSignIn != null) { - console.info('handling sign-in using provided callback function') - onSignIn({ - user, - authorization, - returnUrl: resolvedReturnUrl, - }) - } else { - console.warn('no callback function was provided to handle sign-in') - } - } - if (isAuthorizationCodeInUrl(window.location)) { - /* Handle the callback from the authorization server: extract the code + /** + * Handle the callback from the authorization server: extract the code * (or implicit tokens) from the callback URL, obtain user information * and the access token for the DICOMweb server. */ console.info('obtaining authorization') - const userData = await oidc.signinRedirectCallback() + const userData = + this._implicit != null + ? await this._implicit.signinRedirectCallback(window.location.href) + : await this._oidc.signinRedirectCallback() clearAuthParamsFromUrl() console.info('obtained user data: ', userData) - handleSignIn(userData, { includeReturnUrl: true }) + handleSignIn(userData, onSignIn, { includeReturnUrl: true }) return 'completed' } - /* Redirect to the authorization server to authenticate the user + /** + * Redirect to the authorization server to authenticate the user * and authorize the application to obtain user information and access * the DICOMweb server. */ - const userData = await oidc.getUser() - if (userData === null || userData === undefined || userData.expired) { + const userData = await this._oidc.getUser() + if (userData === null || userData.expired === true) { console.info('authenticating user') - await oidc.signinRedirect({ - state: { - returnUrl: returnUrl ?? currentReturnUrl(), - }, - }) - // oidc-client resolves as soon as navigation is assigned; page unload follows. + const state: ReturnUrlState = { + returnUrl: returnUrl ?? currentReturnUrl(), + } + if (this._implicit != null) { + await this._implicit.signinRedirect(state) + } else { + /** Stays pending until the page unloads (or is restored from bfcache) */ + await this._oidc.signinRedirect({ state }) + } return 'redirected' } console.info('user has already been authenticated') - // Do not re-apply persisted returnUrl on warm sessions. - handleSignIn(userData, { includeReturnUrl: false }) + /** Do not re-apply persisted returnUrl on warm sessions. */ + handleSignIn(userData, onSignIn, { includeReturnUrl: false }) return 'completed' } @@ -327,14 +258,12 @@ export default class OidcManager implements AuthManager { */ signOut = async (): Promise => { console.log('signing out user and revoking authorization') - const oidc = await this._ensureReady() - const logoutUri = joinUrl('logout', oidc.settings.redirect_uri ?? '/') + const oidc = this._oidc + const logoutUri = joinUrl('logout', oidc.settings.redirect_uri) try { const metadata = await oidc.metadataService.getMetadata() - if ( - metadata.end_session_endpoint == null || - metadata.end_session_endpoint === '' - ) { + await this._revokeTokens(metadata) + if (!isNonEmpty(metadata.end_session_endpoint)) { await oidc.removeUser() window.location.assign(logoutUri) return @@ -347,14 +276,28 @@ export default class OidcManager implements AuthManager { } } + /** + * oidc-client-ts fails the whole sign-out when revocation is unsupported or + * rejected, so tokens are revoked here on a best-effort basis instead. + */ + private async _revokeTokens(metadata: Partial): Promise { + if (!isNonEmpty(metadata.revocation_endpoint)) { + return + } + try { + await this._oidc.revokeTokens() + } catch (error) { + console.warn('token revocation failed', error) + } + } + /** * Get authorization. Requires prior sign-in. * Returns a full HTTP Authorization header value (e.g. "Bearer …"). */ getAuthorization = async (): Promise => { - const oidc = await this._ensureReady() - const userData = await oidc.getUser() - if (userData !== null && userData !== undefined && !userData.expired) { + const userData = await this._oidc.getUser() + if (userData !== null && userData.expired !== true) { return authorizationFromUser(userData) } NotificationMiddleware.onError( @@ -371,9 +314,8 @@ export default class OidcManager implements AuthManager { * Get user information. Requires prior sign-in. */ getUser = async (): Promise => { - const oidc = await this._ensureReady() - const userData = await oidc.getUser() - if (userData === null || userData === undefined) { + const userData = await this._oidc.getUser() + if (userData === null) { NotificationMiddleware.onError( NotificationMiddlewareContext.AUTH, new CustomError( @@ -386,10 +328,12 @@ export default class OidcManager implements AuthManager { } renewAuthorization = async (): Promise => { - const oidc = await this._ensureReady() try { - const userData = await oidc.signinSilent() - if (userData == null || userData.expired) { + const userData = + this._implicit != null + ? await this._implicit.signinSilent() + : await this._oidc.signinSilent() + if (userData == null || userData.expired === true) { return undefined } const authorization = authorizationFromUser(userData) diff --git a/src/auth/__tests__/OidcManager.test.ts b/src/auth/__tests__/OidcManager.test.ts index a4668f89..f666b795 100644 --- a/src/auth/__tests__/OidcManager.test.ts +++ b/src/auth/__tests__/OidcManager.test.ts @@ -1,71 +1,413 @@ -import { UserManager } from 'oidc-client' +import { InMemoryWebStorage, type UserManagerSettings } from 'oidc-client-ts' +import type { Mock } from 'vitest' -import OidcManager from '../OidcManager' +import type { OidcSettings } from '../../AppConfig' +import NotificationMiddleware from '../../services/NotificationMiddleware' +import OidcManager, { + buildMetadataSeed, + completeSilentRenewIfFrame, +} from '../OidcManager' +import { type OidcStores, SafeStateStore } from '../oidcStore' +import { + CLIENT_ID, + createFakeUserManager, + type FakeUserManager, + ISSUER, + makeUser, + stubLocation, +} from '../testing/fakeUserManager' -jest.mock('oidc-client', () => ({ UserManager: jest.fn() })) +const APP_URI = 'http://localhost/' -const MockUserManager = UserManager as unknown as jest.Mock +const settings: OidcSettings = { + authority: ISSUER, + clientId: CLIENT_ID, + scope: 'openid email', +} + +const createStores = (): OidcStores => ({ + stateStore: new SafeStateStore(new InMemoryWebStorage()), + userStore: new SafeStateStore(new InMemoryWebStorage()), +}) -const settings = { - authority: 'https://idp.example.com', - clientId: 'slim', - scope: 'openid', +interface Setup { + manager: OidcManager + oidc: FakeUserManager + stores: OidcStores } -const mockUserManagers = (getMetadata: jest.Mock): void => { - MockUserManager.mockImplementation(() => ({ - events: { addUserLoaded: jest.fn() }, - metadataService: { getMetadata }, - getUser: jest.fn().mockResolvedValue({ profile: {} }), - })) +const setup = ( + overrides: Partial = {}, + metadata: Parameters[1] = {}, +): Setup => { + const stores = createStores() + let oidc: FakeUserManager | undefined + const manager = new OidcManager( + APP_URI, + { ...settings, ...overrides }, + { + stores, + createUserManager: (managerSettings: UserManagerSettings) => { + oidc = createFakeUserManager(managerSettings, metadata) + return oidc + }, + }, + ) + if (oidc === undefined) { + throw new Error('user manager was not created') + } + return { manager, oidc, stores } } -const constructorSettings = (): Array> => - MockUserManager.mock.calls.map(([options]) => options) +describe('buildMetadataSeed', () => { + it('is undefined without endpoint overrides', () => { + expect(buildMetadataSeed(settings)).toBeUndefined() + expect( + buildMetadataSeed({ ...settings, endSessionEndpoint: '' }), + ).toBeUndefined() + }) + + it('overrides the configured endpoints', () => { + expect( + buildMetadataSeed({ + ...settings, + authorizationEndpoint: `${ISSUER}/auth`, + endSessionEndpoint: `${ISSUER}/logout`, + }), + ).toEqual({ + authorization_endpoint: `${ISSUER}/auth`, + end_session_endpoint: `${ISSUER}/logout`, + }) + }) +}) + +describe('completeSilentRenewIfFrame', () => { + const parentDescriptor = Object.getOwnPropertyDescriptor(window, 'parent') + + const runInFrame = async ( + url: string, + ): Promise<{ handled: boolean; postMessage: Mock }> => { + const postMessage = vi.fn() + Object.defineProperty(window, 'parent', { + configurable: true, + value: { postMessage }, + }) + window.history.replaceState({}, '', url) + const handled = await completeSilentRenewIfFrame() + return { handled, postMessage } + } + + afterEach(() => { + if (parentDescriptor !== undefined) { + Object.defineProperty(window, 'parent', parentDescriptor) + } + window.history.replaceState({}, '', '/') + }) + + it('mounts the app in a top-level window', async () => { + window.history.replaceState({}, '', '/?code=c&state=s') + await expect(completeSilentRenewIfFrame()).resolves.toBe(false) + }) + + it('hands a callback in a frame to the parent without mounting', async () => { + const { handled, postMessage } = await runInFrame( + '/#error=login_required&state=s', + ) + expect(handled).toBe(true) + expect(postMessage).toHaveBeenCalledWith( + expect.objectContaining({ url: window.location.href }), + window.location.origin, + ) + }) + + it('mounts embedded Slim in a frame that is not a callback', async () => { + const { handled, postMessage } = await runInFrame('/studies/1') + expect(handled).toBe(false) + expect(postMessage).not.toHaveBeenCalled() + }) +}) describe('OidcManager', () => { - it('creates a single renewing manager when no metadata override is set', async () => { - mockUserManagers(jest.fn().mockResolvedValue({})) - const manager = new OidcManager('https://app.example.com', settings) - await manager.getUser() - - const [only, ...rest] = constructorSettings() - expect(rest).toHaveLength(0) - expect(only.automaticSilentRenew).toBe(true) - expect(only.monitorSession).toBeUndefined() + beforeEach(() => { + vi.spyOn(console, 'info').mockImplementation(vi.fn()) + vi.spyOn(console, 'log').mockImplementation(vi.fn()) + vi.spyOn(NotificationMiddleware, 'onError').mockImplementation(vi.fn()) + }) + + afterEach(() => { + vi.restoreAllMocks() + window.history.replaceState({}, '', '/') + }) + + describe('settings', () => { + it('uses the authorization code flow with renewal on the app URI', () => { + const { oidc, stores } = setup() + expect(oidc.createdWith).toEqual({ + authority: ISSUER, + client_id: CLIENT_ID, + redirect_uri: APP_URI, + silent_redirect_uri: APP_URI, + post_logout_redirect_uri: `${APP_URI}logout`, + scope: 'openid email', + response_type: 'code', + metadataSeed: undefined, + loadUserInfo: true, + automaticSilentRenew: true, + monitorSession: true, + includeIdTokenInSilentRenew: true, + revokeTokensOnSignout: false, + stateStore: stores.stateStore, + userStore: stores.userStore, + }) + }) + + it('seeds discovery with configured endpoints on a single manager', () => { + const { oidc } = setup({ endSessionEndpoint: `${ISSUER}/logout` }) + expect(oidc.createdWith.metadataSeed).toEqual({ + end_session_endpoint: `${ISSUER}/logout`, + }) + expect(oidc.createdWith.automaticSilentRenew).toBe(true) + }) + + it('leaves renewal of implicit sessions to the manager', () => { + const { oidc } = setup({ grantType: 'implicit' }) + expect(oidc.createdWith.automaticSilentRenew).toBe(false) + expect(oidc.tokenExpiringListeners).toHaveLength(1) + }) + }) + + describe('signIn', () => { + it('redirects with the current route in the state when signed out', async () => { + window.history.replaceState({}, '', '/studies/1?gcp=x') + const { manager, oidc } = setup() + + await expect(manager.signIn({})).resolves.toBe('redirected') + + expect(oidc.signinRedirect).toHaveBeenCalledWith({ + state: { returnUrl: '/studies/1?gcp=x' }, + }) + }) + + it('redirects when the stored session has expired', async () => { + const { manager, oidc } = setup() + oidc.stored = makeUser({ expiresAt: 1 }) + + await expect(manager.signIn({ returnUrl: '/x' })).resolves.toBe( + 'redirected', + ) + expect(oidc.signinRedirect).toHaveBeenCalledWith({ + state: { returnUrl: '/x' }, + }) + }) + + it('uses the implicit grant when configured', async () => { + const location = stubLocation(`${APP_URI}studies/1`) + try { + const { manager, oidc } = setup({ grantType: 'implicit' }) + + await expect(manager.signIn({})).resolves.toBe('redirected') + + expect(oidc.signinRedirect).not.toHaveBeenCalled() + const [authorizeUrl] = location.assign.mock.calls[0] + expect(new URL(authorizeUrl).searchParams.get('response_type')).toBe( + 'id_token token', + ) + } finally { + location.restore() + } + }) + + it('completes the code callback and restores the deep link', async () => { + window.history.replaceState({}, '', '/?code=c&state=s') + const { manager, oidc } = setup() + oidc.signinRedirectCallback.mockResolvedValue( + makeUser({ accessToken: 't', state: { returnUrl: '/studies/1' } }), + ) + const onSignIn = vi.fn() + + await expect(manager.signIn({ onSignIn })).resolves.toBe('completed') + + expect(onSignIn).toHaveBeenCalledWith({ + user: { name: 'Ada Lovelace', email: 'ada@example.com' }, + authorization: 'Bearer t', + returnUrl: '/studies/1', + }) + expect(window.location.search).toBe('') + }) + + it('drops an off-site return URL from the callback', async () => { + window.history.replaceState({}, '', '/?code=c&state=s') + const { manager, oidc } = setup() + oidc.signinRedirectCallback.mockResolvedValue( + makeUser({ state: { returnUrl: '//evil.example.com' } }), + ) + const onSignIn = vi.fn() + + await manager.signIn({ onSignIn }) + + expect(onSignIn).toHaveBeenCalledWith( + expect.objectContaining({ returnUrl: undefined }), + ) + }) + + it('reuses a live session without the persisted return URL', async () => { + const { manager, oidc } = setup() + oidc.stored = makeUser({ state: { returnUrl: '/studies/1' } }) + const onSignIn = vi.fn() + + await expect(manager.signIn({ onSignIn })).resolves.toBe('completed') + + expect(oidc.signinRedirect).not.toHaveBeenCalled() + expect(onSignIn).toHaveBeenCalledWith( + expect.objectContaining({ returnUrl: undefined }), + ) + }) }) - it('renews and monitors only on the manager built with the override', async () => { - mockUserManagers(jest.fn().mockResolvedValue({})) - const manager = new OidcManager('https://app.example.com', { - ...settings, - endSessionEndpoint: 'https://idp.example.com/logout', + describe('authorization', () => { + it('returns the header for a live session', async () => { + const { manager, oidc } = setup() + oidc.stored = makeUser({ accessToken: 'live' }) + await expect(manager.getAuthorization()).resolves.toBe('Bearer live') + }) + + it('returns undefined for an expired session', async () => { + const { manager, oidc } = setup() + oidc.stored = makeUser({ expiresAt: 1 }) + await expect(manager.getAuthorization()).resolves.toBeUndefined() + }) + + it('notifies listeners when the library loads a user', async () => { + const { manager, oidc } = setup() + const listener = vi.fn() + manager.onAuthorizationChange(listener) + + await oidc.events.load(makeUser({ accessToken: 'renewed' })) + + expect(listener).toHaveBeenCalledWith('Bearer renewed') }) - await manager.getUser() - const [discovery, active, ...rest] = constructorSettings() - expect(rest).toHaveLength(0) - expect(discovery.automaticSilentRenew).toBe(false) - expect(discovery.monitorSession).toBe(false) - expect(active.automaticSilentRenew).toBe(true) - expect(active.monitorSession).toBeUndefined() - expect(active.metadata).toEqual({ - end_session_endpoint: 'https://idp.example.com/logout', + it('stops notifying after unsubscribe', async () => { + const { manager, oidc } = setup() + const listener = vi.fn() + manager.onAuthorizationChange(listener)() + + await oidc.events.load(makeUser()) + + expect(listener).not.toHaveBeenCalled() }) }) - it('falls back to a renewing manager without metadata when discovery fails', async () => { - jest.spyOn(console, 'error').mockImplementation(jest.fn()) - mockUserManagers(jest.fn().mockRejectedValue(new Error('offline'))) - const manager = new OidcManager('https://app.example.com', { - ...settings, - authorizationEndpoint: 'https://idp.example.com/authorize', + describe('renewAuthorization', () => { + it('renews silently and notifies listeners', async () => { + const { manager, oidc } = setup() + oidc.signinSilent.mockResolvedValue(makeUser({ accessToken: 'new' })) + const listener = vi.fn() + manager.onAuthorizationChange(listener) + + await expect(manager.renewAuthorization()).resolves.toBe('Bearer new') + expect(listener).toHaveBeenCalledWith('Bearer new') + }) + + it('returns undefined when silent renew fails', async () => { + vi.spyOn(console, 'warn').mockImplementation(vi.fn()) + const { manager, oidc } = setup() + oidc.signinSilent.mockRejectedValue(new Error('login_required')) + + await expect(manager.renewAuthorization()).resolves.toBeUndefined() }) - await manager.getUser() - const [, active, ...rest] = constructorSettings() - expect(rest).toHaveLength(0) - expect(active.automaticSilentRenew).toBe(true) - expect(active.metadata).toBeUndefined() + it('renews implicit sessions through the implicit grant', async () => { + vi.spyOn(console, 'warn').mockImplementation(vi.fn()) + const { manager, oidc } = setup({ grantType: 'implicit' }) + const getAuthorizationEndpoint = vi.fn(() => + Promise.reject(new Error('offline')), + ) + oidc.metadataService.getAuthorizationEndpoint = getAuthorizationEndpoint + + await expect(manager.renewAuthorization()).resolves.toBeUndefined() + + expect(getAuthorizationEndpoint).toHaveBeenCalled() + expect(oidc.signinSilent).not.toHaveBeenCalled() + }) + }) + + describe('signOut', () => { + let location: ReturnType + + beforeEach(() => { + location = stubLocation(APP_URI) + }) + + afterEach(() => { + location.restore() + }) + + it('clears the local session without an end-session endpoint', async () => { + const { manager, oidc } = setup() + + await manager.signOut() + + expect(oidc.removeUser).toHaveBeenCalled() + expect(oidc.signoutRedirect).not.toHaveBeenCalled() + expect(location.assign).toHaveBeenCalledWith(`${APP_URI}logout`) + }) + + it('redirects to the provider and revokes tokens when supported', async () => { + const { manager, oidc } = setup( + {}, + { + end_session_endpoint: `${ISSUER}/logout`, + revocation_endpoint: `${ISSUER}/revoke`, + }, + ) + + await manager.signOut() + + expect(oidc.revokeTokens).toHaveBeenCalled() + expect(oidc.signoutRedirect).toHaveBeenCalled() + }) + + it('still signs out when revocation fails', async () => { + vi.spyOn(console, 'warn').mockImplementation(vi.fn()) + const { manager, oidc } = setup( + {}, + { + end_session_endpoint: `${ISSUER}/logout`, + revocation_endpoint: `${ISSUER}/revoke`, + }, + ) + oidc.revokeTokens.mockRejectedValue(new Error('unsupported_token_type')) + + await manager.signOut() + + expect(oidc.signoutRedirect).toHaveBeenCalled() + }) + + it('does not attempt revocation without an endpoint', async () => { + const { manager, oidc } = setup( + {}, + { end_session_endpoint: `${ISSUER}/logout` }, + ) + + await manager.signOut() + + expect(oidc.revokeTokens).not.toHaveBeenCalled() + expect(oidc.signoutRedirect).toHaveBeenCalled() + }) + + it('falls back to a local sign-out when the redirect fails', async () => { + vi.spyOn(console, 'error').mockImplementation(vi.fn()) + const { manager, oidc } = setup( + {}, + { end_session_endpoint: `${ISSUER}/logout` }, + ) + oidc.signoutRedirect.mockRejectedValue(new Error('offline')) + + await manager.signOut() + + expect(oidc.removeUser).toHaveBeenCalled() + expect(location.assign).toHaveBeenCalledWith(`${APP_URI}logout`) + }) }) }) diff --git a/src/auth/__tests__/implicitGrant.test.ts b/src/auth/__tests__/implicitGrant.test.ts new file mode 100644 index 00000000..b6151244 --- /dev/null +++ b/src/auth/__tests__/implicitGrant.test.ts @@ -0,0 +1,511 @@ +import { InMemoryWebStorage, type UserManagerSettings } from 'oidc-client-ts' +import type { Mock } from 'vitest' + +import { type IdTokenCrypto, webIdTokenCrypto } from '../idTokenCrypto' +import { + buildImplicitAuthorizeUrl, + decodeJwtClaims, + ImplicitGrant, + parseImplicitCallback, + validateIdTokenClaims, + verifyIdToken, +} from '../implicitGrant' +import { SafeStateStore } from '../oidcStore' +import { softwareIdTokenCrypto } from '../softwareCrypto' +import { + CLIENT_ID, + createFakeUserManager, + createIdTokenSigner, + type FakeUserManager, + type IdTokenSigner, + ISSUER, + makeJwt, + makeProfile, + makeUser, + stubLocation, + subtle, +} from '../testing/fakeUserManager' + +const APP_URI = 'https://slim.example.com/' +const NOW = 1_700_000_000 + +let signer: IdTokenSigner + +beforeAll(async () => { + signer = await createIdTokenSigner() +}) + +describe('buildImplicitAuthorizeUrl', () => { + it('requests an ID token and access token in one response', () => { + const url = new URL( + buildImplicitAuthorizeUrl({ + authorizationEndpoint: `${ISSUER}/authorize?hd=example.com`, + clientId: CLIENT_ID, + redirectUri: APP_URI, + scope: 'openid email', + state: 's1', + nonce: 'n1', + }), + ) + expect(url.origin + url.pathname).toBe(`${ISSUER}/authorize`) + expect(Object.fromEntries(url.searchParams)).toEqual({ + hd: 'example.com', + client_id: CLIENT_ID, + redirect_uri: APP_URI, + response_type: 'id_token token', + scope: 'openid email', + state: 's1', + nonce: 'n1', + }) + }) + + it('adds prompt and id_token_hint for silent requests', () => { + const url = new URL( + buildImplicitAuthorizeUrl({ + authorizationEndpoint: `${ISSUER}/authorize`, + clientId: CLIENT_ID, + redirectUri: APP_URI, + scope: 'openid', + state: 's1', + nonce: 'n1', + prompt: 'none', + idTokenHint: 'hint', + }), + ) + expect(url.searchParams.get('prompt')).toBe('none') + expect(url.searchParams.get('id_token_hint')).toBe('hint') + }) +}) + +describe('parseImplicitCallback', () => { + it('reads tokens from the fragment', () => { + expect( + parseImplicitCallback( + `${APP_URI}#access_token=at&token_type=Bearer&expires_in=3599&id_token=it&state=s1&scope=openid`, + ), + ).toEqual({ + state: 's1', + accessToken: 'at', + tokenType: 'Bearer', + expiresIn: 3599, + idToken: 'it', + scope: 'openid', + sessionState: null, + error: null, + errorDescription: null, + }) + }) + + it('reads errors reported in the query', () => { + const callback = parseImplicitCallback( + `${APP_URI}?error=login_required&state=s1`, + ) + expect(callback.error).toBe('login_required') + expect(callback.state).toBe('s1') + expect(callback.expiresIn).toBeUndefined() + }) +}) + +describe('decodeJwtClaims', () => { + it('decodes the payload, including non-ASCII text', () => { + expect(decodeJwtClaims(makeJwt({ sub: 'u', name: 'José' }))).toEqual({ + sub: 'u', + name: 'José', + }) + }) + + it.each([ + 'not-a-jwt', + 'a.b', + 'a.!!!.c', + `x.${window.btoa('[1]')}.y`, + ])('returns undefined for %s', (jwt) => { + expect(decodeJwtClaims(jwt)).toBeUndefined() + }) +}) + +describe.each([ + ['Web Crypto', webIdTokenCrypto(subtle)], + ['the JavaScript fallback', softwareIdTokenCrypto], +])('verifyIdToken with %s', (_, crypto) => { + const verify = async ( + idToken: string, + accessToken = 'at', + keys = [signer.jwk], + ): Promise => { + await verifyIdToken({ idToken, accessToken, keys, crypto }) + } + + const signWithAtHash = async ( + claims: Record, + ): Promise => + await signer.sign({ ...claims, at_hash: await signer.atHash('at') }) + + it('accepts a token signed by a published key with a matching at_hash', async () => { + await expect( + verify(await signWithAtHash({ sub: 'u' }), 'at'), + ).resolves.toBeUndefined() + }) + + it('rejects a token without at_hash', async () => { + await expect(verify(await signer.sign({ sub: 'u' }))).rejects.toThrow( + 'no at_hash', + ) + }) + + it('rejects an access token the ID token was not issued with', async () => { + const idToken = await signer.sign({ at_hash: await signer.atHash('at') }) + await expect(verify(idToken, 'swapped')).rejects.toThrow( + 'does not match the ID token', + ) + }) + + it('rejects a tampered payload', async () => { + const [header, , signature] = (await signer.sign({ sub: 'u' })).split('.') + const forgedPayload = makeJwt({ sub: 'admin' }).split('.')[1] + await expect( + verify(`${header}.${forgedPayload}.${signature}`), + ).rejects.toThrow('signature is invalid') + }) + + it('rejects a token signed by another key', async () => { + const other = await createIdTokenSigner('key-1') + await expect(verify(await other.sign({ sub: 'u' }))).rejects.toThrow( + 'signature is invalid', + ) + }) + + it('rejects a token whose key id is not published', async () => { + await expect( + verify(await signer.sign({ sub: 'u' }), 'at', [ + { ...signer.jwk, kid: 'rotated' }, + ]), + ).rejects.toThrow('signature is invalid') + }) + + it.each([ + { alg: 'none' }, + { alg: 'HS256' }, + {}, + ])('rejects header %p', async (header) => { + await expect(verify(makeJwt({ sub: 'u' }, header))).rejects.toThrow( + 'Unsupported', + ) + }) +}) + +describe('validateIdTokenClaims', () => { + const expected = { + issuer: ISSUER, + clientId: CLIENT_ID, + nonce: 'n1', + nowInSeconds: NOW, + } + const claims = { + iss: ISSUER, + sub: 'user-1', + aud: CLIENT_ID, + exp: NOW + 60, + iat: NOW, + nonce: 'n1', + email: 'ada@example.com', + } + + it('returns the claims as a profile', () => { + expect(validateIdTokenClaims(claims, expected)).toEqual(claims) + }) + + it('accepts an audience list that includes the client as authorized party', () => { + expect( + validateIdTokenClaims( + { ...claims, aud: ['other', CLIENT_ID], azp: CLIENT_ID }, + expected, + ).aud, + ).toEqual(['other', CLIENT_ID]) + }) + + it('tolerates clock skew on expiry', () => { + expect(() => + validateIdTokenClaims({ ...claims, exp: NOW - 120 }, expected), + ).not.toThrow() + }) + + it.each([ + [{ sub: undefined }, 'subject'], + [{ iss: 'https://evil.example.com' }, 'issuer'], + [{ aud: 'other' }, 'audience'], + [{ aud: ['other'] }, 'audience'], + [{ aud: ['other', CLIENT_ID] }, 'authorized party'], + [{ azp: 'other' }, 'authorized party'], + [{ nonce: 'replayed' }, 'nonce'], + [{ nonce: undefined }, 'nonce'], + [{ exp: NOW - 3600 }, 'expired'], + [{ iat: undefined }, 'issue time'], + ])('rejects %p', (override, message) => { + expect(() => + validateIdTokenClaims({ ...claims, ...override }, expected), + ).toThrow(message) + }) +}) + +describe('ImplicitGrant', () => { + let oidc: FakeUserManager + let stateStore: SafeStateStore + let loadFrame: Mock<(...args: [string, number]) => Promise> + + const managerSettings = ( + overrides: Partial = {}, + ): UserManagerSettings => ({ + authority: ISSUER, + client_id: CLIENT_ID, + redirect_uri: APP_URI, + scope: 'openid email', + includeIdTokenInSilentRenew: true, + ...overrides, + }) + + const createGrant = ( + crypto: IdTokenCrypto = webIdTokenCrypto(subtle), + ): ImplicitGrant => + new ImplicitGrant(oidc, stateStore, { + loadFrame, + crypto, + }) + + /** Provider redirect answering the request in `authorizeUrl` */ + const callbackFor = async ( + authorizeUrl: string, + overrides: Record = {}, + params: Record = {}, + ): Promise => { + const request = new URL(authorizeUrl).searchParams + const now = Math.floor(Date.now() / 1000) + const idToken = await signer.sign({ + iss: ISSUER, + sub: 'user-1', + aud: CLIENT_ID, + exp: now + 3600, + iat: now, + nonce: request.get('nonce'), + at_hash: await signer.atHash('fresh-token'), + name: 'Ada Lovelace', + email: 'ada@example.com', + ...overrides, + }) + const fragment = new URLSearchParams({ + access_token: 'fresh-token', + token_type: 'Bearer', + expires_in: '3600', + id_token: idToken, + state: request.get('state') ?? '', + ...params, + }) + return `${APP_URI}#${fragment.toString()}` + } + + beforeEach(() => { + oidc = createFakeUserManager(managerSettings(), {}, [signer.jwk]) + stateStore = new SafeStateStore(new InMemoryWebStorage()) + loadFrame = vi.fn<(...args: [string, number]) => Promise>() + }) + + describe('redirect sign-in', () => { + let location: ReturnType + + beforeEach(() => { + location = stubLocation(APP_URI) + }) + + afterEach(() => { + location.restore() + }) + + const redirect = async (data: unknown): Promise => { + await createGrant().signinRedirect(data) + const [authorizeUrl] = location.assign.mock.calls[0] + return authorizeUrl + } + + it('navigates to the provider and stores the pending request', async () => { + const authorizeUrl = await redirect({ returnUrl: '/studies/1' }) + const params = new URL(authorizeUrl).searchParams + expect(params.get('response_type')).toBe('id_token token') + expect(params.get('redirect_uri')).toBe(APP_URI) + expect(params.get('prompt')).toBeNull() + expect(await stateStore.getAllKeys()).toEqual([params.get('state')]) + }) + + it('completes the callback into a stored user with the original state', async () => { + const authorizeUrl = await redirect({ returnUrl: '/studies/1' }) + const user = await createGrant().signinRedirectCallback( + await callbackFor(authorizeUrl), + ) + + expect(user.access_token).toBe('fresh-token') + expect(user.state).toEqual({ returnUrl: '/studies/1' }) + expect(user.profile.email).toBe('ada@example.com') + expect(user.expired).toBe(false) + expect(oidc.storeUser).toHaveBeenCalledWith(user) + expect(await stateStore.getAllKeys()).toEqual([]) + }) + + it('rejects a replayed response', async () => { + const authorizeUrl = await redirect(undefined) + const callbackUrl = await callbackFor(authorizeUrl) + await createGrant().signinRedirectCallback(callbackUrl) + + await expect( + createGrant().signinRedirectCallback(callbackUrl), + ).rejects.toThrow('No matching state') + }) + + it('rejects a response for a different request', async () => { + const authorizeUrl = await redirect(undefined) + await expect( + createGrant().signinRedirectCallback( + await callbackFor(authorizeUrl, {}, { state: 'forged' }), + ), + ).rejects.toThrow('No matching state') + expect(oidc.storeUser).not.toHaveBeenCalled() + }) + + it('rejects an ID token issued for another request', async () => { + const authorizeUrl = await redirect(undefined) + await expect( + createGrant().signinRedirectCallback( + await callbackFor(authorizeUrl, { nonce: 'other' }), + ), + ).rejects.toThrow('nonce') + }) + + it('rejects an unsigned ID token', async () => { + const authorizeUrl = await redirect(undefined) + const state = new URL(authorizeUrl).searchParams.get('state') ?? '' + const idToken = makeJwt({ iss: ISSUER, sub: 'user-1', aud: CLIENT_ID }) + await expect( + createGrant().signinRedirectCallback( + `${APP_URI}#access_token=t&id_token=${idToken}&state=${state}`, + ), + ).rejects.toThrow('Unsupported') + expect(oidc.storeUser).not.toHaveBeenCalled() + }) + + it('signs in without Web Crypto (plain HTTP)', async () => { + const authorizeUrl = await redirect(undefined) + const user = await createGrant( + softwareIdTokenCrypto, + ).signinRedirectCallback(await callbackFor(authorizeUrl)) + expect(user.access_token).toBe('fresh-token') + expect(oidc.storeUser).toHaveBeenCalledWith(user) + }) + + it('surfaces provider errors', async () => { + const authorizeUrl = await redirect(undefined) + const state = new URL(authorizeUrl).searchParams.get('state') ?? '' + await expect( + createGrant().signinRedirectCallback( + `${APP_URI}#error=access_denied&error_description=Denied&state=${state}`, + ), + ).rejects.toThrow('Denied') + }) + }) + + describe('user info', () => { + const originalFetch = window.fetch + + afterEach(() => { + window.fetch = originalFetch + }) + + const signInWithUserInfo = async ( + response: Partial, + ): Promise => { + oidc = createFakeUserManager( + managerSettings({ loadUserInfo: true }), + {}, + [signer.jwk], + ) + window.fetch = vi.fn(() => Promise.resolve(response as Response)) + loadFrame.mockImplementation((url) => callbackFor(url)) + const user = await createGrant().signinSilent() + return user.profile.name + } + + it('merges claims from the user info endpoint', async () => { + expect( + await signInWithUserInfo({ + ok: true, + json: () => Promise.resolve({ sub: 'user-1', name: 'Countess' }), + }), + ).toBe('Countess') + expect(window.fetch).toHaveBeenCalledWith(`${ISSUER}/userinfo`, { + headers: { Authorization: 'Bearer fresh-token' }, + }) + }) + + it('ignores user info for another subject', async () => { + expect( + await signInWithUserInfo({ + ok: true, + json: () => Promise.resolve({ sub: 'user-2', name: 'Mallory' }), + }), + ).toBe('Ada Lovelace') + }) + + it('falls back to the ID token claims when the request fails', async () => { + expect(await signInWithUserInfo({ ok: false })).toBe('Ada Lovelace') + }) + }) + + describe('silent renew', () => { + it('renews in a frame without prompting, hinting the current user', async () => { + oidc.stored = makeUser({ profile: makeProfile({ sub: 'user-1' }) }) + loadFrame.mockImplementation((url) => callbackFor(url)) + + const user = await createGrant().signinSilent() + + const [authorizeUrl, timeout] = loadFrame.mock.calls[0] + const params = new URL(authorizeUrl).searchParams + expect(params.get('prompt')).toBe('none') + expect(params.get('id_token_hint')).toBe('id-token') + expect(timeout).toBe(10_000) + expect(user.access_token).toBe('fresh-token') + expect(oidc.stored).toBe(user) + }) + + it('rejects a renewal that signs in a different user', async () => { + oidc.stored = makeUser({ profile: makeProfile({ sub: 'user-1' }) }) + loadFrame.mockImplementation((url) => callbackFor(url, { sub: 'user-2' })) + + await expect(createGrant().signinSilent()).rejects.toThrow( + 'different user', + ) + expect(oidc.storeUser).not.toHaveBeenCalled() + }) + + it('propagates login_required from the provider', async () => { + loadFrame.mockImplementation((url) => { + const state = new URL(url).searchParams.get('state') ?? '' + return Promise.resolve(`${APP_URI}#error=login_required&state=${state}`) + }) + + await expect(createGrant().signinSilent()).rejects.toThrow( + 'login_required', + ) + }) + + it('does not accept a silent response as a redirect callback', async () => { + let callbackUrl = '' + loadFrame.mockImplementation(async (url) => { + callbackUrl = await callbackFor(url) + throw new Error('frame closed') + }) + await expect(createGrant().signinSilent()).rejects.toThrow('frame closed') + + await expect( + createGrant().signinRedirectCallback(callbackUrl), + ).rejects.toThrow('No matching state') + expect(oidc.storeUser).not.toHaveBeenCalled() + }) + }) +}) diff --git a/src/auth/__tests__/isSafeReturnUrl.test.ts b/src/auth/__tests__/isSafeReturnUrl.test.ts index 19820724..caf178ab 100644 --- a/src/auth/__tests__/isSafeReturnUrl.test.ts +++ b/src/auth/__tests__/isSafeReturnUrl.test.ts @@ -1,4 +1,4 @@ -import { isSafeReturnUrl } from '../OidcManager' +import { isSafeReturnUrl } from '../oidcUser' describe('isSafeReturnUrl', () => { const originalLocation = window.location diff --git a/src/auth/__tests__/oidcConfig.test.ts b/src/auth/__tests__/oidcConfig.test.ts index 6b3e9817..f201cdf2 100644 --- a/src/auth/__tests__/oidcConfig.test.ts +++ b/src/auth/__tests__/oidcConfig.test.ts @@ -1,3 +1,4 @@ +import type { MockInstance } from 'vitest' import { clearAuthorizationDecisions, readAuthorizationDecision, @@ -166,6 +167,53 @@ describe('readCachedOidcConfig', () => { }) }) +describe('blocked storage', () => { + let localStorageSpy: MockInstance | undefined + + beforeEach(() => { + localStorageSpy = vi + .spyOn(window, 'localStorage', 'get') + .mockImplementation(() => { + throw new DOMException('Storage is blocked', 'SecurityError') + }) + }) + + afterEach(() => { + localStorageSpy?.mockRestore() + }) + + it('reads an empty entry instead of throwing', () => { + expect(readCachedOidcConfigInput()).toBe('') + expect(readCachedOidcConfig()).toBeUndefined() + }) + + it('does not throw when caching or clearing an entry', () => { + expect(() => cacheOidcConfigInput(JSON.stringify(settings))).not.toThrow() + expect(() => cacheOidcConfigInput('')).not.toThrow() + }) +}) + +describe('parseOidcConfig optional fields', () => { + it('keeps string optional fields and drops non-string ones', () => { + const input = JSON.stringify({ + ...settings, + grantType: 'implicit', + authorizationEndpoint: 42, + endSessionEndpoint: 'https://idp.example.com/logout', + }) + expect(parseOidcConfig(input)).toEqual({ + ...settings, + grantType: 'implicit', + authorizationEndpoint: undefined, + endSessionEndpoint: 'https://idp.example.com/logout', + }) + }) + + it('rejects arrays', () => { + expect(parseOidcConfig('[1, 2]')).toBeUndefined() + }) +}) + describe('resetCachedOidcConfigFromUrl', () => { beforeEach(() => { window.localStorage.setItem( diff --git a/src/auth/__tests__/oidcStore.test.ts b/src/auth/__tests__/oidcStore.test.ts new file mode 100644 index 00000000..f22d0538 --- /dev/null +++ b/src/auth/__tests__/oidcStore.test.ts @@ -0,0 +1,43 @@ +import { InMemoryWebStorage } from 'oidc-client-ts' + +import { SafeStateStore } from '../oidcStore' + +describe('SafeStateStore', () => { + it('stores values under the oidc prefix', async () => { + const storage = new InMemoryWebStorage() + storage.setItem('unrelated', 'x') + const store = new SafeStateStore(storage) + + await store.set('abc', 'value') + + expect(storage.getItem('oidc.abc')).toBe('value') + expect(await store.get('abc')).toBe('value') + expect(await store.getAllKeys()).toEqual(['abc']) + }) + + it('returns the removed value', async () => { + const store = new SafeStateStore(new InMemoryWebStorage()) + await store.set('abc', 'value') + + expect(await store.remove('abc')).toBe('value') + expect(await store.get('abc')).toBeNull() + expect(await store.remove('abc')).toBeNull() + }) + + it('does not reject when storage throws', async () => { + const failing = new InMemoryWebStorage() + const fail = (): never => { + throw new Error('QuotaExceededError') + } + failing.setItem = fail + failing.getItem = fail + failing.removeItem = fail + failing.key = fail + const store = new SafeStateStore(failing) + + await expect(store.set('abc', 'value')).resolves.toBeUndefined() + await expect(store.get('abc')).resolves.toBeNull() + await expect(store.remove('abc')).resolves.toBeNull() + await expect(store.getAllKeys()).resolves.toEqual([]) + }) +}) diff --git a/src/auth/__tests__/oidcUser.test.ts b/src/auth/__tests__/oidcUser.test.ts new file mode 100644 index 00000000..5c99496a --- /dev/null +++ b/src/auth/__tests__/oidcUser.test.ts @@ -0,0 +1,90 @@ +import type { MockInstance } from 'vitest' +import NotificationMiddleware from '../../services/NotificationMiddleware' +import { + authorizationFromUser, + clearAuthParamsFromUrl, + createUser, + readReturnUrl, +} from '../oidcUser' +import { makeProfile, makeUser } from '../testing/fakeUserManager' + +describe('createUser', () => { + let onError: MockInstance + + beforeEach(() => { + onError = vi + .spyOn(NotificationMiddleware, 'onError') + .mockImplementation(vi.fn()) + }) + + afterEach(() => { + onError.mockRestore() + }) + + it('takes name and email from the profile', () => { + expect(createUser(makeUser())).toEqual({ + name: 'Ada Lovelace', + email: 'ada@example.com', + }) + expect(onError).not.toHaveBeenCalled() + }) + + it('reports a profile without name or email', () => { + expect( + createUser(makeUser({ profile: makeProfile({ email: undefined }) })), + ).toEqual({ name: undefined, email: undefined }) + expect(onError).toHaveBeenCalledTimes(1) + }) + + it('reports a missing user', () => { + expect(createUser(null)).toEqual({ name: undefined, email: undefined }) + expect(onError).toHaveBeenCalledTimes(1) + }) +}) + +describe('authorizationFromUser', () => { + it('prefixes the token type', () => { + expect(authorizationFromUser(makeUser({ accessToken: 'abc' }))).toBe( + 'Bearer abc', + ) + }) + + it('defaults to Bearer', () => { + const user = makeUser({ accessToken: 'abc' }) + user.token_type = '' + expect(authorizationFromUser(user)).toBe('Bearer abc') + }) +}) + +describe('readReturnUrl', () => { + it.each([ + [{ returnUrl: '/studies/1?gcp=x' }, '/studies/1?gcp=x'], + ['/studies/1', '/studies/1'], + [{ returnUrl: 'https://evil.example.com/' }, undefined], + [{ returnUrl: '//evil.example.com/' }, undefined], + [{ returnUrl: '' }, undefined], + [{ other: '/x' }, undefined], + [undefined, undefined], + ])('reads %p as %p', (state, expected) => { + expect(readReturnUrl(makeUser({ state }))).toBe(expected) + }) +}) + +describe('clearAuthParamsFromUrl', () => { + afterEach(() => { + window.history.replaceState({}, '', '/') + }) + + it('removes authorize response parameters and the fragment', () => { + window.history.replaceState( + {}, + '', + '/studies/1?code=c&state=s&session_state=x&gcp=keep#access_token=t', + ) + clearAuthParamsFromUrl() + expect(`${window.location.pathname}${window.location.search}`).toBe( + '/studies/1?gcp=keep', + ) + expect(window.location.hash).toBe('') + }) +}) diff --git a/src/auth/__tests__/pkce.test.ts b/src/auth/__tests__/pkce.test.ts new file mode 100644 index 00000000..60dae465 --- /dev/null +++ b/src/auth/__tests__/pkce.test.ts @@ -0,0 +1,96 @@ +import { webcrypto } from 'node:crypto' +import { OidcClient, SigninState } from 'oidc-client-ts' + +import { installPkceFallback } from '../pkce' +import { pkceChallenge } from '../softwareCrypto' + +const originalCreate = SigninState.create + +const STATE_ARGS = { + authority: 'https://idp.example.com', + client_id: 'slim', + redirect_uri: 'http://slim.internal/', + scope: 'openid', +} + +/** What an `http://` page sees: random values, but no `crypto.subtle` */ +const hideSubtleCrypto = (): void => { + vi.stubGlobal('crypto', { + getRandomValues: webcrypto.getRandomValues.bind(webcrypto), + }) +} + +afterEach(() => { + SigninState.create = originalCreate + vi.unstubAllGlobals() +}) + +describe('installPkceFallback', () => { + it('leaves the library alone where Web Crypto is available', () => { + installPkceFallback(webcrypto.subtle as SubtleCrypto) + expect(SigninState.create).toBe(originalCreate) + }) + + it('installs once', () => { + installPkceFallback(undefined) + const installed = SigninState.create + installPkceFallback(undefined) + expect(SigninState.create).toBe(installed) + }) + + describe('without Web Crypto', () => { + beforeEach(() => { + hideSubtleCrypto() + installPkceFallback() + }) + + it('generates a verifier and its S256 challenge', async () => { + const state = await SigninState.create({ + ...STATE_ARGS, + code_verifier: true, + }) + expect(state.code_verifier).toMatch(/^[\w-]{43}$/) + expect(state.code_challenge).toBe( + pkceChallenge(state.code_verifier ?? ''), + ) + expect(state.client_id).toBe('slim') + }) + + it('restores the stored verifier after the redirect', async () => { + const state = await SigninState.create({ + ...STATE_ARGS, + code_verifier: true, + }) + const restored = await SigninState.fromStorageString( + state.toStorageString(), + ) + expect(restored.code_verifier).toBe(state.code_verifier) + expect(restored.code_challenge).toBe(state.code_challenge) + }) + + it('creates no challenge when PKCE is off', async () => { + const state = await SigninState.create({ + ...STATE_ARGS, + code_verifier: false, + }) + expect(state.code_verifier).toBeUndefined() + expect(state.code_challenge).toBeUndefined() + }) + + it('lets the code flow build its authorization request', async () => { + const client = new OidcClient({ + ...STATE_ARGS, + metadata: { + issuer: STATE_ARGS.authority, + authorization_endpoint: `${STATE_ARGS.authority}/authorize`, + }, + }) + const request = await client.createSigninRequest({}) + const url = new URL(request.url) + expect(url.searchParams.get('code_challenge_method')).toBe('S256') + expect(url.searchParams.get('code_challenge')).toBe( + request.state.code_challenge, + ) + }) + }) +}) diff --git a/src/auth/__tests__/softwareCrypto.test.ts b/src/auth/__tests__/softwareCrypto.test.ts new file mode 100644 index 00000000..93825031 --- /dev/null +++ b/src/auth/__tests__/softwareCrypto.test.ts @@ -0,0 +1,181 @@ +import { webcrypto } from 'node:crypto' + +import type { HashName, SignatureScheme } from '../idTokenCrypto' +import { pkceChallenge, softwareIdTokenCrypto } from '../softwareCrypto' + +const { subtle } = webcrypto as unknown as Crypto + +const bytes = (text: string): Uint8Array => + Uint8Array.from(text, (char) => char.charCodeAt(0)) + +const DATA = bytes('header.payload') + +async function rsaKeyPair(hash: HashName): Promise { + return await subtle.generateKey( + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash, + }, + true, + ['sign', 'verify'], + ) +} + +async function ecKeyPair( + namedCurve: 'P-256' | 'P-384', +): Promise { + return await subtle.generateKey({ name: 'ECDSA', namedCurve }, true, [ + 'sign', + 'verify', + ]) +} + +async function sign( + scheme: SignatureScheme, + privateKey: CryptoKey, + data: Uint8Array = DATA, +): Promise> { + const params = + scheme.kty === 'RSA' + ? { name: 'RSASSA-PKCS1-v1_5' } + : { name: 'ECDSA', hash: scheme.hash } + return new Uint8Array(await subtle.sign(params, privateKey, data)) +} + +describe('softwareIdTokenCrypto.digest', () => { + it.each([ + 'SHA-256', + 'SHA-384', + 'SHA-512', + ])('matches Web Crypto for %s', async (hash) => { + const expected = new Uint8Array(await subtle.digest(hash, DATA)) + expect(await softwareIdTokenCrypto.digest(hash, DATA)).toEqual(expected) + }) +}) + +describe('softwareIdTokenCrypto.verify with RSA', () => { + it.each([ + 'SHA-256', + 'SHA-384', + 'SHA-512', + ])('accepts a valid %s signature and rejects tampering', async (hash) => { + const scheme: SignatureScheme = { kty: 'RSA', hash } + const { publicKey, privateKey } = await rsaKeyPair(hash) + const jwk = await subtle.exportKey('jwk', publicKey) + const signature = await sign(scheme, privateKey) + + expect( + await softwareIdTokenCrypto.verify(scheme, jwk, signature, DATA), + ).toBe(true) + expect( + await softwareIdTokenCrypto.verify( + scheme, + jwk, + signature, + bytes('header.forged'), + ), + ).toBe(false) + const flipped = signature.slice() + flipped[10] ^= 1 + expect(await softwareIdTokenCrypto.verify(scheme, jwk, flipped, DATA)).toBe( + false, + ) + }) + + it('rejects a signature made with another hash', async () => { + const { publicKey, privateKey } = await rsaKeyPair('SHA-256') + const jwk = await subtle.exportKey('jwk', publicKey) + const signature = await sign({ kty: 'RSA', hash: 'SHA-256' }, privateKey) + expect( + await softwareIdTokenCrypto.verify( + { kty: 'RSA', hash: 'SHA-384' }, + jwk, + signature, + DATA, + ), + ).toBe(false) + }) + + it('rejects a truncated signature or a key without a modulus', async () => { + const scheme: SignatureScheme = { kty: 'RSA', hash: 'SHA-256' } + const { publicKey, privateKey } = await rsaKeyPair('SHA-256') + const jwk = await subtle.exportKey('jwk', publicKey) + const signature = await sign(scheme, privateKey) + expect( + await softwareIdTokenCrypto.verify(scheme, jwk, signature.slice(1), DATA), + ).toBe(false) + expect( + await softwareIdTokenCrypto.verify( + scheme, + { ...jwk, n: undefined }, + signature, + DATA, + ), + ).toBe(false) + }) +}) + +describe('softwareIdTokenCrypto.verify with ECDSA', () => { + it.each([ + ['P-256', 'SHA-256'], + ['P-384', 'SHA-384'], + ] as const)('accepts a valid %s signature and rejects tampering', async (namedCurve, hash) => { + const scheme: SignatureScheme = { kty: 'EC', namedCurve, hash } + const { publicKey, privateKey } = await ecKeyPair(namedCurve) + const jwk = await subtle.exportKey('jwk', publicKey) + + /** Web Crypto does not normalize `s`, so this covers high-S too */ + for (let attempt = 0; attempt < 8; attempt++) { + const signature = await sign(scheme, privateKey) + expect( + await softwareIdTokenCrypto.verify(scheme, jwk, signature, DATA), + ).toBe(true) + } + const signature = await sign(scheme, privateKey) + expect( + await softwareIdTokenCrypto.verify( + scheme, + jwk, + signature, + bytes('header.forged'), + ), + ).toBe(false) + }) + + it('rejects a key on another curve or a malformed point', async () => { + const scheme: SignatureScheme = { + kty: 'EC', + namedCurve: 'P-256', + hash: 'SHA-256', + } + const { publicKey, privateKey } = await ecKeyPair('P-256') + const jwk = await subtle.exportKey('jwk', publicKey) + const signature = await sign(scheme, privateKey) + expect( + await softwareIdTokenCrypto.verify( + scheme, + { ...jwk, crv: 'P-384' }, + signature, + DATA, + ), + ).toBe(false) + expect( + await softwareIdTokenCrypto.verify( + scheme, + { ...jwk, y: jwk.x }, + signature, + DATA, + ), + ).toBe(false) + }) +}) + +describe('pkceChallenge', () => { + it('matches the RFC 7636 example', () => { + expect(pkceChallenge('dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk')).toBe( + 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM', + ) + }) +}) diff --git a/src/auth/idTokenCrypto.ts b/src/auth/idTokenCrypto.ts new file mode 100644 index 00000000..3b4874ed --- /dev/null +++ b/src/auth/idTokenCrypto.ts @@ -0,0 +1,69 @@ +export type HashName = 'SHA-256' | 'SHA-384' | 'SHA-512' + +/** JWS signature schemes accepted for ID tokens */ +export type SignatureScheme = + | { kty: 'RSA'; hash: HashName } + | { kty: 'EC'; namedCurve: 'P-256' | 'P-384'; hash: HashName } + +/** The hashing and signature checks needed to verify an ID token */ +export interface IdTokenCrypto { + digest: (hash: HashName, data: Uint8Array) => Promise + /** False for a bad signature or an unusable key, never throws for those */ + verify: ( + scheme: SignatureScheme, + key: JsonWebKey, + signature: Uint8Array, + data: Uint8Array, + ) => Promise +} + +const importParams = ( + scheme: SignatureScheme, +): RsaHashedImportParams | EcKeyImportParams => + scheme.kty === 'RSA' + ? { name: 'RSASSA-PKCS1-v1_5', hash: scheme.hash } + : { name: 'ECDSA', namedCurve: scheme.namedCurve } + +const verifyParams = (scheme: SignatureScheme): Algorithm | EcdsaParams => + scheme.kty === 'RSA' + ? { name: 'RSASSA-PKCS1-v1_5' } + : { name: 'ECDSA', hash: scheme.hash } + +export function webIdTokenCrypto(subtle: SubtleCrypto): IdTokenCrypto { + return { + digest: async (hash, data) => + new Uint8Array(await subtle.digest(hash, data)), + verify: async (scheme, key, signature, data) => { + try { + const cryptoKey = await subtle.importKey( + 'jwk', + key, + importParams(scheme), + false, + ['verify'], + ) + return await subtle.verify( + verifyParams(scheme), + cryptoKey, + signature, + data, + ) + } catch { + return false + } + }, + } +} + +/** + * Web Crypto where the browser offers it. Browsers hide `crypto.subtle` + * outside secure contexts (plain HTTP on a non-loopback host), so there the + * JavaScript implementation is loaded instead. + */ +export async function loadIdTokenCrypto( + subtle: SubtleCrypto | undefined = globalThis.crypto?.subtle, +): Promise { + if (subtle !== undefined) return webIdTokenCrypto(subtle) + const { softwareIdTokenCrypto } = await import('./softwareCrypto') + return softwareIdTokenCrypto +} diff --git a/src/auth/implicitGrant.ts b/src/auth/implicitGrant.ts new file mode 100644 index 00000000..4d3ce207 --- /dev/null +++ b/src/auth/implicitGrant.ts @@ -0,0 +1,598 @@ +import { + type IdTokenClaims, + type MetadataService, + type SigningKey, + type StateStore, + User, + type UserManagerEvents, + type UserManagerSettingsStore, +} from 'oidc-client-ts' +import { v4 as generateUuid } from 'uuid' + +import { isOidcAuthorizeCallbackUrl } from '../utils/url' +import { + type IdTokenCrypto, + loadIdTokenCrypto, + type SignatureScheme, +} from './idTokenCrypto' + +export const IMPLICIT_RESPONSE_TYPE = 'id_token token' + +/** Same as the oidc-client-ts default `silentRequestTimeoutInSeconds` */ +const SILENT_RENEW_TIMEOUT_MS = 10_000 + +/** Leeway for ID token expiry, as in oidc-client 1.x */ +const CLOCK_SKEW_SECONDS = 300 + +const REDIRECT_REQUEST_TYPE = 'si:r' +const SILENT_REQUEST_TYPE = 'si:s' + +export interface ImplicitAuthorizeRequest { + authorizationEndpoint: string + clientId: string + redirectUri: string + scope: string + state: string + nonce: string + prompt?: string + idTokenHint?: string +} + +export const buildImplicitAuthorizeUrl = ( + request: ImplicitAuthorizeRequest, +): string => { + const url = new URL(request.authorizationEndpoint) + url.searchParams.set('client_id', request.clientId) + url.searchParams.set('redirect_uri', request.redirectUri) + url.searchParams.set('response_type', IMPLICIT_RESPONSE_TYPE) + url.searchParams.set('scope', request.scope) + url.searchParams.set('state', request.state) + url.searchParams.set('nonce', request.nonce) + if (request.prompt !== undefined) { + url.searchParams.set('prompt', request.prompt) + } + if (request.idTokenHint !== undefined) { + url.searchParams.set('id_token_hint', request.idTokenHint) + } + return url.toString() +} + +export interface ImplicitCallback { + state: string | null + accessToken: string | null + tokenType: string | null + expiresIn: number | undefined + idToken: string | null + scope: string | null + sessionState: string | null + error: string | null + errorDescription: string | null +} + +/** + * Implicit responses are returned in the URL fragment; some providers report + * errors in the query instead, so both are read with the fragment first. + */ +export const parseImplicitCallback = (url: string): ImplicitCallback => { + const parsed = new URL(url) + const fragment = new URLSearchParams(parsed.hash.replace(/^#/, '')) + const read = (key: string): string | null => + fragment.get(key) ?? parsed.searchParams.get(key) + const expiresIn = Number.parseInt(read('expires_in') ?? '', 10) + return { + state: read('state'), + accessToken: read('access_token'), + tokenType: read('token_type'), + expiresIn: Number.isFinite(expiresIn) ? expiresIn : undefined, + idToken: read('id_token'), + scope: read('scope'), + sessionState: read('session_state'), + error: read('error'), + errorDescription: read('error_description'), + } +} + +const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null && !Array.isArray(value) + +const decodeBase64UrlToBinary = (input: string): string => { + const base64 = input.replace(/-/g, '+').replace(/_/g, '/') + const padded = base64.padEnd( + base64.length + ((4 - (base64.length % 4)) % 4), + '=', + ) + return window.atob(padded) +} + +const decodeBase64Url = (input: string): string => { + const percentEncoded = Array.from( + decodeBase64UrlToBinary(input), + (char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`, + ).join('') + return decodeURIComponent(percentEncoded) +} + +const binaryToBytes = (binary: string): Uint8Array => + Uint8Array.from(binary, (char) => char.charCodeAt(0)) + +const bytesToBase64Url = (bytes: Uint8Array): string => + window + .btoa(String.fromCharCode(...bytes)) + .replaceAll('+', '-') + .replaceAll('/', '_') + .replace(/[=]+$/, '') + +const decodeJwtPart = (part: string): Record | undefined => { + try { + const decoded: unknown = JSON.parse(decodeBase64Url(part)) + return isRecord(decoded) ? decoded : undefined + } catch { + return undefined + } +} + +/** Payload of a compact JWT, or undefined when it cannot be decoded */ +export const decodeJwtClaims = ( + jwt: string, +): Record | undefined => { + const parts = jwt.split('.') + return parts.length === 3 ? decodeJwtPart(parts[1]) : undefined +} + +/** JWS algorithms accepted for ID tokens; `none` and HMAC are deliberately absent */ +const SIGNING_ALGORITHMS = new Map([ + ['RS256', { kty: 'RSA', hash: 'SHA-256' }], + ['RS384', { kty: 'RSA', hash: 'SHA-384' }], + ['RS512', { kty: 'RSA', hash: 'SHA-512' }], + ['ES256', { kty: 'EC', namedCurve: 'P-256', hash: 'SHA-256' }], + ['ES384', { kty: 'EC', namedCurve: 'P-384', hash: 'SHA-384' }], +]) + +const toJsonWebKey = (key: SigningKey): JsonWebKey => { + const read = (name: string): string | undefined => { + const value = key[name] + return typeof value === 'string' ? value : undefined + } + return { + kty: read('kty'), + n: read('n'), + e: read('e'), + crv: read('crv'), + x: read('x'), + y: read('y'), + } +} + +/** + * Verify the ID token signature against the provider's published keys, and + * that the access token is the one the ID token was issued with (`at_hash`). + * Without this, a response injected into the callback could carry any token. + * + * @throws Error when the token is not signed by the provider or does not + * match the access token + */ +export async function verifyIdToken({ + idToken, + accessToken, + keys, + crypto, +}: { + idToken: string + accessToken: string + keys: readonly SigningKey[] + crypto: IdTokenCrypto +}): Promise { + const [headerPart, payloadPart, signaturePart] = idToken.split('.') + const header = decodeJwtPart(headerPart ?? '') + const algorithm = + typeof header?.alg === 'string' + ? SIGNING_ALGORITHMS.get(header.alg) + : undefined + if (header === undefined || algorithm === undefined) { + throw new Error('Unsupported ID token signing algorithm') + } + const kid = typeof header.kid === 'string' ? header.kid : undefined + const candidates = keys.filter( + (key) => + key.kty === algorithm.kty && + (kid === undefined || key.kid === kid) && + (key.use === undefined || key.use === 'sig'), + ) + const signature = binaryToBytes(decodeBase64UrlToBinary(signaturePart ?? '')) + const signingInput = binaryToBytes(`${headerPart}.${payloadPart}`) + let isVerified = false + for (const key of candidates) { + if ( + await crypto.verify(algorithm, toJsonWebKey(key), signature, signingInput) + ) { + isVerified = true + break + } + } + if (!isVerified) { + throw new Error('ID token signature is invalid') + } + + /** OIDC Core 3.2.2.10: `at_hash` is required when the response carries an access token */ + const atHash = decodeJwtPart(payloadPart ?? '')?.at_hash + if (typeof atHash !== 'string') { + throw new Error('ID token has no at_hash for the access token') + } + const digest = await crypto.digest(algorithm.hash, binaryToBytes(accessToken)) + if (bytesToBase64Url(digest.slice(0, digest.length / 2)) !== atHash) { + throw new Error('Access token does not match the ID token') + } +} + +export interface IdTokenExpectations { + issuer: string + clientId: string + nonce: string + nowInSeconds: number +} + +/** + * Check the ID token claims that bind it to this request and client. + * + * @throws Error naming the first claim that does not match + */ +export const validateIdTokenClaims = ( + claims: Record, + expected: IdTokenExpectations, +): IdTokenClaims => { + const { iss, sub, aud, azp, exp, iat, nonce } = claims + if (typeof sub !== 'string' || sub === '') { + throw new Error('ID token has no subject') + } + if (iss !== expected.issuer) { + throw new Error('ID token issuer does not match the provider') + } + const audiences = typeof aud === 'string' ? [aud] : aud + if ( + !Array.isArray(audiences) || + !audiences.some((audience) => audience === expected.clientId) + ) { + throw new Error('ID token audience does not include the client') + } + /** OIDC Core 3.1.3.7: with several audiences the token must name this client as `azp` */ + if ( + (audiences.length > 1 || azp !== undefined) && + azp !== expected.clientId + ) { + throw new Error('ID token authorized party is not the client') + } + if (nonce !== expected.nonce) { + throw new Error('ID token nonce does not match the request') + } + if ( + typeof exp !== 'number' || + exp + CLOCK_SKEW_SECONDS < expected.nowInSeconds + ) { + throw new Error('ID token has expired') + } + if (typeof iat !== 'number') { + throw new Error('ID token has no issue time') + } + return { + ...claims, + iss: expected.issuer, + sub, + aud: typeof aud === 'string' ? aud : audiences.map(String), + exp, + iat, + } +} + +/** Stored like an oidc-client-ts `State` so the library's stale-state sweep applies */ +interface PendingRequest { + id: string + created: number + request_type: string + nonce: string + data?: unknown +} + +const parsePendingRequest = (raw: string): PendingRequest | undefined => { + try { + const parsed: unknown = JSON.parse(raw) + if ( + !isRecord(parsed) || + typeof parsed.id !== 'string' || + typeof parsed.nonce !== 'string' + ) { + return undefined + } + return { + id: parsed.id, + nonce: parsed.nonce, + created: typeof parsed.created === 'number' ? parsed.created : 0, + request_type: + typeof parsed.request_type === 'string' ? parsed.request_type : '', + data: parsed.data, + } + } catch { + return undefined + } +} + +const nowInSeconds = (): number => Math.floor(Date.now() / 1000) + +/** + * Load `url` in a hidden frame and resolve with the URL the provider redirects + * back to. The callback lands on Slim's own origin, so its URL is readable; + * pages on the provider's origin throw on access and are skipped. + */ +export const loadInHiddenFrame = ( + url: string, + timeoutMs: number, +): Promise => + new Promise((resolve, reject) => { + const frame = document.createElement('iframe') + frame.setAttribute('aria-hidden', 'true') + frame.style.visibility = 'hidden' + frame.style.position = 'fixed' + frame.style.left = '-1000px' + frame.style.top = '0' + frame.width = '0' + frame.height = '0' + + const listeners = new AbortController() + let timer = 0 + const cleanup = (): void => { + window.clearTimeout(timer) + listeners.abort() + frame.remove() + } + const handleLoad = (): void => { + let href: string | undefined + try { + href = frame.contentWindow?.location.href + } catch { + return + } + if (href === undefined || href === 'about:blank') { + return + } + if (!isOidcAuthorizeCallbackUrl(new URL(href))) { + return + } + cleanup() + resolve(href) + } + timer = window.setTimeout(() => { + cleanup() + reject(new Error('silent renew timed out')) + }, timeoutMs) + + frame.addEventListener('load', handleLoad, { signal: listeners.signal }) + document.body.appendChild(frame) + frame.src = url + }) + +/** The part of oidc-client-ts's `UserManager` the implicit grant relies on */ +export interface ImplicitGrantHost { + settings: Pick< + UserManagerSettingsStore, + | 'client_id' + | 'scope' + | 'redirect_uri' + | 'silent_redirect_uri' + | 'loadUserInfo' + | 'includeIdTokenInSilentRenew' + | 'validateSubOnSilentRenew' + > + metadataService: Pick< + MetadataService, + | 'getAuthorizationEndpoint' + | 'getIssuer' + | 'getUserInfoEndpoint' + | 'getSigningKeys' + > + events: Pick + getUser: () => Promise + storeUser: (user: User | null) => Promise +} + +export interface ImplicitGrantOptions { + loadFrame?: (url: string, timeoutMs: number) => Promise + /** Defaults to Web Crypto, or a JavaScript fallback outside secure contexts */ + crypto?: IdTokenCrypto +} + +/** + * Implicit grant for deployments whose identity provider client only allows it + * (common for Google Cloud Healthcare setups). oidc-client-ts dropped the + * implicit flow, so the authorize request, callback validation and silent + * renew are done here, and the result is handed to the `UserManager` as a + * regular `User` so storage, expiry events and sign-out stay in the library. + */ +export class ImplicitGrant { + private readonly oidc: ImplicitGrantHost + private readonly stateStore: StateStore + private readonly loadFrame: ( + url: string, + timeoutMs: number, + ) => Promise + private readonly crypto: IdTokenCrypto | undefined + + constructor( + oidc: ImplicitGrantHost, + stateStore: StateStore, + options: ImplicitGrantOptions = {}, + ) { + this.oidc = oidc + this.stateStore = stateStore + this.loadFrame = options.loadFrame ?? loadInHiddenFrame + this.crypto = options.crypto + } + + /** Navigate to the provider; `data` comes back as `User.state` */ + async signinRedirect(data: unknown): Promise { + const url = await this.createAuthorizeUrl({ + data, + requestType: REDIRECT_REQUEST_TYPE, + redirectUri: this.oidc.settings.redirect_uri, + }) + window.location.assign(url) + } + + async signinRedirectCallback(url: string): Promise { + return await this.complete( + parseImplicitCallback(url), + REDIRECT_REQUEST_TYPE, + ) + } + + /** Renew without user interaction (`prompt=none`) in a hidden frame */ + async signinSilent(): Promise { + const { settings } = this.oidc + const current = await this.oidc.getUser() + const url = await this.createAuthorizeUrl({ + requestType: SILENT_REQUEST_TYPE, + redirectUri: settings.silent_redirect_uri, + prompt: 'none', + idTokenHint: settings.includeIdTokenInSilentRenew + ? current?.id_token + : undefined, + }) + const callbackUrl = await this.loadFrame(url, SILENT_RENEW_TIMEOUT_MS) + const expectedSubject = settings.validateSubOnSilentRenew + ? current?.profile.sub + : undefined + return await this.complete( + parseImplicitCallback(callbackUrl), + SILENT_REQUEST_TYPE, + expectedSubject, + ) + } + + private async createAuthorizeUrl({ + data, + requestType, + redirectUri, + prompt, + idTokenHint, + }: { + data?: unknown + requestType: string + redirectUri: string + prompt?: string + idTokenHint?: string + }): Promise { + const authorizationEndpoint = + await this.oidc.metadataService.getAuthorizationEndpoint() + const request: PendingRequest = { + id: generateUuid().replace(/-/g, ''), + created: nowInSeconds(), + request_type: requestType, + nonce: generateUuid().replace(/-/g, ''), + data, + } + await this.stateStore.set(request.id, JSON.stringify(request)) + return buildImplicitAuthorizeUrl({ + authorizationEndpoint, + clientId: this.oidc.settings.client_id, + redirectUri, + scope: this.oidc.settings.scope, + state: request.id, + nonce: request.nonce, + prompt, + idTokenHint, + }) + } + + private async complete( + callback: ImplicitCallback, + requestType: string, + expectedSubject?: string, + ): Promise { + if (callback.state === null) { + throw new Error('No state in response') + } + /** Removed before anything else so a response can be used only once */ + const raw = await this.stateStore.remove(callback.state) + const pending = raw === null ? undefined : parsePendingRequest(raw) + if (pending === undefined || pending.request_type !== requestType) { + throw new Error('No matching state found in storage') + } + if (callback.error !== null) { + throw new Error(callback.errorDescription ?? callback.error) + } + if (callback.accessToken === null || callback.idToken === null) { + throw new Error('Response is missing the access token or ID token') + } + const claims = decodeJwtClaims(callback.idToken) + if (claims === undefined) { + throw new Error('Malformed ID token') + } + await verifyIdToken({ + idToken: callback.idToken, + accessToken: callback.accessToken, + keys: (await this.oidc.metadataService.getSigningKeys()) ?? [], + crypto: this.crypto ?? (await loadIdTokenCrypto()), + }) + const now = nowInSeconds() + const { settings } = this.oidc + const idTokenClaims = validateIdTokenClaims(claims, { + issuer: await this.oidc.metadataService.getIssuer(), + clientId: settings.client_id, + nonce: pending.nonce, + nowInSeconds: now, + }) + if ( + expectedSubject !== undefined && + idTokenClaims.sub !== expectedSubject + ) { + throw new Error('Silent renew returned a different user') + } + const profile = settings.loadUserInfo + ? await this.mergeUserInfo(idTokenClaims, callback.accessToken) + : idTokenClaims + const user = new User({ + access_token: callback.accessToken, + token_type: callback.tokenType ?? 'Bearer', + id_token: callback.idToken, + scope: callback.scope ?? settings.scope, + session_state: callback.sessionState, + profile, + expires_at: + callback.expiresIn !== undefined ? now + callback.expiresIn : undefined, + userState: pending.data, + }) + await this.oidc.storeUser(user) + await this.oidc.events.load(user) + return user + } + + /** Profile claims from the user info endpoint; the ID token claims on failure */ + private async mergeUserInfo( + claims: IdTokenClaims, + accessToken: string, + ): Promise { + try { + const endpoint = await this.oidc.metadataService.getUserInfoEndpoint() + const response = await fetch(endpoint, { + headers: { Authorization: `Bearer ${accessToken}` }, + }) + if (!response.ok) { + return claims + } + const info: unknown = await response.json() + if (!isRecord(info) || info.sub !== claims.sub) { + return claims + } + return { + ...claims, + ...info, + iss: claims.iss, + sub: claims.sub, + aud: claims.aud, + exp: claims.exp, + iat: claims.iat, + } + } catch (error) { + console.warn('could not load user info', error) + return claims + } + } +} diff --git a/src/auth/oidcConfig.ts b/src/auth/oidcConfig.ts index 9d99e49c..a18c474d 100644 --- a/src/auth/oidcConfig.ts +++ b/src/auth/oidcConfig.ts @@ -1,5 +1,6 @@ import type { OidcSettings } from '../AppConfig' import { clearAuthorizationDecisions } from '../utils/authPolicy' +import { readStorage, removeStorage, writeStorage } from '../utils/safeStorage' /** localStorage key for the OIDC config entered in server selection */ export const OIDC_CONFIG_STORAGE_KEY = 'slim_oidc_config' @@ -10,6 +11,12 @@ export const OIDC_RESET_PARAM = 'resetOidc' const isNonEmptyString = (value: unknown): value is string => typeof value === 'string' && value !== '' +const optionalString = (value: unknown): string | undefined => + typeof value === 'string' ? value : undefined + +const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null && !Array.isArray(value) + /** * Quote unquoted keys so JavaScript object notation parses as JSON, * e.g. `{ authority: "value" }` -> `{ "authority": "value" }`. @@ -46,13 +53,10 @@ export const parseOidcConfig = ( } catch { return undefined } - if (typeof parsed !== 'object' || parsed === null) { + if (!isRecord(parsed)) { return undefined } - const { authority, clientId, scope, ...optional } = parsed as Record< - string, - unknown - > + const { authority, clientId, scope, ...optional } = parsed if ( !isNonEmptyString(authority) || !isNonEmptyString(clientId) || @@ -64,9 +68,9 @@ export const parseOidcConfig = ( authority, clientId, scope, - grantType: optional.grantType as OidcSettings['grantType'], - authorizationEndpoint: optional.authorizationEndpoint as string | undefined, - endSessionEndpoint: optional.endSessionEndpoint as string | undefined, + grantType: optionalString(optional.grantType), + authorizationEndpoint: optionalString(optional.authorizationEndpoint), + endSessionEndpoint: optionalString(optional.endSessionEndpoint), } } @@ -96,7 +100,7 @@ export const getOidcConfigToApply = ( /** The raw cached entry, or an empty string when there is none */ export const readCachedOidcConfigInput = (): string => - window.localStorage.getItem(OIDC_CONFIG_STORAGE_KEY) ?? '' + readStorage(OIDC_CONFIG_STORAGE_KEY) ?? '' export const readCachedOidcConfig = (): OidcSettings | undefined => parseOidcConfig(readCachedOidcConfigInput()) @@ -112,9 +116,9 @@ const replaceCachedOidcConfigInput = (input: string): void => { } clearAuthorizationDecisions() if (input === '') { - window.localStorage.removeItem(OIDC_CONFIG_STORAGE_KEY) + removeStorage(OIDC_CONFIG_STORAGE_KEY) } else { - window.localStorage.setItem(OIDC_CONFIG_STORAGE_KEY, input) + writeStorage(OIDC_CONFIG_STORAGE_KEY, input) } } diff --git a/src/auth/oidcStore.ts b/src/auth/oidcStore.ts new file mode 100644 index 00000000..b51eadd7 --- /dev/null +++ b/src/auth/oidcStore.ts @@ -0,0 +1,87 @@ +import { InMemoryWebStorage, type StateStore } from 'oidc-client-ts' + +import { + getLocalStorage, + getSessionStorage, + readFromStorage, + removeFromStorage, + writeToStorage, +} from '../utils/safeStorage' + +const OIDC_KEY_PREFIX = 'oidc.' + +const listKeys = (storage: Storage): string[] => { + const keys: string[] = [] + try { + for (let index = 0; index < storage.length; index += 1) { + const key = storage.key(index) + if (key !== null) { + keys.push(key) + } + } + } catch { + /** Storage blocked mid-iteration: report what was read */ + } + return keys +} + +/** + * oidc-client-ts store backed by Web Storage that never throws. The library's + * own `WebStorageStateStore` lets quota and access errors escape, which would + * turn a full or blocked storage into a failed sign-in. + */ +export class SafeStateStore implements StateStore { + private readonly storage: Storage + + constructor(storage: Storage) { + this.storage = storage + } + + set(key: string, value: string): Promise { + writeToStorage(this.storage, OIDC_KEY_PREFIX + key, value) + return Promise.resolve() + } + + /** `InMemoryWebStorage` answers a missing key with undefined, not null */ + private read(prefixedKey: string): string | null { + return readFromStorage(this.storage, prefixedKey) ?? null + } + + get(key: string): Promise { + return Promise.resolve(this.read(OIDC_KEY_PREFIX + key)) + } + + remove(key: string): Promise { + const prefixedKey = OIDC_KEY_PREFIX + key + const value = this.read(prefixedKey) + removeFromStorage(this.storage, prefixedKey) + return Promise.resolve(value) + } + + getAllKeys(): Promise { + return Promise.resolve( + listKeys(this.storage) + .filter((key) => key.startsWith(OIDC_KEY_PREFIX)) + .map((key) => key.slice(OIDC_KEY_PREFIX.length)), + ) + } +} + +export interface OidcStores { + /** Pending sign-in requests; must outlive the redirect to the provider */ + stateStore: StateStore + /** The signed-in user; scoped to the browser tab */ + userStore: StateStore +} + +/** + * Same split as the library defaults: requests in localStorage, the user in + * sessionStorage. Blocked storage falls back to memory so the app still loads, + * though a redirect sign-in cannot complete without persistent state. + */ +export const createOidcStores = (): OidcStores => ({ + stateStore: new SafeStateStore(getLocalStorage() ?? new InMemoryWebStorage()), + userStore: new SafeStateStore( + getSessionStorage() ?? new InMemoryWebStorage(), + ), +}) diff --git a/src/auth/oidcUser.ts b/src/auth/oidcUser.ts new file mode 100644 index 00000000..e8f8b108 --- /dev/null +++ b/src/auth/oidcUser.ts @@ -0,0 +1,106 @@ +import type { User as UserData } from 'oidc-client-ts' + +import NotificationMiddleware, { + NotificationMiddlewareContext, +} from '../services/NotificationMiddleware' +import { CustomError, errorTypes } from '../utils/CustomError' +import type { User } from '.' + +/** Query and fragment parameters an authorize redirect may append */ +const AUTH_RESPONSE_PARAMS = [ + 'code', + 'state', + 'session_state', + 'iss', + 'id_token', + 'access_token', + 'token_type', + 'expires_in', + 'scope', + 'error', + 'error_description', +] + +export const createUser = (userData: UserData | null): User => { + const profile = userData?.profile + if (profile === undefined) { + NotificationMiddleware.onError( + NotificationMiddlewareContext.AUTH, + new CustomError( + errorTypes.AUTHENTICATION, + 'Failed to obtain user profile.', + ), + ) + return { name: undefined, email: undefined } + } + if (profile.name === undefined || profile.email === undefined) { + NotificationMiddleware.onError( + NotificationMiddlewareContext.AUTH, + new CustomError( + errorTypes.AUTHENTICATION, + 'Failed to obtain user "name" and "email".', + ), + ) + return { name: undefined, email: undefined } + } + return { name: profile.name, email: profile.email } +} + +/** Full HTTP Authorization header value, e.g. "Bearer …" */ +export const authorizationFromUser = (userData: UserData): string => { + const tokenType = userData.token_type || 'Bearer' + return `${tokenType} ${userData.access_token}` +} + +/** Only allow same-origin relative paths (block open redirects). */ +export const isSafeReturnUrl = (returnUrl: string): boolean => { + if (!returnUrl.startsWith('/') || returnUrl.startsWith('//')) { + return false + } + try { + const parsed = new URL(returnUrl, window.location.origin) + return parsed.origin === window.location.origin + } catch { + return false + } +} + +/** Return URL carried through sign-in in the OIDC `state`, if it is safe */ +export const readReturnUrl = (userData: UserData): string | undefined => { + const { state } = userData + let returnUrl: unknown + if (typeof state === 'string') { + returnUrl = state + } else if ( + typeof state === 'object' && + state !== null && + 'returnUrl' in state + ) { + returnUrl = state.returnUrl + } + if (typeof returnUrl !== 'string' || returnUrl === '') { + return undefined + } + return isSafeReturnUrl(returnUrl) ? returnUrl : undefined +} + +export const currentReturnUrl = (): string => + `${window.location.pathname}${window.location.search}` + +/** + * Drop authorize response parameters from the address bar so tokens are not + * left in history and a reload does not replay the callback. + */ +export const clearAuthParamsFromUrl = (): void => { + const url = new URL(window.location.href) + for (const key of AUTH_RESPONSE_PARAMS) { + url.searchParams.delete(key) + } + /** Implicit / hybrid responses put tokens in the hash fragment. */ + url.hash = '' + window.history.replaceState( + {}, + document.title, + `${url.pathname}${url.search}`, + ) +} diff --git a/src/auth/pkce.ts b/src/auth/pkce.ts new file mode 100644 index 00000000..da6cf9ef --- /dev/null +++ b/src/auth/pkce.ts @@ -0,0 +1,43 @@ +import { SigninState } from 'oidc-client-ts' + +type CreateSigninState = typeof SigninState.create + +const fallbacks = new WeakSet() + +/** 32 random bytes give the 43-character verifier RFC 7636 recommends */ +const VERIFIER_BYTES = 32 + +/** + * oidc-client-ts derives the PKCE challenge with `crypto.subtle`, which + * browsers hide on plain-HTTP pages, so the code flow could not start there. + * Without Web Crypto, compute the verifier and challenge in JavaScript. + */ +export function installPkceFallback( + subtle: SubtleCrypto | undefined = globalThis.crypto?.subtle, +): void { + if (subtle !== undefined || fallbacks.has(SigninState.create)) return + const create = SigninState.create.bind(SigninState) + const createWithFallback: CreateSigninState = async (args) => { + if ( + args.code_verifier === undefined || + args.code_verifier === false || + args.code_verifier === '' + ) { + return await create(args) + } + const { bytesToBase64Url, pkceChallenge } = await import('./softwareCrypto') + const codeVerifier = + args.code_verifier === true + ? bytesToBase64Url( + globalThis.crypto.getRandomValues(new Uint8Array(VERIFIER_BYTES)), + ) + : args.code_verifier + const state = await create({ ...args, code_verifier: undefined }) + return Object.assign(state, { + code_verifier: codeVerifier, + code_challenge: pkceChallenge(codeVerifier), + }) + } + fallbacks.add(createWithFallback) + SigninState.create = createWithFallback +} diff --git a/src/auth/softwareCrypto.ts b/src/auth/softwareCrypto.ts new file mode 100644 index 00000000..5bc82780 --- /dev/null +++ b/src/auth/softwareCrypto.ts @@ -0,0 +1,155 @@ +import { p256, p384 } from '@noble/curves/nist.js' +import { sha256, sha384, sha512 } from '@noble/hashes/sha2.js' + +import type { HashName, IdTokenCrypto, SignatureScheme } from './idTokenCrypto' + +/** + * JavaScript stand-ins for the Web Crypto calls sign-in needs, for pages + * served where browsers hide `crypto.subtle`. Only public-key verification + * and hashing happen here; nothing secret is computed. + */ + +const HASHES: Record Uint8Array> = { + 'SHA-256': sha256, + 'SHA-384': sha384, + 'SHA-512': sha512, +} + +/** DER DigestInfo prefixes of EMSA-PKCS1-v1_5 (RFC 8017, section 9.2) */ +const DIGEST_INFO_PREFIX: Record = { + 'SHA-256': [ + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, + ], + 'SHA-384': [ + 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30, + ], + 'SHA-512': [ + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, + ], +} + +const CURVES = { + 'P-256': { ecdsa: p256, size: 32 }, + 'P-384': { ecdsa: p384, size: 48 }, +} as const + +export const base64UrlToBytes = (input: string): Uint8Array => { + const base64 = input.replace(/-/g, '+').replace(/_/g, '/') + const padded = base64.padEnd( + base64.length + ((4 - (base64.length % 4)) % 4), + '=', + ) + return Uint8Array.from(atob(padded), (char) => char.charCodeAt(0)) +} + +export const bytesToBase64Url = (bytes: Uint8Array): string => + btoa(String.fromCharCode(...bytes)) + .replaceAll('+', '-') + .replaceAll('/', '_') + .replace(/=+$/, '') + +const bytesToBigInt = (bytes: Uint8Array): bigint => + bytes.reduce((value, byte) => (value << 8n) | BigInt(byte), 0n) + +const bigIntToBytes = (value: bigint, length: number): Uint8Array => { + const bytes = new Uint8Array(length) + let rest = value + for (let index = length - 1; index >= 0; index--) { + bytes[index] = Number(rest & 0xffn) + rest >>= 8n + } + return bytes +} + +const modPow = (base: bigint, exponent: bigint, modulus: bigint): bigint => { + let result = 1n + let factor = base % modulus + let rest = exponent + while (rest > 0n) { + if ((rest & 1n) === 1n) result = (result * factor) % modulus + factor = (factor * factor) % modulus + rest >>= 1n + } + return result +} + +const bytesEqual = (a: Uint8Array, b: Uint8Array): boolean => + a.length === b.length && a.every((byte, index) => byte === b[index]) + +/** RSASSA-PKCS1-v1_5 verification (RFC 8017, section 8.2.2) */ +export function verifyRsaPkcs1( + hash: HashName, + key: JsonWebKey, + signature: Uint8Array, + data: Uint8Array, +): boolean { + if (key.n === undefined || key.e === undefined) return false + const modulusBytes = base64UrlToBytes(key.n) + const modulus = bytesToBigInt(modulusBytes) + const exponent = bytesToBigInt(base64UrlToBytes(key.e)) + const length = modulusBytes.length - (modulusBytes[0] === 0 ? 1 : 0) + const digestInfo = [...DIGEST_INFO_PREFIX[hash], ...HASHES[hash](data)] + if ( + modulus === 0n || + signature.length !== length || + length < digestInfo.length + 11 + ) { + return false + } + const value = bytesToBigInt(signature) + if (value >= modulus) return false + const encoded = bigIntToBytes(modPow(value, exponent, modulus), length) + const expected = new Uint8Array(length) + expected[1] = 0x01 + expected.fill(0xff, 2, length - digestInfo.length - 1) + expected.set(digestInfo, length - digestInfo.length) + return bytesEqual(encoded, expected) +} + +/** ECDSA verification of a JWS (raw `r || s`) signature */ +export function verifyEcdsa( + namedCurve: 'P-256' | 'P-384', + hash: HashName, + key: JsonWebKey, + signature: Uint8Array, + data: Uint8Array, +): boolean { + const curve = CURVES[namedCurve] + if (key.crv !== namedCurve || key.x === undefined || key.y === undefined) { + return false + } + const x = base64UrlToBytes(key.x) + const y = base64UrlToBytes(key.y) + if (x.length !== curve.size || y.length !== curve.size) return false + const publicKey = new Uint8Array(1 + 2 * curve.size) + publicKey[0] = 0x04 + publicKey.set(x, 1) + publicKey.set(y, 1 + curve.size) + try { + /** JWS signers do not normalize `s`, so high-S signatures are valid */ + return curve.ecdsa.verify(signature, HASHES[hash](data), publicKey, { + prehash: false, + lowS: false, + format: 'compact', + }) + } catch { + return false + } +} + +export const softwareIdTokenCrypto: IdTokenCrypto = { + digest: async (hash, data) => HASHES[hash](data), + verify: async (scheme: SignatureScheme, key, signature, data) => + scheme.kty === 'RSA' + ? verifyRsaPkcs1(scheme.hash, key, signature, data) + : verifyEcdsa(scheme.namedCurve, scheme.hash, key, signature, data), +} + +/** PKCE S256 challenge (RFC 7636): base64url(SHA-256(ASCII(verifier))) */ +export function pkceChallenge(codeVerifier: string): string { + const bytes = Uint8Array.from(codeVerifier, (char) => char.charCodeAt(0)) + return bytesToBase64Url(sha256(bytes)) +} diff --git a/src/auth/testing/fakeUserManager.ts b/src/auth/testing/fakeUserManager.ts new file mode 100644 index 00000000..37121ee9 --- /dev/null +++ b/src/auth/testing/fakeUserManager.ts @@ -0,0 +1,266 @@ +import { webcrypto } from 'node:crypto' +import { + type AccessTokenCallback, + type OidcMetadata, + type SigningKey, + type SigninRedirectArgs, + User, + type UserLoadedCallback, + type UserManagerSettings, + type UserProfile, +} from 'oidc-client-ts' +import type { Mock } from 'vitest' + +import type { OidcUserManager } from '../OidcManager' + +export const ISSUER = 'https://idp.example.com' +export const CLIENT_ID = 'slim' + +export interface FakeUserManager extends OidcUserManager { + /** Settings the manager was created with */ + readonly createdWith: UserManagerSettings + readonly userLoadedListeners: UserLoadedCallback[] + readonly tokenExpiringListeners: AccessTokenCallback[] + stored: User | null + getUser: Mock<(...args: []) => Promise> + storeUser: Mock<(...args: [User | null]) => Promise> + signinRedirect: Mock<(...args: [SigninRedirectArgs?]) => Promise> + signinRedirectCallback: Mock<(...args: [string?]) => Promise> + signinSilent: Mock<(...args: []) => Promise> + signoutRedirect: Mock<(...args: []) => Promise> + removeUser: Mock<(...args: []) => Promise> + revokeTokens: Mock<(...args: []) => Promise> +} + +export function createFakeUserManager( + settings: UserManagerSettings, + metadata: Partial = {}, + signingKeys: SigningKey[] = [], +): FakeUserManager { + const resolvedMetadata: Partial = { + issuer: ISSUER, + authorization_endpoint: `${ISSUER}/authorize`, + userinfo_endpoint: `${ISSUER}/userinfo`, + ...metadata, + ...settings.metadataSeed, + } + const userLoadedListeners: UserLoadedCallback[] = [] + const tokenExpiringListeners: AccessTokenCallback[] = [] + + const fake: FakeUserManager = { + createdWith: settings, + userLoadedListeners, + tokenExpiringListeners, + stored: null, + settings: { + client_id: settings.client_id, + scope: settings.scope ?? 'openid', + redirect_uri: settings.redirect_uri, + silent_redirect_uri: + settings.silent_redirect_uri ?? settings.redirect_uri, + loadUserInfo: settings.loadUserInfo ?? false, + includeIdTokenInSilentRenew: + settings.includeIdTokenInSilentRenew ?? false, + validateSubOnSilentRenew: settings.validateSubOnSilentRenew ?? true, + }, + metadataService: { + getMetadata: () => Promise.resolve(resolvedMetadata), + getAuthorizationEndpoint: () => + Promise.resolve(resolvedMetadata.authorization_endpoint ?? ''), + getIssuer: () => Promise.resolve(resolvedMetadata.issuer ?? ''), + getUserInfoEndpoint: () => + Promise.resolve(resolvedMetadata.userinfo_endpoint ?? ''), + getSigningKeys: () => Promise.resolve(signingKeys), + }, + events: { + load: async (user: User): Promise => { + for (const listener of userLoadedListeners) { + await listener(user) + } + }, + addUserLoaded: (callback: UserLoadedCallback) => { + userLoadedListeners.push(callback) + return () => {} + }, + addAccessTokenExpiring: (callback: AccessTokenCallback) => { + tokenExpiringListeners.push(callback) + return () => {} + }, + }, + getUser: vi.fn(() => Promise.resolve(fake.stored)), + storeUser: vi.fn((user: User | null) => { + fake.stored = user + return Promise.resolve() + }), + signinRedirect: vi.fn(() => Promise.resolve()), + signinRedirectCallback: vi.fn(() => Promise.resolve(makeUser())), + signinSilent: vi.fn(() => Promise.resolve(makeUser())), + signoutRedirect: vi.fn(() => Promise.resolve()), + removeUser: vi.fn(() => { + fake.stored = null + return Promise.resolve() + }), + revokeTokens: vi.fn(() => Promise.resolve()), + } + return fake +} + +const nowInSeconds = (): number => Math.floor(Date.now() / 1000) + +export function makeProfile(overrides: Partial = {}): UserProfile { + return { + iss: ISSUER, + sub: 'user-1', + aud: CLIENT_ID, + exp: nowInSeconds() + 3600, + iat: nowInSeconds(), + name: 'Ada Lovelace', + email: 'ada@example.com', + ...overrides, + } +} + +export function makeUser({ + accessToken = 'access-token', + profile = makeProfile(), + state, + expiresAt = nowInSeconds() + 3600, +}: { + accessToken?: string + profile?: UserProfile + state?: unknown + expiresAt?: number +} = {}): User { + return new User({ + access_token: accessToken, + token_type: 'Bearer', + id_token: 'id-token', + profile, + expires_at: expiresAt, + userState: state, + }) +} + +/** UTF-8 as a binary string, since jsdom has no `TextEncoder` */ +const toUtf8Binary = (value: string): string => + encodeURIComponent(value).replace(/%([0-9A-F]{2})/g, (_match, hex: string) => + String.fromCharCode(Number.parseInt(hex, 16)), + ) + +const binaryToBase64Url = (binary: string): string => + window.btoa(binary).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_') + +export const encodeBase64Url = (value: string): string => + binaryToBase64Url(toUtf8Binary(value)) + +const bytesToBase64Url = (bytes: ArrayBuffer): string => + binaryToBase64Url(String.fromCharCode(...new Uint8Array(bytes))) + +const toBytes = (binary: string): Uint8Array => + Uint8Array.from(binary, (char) => char.charCodeAt(0)) + +/** Unsigned compact JWT carrying `claims` */ +export function makeJwt( + claims: Record, + header: Record = { alg: 'none', typ: 'JWT' }, +): string { + return `${encodeBase64Url(JSON.stringify(header))}.${encodeBase64Url( + JSON.stringify(claims), + )}.signature` +} + +/** jsdom has no Web Crypto, so expose Node's where browsers do */ +if (window.crypto?.subtle === undefined) { + Object.defineProperty(window, 'crypto', { + configurable: true, + value: webcrypto, + }) +} + +export const subtle: SubtleCrypto = window.crypto.subtle + +export interface IdTokenSigner { + /** Public key as published in the provider's JWKS */ + jwk: SigningKey + sign: (claims: Record) => Promise + atHash: (accessToken: string) => Promise +} + +/** RS256 signer standing in for the identity provider */ +export async function createIdTokenSigner( + kid = 'key-1', +): Promise { + const { publicKey, privateKey } = await subtle.generateKey( + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + true, + ['sign', 'verify'], + ) + const exported = await subtle.exportKey('jwk', publicKey) + return { + jwk: { + kty: 'RSA', + n: exported.n ?? '', + e: exported.e ?? '', + kid, + use: 'sig', + alg: 'RS256', + }, + sign: async (claims) => { + const signingInput = `${encodeBase64Url( + JSON.stringify({ alg: 'RS256', kid, typ: 'JWT' }), + )}.${encodeBase64Url(JSON.stringify(claims))}` + const signature = await subtle.sign( + 'RSASSA-PKCS1-v1_5', + privateKey, + toBytes(signingInput), + ) + return `${signingInput}.${bytesToBase64Url(signature)}` + }, + atHash: async (accessToken) => { + const digest = await subtle.digest('SHA-256', toBytes(accessToken)) + return bytesToBase64Url(digest.slice(0, digest.byteLength / 2)) + }, + } +} + +export interface LocationStub { + assign: Mock<(...args: [string]) => void> + restore: () => void +} + +/** Replace `window.location` so navigation can be observed */ +export function stubLocation(url: string): LocationStub { + const original = window.location + const parsed = new URL(url) + const assign = vi.fn<(...args: [string]) => void>() + Object.defineProperty(window, 'location', { + configurable: true, + value: { + href: parsed.href, + origin: parsed.origin, + protocol: parsed.protocol, + host: parsed.host, + hostname: parsed.hostname, + pathname: parsed.pathname, + search: parsed.search, + hash: parsed.hash, + assign, + replace: vi.fn(), + reload: vi.fn(), + }, + }) + return { + assign, + restore: () => { + Object.defineProperty(window, 'location', { + configurable: true, + value: original, + }) + }, + } +} diff --git a/src/components/AnnotationCategoryItem.tsx b/src/components/AnnotationCategoryItem.tsx index af2de59a..4d87a4d8 100644 --- a/src/components/AnnotationCategoryItem.tsx +++ b/src/components/AnnotationCategoryItem.tsx @@ -1,218 +1,175 @@ -import { SettingOutlined } from '@ant-design/icons' -import { Button, Checkbox, Menu, Popover, Space, Tooltip } from 'antd' -import type { CheckboxChangeEvent } from 'antd/es/checkbox' -import { useCallback, useMemo } from 'react' -import type { Category, Type } from './AnnotationCategoryList' +import type React from 'react' +import { useState } from 'react' + +import { cn } from '../lib/utils' +import type { AnnotationStyle } from '../types/layerStyles' +import { + type Category, + getCategoryVisibility, + type Type, +} from '../utils/annotationCategories' +import { rgbToHex } from '../utils/color' +import { + computeBulkVisibility, + getToggleTarget, + type VisibilityChange, +} from '../utils/visibility' +import { formatVisibilitySummary } from '../utils/visibilitySummary' import ColorSettingsMenu from './ColorSettingsMenu' -import type { StyleOptions } from './SlideViewer/types' +import { Popover, PopoverContent, PopoverTrigger } from './ui/popover' +import { Switch } from './ui/switch' + +/** Not every annotation UID has a style entry */ +export type AnnotationStyleMap = Record + +export type AnnotationStyleChangeHandler = (change: { + uids: string[] + styleOptions: AnnotationStyle +}) => void + +export type AnnotationVisibilityChangeHandler = ( + changes: VisibilityChange[], +) => void + +interface AnnotationTypeChipProps { + category: Category + type: Type + checkedAnnotationUids: Set + onVisibilityChange: (type: Type, isVisible: boolean) => void + onStyleChange: AnnotationStyleChangeHandler + defaultAnnotationStyles: AnnotationStyleMap +} -function AnnotationTypeRow({ +/** One annotation type as a pill chip; clicking opens visibility and color settings. */ +function AnnotationTypeChip({ + category, type, checkedAnnotationUids, - onCheckboxChange, + onVisibilityChange, onStyleChange, defaultAnnotationStyles, -}: { - type: Type - checkedAnnotationUids: Set - onCheckboxChange: (type: Type, e: CheckboxChangeEvent) => void - onStyleChange: (arg: { uid: string; styleOptions: StyleOptions }) => void - defaultAnnotationStyles: { - [annotationUID: string]: { - opacity: number - color: number[] - contourOnly: boolean - } - } -}): JSX.Element { +}: AnnotationTypeChipProps): React.ReactElement { const { CodeMeaning, CodingSchemeDesignator, CodeValue, uids } = type - const shortenedCodeMeaning = CodeMeaning.slice(0, 22) - const displayCodeMeaning = - shortenedCodeMeaning === CodeMeaning - ? CodeMeaning - : `${shortenedCodeMeaning}...` - const isChecked = uids.every((uid: string) => checkedAnnotationUids.has(uid)) - const indeterminateType = - !isChecked && uids.some((uid: string) => checkedAnnotationUids.has(uid)) - const handleChange = useCallback( - (e: CheckboxChangeEvent) => onCheckboxChange(type, e), - [type, onCheckboxChange], - ) + /** + * The viewer's style map is mutated in place, so the chip keeps the style + * the user picked instead of relying on the map to re-render it. + */ + const [editedStyle, setEditedStyle] = useState() + const style = editedStyle ?? defaultAnnotationStyles[uids[0]] + const visibleCount = uids.filter((uid) => + checkedAnnotationUids.has(uid), + ).length + const visibility = getCategoryVisibility(type, checkedAnnotationUids) + const isVisible = visibility !== 'none' + const color = style !== undefined ? rgbToHex(style.color) : undefined + + const commitStyle = (next: AnnotationStyle): void => { + setEditedStyle(next) + onStyleChange({ uids, styleOptions: next }) + } + return ( -
- -
- + + + + +
+
+
+ {CodeMeaning} +
+
+ {category.CodeMeaning} · {CodeValue}:{CodingSchemeDesignator} +
+
+
+ + Visible + + {visibleCount} / {uids.length} + + + + onVisibilityChange(type, getToggleTarget(visibility)) + } + aria-label={`Show all ${CodeMeaning}`} + /> +
+ {style !== undefined && ( - } - > -
-
+ )} +
+ + ) } -const AnnotationCategoryItem = ({ +export interface AnnotationCategoryItemProps { + category: Category + onChange: AnnotationVisibilityChangeHandler + onStyleChange: AnnotationStyleChangeHandler + defaultAnnotationStyles: AnnotationStyleMap + checkedAnnotationUids: Set +} + +/** Chips for all annotation types of one category. */ +function AnnotationCategoryItem({ category, onChange, checkedAnnotationUids, onStyleChange, defaultAnnotationStyles, - ...props -}: { - category: Category - onChange: (arg: { roiUID: string; isVisible: boolean }) => void - onStyleChange: (arg: { uid: string; styleOptions: StyleOptions }) => void - defaultAnnotationStyles: { - [annotationUID: string]: { - opacity: number - color: number[] - contourOnly: boolean - } - } - checkedAnnotationUids: Set -}): JSX.Element => { - const { types } = category - - const handleChangeCheckedType = useCallback( - ({ type, isVisible }: { type: Type; isVisible: boolean }): void => { - type.uids.forEach((uid: string) => { - onChange({ roiUID: uid, isVisible }) - }) - }, - [onChange], - ) - - const onCheckCategoryChange = useCallback( - (e: CheckboxChangeEvent): void => { - const isVisible = e.target.checked - types.forEach((type: Type) => { - handleChangeCheckedType({ type, isVisible }) - }) - }, - [types, handleChangeCheckedType], - ) - - const checkAll = types.every((type: Type) => - type.uids.every((uid: string) => checkedAnnotationUids.has(uid)), - ) - const indeterminate = - !checkAll && - types.some((type: Type) => - type.uids.some((uid: string) => checkedAnnotationUids.has(uid)), +}: AnnotationCategoryItemProps): React.ReactElement { + const handleVisibilityChange = (type: Type, isVisible: boolean): void => { + const changes = computeBulkVisibility( + type.uids, + checkedAnnotationUids, + isVisible, ) - - const categoryColorSettingsContent = useMemo( - () => ( - ((acc, type) => { - acc.push(...type.uids) - return acc - }, [])} - onStyleChange={onStyleChange} - defaultStyle={defaultAnnotationStyles[types[0].uids[0]]} - /> - ), - [types, onStyleChange, defaultAnnotationStyles], - ) - - const handleCheckboxChangeType = useCallback( - (type: Type, e: CheckboxChangeEvent): void => { - handleChangeCheckedType({ type, isVisible: e.target.checked }) - }, - [handleChangeCheckedType], - ) - - const categoryHeader = useMemo( - () => ( - - - {category.CodeMeaning} - - - - ) -} - -// Interfaces -interface AnnotationGroupItemProps { +export interface AnnotationGroupItemProps { annotationGroup: dmv.annotation.AnnotationGroup isVisible: boolean - metadata: dmv.metadata.MicroscopyBulkSimpleAnnotations - defaultStyle: { - opacity: number - color: number[] - fill?: boolean - fillOpacity?: number - } + metadata?: dmv.metadata.MicroscopyBulkSimpleAnnotations + defaultStyle: AnnotationGroupStyle onAnnotationGroupClick: (annotationGroupUID: string) => void - onVisibilityChange: ({ - annotationGroupUID, - isVisible, - }: { + onVisibilityChange: (change: { annotationGroupUID: string isVisible: boolean }) => void - onStyleChange: ({ - uid, - styleOptions, - }: { + onStyleChange: (change: { uid: string - styleOptions: { - opacity?: number - color?: number[] - limitValues?: number[] - measurement?: dcmjs.sr.coding.CodedConcept - fill?: boolean - fillOpacity?: number - } + styleOptions: AnnotationGroupStyleChange }) => void } -interface AnnotationGroupItemState { - isVisible: boolean - currentStyle: { - opacity: number - color?: number[] - limitValues?: number[] - measurement?: dcmjs.sr.coding.CodedConcept - fill?: boolean - fillOpacity?: number - } +interface AnnotationGroupLocalStyle { + opacity: number + /** Only RGB colors can be edited */ + color?: RGB + fill: boolean + fillOpacity: number } -// Class -/** - * React component representing an Annotation Group. - */ -class AnnotationGroupItem extends React.Component< - AnnotationGroupItemProps, - AnnotationGroupItemState -> { - constructor(props: AnnotationGroupItemProps) { - super(props) - this.state = { - isVisible: this.props.isVisible, - currentStyle: { - opacity: this.props.defaultStyle.opacity, - color: this.props.defaultStyle.color, - fill: this.props.defaultStyle.fill ?? false, - fillOpacity: this.props.defaultStyle.fillOpacity ?? 0.5, - }, - } - } - - handleVisibilityChange = ( - checked: boolean, - _event: React.MouseEvent, - ): void => { - this.props.onVisibilityChange({ - annotationGroupUID: this.props.annotationGroup.uid, - isVisible: checked, - }) - this.setState({ isVisible: checked }) - } - - handleColorChange = (color: number[]): void => { - this.setState((state) => ({ - currentStyle: { - ...state.currentStyle, - color, - }, - })) - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { color }, - }) - } - - handleOpacityChange = (opacity: number | null): void => { - if (opacity !== null) { - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { - opacity, - }, - }) - this.setState((state) => ({ - currentStyle: { - ...state.currentStyle, - opacity, - }, - })) - } - } - - handleFillChange = (checked: boolean): void => { - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { - fill: checked, - }, - }) - this.setState((state) => ({ - currentStyle: { - ...state.currentStyle, - fill: checked, - }, - })) - } - - handleFillOpacityChange = (fillOpacity: number | null): void => { - if (fillOpacity !== null) { - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { - fillOpacity, - }, - }) - this.setState((state) => ({ - currentStyle: { - ...state.currentStyle, - fillOpacity, - }, - })) - } - } - - getCurrentColor = (): string => { - const rgb2hex = (values: number[]): string => { - const r = values[0] - const g = values[1] - const b = values[2] - return `#${(0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1)}` - } - - if ( - this.state.currentStyle.color !== null && - this.state.currentStyle.color !== undefined - ) { - return rgb2hex(this.state.currentStyle.color) - } else { - return 'white' - } - } - - handleLowerLimitChange = (value: number | null): void => { - if ( - value !== null && - value !== undefined && - this.state.currentStyle.limitValues !== undefined - ) { - this.setState((state) => { - if (state.currentStyle.limitValues !== undefined) { - return { - currentStyle: { - ...state.currentStyle, - limitValues: [value, state.currentStyle.limitValues[1]], - }, - } - } else { - return { currentStyle: state.currentStyle } - } - }) - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { - limitValues: [value, this.state.currentStyle.limitValues[1]], - }, - }) - } - } - - handleUpperLimitChange = (value: number | null): void => { - if ( - value !== null && - value !== undefined && - this.state.currentStyle.limitValues !== undefined - ) { - this.setState((state) => { - if (state.currentStyle.limitValues !== undefined) { - return { - currentStyle: { - ...state.currentStyle, - limitValues: [state.currentStyle.limitValues[0], value], - }, - } - } else { - return { currentStyle: state.currentStyle } - } - }) - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { - limitValues: [this.state.currentStyle.limitValues[0], value], - }, - }) - } - } +function toEditableColor(color: readonly number[]): RGB | undefined { + return color.length === 3 ? toRGB(color) : undefined +} - handleLimitChange = (values: number[]): void => { - this.setState((state) => ({ - currentStyle: { - ...state.currentStyle, - limitValues: values, +/** One annotation group with visibility, style and measurement coloring. */ +function AnnotationGroupItem({ + annotationGroup, + isVisible, + metadata, + defaultStyle, + onAnnotationGroupClick, + onVisibilityChange, + onStyleChange, +}: AnnotationGroupItemProps): React.ReactElement { + const uid = annotationGroup.uid + const [style, updateStyle, previewStyle] = + useLayerStyle( + { + opacity: defaultStyle.opacity, + color: toEditableColor(defaultStyle.color), + fill: defaultStyle.fill ?? false, + fillOpacity: defaultStyle.fillOpacity ?? DEFAULT_FILL_OPACITY, }, - })) - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { limitValues: values }, - }) - } + (styleOptions) => onStyleChange({ uid, styleOptions }), + ) + /** Select value; survives the settings popover unmounting */ + const [measurementKey, setMeasurementKey] = useState(NO_MEASUREMENT) - handleAnnotationGroupClick = (): void => { - this.props.onAnnotationGroupClick(this.props.annotationGroup.uid) - } + const item = getAnnotationGroupItem(metadata, uid) + const { meta, attributes, count } = useMemo( + () => + describeAnnotationGroup( + annotationGroup, + item, + metadata?.AnnotationCoordinateType, + ), + [annotationGroup, item, metadata], + ) + const details = useMemo(() => toKeyValueItems(attributes), [attributes]) + const measurementOptions = useMemo(() => getMeasurementOptions(item), [item]) + const isColoredByMeasurement = measurementKey !== NO_MEASUREMENT + const isFillable = isFillableGraphicType(item?.GraphicType) + const label = annotationGroup.label - handleMeasurementSelection: SelectProps['onChange'] = (value, option) => { - if ( - value !== null && - value !== undefined && - option !== null && - option !== undefined && - Array.isArray(option) && - option.length > 0 && - option[0] !== null && - option[0] !== undefined && - option[0].children !== null && - option[0].children !== undefined - ) { - const codeComponents = value.split('-') - const measurement = new dcmjs.sr.coding.CodedConcept({ - value: codeComponents[1], - schemeDesignator: codeComponents[0], - meaning: Array.isArray(option[0].children) - ? String(option[0].children[0]) - : String(option[0].children), + const handleMeasurementSelection = (key: string): void => { + const option = measurementOptions.find((candidate) => candidate.key === key) + setMeasurementKey(option?.key ?? NO_MEASUREMENT) + if (option !== undefined) { + onStyleChange({ + uid, + styleOptions: { measurement: measurementOptionToConcept(option) }, }) - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { measurement }, - }) - this.setState((state) => ({ - currentStyle: { - ...state.currentStyle, - measurement, - }, - })) - } else { - this.props.onStyleChange({ - uid: this.props.annotationGroup.uid, - styleOptions: { - color: this.props.defaultStyle.color, - }, - }) - this.setState((state) => ({ - currentStyle: { - ...state.currentStyle, - color: this.props.defaultStyle.color, - limitValues: undefined, - }, - })) + return } + const defaultColor = toRGB(defaultStyle.color) + if (defaultColor === undefined) return + previewStyle({ color: toEditableColor(defaultStyle.color) }) + onStyleChange({ uid, styleOptions: { color: defaultColor } }) } - render(): React.ReactNode { - const index = this.props.metadata.AnnotationGroupSequence.findIndex( - (item) => item.AnnotationGroupUID === this.props.annotationGroup.uid, - ) - const item = this.props.metadata.AnnotationGroupSequence[index] - const attributes: Array<{ name: string; value: string }> = [ - { - name: 'Property type', - value: this.props.annotationGroup.propertyType.CodeMeaning, - }, - { - name: 'Property category', - value: this.props.annotationGroup.propertyCategory.CodeMeaning, - }, - // { - // name: 'Algorithm Name', - // value: this.props.annotationGroup.algorithmName - // }, - { - name: 'Graphic type', - value: item.GraphicType, - }, - { - name: 'Annotation coordinate type', - value: this.props.metadata.AnnotationCoordinateType, - }, - ] - - const measurementsSequence = item.MeasurementsSequence ?? [] - const createMeasurementOption = (measurementItem: { - ConceptNameCodeSequence: Array<{ - CodingSchemeDesignator: string - CodeValue: string - CodeMeaning: string - }> - }): React.ReactElement => { - const name = measurementItem.ConceptNameCodeSequence[0] - const key = `${name.CodingSchemeDesignator}-${name.CodeValue}` - return ( - + {isColoredByMeasurement ? ( + - {name.CodeMeaning} - - ) - } - const measurementOptions = measurementsSequence.map(createMeasurementOption) - measurementOptions.push( - - {null} - , - ) - - let colorSettings: React.ReactNode - if ( - this.state.currentStyle.color !== null && - this.state.currentStyle.color !== undefined && - this.state.currentStyle.color.length === 3 - ) { - colorSettings = ( - <> - Color - + + ) : ( + + )} +
+ + + + + {meta !== '' && ( + {meta} + )} +
+ {count !== undefined && ( + + {formatGroupedNumber(count)} + + )} + + +
+
+ Display settings +
+ {style.color !== undefined && ( +
+

Color

+ previewStyle({ color })} + onCommit={(color) => updateStyle({ color })} + /> +
+ )} + previewStyle({ opacity })} + onCommit={(opacity) => updateStyle({ opacity })} /> - - - ) - } - - let windowSettings: React.ReactNode - let explorationSettings: React.ReactNode - if (measurementsSequence.length > 0) { - if ( - this.state.currentStyle.limitValues !== null && - this.state.currentStyle.limitValues !== undefined - ) { - // TODO: need to get default min/max values from viewer first - const minValue = 0 - const maxValue = 1000 - windowSettings = ( - <> - Values of interest - - - +
+ Fill + updateStyle({ fill })} + aria-label="Fill shapes" /> - - - - - - - - - - ) - } - explorationSettings = ( - <> - Exploration - - Measurement - +
+ previewStyle({ fillOpacity })} + onCommit={(fillOpacity) => updateStyle({ fillOpacity })} + /> +
+ )} + {measurementOptions.length > 0 && ( +
+

Color by measurement

- - - - ) - } - - // Fill settings for POLYGON, RECTANGLE, ELLIPSE graphic types - let fillSettings: React.ReactNode - if ( - item.GraphicType === 'POLYGON' || - item.GraphicType === 'RECTANGLE' || - item.GraphicType === 'ELLIPSE' - ) { - fillSettings = ( - <> - Fill - - Enable fill - - - - - - Fill opacity - - - - - - - - - ) - } - - const settings = ( -
- {colorSettings} - {windowSettings} - - {fillSettings} - {explorationSettings} -
- ) - - const color = this.getCurrentColor() - const isBadgeVisible = - this.state.isVisible && this.state.currentStyle.measurement === null - return ( - -
- +
+ )}
- - - ) - } +
+ + onVisibilityChange({ + annotationGroupUID: uid, + isVisible: nextIsVisible, + }) + } + /> + + ) } -export default AnnotationGroupItem +export default memo(AnnotationGroupItem, areLayerItemPropsEqual) diff --git a/src/components/AnnotationGroupList.tsx b/src/components/AnnotationGroupList.tsx index 5d4d419e..fc92f4ea 100644 --- a/src/components/AnnotationGroupList.tsx +++ b/src/components/AnnotationGroupList.tsx @@ -1,119 +1,107 @@ -import type { MenuProps } from 'antd' -import { Menu, Switch } from 'antd' -// skipcq: JS-C1003 -import type * as dcmjs from 'dcmjs' -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import type * as dmv from 'dicom-microscopy-viewer' -import React from 'react' -import { FaEye, FaEyeSlash } from 'react-icons/fa' +import type React from 'react' + +import type { + AnnotationGroupStyle, + AnnotationGroupStyleChange, +} from '../types/layerStyles' +import { + buildAnnotationGroupDisplayOptions, + buildClusteringThresholdInput, +} from '../utils/displayOptions' +import { bindDisplayOptions } from '../utils/displayOptionsBinding' +import type { VisibilityChange } from '../utils/visibility' import AnnotationGroupItem from './AnnotationGroupItem' +import { BulkVisibilityControl } from './slim/BulkVisibilityControl' +import { DisplayOptionsPanel } from './slim/DisplayOptionsPanel' + +export interface AnnotationGroupDisplaySettings { + clusteringEnabled: boolean + /** Raw text of the pixel size threshold field (mm); '' means zoom-based */ + clusteringThreshold: string +} -interface AnnotationGroupListProps { +export interface AnnotationGroupListProps { annotationGroups: dmv.annotation.AnnotationGroup[] visibleAnnotationGroupUIDs: Set metadata: { [annotationGroupUID: string]: dmv.metadata.MicroscopyBulkSimpleAnnotations } defaultAnnotationGroupStyles: { - [annotationGroupUID: string]: { - opacity: number - color: number[] - fill?: boolean - fillOpacity?: number - } + [annotationGroupUID: string]: AnnotationGroupStyle } onAnnotationGroupClick: (annotationGroupUID: string) => void - onAnnotationGroupVisibilityChange: ({ - annotationGroupUID, - isVisible, - }: { + onAnnotationGroupVisibilityChange: (change: { annotationGroupUID: string isVisible: boolean }) => void - onAnnotationGroupStyleChange: ({ - uid, - styleOptions, - }: { + onBulkAnnotationGroupVisibilityChange: (changes: VisibilityChange[]) => void + onAnnotationGroupStyleChange: (change: { uid: string - styleOptions: { - opacity?: number - color?: number[] - measurement?: dcmjs.sr.coding.CodedConcept - fill?: boolean - fillOpacity?: number - } + styleOptions: AnnotationGroupStyleChange }) => void + /** Clustering display settings; the panel is hidden when omitted */ + displaySettings?: AnnotationGroupDisplaySettings + onDisplaySettingsChange?: (settings: AnnotationGroupDisplaySettings) => void } -/** - * React component representing a list of Annotation Groups. - */ -class AnnotationGroupList extends React.Component< - AnnotationGroupListProps, - unknown -> { - handleVisibilityChange = (checked: boolean): void => { - if (checked) { - this.props.annotationGroups.forEach((annotationGroup) => { - this.props.onAnnotationGroupVisibilityChange({ - annotationGroupUID: annotationGroup.uid, - isVisible: checked, - }) - }) - return - } - - this.props.visibleAnnotationGroupUIDs.forEach((annotationGroupUID) => { - this.props.onAnnotationGroupVisibilityChange({ - annotationGroupUID, - isVisible: checked, - }) - }) - } - - render(): React.ReactNode { - const items: MenuProps['items'] = this.props.annotationGroups.map( - (annotationGroup) => { - const uid = annotationGroup.uid - return { - key: uid, - style: { height: '100%', paddingLeft: '3px' }, - label: ( - - ), - } - }, - ) - - return ( - <> -
- 0} - checkedChildren={} - unCheckedChildren={} +/** Annotation groups of bulk simple annotations with clustering options. */ +function AnnotationGroupList({ + annotationGroups, + visibleAnnotationGroupUIDs, + metadata, + defaultAnnotationGroupStyles, + onAnnotationGroupClick, + onAnnotationGroupVisibilityChange, + onBulkAnnotationGroupVisibilityChange, + onAnnotationGroupStyleChange, + displaySettings, + onDisplaySettingsChange, +}: AnnotationGroupListProps): React.ReactElement { + return ( +
+ group.uid)} + visibleUids={visibleAnnotationGroupUIDs} + onChange={onBulkAnnotationGroupVisibilityChange} + /> + {annotationGroups.map((annotationGroup) => ( + + ))} + {displaySettings !== undefined && + onDisplaySettingsChange !== undefined && ( + + onDisplaySettingsChange({ + ...displaySettings, + clusteringThreshold, + }), + }} /> -
- - - ) - } + )} +
+ ) } export default AnnotationGroupList diff --git a/src/components/AnnotationItem.tsx b/src/components/AnnotationItem.tsx index a32a2752..9f14b147 100644 --- a/src/components/AnnotationItem.tsx +++ b/src/components/AnnotationItem.tsx @@ -1,137 +1,96 @@ -import { Menu, Space, Switch } from 'antd' -// skipcq: JS-C1003 -import * as dcmjs from 'dcmjs' -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import type * as dmv from 'dicom-microscopy-viewer' -import React from 'react' -import { FaEye, FaEyeSlash } from 'react-icons/fa' +import type React from 'react' +import { memo, useMemo } from 'react' -import Description from './Description' +import { cn } from '../lib/utils' +import { + describeRoiMeasurement, + describeRoiType, + formatRoiAttributes, + getRoiAttributes, + type LengthUnit, +} from '../utils/roiDescription' +import { VisibilityToggleButton } from './panel/VisibilityToggleButton' -interface AnnotationItemProps { +export interface AnnotationItemProps { roi: dmv.roi.ROI index: number + color: string + isSelected: boolean isVisible: boolean - onVisibilityChange: ({ - roiUID, - isVisible, - }: { - roiUID: string - isVisible: boolean - }) => void + /** Display unit of lengths and areas */ + units: LengthUnit + onSelection: (uid: string) => void + onVisibilityChange: (change: { roiUID: string; isVisible: boolean }) => void } -/** - * React component representing a Region of Interest (ROI) annotation. - */ -class AnnotationItem extends React.Component< - AnnotationItemProps, - Record -> { - constructor(props: AnnotationItemProps) { - super(props) - this.handleVisibilityChange = this.handleVisibilityChange.bind(this) - } +/** One ROI row in the Annotations section. */ +function AnnotationItem({ + roi, + index, + color, + isSelected, + isVisible, + units, + onSelection, + onVisibilityChange, +}: AnnotationItemProps): React.ReactElement { + const label = `ROI ${index + 1}` + const { type, measurement, details } = useMemo( + () => ({ + type: describeRoiType(roi), + measurement: describeRoiMeasurement(roi, { unit: units }), + details: formatRoiAttributes(getRoiAttributes(roi, { unit: units })), + }), + [roi, units], + ) - handleVisibilityChange( - checked: boolean, - _event: React.MouseEvent, - ): void { - this.props.onVisibilityChange({ - roiUID: this.props.roi.uid, - isVisible: checked, - }) - } - - render(): React.ReactNode { - const identifier = `ROI ${this.props.index + 1}` - const attributes: Array<{ name: string; value: string }> = [] - /** - * This hack is required for Menu.Item to work properly: - * https://github.com/react-component/menu/issues/142 - */ - const { isVisible, onVisibilityChange, ...otherProps } = this.props - this.props.roi.evaluations.forEach( - ( - item: - | dcmjs.sr.valueTypes.TextContentItem - | dcmjs.sr.valueTypes.CodeContentItem, - ) => { - const nameValue = item.ConceptNameCodeSequence[0].CodeValue - const nameMeaning = item.ConceptNameCodeSequence[0].CodeMeaning - const name = `${nameMeaning}` - if (item.ValueType === dcmjs.sr.valueTypes.ValueTypes.CODE) { - const codeContentItem = item as dcmjs.sr.valueTypes.CodeContentItem - const valueMeaning = - codeContentItem.ConceptCodeSequence[0].CodeMeaning - // For consistency with Segment and Annotation Group - if (nameValue === '276214006') { - attributes.push({ - name: 'Property category', - value: `${valueMeaning}`, - }) - } else if (nameValue === '121071') { - attributes.push({ - name: 'Property type', - value: `${valueMeaning}`, - }) - } else if (nameValue === '111001') { - attributes.push({ - name: 'Algorithm Name', - value: `${valueMeaning}`, - }) - } else { - attributes.push({ - name, - value: `${valueMeaning}`, - }) - } - } else if (item.ValueType === dcmjs.sr.valueTypes.ValueTypes.TEXT) { - const textContentItem = item as dcmjs.sr.valueTypes.TextContentItem - attributes.push({ - name, - value: textContentItem.TextValue, - }) + return ( +
+ + + onVisibilityChange({ roiUID: roi.uid, isVisible: nextIsVisible }) } - }, - ) - this.props.roi.measurements.forEach((item) => { - const nameMeaning = item.ConceptNameCodeSequence[0].CodeMeaning - const name = `${nameMeaning}` - const seq = item.MeasuredValueSequence[0] - const value = seq.NumericValue.toPrecision(6) - const unit = seq.MeasurementUnitsCodeSequence[0].CodeValue - attributes.push({ - name, - value: `${value} ${unit}`, - }) - }) - return ( - -
- } - unCheckedChildren={} - /> -
- - - -
- ) - } + /> +
+ ) } -export default AnnotationItem +export default memo(AnnotationItem) diff --git a/src/components/AnnotationList.tsx b/src/components/AnnotationList.tsx index 436a4c26..a236b6fd 100644 --- a/src/components/AnnotationList.tsx +++ b/src/components/AnnotationList.tsx @@ -1,96 +1,59 @@ -import { Menu, Switch } from 'antd' -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import type * as dmv from 'dicom-microscopy-viewer' -import React from 'react' -import { FaEye, FaEyeSlash } from 'react-icons/fa' +import type React from 'react' +import { usePreferences } from '../features/preferences' +import type { VisibilityChange } from '../utils/visibility' import AnnotationItem from './AnnotationItem' +import { BulkVisibilityControl } from './slim/BulkVisibilityControl' -interface AnnotationListProps { +export interface AnnotationListProps { rois: dmv.roi.ROI[] selectedRoiUIDs: Set visibleRoiUIDs: Set - onVisibilityChange: ({ - roiUID, - isVisible, - }: { - roiUID: string - isVisible: boolean - }) => void + /** CSS color of the ROI stroke */ + getRoiColor: (roi: dmv.roi.ROI) => string + onVisibilityChange: (change: { roiUID: string; isVisible: boolean }) => void + onBulkVisibilityChange: (changes: VisibilityChange[]) => void onSelection: (uid: string) => void } -/** - * React component representing a list of Region of Interest (ROI) - * annotations. - */ -class AnnotationList extends React.Component< - AnnotationListProps, - Record -> { - constructor(props: AnnotationListProps) { - super(props) - this.handleMenuItemSelection = this.handleMenuItemSelection.bind(this) - this.handleVisibilityChange = this.handleVisibilityChange.bind(this) - } - - handleVisibilityChange( - checked: boolean, - _event: React.MouseEvent, - ): void { - if (checked) { - this.props.rois.forEach((roi) => { - this.props.onVisibilityChange({ roiUID: roi.uid, isVisible: checked }) - }) - } else { - this.props.visibleRoiUIDs.forEach((roiUID) => { - this.props.onVisibilityChange({ roiUID, isVisible: checked }) - }) - } - } - - handleMenuItemSelection(object: { key: string }): void { - this.props.onSelection(object.key) - } - - render(): React.ReactNode { - const items = this.props.rois.map((roi, index) => ( - + roi.uid)} + visibleUids={visibleRoiUIDs} + onChange={onBulkVisibilityChange} /> - )) - - return ( - <> -
- 0} - checkedChildren={} - unCheckedChildren={} - /> -
- - {items} - - - ) - } + {rois.map((roi, index) => ( + + ))} + + ) } export default AnnotationList diff --git a/src/components/AppLoading.tsx b/src/components/AppLoading.tsx index e01787dc..d4b3d1f2 100644 --- a/src/components/AppLoading.tsx +++ b/src/components/AppLoading.tsx @@ -1,4 +1,4 @@ -import { createElement } from 'react' +import { createElement, type JSX } from 'react' interface AppLoadingProps { label?: string diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index e0226d2a..582632d7 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -1,38 +1,14 @@ import type React from 'react' -import MemoryFooter from './MemoryFooter' - -const shellStyle: React.CSSProperties = { - height: '100vh', - display: 'flex', - flexDirection: 'column', - overflow: 'hidden', -} - -const mainStyle: React.CSSProperties = { - flex: '1 1 0%', - minHeight: 0, - overflow: 'hidden', - display: 'flex', - flexDirection: 'column', -} interface AppShellProps { children: React.ReactNode - enableMemoryMonitoring: boolean } -/** - * Column shell: main pane fills leftover height; MemoryFooter stays in normal - * document flow underneath so the map cannot extend under the bar. - */ -const AppShell: React.FC = ({ - children, - enableMemoryMonitoring, -}) => { +/** Full-height column shell: header on top, route content fills the rest. */ +const AppShell: React.FC = ({ children }) => { return ( -
-
{children}
- +
+ {children}
) } diff --git a/src/components/Button.tsx b/src/components/Button.tsx deleted file mode 100644 index 9fdb9d34..00000000 --- a/src/components/Button.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Button as Btn, Divider, Tooltip } from 'antd' -import React from 'react' -import type { IconType } from 'react-icons' - -interface ButtonProps { - icon: IconType | React.ComponentType> - tooltip?: string - label?: string - onClick?: (options: React.SyntheticEvent) => void - isSelected?: boolean -} - -/** - * React component for a button. - */ -class Button extends React.Component> { - constructor(props: ButtonProps) { - super(props) - this.handleClick = this.handleClick.bind(this) - } - - handleClick(event: React.SyntheticEvent): void { - if (this.props.onClick !== undefined) { - this.props.onClick(event) - } - } - - render(): React.ReactNode { - const Icon = this.props.icon - if (Icon === undefined) { - return null - } - - let text: React.ReactNode - if (this.props.label != null) { - text = ( - <> - - {this.props.label} - - ) - } - - let button: React.ReactNode - if (this.props.isSelected ?? false) { - button = ( - } - type="primary" - style={{ lineHeight: '1.0' }} - > - {text} - - ) - } else { - button = ( - } - type="default" - style={{ lineHeight: '1.0' }} - > - {text} - - ) - } - - if (this.props.tooltip !== undefined) { - return {button} - } else { - return button - } - } -} - -export default Button diff --git a/src/components/CaseViewer.tsx b/src/components/CaseViewer.tsx index 152e5f98..38884bc4 100644 --- a/src/components/CaseViewer.tsx +++ b/src/components/CaseViewer.tsx @@ -1,105 +1,63 @@ -import type { MenuProps, ResultProps } from 'antd' -import { Button, Layout, Menu, Result } from 'antd' -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import * as dcmjs from 'dcmjs' -import { type ReactNode, useEffect, useState } from 'react' +import { type JSX, useCallback, useEffect, useState } from 'react' import { Route, Routes, useLocation, useNavigate, useParams, -} from 'react-router-dom' +} from 'react-router' import type { AnnotationSettings } from '../AppConfig' import type { User } from '../auth' +import { useStudySummary } from '../contexts/StudySummaryContext' import type DicomWebManager from '../DicomWebManager' import type { Slide } from '../data/slides' import { StorageClasses } from '../data/uids' +import { ViewerLoadingLayout } from '../features/viewer/components/ViewerLoadingLayout' +import { ViewerMessage } from '../features/viewer/components/ViewerMessage' +import { ViewportLoadingIndicator } from '../features/viewer/components/ViewportLoadingIndicator' +import { STUDY_PANEL_ID } from '../features/viewer/utils/panelIds' +import { + isReferencingInstance, + type ReferencingInstance, + resolveReferencedSlide, +} from '../features/viewer/utils/referencedSlide' +import { + buildSeriesSelectionPath, + defaultSeriesRedirectPath, +} from '../features/viewer/utils/seriesPath' import { useSlides } from '../hooks/useSlides' +import { cn } from '../lib/utils' +import type { AppInfo } from '../utils/appInfo' +import { buildStudySummary } from '../utils/displayFormat' +import { logger } from '../utils/logger' import { findSlideBySeriesInstanceUID, seriesUidFromSlide, } from '../utils/recoverSeriesInstanceUID' import { type RouteComponentProps, withRouter } from '../utils/router' -import { - buildSeriesPath, - hasSeriesInPath, - isProjectsPath, - parseSeriesInstanceUID, - RoutePaths, - withSeriesInProjectPath, -} from '../utils/routes' -import AppLoading from './AppLoading' +import { parseSeriesInstanceUID, RoutePaths } from '../utils/routes' import ClinicalTrial from './ClinicalTrial' import Patient from './Patient' import SlideList from './SlideList' -// skipcq: JS-W1028 - SlideViewer has a default export +/** skipcq: JS-W1028 - SlideViewer has a default export */ import SlideViewer from './SlideViewer' import Study from './Study' +import { + CountBadge, + PanelDivider, + SlimCollapsibleSection, +} from './slim/SlimCollapsibleSection' const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary -interface NaturalizedInstance { - SeriesInstanceUID: string - SOPInstanceUID: string - FrameOfReferenceUID?: string - ContainerIdentifier?: string - ReferencedSeriesSequence?: Array<{ - SeriesInstanceUID: string - }> - ContentSequence?: Array<{ - ConceptNameCodeSequence: Array<{ - CodeValue: string - }> - ContentSequence?: Array<{ - ContentSequence: Array<{ - ReferencedSOPSequence: Array<{ - ReferencedSOPInstanceUID: string - }> - }> - }> - }> -} - const findSeriesSlide = ( slides: Slide[], seriesInstanceUID: string, ): Slide | undefined => findSlideBySeriesInstanceUID(slides, seriesInstanceUID) -/** Explains, in place of the viewer, why there is nothing to display */ -function ViewerMessage({ - status, - title, - subTitle, - extra, -}: { - status: ResultProps['status'] - title: string - subTitle: string - extra?: ReactNode -}): JSX.Element { - return ( -
- -
- ) -} - function ParametrizedSlideViewer({ clients, slides, @@ -107,20 +65,21 @@ function ParametrizedSlideViewer({ app, preload, enableAnnotationTools, + enableMemoryMonitoring, annotations, + isLeftPanelOpen, + onToggleLeftPanel, }: { clients: { [key: string]: DicomWebManager } slides: Slide[] user?: User - app: { - name: string - version: string - uid: string - organization?: string - } + app: AppInfo preload: boolean enableAnnotationTools: boolean + enableMemoryMonitoring: boolean annotations: AnnotationSettings[] + isLeftPanelOpen: boolean + onToggleLeftPanel: () => void }): JSX.Element | null { const { studyInstanceUID = '', seriesInstanceUID = '' } = useParams<{ studyInstanceUID: string @@ -133,7 +92,7 @@ function ParametrizedSlideViewer({ findSeriesSlide(slides, seriesInstanceUID), ) const [derivedDataset, setDerivedDataset] = - useState(null) + useState(null) /** Series from the URL that resolved to no slide of this study */ const [unresolvedSeriesUID, setUnresolvedSeriesUID] = useState( null, @@ -147,108 +106,80 @@ function ParametrizedSlideViewer({ selectedSlide if ( - selectedSlide === null || - selectedSlide === undefined || - !currentSlideMatchesSeries + selectedSlide !== null && + selectedSlide !== undefined && + currentSlideMatchesSeries ) { - const imageSlide = findSeriesSlide(slides, seriesInstanceUID) - if (imageSlide !== null && imageSlide !== undefined) { - const resolvedSeriesUID = seriesUidFromSlide( - imageSlide, - seriesInstanceUID, + return + } + + const imageSlide = findSeriesSlide(slides, seriesInstanceUID) + if (imageSlide !== null && imageSlide !== undefined) { + const resolvedSeriesUID = seriesUidFromSlide( + imageSlide, + seriesInstanceUID, + ) + setSelectedSlide(imageSlide) + setDerivedDataset(null) + setUnresolvedSeriesUID(null) + if (resolvedSeriesUID !== seriesInstanceUID) { + logger.warn( + `Corrected mangled series UID in route: "${seriesInstanceUID}" → "${resolvedSeriesUID}"`, + ) + navigate( + { + pathname: location.pathname.replace( + `/series/${seriesInstanceUID}`, + `/series/${resolvedSeriesUID}`, + ), + search: location.search, + }, + { replace: true }, ) - setSelectedSlide(imageSlide) - setDerivedDataset(null) - setUnresolvedSeriesUID(null) - if (resolvedSeriesUID !== seriesInstanceUID) { - console.warn( - `Corrected mangled series UID in route: "${seriesInstanceUID}" → "${resolvedSeriesUID}"`, - ) - navigate( - { - pathname: location.pathname.replace( - `/series/${seriesInstanceUID}`, - `/series/${resolvedSeriesUID}`, - ), - search: location.search, - }, - { replace: true }, - ) - } - return } + return + } - const findReferencedSlide = async (): Promise => { - try { - const client = clients[StorageClasses.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE] - const derivedSeriesMetadata = await client.retrieveSeriesMetadata({ - studyInstanceUID, - seriesInstanceUID, - }) - const naturalizedDerivedMetadata = naturalizeDataset( - derivedSeriesMetadata[0], - ) as NaturalizedInstance - if ( - naturalizedDerivedMetadata.ReferencedSeriesSequence != null && - naturalizedDerivedMetadata.ReferencedSeriesSequence.length > 0 - ) { - for (const referencedSeries of naturalizedDerivedMetadata.ReferencedSeriesSequence) { - const referencedImageSeriesUID = - referencedSeries.SeriesInstanceUID - const referencedSlide = slides.find((slide: Slide) => { - return slide.seriesInstanceUIDs.some( - (uid: string) => uid === referencedImageSeriesUID, - ) - }) - if (referencedSlide !== null && referencedSlide !== undefined) { - setSelectedSlide(referencedSlide) - setDerivedDataset(naturalizedDerivedMetadata) - setUnresolvedSeriesUID(null) - return - } - } - } - const IMAGE_LIBRARY_CONCEPT_NAME_CODE = '111028' - const imageLibrary = naturalizedDerivedMetadata.ContentSequence?.find( - (contentItem) => - contentItem.ConceptNameCodeSequence[0].CodeValue === - IMAGE_LIBRARY_CONCEPT_NAME_CODE, + /** Set on cleanup so a superseded lookup cannot select a stale slide */ + let isCancelled = false + const findReferencedSlide = async (): Promise => { + try { + const client = clients[StorageClasses.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE] + const derivedSeriesMetadata = await client.retrieveSeriesMetadata({ + studyInstanceUID, + seriesInstanceUID, + }) + if (isCancelled) return + const naturalizedDerivedMetadata = naturalizeDataset( + derivedSeriesMetadata[0], + ) + if (isReferencingInstance(naturalizedDerivedMetadata)) { + const referencedSlide = resolveReferencedSlide( + slides, + naturalizedDerivedMetadata, ) - if ( - imageLibrary?.ContentSequence?.[0]?.ContentSequence?.[0] - ?.ReferencedSOPSequence?.[0] !== undefined && - imageLibrary?.ContentSequence?.[0]?.ContentSequence?.[0] - ?.ReferencedSOPSequence?.[0] !== null - ) { - const referencedSOPInstanceUID = - imageLibrary.ContentSequence[0].ContentSequence[0] - .ReferencedSOPSequence[0].ReferencedSOPInstanceUID - const referencedSlide = slides.find((slide: Slide) => { - return slide.volumeImages.find( - (image: { SOPInstanceUID: string }) => { - return image.SOPInstanceUID === referencedSOPInstanceUID - }, - ) - }) - if (referencedSlide !== undefined) { - setSelectedSlide(referencedSlide) - setDerivedDataset(naturalizedDerivedMetadata) - setUnresolvedSeriesUID(null) - return - } + if (referencedSlide !== undefined) { + setSelectedSlide(referencedSlide) + setDerivedDataset(naturalizedDerivedMetadata) + setUnresolvedSeriesUID(null) + return } - setUnresolvedSeriesUID(seriesInstanceUID) - } catch (error) { - console.warn( - `Failed to resolve referenced slide for series "${seriesInstanceUID}"`, - error, - ) - setUnresolvedSeriesUID(seriesInstanceUID) } + setUnresolvedSeriesUID(seriesInstanceUID) + } catch (error) { + if (isCancelled) return + logger.warn( + `Failed to resolve referenced slide for series "${seriesInstanceUID}"`, + error, + ) + setUnresolvedSeriesUID(seriesInstanceUID) } + } - // skipcq: JS-0098 - void operator intentionally discards the Promise - void findReferencedSlide() + /** skipcq: JS-0098 - void operator intentionally discards the Promise */ + void findReferencedSlide() + return () => { + isCancelled = true } }, [ slides, @@ -273,7 +204,7 @@ function ParametrizedSlideViewer({ + let viewer = ( +
+ +
+ ) if (selectedSlide != null && selectedSlide !== undefined) { const resolvedSeriesInstanceUID = seriesUidFromSlide( selectedSlide, @@ -297,9 +232,12 @@ function ParametrizedSlideViewer({ preload={preload} annotations={annotations} enableAnnotationTools={enableAnnotationTools} + enableMemoryMonitoring={enableMemoryMonitoring} app={app} user={user} derivedDataset={derivedDataset ?? undefined} + isLeftPanelOpen={isLeftPanelOpen} + onToggleLeftPanel={onToggleLeftPanel} /> ) } @@ -309,14 +247,10 @@ function ParametrizedSlideViewer({ interface ViewerProps extends RouteComponentProps { clients: { [key: string]: DicomWebManager } studyInstanceUID: string - app: { - name: string - version: string - uid: string - organization?: string - } + app: AppInfo annotations: AnnotationSettings[] enableAnnotationTools: boolean + enableMemoryMonitoring: boolean preload: boolean user?: User } @@ -327,55 +261,81 @@ function Viewer(props: ViewerProps): JSX.Element | null { clients, studyInstanceUID, }) + const { setSummary } = useStudySummary() + const [isLeftPanelOpen, setIsLeftPanelOpen] = useState(true) + const toggleLeftPanel = useCallback( + () => setIsLeftPanelOpen((isOpen) => !isOpen), + [], + ) + + const summaryImage = slides[0]?.volumeImages[0] + useEffect(() => { + if (summaryImage === undefined) { + setSummary(null) + return + } + setSummary(buildStudySummary(summaryImage)) + }, [summaryImage, setSummary]) + useEffect(() => () => setSummary(null), [setSummary]) const serverUrl = clients[StorageClasses.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE]?.baseURL const serverName = serverUrl != null && serverUrl !== '' ? serverUrl : 'the server' + const defaultSeriesInstanceUID = summaryImage?.SeriesInstanceUID + useEffect(() => { + if (isLoading || error !== null) return + const redirectPath = defaultSeriesRedirectPath({ + studyInstanceUID, + defaultSeriesInstanceUID, + pathname: location.pathname, + search: location.search, + hash: location.hash, + }) + if (redirectPath !== undefined) navigate(redirectPath, { replace: true }) + }, [ + isLoading, + error, + studyInstanceUID, + defaultSeriesInstanceUID, + location.pathname, + location.search, + location.hash, + navigate, + ]) + const handleSeriesSelection = ({ seriesInstanceUID, }: { seriesInstanceUID: string }): void => { - console.info(`switch to series "${seriesInstanceUID}"`) - let urlPath = buildSeriesPath(studyInstanceUID, seriesInstanceUID) - - if (isProjectsPath(location.pathname)) { - urlPath = withSeriesInProjectPath(location.pathname, seriesInstanceUID) - } - - if ( - hasSeriesInPath(location.pathname) && - location.search !== null && - location.search !== undefined - ) { - urlPath += location.search - } - - navigate(urlPath, { replace: true }) + logger.log(`switch to series "${seriesInstanceUID}"`) + navigate( + buildSeriesSelectionPath({ + studyInstanceUID, + seriesInstanceUID, + pathname: location.pathname, + search: location.search, + }), + { replace: true }, + ) } if (isLoading) { - return + return } - const retryButton = ( - - ) - if (error !== null) { return ( ) } @@ -386,18 +346,19 @@ function Viewer(props: ViewerProps): JSX.Element | null { ) } const refImage = volumeInstances[0] - /* If a series is encoded in the path, route the viewer to this series. + /** + * If a series is encoded in the path, route the viewer to this series. * Otherwise select the first series correspondent to * the first slide contained in the study. */ @@ -413,99 +374,74 @@ function Viewer(props: ViewerProps): JSX.Element | null { selectedSeriesInstanceUID = volumeInstances[0].SeriesInstanceUID } - const siderMenuItems: MenuProps['items'] = [ - { - key: 'patient', - label: 'Patient', - children: [ - { - key: 'patient-info', - style: { cursor: 'default', height: 'auto' }, - label: , - }, - ], - }, - { - key: 'study', - label: 'Study', - children: [ - { - key: 'study-info', - style: { cursor: 'default', height: 'auto' }, - label: , - }, - ], - }, - ...(refImage.ClinicalTrialSponsorName != null - ? [ - { - key: 'clinical-trial', - label: 'Clinical Trial', - children: [ - { - key: 'clinical-trial-info', - style: { cursor: 'default', height: 'auto' }, - label: , - }, - ], - }, - ] - : []), - ] - return ( - - + - - - } - /> - - +
+ + + } + /> + +
+
) } diff --git a/src/components/ClinicalTrial.tsx b/src/components/ClinicalTrial.tsx index 28b2f6a2..0f1c40a8 100644 --- a/src/components/ClinicalTrial.tsx +++ b/src/components/ClinicalTrial.tsx @@ -1,53 +1,34 @@ -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import type * as dmv from 'dicom-microscopy-viewer' -import React from 'react' +import type React from 'react' -import Description from './Description' +import { type KeyValueItem, SlimKeyValueGrid } from './slim/SlimKeyValueGrid' interface ClinicalTrialProps { metadata: dmv.metadata.SOPClass } /** - * React component representing a DICOM ClinicalTrial Information Entity that displays - * common study-level attributes of contained DICOM Slide Microscopy images. + * Clinical trial information entity (Clinical Trial Subject and Clinical + * Trial Study modules). */ -class ClinicalTrial extends React.Component { - render(): React.ReactNode { - const attributes = [] - if (this.props.metadata.ClinicalTrialSponsorName != null) { - // Attributes of Clinical Trial Subject module - attributes.push( - ...[ - { - name: 'Sponsor Name', - value: this.props.metadata.ClinicalTrialSponsorName, - }, - { - name: 'Protocol ID', - value: this.props.metadata.ClinicalTrialProtocolID, - }, - { - name: 'Protocol Name', - value: this.props.metadata.ClinicalTrialProtocolName, - }, - { - name: 'Site Name', - value: this.props.metadata.ClinicalTrialSiteName, - }, - ], - ) - } - if (this.props.metadata.ClinicalTrialTimePointID != null) { - // Attributes of Clinical Trial Study module - attributes.push({ - name: 'Time Point ID', - value: this.props.metadata.ClinicalTrialTimePointID, - }) - } - // Attributes of Clinical Trial Subject module - return +function ClinicalTrial({ metadata }: ClinicalTrialProps): React.ReactElement { + const items: KeyValueItem[] = [] + if (metadata.ClinicalTrialSponsorName != null) { + items.push( + { label: 'Sponsor', value: metadata.ClinicalTrialSponsorName }, + { label: 'Protocol ID', value: metadata.ClinicalTrialProtocolID }, + { label: 'Protocol', value: metadata.ClinicalTrialProtocolName }, + { label: 'Site', value: metadata.ClinicalTrialSiteName }, + ) } + if (metadata.ClinicalTrialTimePointID != null) { + items.push({ + label: 'Time point', + value: metadata.ClinicalTrialTimePointID, + }) + } + return } export default ClinicalTrial diff --git a/src/components/ColorSettingsMenu.tsx b/src/components/ColorSettingsMenu.tsx index b95ccd08..b86602b8 100644 --- a/src/components/ColorSettingsMenu.tsx +++ b/src/components/ColorSettingsMenu.tsx @@ -1,170 +1,55 @@ -import { Checkbox, Divider, Row } from 'antd' -import type { CheckboxChangeEvent } from 'antd/es/checkbox' -import React from 'react' +import type React from 'react' + +import type { AnnotationStyle } from '../types/layerStyles' +import { toRGB } from '../utils/color' import ColorSlider from './ColorSlider' import OpacitySlider from './OpacitySlider' -import type { StyleOptions } from './SlideViewer/types' - -interface ColorSettingsMenuProps { - annotationGroupsUIDs: string[] - defaultStyle: { - opacity: number - color: number[] - contourOnly: boolean - } - onStyleChange: (arg: { uid: string; styleOptions: StyleOptions }) => void -} - -interface ColorSettingsMenuState { - currentStyle: { - opacity: number - color?: number[] - contourOnly: boolean - } +import { Switch } from './ui/switch' + +export interface ColorSettingsMenuProps { + style: AnnotationStyle + /** Every change, including while dragging a slider */ + onChange: (style: AnnotationStyle) => void + /** Final value: slider release, keyboard step, typed number or toggle */ + onCommit: (style: AnnotationStyle) => void } -/** - * React component representing an Annotation Group. - */ -class ColorSettingsMenu extends React.Component< - ColorSettingsMenuProps, - ColorSettingsMenuState -> { - constructor(props: ColorSettingsMenuProps) { - super(props) - this.state = { - currentStyle: { - opacity: this.props.defaultStyle.opacity, - color: this.props.defaultStyle.color, - contourOnly: this.props.defaultStyle.contourOnly, - }, - } - } - - handleColorChange = (color: number[]): void => { - this.updateCurrentStyle({ color }) - this.props.annotationGroupsUIDs.forEach((uid) => { - this.props.onStyleChange({ - uid, - styleOptions: { - color, - opacity: - this.state.currentStyle.opacity ?? this.props.defaultStyle.opacity, - contourOnly: this.state.currentStyle.contourOnly, - }, - }) - }) - } - - handleOpacityChange = (opacity: number | null): void => { - if (opacity !== null) { - this.props.annotationGroupsUIDs.forEach((uid) => { - this.props.onStyleChange({ - uid, - styleOptions: { - color: - this.state.currentStyle.color ?? this.props.defaultStyle.color, - opacity, - contourOnly: this.state.currentStyle.contourOnly, - }, - }) - }) - this.updateCurrentStyle({ opacity }) - } - } - - handleShowOutlineOnly = (value: boolean): void => { - this.updateCurrentStyle({ contourOnly: value }) - - this.props.annotationGroupsUIDs.forEach((uid) => { - this.props.onStyleChange({ - uid, - styleOptions: { - color: this.state.currentStyle.color ?? this.props.defaultStyle.color, - opacity: - this.state.currentStyle.opacity ?? this.props.defaultStyle.opacity, - contourOnly: value, - }, - }) - }) - } - - handleShowOutlineOnlyCheckbox = (e: CheckboxChangeEvent): void => { - this.handleShowOutlineOnly(e.target.checked) - } - - getCurrentColor = (): string => { - const rgb2hex = (values: number[]): string => { - const r = values[0] - const g = values[1] - const b = values[2] - return `#${(0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1)}` - } - - if ( - this.state.currentStyle.color !== null && - this.state.currentStyle.color !== undefined - ) { - return rgb2hex(this.state.currentStyle.color) - } else { - return 'white' - } - } - - updateCurrentStyle = ({ - color, - opacity, - contourOnly, - }: { - color?: number[] - opacity?: number - contourOnly?: boolean - }): void => { - this.setState((state) => ({ - currentStyle: { - opacity: opacity ?? state.currentStyle.opacity, - color: color ?? state.currentStyle.color, - contourOnly: contourOnly ?? state.currentStyle.contourOnly, - }, - })) - } - - render(): React.ReactNode { - let colorSettings: React.ReactNode - if ( - this.state.currentStyle.color !== null && - this.state.currentStyle.color !== undefined - ) { - colorSettings = ( - <> - Color +/** Color, opacity and outline settings of a set of annotations. */ +function ColorSettingsMenu({ + style, + onChange, + onCommit, +}: ColorSettingsMenuProps): React.ReactElement { + const color = toRGB(style.color) + + return ( +
+ {color !== undefined && ( +
+ Color onChange({ ...style, color: next })} + onCommit={(next) => onCommit({ ...style, color: next })} /> - - - ) - } - - return ( -
- {colorSettings} - + )} + onChange({ ...style, opacity })} + onCommit={(opacity) => onCommit({ ...style, opacity })} + /> +
+ Outline only + onCommit({ ...style, contourOnly })} + aria-label="Show outline only" /> - - - Show outline only - -
- ) - } +
+ ) } export default ColorSettingsMenu diff --git a/src/components/ColorSlider.tsx b/src/components/ColorSlider.tsx index 72b303ba..1447b2cd 100644 --- a/src/components/ColorSlider.tsx +++ b/src/components/ColorSlider.tsx @@ -1,61 +1,68 @@ -import { Col, InputNumber, Row, Slider } from 'antd' import type React from 'react' -import { useCallback } from 'react' -interface ColorSliderProps { - color: number[] - onChange: (color: number[]) => void -} - -const ColorSlider: React.FC = ({ color, onChange }) => { - const handleColorChange = useCallback( - (index: number, value: number | null): void => { - if (value !== null) { - const newColor = [...color] - newColor[index] = value - onChange(newColor) - } - }, - [color, onChange], - ) +import type { RGB } from '../types/layerStyles' +import { withChannel } from '../utils/color' +import { Input } from './ui/input' +import { Slider } from './ui/slider' - const createChangeHandler = useCallback( - (index: number) => { - return (value: number | null) => handleColorChange(index, value) - }, - [handleColorChange], - ) +export interface ColorSliderProps { + color: RGB + /** Every value change, including while dragging */ + onChange: (color: RGB) => void + /** Final value: slider release, keyboard step or typed number */ + onCommit: (color: RGB) => void +} - const colorLabels = ['Red', 'Green', 'Blue'] +const CHANNELS = [ + { label: 'Red', index: 0 }, + { label: 'Green', index: 1 }, + { label: 'Blue', index: 2 }, +] as const +/** Red, green and blue 0-255 sliders with numeric fields. */ +function ColorSlider({ + color, + onChange, + onCommit, +}: ColorSliderProps): React.ReactElement { return ( - <> - {colorLabels.map((colorLabel, index) => ( - - {colorLabel} - +
+ {CHANNELS.map(({ label, index }) => ( +
+
{label}
+
- - - + onChange(withChannel(color, index, values[0])) + } + onValueCommit={(values) => + onCommit(withChannel(color, index, values[0])) + } + aria-label={label} /> - - +
+ { + const value = Number.parseInt(e.target.value, 10) + if (Number.isNaN(value)) return + const next = withChannel(color, index, value) + onChange(next) + onCommit(next) + }} + /> +
))} - +
) } diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx new file mode 100644 index 00000000..1988b508 --- /dev/null +++ b/src/components/ConfirmDialog.tsx @@ -0,0 +1,119 @@ +import type * as React from 'react' +import { createRoot } from 'react-dom/client' + +import { cn } from '../lib/utils' +import { Button } from './ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogTitle, + SlimDialogFooter, +} from './ui/dialog' +import { Icon } from './ui/icon' + +export interface ConfirmOptions { + title: string + description?: React.ReactNode + confirmLabel?: string + cancelLabel?: string + variant?: 'default' | 'destructive' +} + +export interface ConfirmDialogProps extends ConfirmOptions { + open: boolean + onConfirm: () => void + onCancel: () => void +} + +/** + * A reusable confirmation dialog component. + */ +export function ConfirmDialog({ + open, + title, + description, + confirmLabel = 'Confirm', + cancelLabel = 'Cancel', + onConfirm, + onCancel, + variant = 'default', +}: ConfirmDialogProps): React.ReactElement { + const isDestructive = variant === 'destructive' + return ( + !isOpen && onCancel()}> + +
+ + + +
+ {title} + {description !== undefined && description !== null ? ( + +
{description}
+
+ ) : ( + {title} + )} +
+
+ + + + +
+
+ ) +} + +/** Settles the dialog opened by the last `showConfirmDialog` call, if open */ +let settleOpenDialog: ((confirmed: boolean) => void) | undefined + +/** + * Shows a confirmation dialog outside the React tree and resolves with the + * user's choice. Opening a new dialog cancels (resolves `false`) the one + * still open, so every returned promise settles. + */ +export function showConfirmDialog(options: ConfirmOptions): Promise { + settleOpenDialog?.(false) + return new Promise((resolve) => { + const container = document.createElement('div') + document.body.appendChild(container) + const root = createRoot(container) + let isSettled = false + + const settle = (confirmed: boolean): void => { + if (isSettled) return + isSettled = true + if (settleOpenDialog === settle) settleOpenDialog = undefined + root.unmount() + container.remove() + resolve(confirmed) + } + settleOpenDialog = settle + + root.render( + settle(true)} + onCancel={() => settle(false)} + />, + ) + }) +} diff --git a/src/components/CustomErrorBoundary.tsx b/src/components/CustomErrorBoundary.tsx index 8227321b..f1713304 100644 --- a/src/components/CustomErrorBoundary.tsx +++ b/src/components/CustomErrorBoundary.tsx @@ -1,10 +1,78 @@ -import { Collapse, Modal } from 'antd' -import { useCallback } from 'react' -import { ErrorBoundary, type FallbackProps } from 'react-error-boundary' +import { type ErrorInfo, type JSX, useState } from 'react' +import { + ErrorBoundary, + type FallbackProps, + getErrorMessage, +} from 'react-error-boundary' + +import { logger } from '../utils/logger' +import InfoPage from './InfoPage' +import { Button } from './ui/button' +import { + Dialog, + DialogContent, + SlimDialogFooter, + SlimDialogHeader, +} from './ui/dialog' +import { Icon } from './ui/icon' + +function ErrorFallback({ + error, + context, +}: FallbackProps & { context: string }): JSX.Element { + const [isOpen, setIsOpen] = useState(false) + const stack = error instanceof Error ? error.stack : undefined + + return ( + +
+ + +
+ + + + +
+
+ + + Stack trace + +
+                {stack}
+              
+
+
+ + + +
+
+
+ ) +} /** * React's error boundary component to catch errors during rendering phase - * FallbackComponent is rendered in the event of an error + * The fallback is rendered in the event of an error * * @param context - name of the react component * @param children - the component wrapped inside the Custom Error Boundary @@ -16,86 +84,17 @@ const CustomErrorBoundary = ({ context: string children: JSX.Element }): JSX.Element => { - const { Panel } = Collapse - const ErrorFallback = (error: FallbackProps): JSX.Element => { - const openModal = useCallback((): void => { - Modal.error({ - title: ( - <> -

An unexpected error occured in the {context} component

-

{error.error.message}

- - ), - width: 800, - content: ( - <> - - - {error.error.stack} - - - - ), - onOk: () => { - Modal.destroyAll() - }, - }) - }, [error.error.message, error.error.stack]) - - const handleClick = useCallback((): void => { - openModal() - }, [openModal]) - - const handleKeyDown = useCallback( - (event: React.KeyboardEvent): void => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault() - openModal() - } - }, - [openModal], - ) - - return ( -
-

- There was an error in loading this page.{' '} - {' '} -

-
- ) - } - - const ErrorHandler = ( - error: Error, - info: { - componentStack: string - }, - ): void => { - // Only log errors in development environment - if (process.env.NODE_ENV === 'development') { - console.error('Error caught by boundary:', error, info) + const ErrorHandler = (error: unknown, info: ErrorInfo): void => { + if (import.meta.env.MODE === 'development') { + logger.error('Error caught by boundary:', error, info) } - // In production, you might want to send this to an error reporting service } return ( - + } + > {children} ) diff --git a/src/components/Description.tsx b/src/components/Description.tsx index 99ccbe22..dee105bf 100644 --- a/src/components/Description.tsx +++ b/src/components/Description.tsx @@ -1,85 +1,49 @@ -import { Card, Descriptions } from 'antd' -import React from 'react' -import { v4 as generateUUID } from 'uuid' +import type React from 'react' -export interface Attribute { - name: string - value: React.ReactNode -} - -export interface AttributeGroup { - name: string - attributes: Attribute[] -} +import { cn } from '../lib/utils' +import type { KeyValueItem } from '../utils/keyValue' +import { SlimKeyValueGrid } from './slim/SlimKeyValueGrid' -interface DescriptionProps { +export interface DescriptionProps { header?: React.ReactNode - icon?: React.ComponentType> - attributes: Attribute[] - selectable?: boolean - hasLongValues?: boolean - methods?: React.ReactNode[] + items: KeyValueItem[] children?: React.ReactNode + className?: string } /** - * React component for a description consisting of a header containing a - * header and a body containing a list of name-value pairs. + * Key/value description. With a `header` it renders as a bordered panel card + * (specimens, groups); without, as a bare grid. */ -class Description extends React.Component< - DescriptionProps, - Record -> { - render(): React.ReactNode { - let layout: 'horizontal' | 'vertical' = 'horizontal' - let labelLineHeight = '14px' - const contentLineHeight = '14px' - if (this.props.hasLongValues !== undefined && this.props.hasLongValues) { - layout = 'vertical' - labelLineHeight = '20px' - } - const items = this.props.attributes.map( - (item: Attribute, _index: number) => { - const uid = generateUUID() - return ( - - {item.value} - - ) - }, - ) - let icon = null - if (this.props.icon !== undefined) { - icon = - } +function Description({ + header, + items, + children, + className, +}: DescriptionProps): React.ReactElement { + const grid = + items.length > 0 ? ( + + ) : null + + if (header === undefined) { return ( - - - {items} - - {this.props.children} - +
+ {grid} + {children} +
) } + + return ( +
+
+ {header} +
+ {grid} + {children} +
+ ) } export default Description diff --git a/src/components/DicomTagBrowser/DicomTagBrowser.css b/src/components/DicomTagBrowser/DicomTagBrowser.css deleted file mode 100644 index f6e4676c..00000000 --- a/src/components/DicomTagBrowser/DicomTagBrowser.css +++ /dev/null @@ -1,8 +0,0 @@ -.dicom-tag-browser .ant-table-wrapper { - border: 1px solid #f0f0f0; - border-radius: 2px; -} - -.dicom-tag-browser .ant-table-cell { - word-break: break-word; -} diff --git a/src/components/DicomTagBrowser/DicomTagBrowser.tsx b/src/components/DicomTagBrowser/DicomTagBrowser.tsx index 0e8d546e..ca5978d4 100644 --- a/src/components/DicomTagBrowser/DicomTagBrowser.tsx +++ b/src/components/DicomTagBrowser/DicomTagBrowser.tsx @@ -1,94 +1,179 @@ -import { EyeOutlined, SearchOutlined } from '@ant-design/icons' -import { Input, Select, Slider, Table, Typography } from 'antd' -import { useEffect, useMemo, useState } from 'react' +import { + type JSX, + useCallback, + useEffect, + useId, + useMemo, + useState, +} from 'react' import type DicomWebManager from '../../DicomWebManager' -import './DicomTagBrowser.css' import { useActiveSeries } from '../../hooks/useActiveSeries' import { useDebounce } from '../../hooks/useDebounce' import { useSlides } from '../../hooks/useSlides' +import { cn } from '../../lib/utils' import DicomMetadataStore, { EVENTS, type Series, - type Study, } from '../../services/DICOMMetadataStore' -import { formatDicomDate } from '../../utils/formatDicomDate' -import { logger } from '../../utils/logger' -import { getSortedTags, type TagInfo } from './dicomTagUtils' +import { formatGroupedNumber } from '../../utils/displayFormat' +import { Button } from '../ui/button' +import { SlimDialogFooter } from '../ui/dialog' +import { Icon } from '../ui/icon' +import { SearchInput } from '../ui/search-input' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '../ui/select' +import { Slider } from '../ui/slider' +import { + buildDisplaySets, + buildTagTree, + collectExpandableKeys, + countRows, + filterTagTree, + getInstanceDimensions, + getSeriesLabel, + getSortedTags, + sortInstancesByNumber, + type TagTreeNode, + toggleSetValue, + toTagRecord, +} from './dicomTagUtils' + +export interface DicomTagBrowserProps { + clients: { [key: string]: DicomWebManager } + studyInstanceUID: string + /** Series selected when the browser opens */ + seriesInstanceUID?: string + /** Called by the footer "Done" button */ + onDone?: () => void +} -const { Option } = Select +const TAG_GRID_COLUMNS = 'grid-cols-[150px_52px_minmax(0,1fr)_minmax(0,1.4fr)]' -interface DisplaySet { - displaySetInstanceUID: number - SeriesDate?: string - SeriesTime?: string - SeriesNumber: string - SeriesDescription?: string - SeriesInstanceUID?: string - Modality: string - images: unknown[] -} +const NO_KEYS: ReadonlySet = new Set() -interface TableDataItem { - key: string - tag: string - vr: string - keyword: string - value: string - children?: TableDataItem[] +/** Recursive tag row with expandable sequence items */ +interface TagRowProps { + item: TagTreeNode + depth: number + expandedKeys: ReadonlySet + onToggle: (key: string) => void } -interface DicomTagBrowserProps { - clients: { [key: string]: DicomWebManager } - studyInstanceUID: string - seriesInstanceUID?: string +const TagRow = ({ + item, + depth, + expandedKeys, + onToggle, +}: TagRowProps): JSX.Element => { + const hasChildren = item.children !== undefined && item.children.length > 0 + const isExpanded = expandedKeys.has(item.key) + + const rowClassName = cn( + 'grid min-h-control w-full items-center gap-3 border-b border-line-row px-5 text-left hover:bg-selected', + TAG_GRID_COLUMNS, + depth > 0 ? 'bg-subtle/60' : 'bg-panel', + hasChildren + ? 'cursor-pointer focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40' + : 'cursor-default', + ) + const cells = ( + <> + + + {hasChildren && ( + + )} + + {item.tag} + + + {item.vr !== '' && ( + + {item.vr} + + )} + + {item.keyword} + + {item.value} + + + ) + + return ( + <> + {hasChildren ? ( + + ) : ( +
{cells}
+ )} + {hasChildren && + isExpanded && + item.children?.map((child) => ( + + ))} + + ) } -function bucketContainsSopInstance(bucket: unknown[], sop: string): boolean { - if (sop === '') return false - for (const existing of bucket) { - if ((existing as Record).SOPInstanceUID === sop) { - return true - } - } - return false +function readStudySeries(studyInstanceUID: string): Series[] { + return [...(DicomMetadataStore.getStudy(studyInstanceUID)?.series ?? [])] } const DicomTagBrowser = ({ clients, studyInstanceUID, seriesInstanceUID = '', + onDone, }: DicomTagBrowserProps): JSX.Element => { + const id = useId() + const seriesLabelId = `${id}-series` + const instanceLabelId = `${id}-instance` + const { slides, isLoading } = useSlides({ clients, studyInstanceUID }) const activeSeriesUIDs = useActiveSeries() - const [study, setStudy] = useState(undefined) - - const [displaySets, setDisplaySets] = useState([]) - const [selectedDisplaySetInstanceUID, setSelectedDisplaySetInstanceUID] = - useState(0) + const [studySeries, setStudySeries] = useState(() => + readStudySeries(studyInstanceUID), + ) + const [selectedSeriesUID, setSelectedSeriesUID] = useState(seriesInstanceUID) const [instanceNumber, setInstanceNumber] = useState(1) - const [filterValue, setFilterValue] = useState('') - const [expandedKeys, setExpandedKeys] = useState([]) const [searchInput, setSearchInput] = useState('') + /** Manual expansion, valid only for the filter query it was made under */ + const [expansion, setExpansion] = useState<{ + query: string + keys: ReadonlySet + }>({ query: '', keys: NO_KEYS }) - const debouncedSearchValue = useDebounce(searchInput, 300) - - useEffect(() => { - if (debouncedSearchValue === '') { - setFilterValue('') - setExpandedKeys([]) - } else { - setFilterValue(debouncedSearchValue) - } - }, [debouncedSearchValue]) + const filterQuery = useDebounce(searchInput, 300) useEffect(() => { - const handler = (_event: unknown): void => { - const study: Study | undefined = Object.assign( - {}, - DicomMetadataStore.getStudy(studyInstanceUID), - ) - setStudy(study) + const handler = (): void => { + setStudySeries(readStudySeries(studyInstanceUID)) } const seriesAddedSubscription = DicomMetadataStore.subscribe( EVENTS.SERIES_ADDED, @@ -98,485 +183,241 @@ const DicomTagBrowser = ({ EVENTS.INSTANCES_ADDED, handler, ) - - const study = Object.assign( - {}, - DicomMetadataStore.getStudy(studyInstanceUID), - ) - setStudy(study) - + handler() return () => { seriesAddedSubscription.unsubscribe() instancesAddedSubscription.unsubscribe() } }, [studyInstanceUID]) - useEffect(() => { - let displaySets: DisplaySet[] = [] - let derivedDisplaySets: DisplaySet[] = [] - const processedSeries: string[] = [] - let index = 0 - - if (slides.length > 0) { - displaySets = slides - .flatMap((slide): DisplaySet[] => { - /** One row per SeriesInstanceUID; volume/overview/label often share a series. */ - const imagesBySeries = new Map() - - const addImages = ( - images: unknown[] | undefined, - imageType: string, - ): void => { - if (images?.[0] === undefined) return - logger.debug( - `Found ${images.length} ${imageType} image(s) for slide ${slide.containerIdentifier}`, - ) - for (const image of images) { - const img = image as Record - const seriesUID = img.SeriesInstanceUID as string | undefined - if (seriesUID === undefined || seriesUID === '') continue - - let bucket = imagesBySeries.get(seriesUID) - if (bucket === undefined) { - processedSeries.push(seriesUID) - bucket = [] - imagesBySeries.set(seriesUID, bucket) - } - - const sop = - typeof img.SOPInstanceUID === 'string' ? img.SOPInstanceUID : '' - if (!bucketContainsSopInstance(bucket, sop)) { - bucket.push(image) - } - } - } - - addImages(slide.volumeImages, 'volume') - addImages(slide.overviewImages, 'overview') - addImages(slide.labelImages, 'label') - - const slideDisplaySets: DisplaySet[] = [] - for (const images of imagesBySeries.values()) { - if (images[0] === undefined) continue - const img = images[0] as Record - const { - SeriesDate, - SeriesTime, - SeriesNumber, - SeriesInstanceUID, - SeriesDescription, - Modality, - } = img - slideDisplaySets.push({ - displaySetInstanceUID: index, - SeriesDate: SeriesDate as string | undefined, - SeriesTime: SeriesTime as string | undefined, - SeriesInstanceUID: SeriesInstanceUID as string, - SeriesNumber: String(SeriesNumber), - SeriesDescription: SeriesDescription as string | undefined, - Modality: Modality as string, - images, - }) - index++ - } - return slideDisplaySets - }) - .filter((set): set is DisplaySet => set !== null && set !== undefined) - } - - if (study !== undefined && study.series?.length > 0) { - derivedDisplaySets = study.series - .filter((s) => !processedSeries.includes(s.SeriesInstanceUID)) - .map((series: Series): DisplaySet => { - const ds: DisplaySet = { - displaySetInstanceUID: index, - SeriesDate: series.SeriesDate, - SeriesTime: series.SeriesTime, - SeriesNumber: String(series.SeriesNumber), - SeriesDescription: series.SeriesDescription, - SeriesInstanceUID: series.SeriesInstanceUID, - Modality: series.Modality, - images: series?.instances?.length > 0 ? series.instances : [series], - } - index++ - return ds - }) - } - - setDisplaySets([...displaySets, ...derivedDisplaySets]) - }, [slides, study]) - - const sortedDisplaySets = useMemo(() => { - return [...displaySets].sort((a, b) => { - const aNum = Number(a.SeriesNumber) - const bNum = Number(b.SeriesNumber) - // Normalize non-numeric/missing values to Infinity to sort them last - const aSafe = - Number.isNaN(aNum) || - a.SeriesNumber === undefined || - a.SeriesNumber === '' - ? Infinity - : aNum - const bSafe = - Number.isNaN(bNum) || - b.SeriesNumber === undefined || - b.SeriesNumber === '' - ? Infinity - : bNum - return aSafe - bSafe - }) - }, [displaySets]) - - const displaySetList = useMemo(() => { - return sortedDisplaySets.map((displaySet, index) => { - const { - SeriesDate = '', - SeriesTime = '', - SeriesNumber = '', - SeriesDescription = '', - Modality = '', - SeriesInstanceUID, - } = displaySet - - const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0] - const displayDate = formatDicomDate(dateStr) - - return { - value: index, - label: `${SeriesNumber} (${Modality}): ${SeriesDescription}`, - description: displayDate, - seriesInstanceUID: SeriesInstanceUID ?? '', - } - }) - }, [sortedDisplaySets]) - - useEffect(() => { - if (sortedDisplaySets.length === 0) return - - if (seriesInstanceUID !== '') { - const matchingIndex = sortedDisplaySets.findIndex( - (displaySet) => displaySet.SeriesInstanceUID === seriesInstanceUID, - ) - if (matchingIndex !== -1) { - setSelectedDisplaySetInstanceUID(matchingIndex) - setInstanceNumber(1) - return - } - } - - setSelectedDisplaySetInstanceUID((currentIndex) => { - const needsReset = - currentIndex >= sortedDisplaySets.length || currentIndex < 0 - return needsReset ? 0 : currentIndex - }) - }, [seriesInstanceUID, sortedDisplaySets]) - - useEffect(() => { - const currentIndex = selectedDisplaySetInstanceUID - const needsReset = - currentIndex >= sortedDisplaySets.length || currentIndex < 0 - if (needsReset && sortedDisplaySets.length > 0) { - setInstanceNumber(1) - } - }, [selectedDisplaySetInstanceUID, sortedDisplaySets.length]) - - const showInstanceList = - sortedDisplaySets[selectedDisplaySetInstanceUID]?.images.length > 1 - - const instanceSliderMarks = useMemo(() => { - if (sortedDisplaySets[selectedDisplaySetInstanceUID] === undefined) - return {} - const totalInstances = - sortedDisplaySets[selectedDisplaySetInstanceUID].images.length - - const marks: Record = { - 1: '1', - [Math.ceil(totalInstances / 2)]: String(Math.ceil(totalInstances / 2)), - [totalInstances]: String(totalInstances), - } - - return marks - }, [selectedDisplaySetInstanceUID, sortedDisplaySets]) - - const columns = [ - { - title: 'Tag', - dataIndex: 'tag', - key: 'tag', - width: '30%', - }, - { - title: 'VR', - dataIndex: 'vr', - key: 'vr', - width: '5%', - }, - { - title: 'Keyword', - dataIndex: 'keyword', - key: 'keyword', - width: '30%', - }, - { - title: 'Value', - dataIndex: 'value', - key: 'value', - width: '40%', - }, - ] - - const tableData = useMemo(() => { - const transformTagsToTableData = ( - tags: TagInfo[], - parentKey = '', - ): TableDataItem[] => { - return tags.map((tag, index) => { - // Create a unique key using tag value if available, otherwise use index - const keyBase: string = - tag.tag !== '' ? tag.tag.replace(/[(),]/g, '') : index.toString() - const currentKey: string = - parentKey !== '' ? `${parentKey}-${keyBase}` : keyBase - - const item: TableDataItem = { - key: currentKey, - tag: tag.tag, - vr: tag.vr, - keyword: tag.keyword, - value: tag.value, - } - - if (tag.children !== undefined && tag.children.length > 0) { - item.children = transformTagsToTableData(tag.children, currentKey) - } - - return item - }) - } - - if (sortedDisplaySets[selectedDisplaySetInstanceUID] === undefined) - return [] - const images = sortedDisplaySets[selectedDisplaySetInstanceUID]?.images - const sortedMetadata = Array.isArray(images) - ? [...images].sort((a, b) => { - if ( - a.InstanceNumber !== undefined && - b.InstanceNumber !== undefined - ) { - return Number(a.InstanceNumber) - Number(b.InstanceNumber) - } - return 0 // keep original order if either is missing InstanceNumber - }) - : [] - const metadata = sortedMetadata[instanceNumber - 1] - const tags = getSortedTags(metadata) - return transformTagsToTableData(tags) - }, [instanceNumber, selectedDisplaySetInstanceUID, sortedDisplaySets]) - - const filteredData = useMemo(() => { - if (filterValue === undefined || filterValue === '') return tableData - - const searchLower = filterValue.toLowerCase() - const matchedKeys = new Set() - - const nodeMatches = (node: TableDataItem): boolean => { - return ( - (node.tag?.toLowerCase() ?? '').includes(searchLower) || - (node.vr?.toLowerCase() ?? '').includes(searchLower) || - (node.keyword?.toLowerCase() ?? '').includes(searchLower) || - (node.value?.toString().toLowerCase() ?? '').includes(searchLower) - ) - } - - const findMatchingPaths = ( - node: TableDataItem, - parentPath: TableDataItem[] = [], - ): TableDataItem[][] => { - const currentPath = [...parentPath, node] - let matchingPaths: TableDataItem[][] = [] - - if (nodeMatches(node)) { - matchingPaths.push(currentPath) - } - - node.children?.forEach((child) => { - const childPaths = findMatchingPaths(child, currentPath) - matchingPaths = [...matchingPaths, ...childPaths] - }) - - return matchingPaths - } - - const matchingPaths = tableData.flatMap((node) => findMatchingPaths(node)) - - const reconstructTree = ( - paths: TableDataItem[][], - level = 0, - ): TableDataItem[] => { - if (paths.length === 0 || level >= paths[0].length) return [] - - const nodesAtLevel = new Map< - string, - { - node: TableDataItem - childPaths: TableDataItem[][] - } - >() + const displaySets = useMemo( + () => buildDisplaySets(slides, studySeries), + [slides, studySeries], + ) - paths.forEach((path) => { - if (level < path.length) { - const node = path[level] - if (!nodesAtLevel.has(node.key)) { - nodesAtLevel.set(node.key, { - node: { ...node }, - childPaths: [], - }) - } - if (level + 1 < path.length) { - nodesAtLevel.get(node.key)?.childPaths.push(path) - } - } - }) + /** Falls back to the first series until the selected one is loaded */ + const selectedDisplaySet = + displaySets.find( + (displaySet) => displaySet.SeriesInstanceUID === selectedSeriesUID, + ) ?? displaySets[0] + + const sortedImages = useMemo( + () => + selectedDisplaySet !== undefined + ? sortInstancesByNumber(selectedDisplaySet.images) + : [], + [selectedDisplaySet], + ) + const totalInstances = Math.max(sortedImages.length, 1) + const currentInstanceNumber = Math.min(instanceNumber, totalInstances) + const currentImage = sortedImages[currentInstanceNumber - 1] + const currentMetadata = useMemo( + () => (currentImage !== undefined ? toTagRecord(currentImage) : undefined), + [currentImage], + ) - return Array.from(nodesAtLevel.values()).map(({ node, childPaths }) => { - matchedKeys.add(node.key) - const children = reconstructTree(childPaths, level + 1) - return children.length > 0 ? { ...node, children } : node + const tableData = useMemo( + () => + currentMetadata !== undefined + ? buildTagTree(getSortedTags(currentMetadata)) + : [], + [currentMetadata], + ) + const filterResult = useMemo( + () => filterTagTree(tableData, filterQuery), + [tableData, filterQuery], + ) + const filteredData = filterResult.tree + const isFiltering = filterQuery.trim() !== '' + + const expandedKeys: ReadonlySet = + expansion.query === filterQuery + ? expansion.keys + : isFiltering + ? filterResult.matchedKeys + : NO_KEYS + + const handleToggleExpand = useCallback( + (key: string) => { + setExpansion({ + query: filterQuery, + keys: toggleSetValue(expandedKeys, key), }) - } - - const filtered = reconstructTree(matchingPaths) - setExpandedKeys(Array.from(matchedKeys)) - - return filtered - }, [tableData, filterValue]) + }, + [filterQuery, expandedKeys], + ) - if (isLoading) { - return
Loading...
- } + const dimensions = getInstanceDimensions(currentMetadata) + const instanceLabel = + dimensions?.columns !== undefined && dimensions.rows !== undefined + ? `${formatGroupedNumber(dimensions.columns)} × ${formatGroupedNumber(dimensions.rows)} px` + : '' + const selectedModality = selectedDisplaySet?.Modality ?? '' + const hasMultipleInstances = sortedImages.length > 1 return ( -
-
-
-
- - Series - - { + setSelectedSeriesUID(value) + setInstanceNumber(1) + }} + disabled={isLoading || displaySets.length === 0} + > + + + {selectedModality !== '' && ( + + {selectedModality} + + )} + + + + + + + {displaySets.map((displaySet) => { + const { label, description } = getSeriesLabel(displaySet) + const isActive = activeSeriesUIDs.has( + displaySet.SeriesInstanceUID, + ) return ( - + {isActive && ( + <> + + (active in viewport) + + )} + + ) })} - + + +
+ +
+ + Instance + + {instanceLabel} + + +
+ { + const [value] = values + if (value !== undefined) setInstanceNumber(value) + }} + className="min-w-[80px] flex-1" + /> + + {currentInstanceNumber} / {totalInstances} +
+
- {showInstanceList && ( -
- - Instance Number: {instanceNumber} - - setInstanceNumber(value)} - marks={instanceSliderMarks} - tooltip={{ - formatter: (value: number | undefined) => - value !== undefined ? `Instance ${value}` : '', - }} - /> -
- )} +
+ +
+
- } - onChange={(e) => setSearchInput(e.target.value)} - value={searchInput} - /> +
+
Tag
+
VR
+
Keyword
+
Value
+
- setExpandedKeys(keys as string[]), - }} - size="small" - scroll={{ y: 500 }} - /> +
+ {isLoading ? ( +
Loading…
+ ) : filteredData.length === 0 ? ( +
+ {isFiltering ? 'No matching tags found' : 'No tags available'} +
+ ) : ( + filteredData.map((item) => ( + + )) + )}
+ + + {countRows(filteredData, expandedKeys)} attributes + + + + {onDone !== undefined && ( + + )} + ) } diff --git a/src/components/DicomTagBrowser/__tests__/dicomTagUtils.test.ts b/src/components/DicomTagBrowser/__tests__/dicomTagUtils.test.ts new file mode 100644 index 00000000..58050d06 --- /dev/null +++ b/src/components/DicomTagBrowser/__tests__/dicomTagUtils.test.ts @@ -0,0 +1,322 @@ +import { + buildDisplaySets, + buildTagTree, + collectExpandableKeys, + countRows, + filterTagTree, + getInstanceDimensions, + getSeriesLabel, + sortInstancesByNumber, + sortSeriesByNumber, + type TagInfo, + type TagTreeNode, + toggleSetValue, + toTagRecord, +} from '../dicomTagUtils' + +const tags: TagInfo[] = [ + { + tag: '(0008,0060)', + vr: 'CS', + keyword: 'Modality', + value: 'SM', + level: 0, + }, + { + tag: '(0040,0560)', + vr: 'SQ', + keyword: 'SpecimenDescriptionSequence', + value: 'Sequence with 1 item(s)', + level: 0, + children: [ + { + tag: '(0040,0560).1', + vr: 'Item', + keyword: 'Item 1', + value: 'Sequence Item 1', + level: 1, + children: [ + { + tag: '(0040,0551)', + vr: 'LO', + keyword: 'SpecimenIdentifier', + value: 'S-42', + level: 2, + }, + { + tag: '(0040,0554)', + vr: 'UI', + keyword: 'SpecimenUID', + value: '1.2.3', + level: 2, + }, + ], + }, + ], + }, +] + +describe('buildTagTree', () => { + it('derives keys from the tag path', () => { + const tree = buildTagTree(tags) + expect(tree[0].key).toBe('00080060') + expect(tree[1].children?.[0].key).toBe('00400560-00400560.1') + expect(tree[1].children?.[0].children?.[0].key).toBe( + '00400560-00400560.1-00400551', + ) + }) + + it('falls back to the index for empty tags', () => { + expect( + buildTagTree([{ tag: '', vr: '', keyword: 'x', value: '', level: 0 }])[0] + .key, + ).toBe('0') + }) +}) + +describe('filterTagTree', () => { + const tree = buildTagTree(tags) + + it('returns the tree unchanged for an empty query', () => { + const result = filterTagTree(tree, ' ') + expect(result.tree).toBe(tree) + expect(result.matchedKeys.size).toBe(0) + }) + + it('keeps ancestors of nested matches and reports them as expanded', () => { + const { tree: filtered, matchedKeys } = filterTagTree(tree, 's-42') + expect(filtered).toHaveLength(1) + expect(filtered[0].keyword).toBe('SpecimenDescriptionSequence') + const item = filtered[0].children?.[0] + expect(item?.children?.map((node) => node.keyword)).toEqual([ + 'SpecimenIdentifier', + ]) + expect([...matchedKeys]).toEqual([ + '00400560-00400560.1-00400551', + '00400560-00400560.1', + '00400560', + ]) + }) + + it('keeps all children of a matching sequence', () => { + const { tree: filtered } = filterTagTree(tree, 'specimendescription') + expect(filtered[0].children?.[0].children).toHaveLength(2) + }) + + it('matches on VR and returns nothing when nothing matches', () => { + expect(filterTagTree(tree, 'cs').tree.map((node) => node.keyword)).toEqual([ + 'Modality', + ]) + expect(filterTagTree(tree, 'zzz').tree).toEqual([]) + }) +}) + +describe('collectExpandableKeys / countRows', () => { + const tree: TagTreeNode[] = buildTagTree(tags) + + it('lists every node with children', () => { + expect(collectExpandableKeys(tree)).toEqual([ + '00400560', + '00400560-00400560.1', + ]) + }) + + it('counts rows for the expanded state', () => { + expect(countRows(tree, new Set())).toBe(2) + expect(countRows(tree, new Set(['00400560']))).toBe(3) + expect(countRows(tree, new Set(collectExpandableKeys(tree)))).toBe(5) + }) +}) + +describe('sortInstancesByNumber', () => { + it('sorts numerically and puts missing numbers last', () => { + const sorted = sortInstancesByNumber([ + { id: 'c', InstanceNumber: 10 }, + { id: 'x' }, + { id: 'a', InstanceNumber: '2' }, + { id: 'b', InstanceNumber: 3 }, + ]) + expect(sorted.map((item) => item.id)).toEqual(['a', 'b', 'c', 'x']) + }) + + it('does not mutate the input', () => { + const input = [{ InstanceNumber: 2 }, { InstanceNumber: 1 }] + sortInstancesByNumber(input) + expect(input[0].InstanceNumber).toBe(2) + }) +}) + +describe('getInstanceDimensions', () => { + it('prefers the total pixel matrix', () => { + expect( + getInstanceDimensions({ + TotalPixelMatrixColumns: 98304, + TotalPixelMatrixRows: '65536', + Columns: 256, + Rows: 256, + }), + ).toEqual({ columns: 98304, rows: 65536 }) + }) + + it('falls back to Rows/Columns and drops invalid values', () => { + expect(getInstanceDimensions({ Columns: 512 })).toEqual({ + columns: 512, + rows: undefined, + }) + expect(getInstanceDimensions(undefined)).toBeUndefined() + }) +}) + +describe('sortSeriesByNumber / getSeriesLabel', () => { + it('sorts series numerically with blanks last', () => { + expect( + sortSeriesByNumber([ + { SeriesNumber: '' }, + { SeriesNumber: '10' }, + { SeriesNumber: 'abc' }, + { SeriesNumber: '2' }, + ]).map((series) => series.SeriesNumber), + ).toEqual(['2', '10', '', 'abc']) + }) + + it('builds the series label and date description', () => { + const { label, description } = getSeriesLabel({ + SeriesNumber: '3', + Modality: 'SM', + SeriesDescription: 'Slide 1', + SeriesDate: '20240115', + SeriesTime: '101500.123', + }) + expect(label).toBe('3 (SM): Slide 1') + expect(description).toBe('15 Jan 2024, 10:15') + }) + + it('shows the date alone without a series time', () => { + expect(getSeriesLabel({ SeriesDate: '20240115' }).description).toBe( + '15 Jan 2024', + ) + expect(getSeriesLabel({}).description).toBe('') + }) +}) + +describe('buildDisplaySets', () => { + const image = ( + series: string, + sop: string, + extra: Record = {}, + ): Record => ({ + SeriesInstanceUID: series, + SOPInstanceUID: sop, + ...extra, + }) + + it('returns nothing without slides or series', () => { + expect(buildDisplaySets([])).toEqual([]) + }) + + it('merges the images of one series across image types and slides', () => { + const volume = image('s1', 'a', { + SeriesNumber: 2, + Modality: 'SM', + SeriesDescription: 'Slide', + SeriesDate: '20240115', + }) + const label = image('s1', 'b') + const sets = buildDisplaySets([ + { volumeImages: [volume], labelImages: [label] }, + { overviewImages: [image('s1', 'c')] }, + ]) + expect(sets).toHaveLength(1) + expect(sets[0]).toMatchObject({ + SeriesInstanceUID: 's1', + SeriesNumber: '2', + Modality: 'SM', + SeriesDescription: 'Slide', + SeriesDate: '20240115', + }) + expect(sets[0].images).toHaveLength(3) + expect(sets[0].images[0]).toBe(volume) + }) + + it('drops duplicate SOP instances but keeps images without a SOP UID', () => { + const sets = buildDisplaySets([ + { + volumeImages: [image('s1', 'a'), image('s1', 'a')], + labelImages: [image('s1', ''), image('s1', '')], + }, + ]) + expect(sets[0].images).toHaveLength(3) + }) + + it('skips images without a series UID', () => { + expect( + buildDisplaySets([{ volumeImages: [{ SOPInstanceUID: 'a' }] }]), + ).toEqual([]) + }) + + it('adds study series that have no slide images', () => { + const instance = { SOPInstanceUID: 'sr-1' } + const annotation = { + SeriesInstanceUID: 'ann', + SeriesNumber: 3, + Modality: 'ANN', + instances: [], + } + const sets = buildDisplaySets( + [{ volumeImages: [image('s1', 'a', { SeriesNumber: '1' })] }], + [ + { SeriesInstanceUID: 's1', SeriesNumber: 1, instances: [] }, + { + SeriesInstanceUID: 'sr', + SeriesNumber: 2, + Modality: 'SR', + instances: [instance], + }, + annotation, + { SeriesInstanceUID: '' }, + ], + ) + expect(sets.map((set) => set.SeriesInstanceUID)).toEqual([ + 's1', + 'sr', + 'ann', + ]) + expect(sets[1].images).toEqual([instance]) + expect(sets[2].images).toEqual([annotation]) + expect(sets[2].SeriesNumber).toBe('3') + }) + + it('sorts by series number with missing numbers last', () => { + const sets = buildDisplaySets( + [{ volumeImages: [image('late', 'a', { SeriesNumber: 10 })] }], + [ + { SeriesInstanceUID: 'none' }, + { SeriesInstanceUID: 'early', SeriesNumber: 1 }, + ], + ) + expect(sets.map((set) => set.SeriesInstanceUID)).toEqual([ + 'early', + 'late', + 'none', + ]) + }) +}) + +describe('toTagRecord / toggleSetValue', () => { + it('copies own enumerable fields', () => { + class Dataset { + Modality = 'SM' + describe(): string { + return 'not a field' + } + } + expect(toTagRecord(new Dataset())).toEqual({ Modality: 'SM' }) + }) + + it('adds and removes values without mutating the input', () => { + const input = new Set(['a']) + expect([...toggleSetValue(input, 'b')]).toEqual(['a', 'b']) + expect([...toggleSetValue(input, 'a')]).toEqual([]) + expect([...input]).toEqual(['a']) + }) +}) diff --git a/src/components/DicomTagBrowser/dicomTagUtils.ts b/src/components/DicomTagBrowser/dicomTagUtils.ts index d9fa3f7f..4fa1f3a2 100644 --- a/src/components/DicomTagBrowser/dicomTagUtils.ts +++ b/src/components/DicomTagBrowser/dicomTagUtils.ts @@ -1,5 +1,7 @@ import dcmjs from 'dcmjs' +import { formatStudyDateTime } from '../../utils/displayFormat' + const { DicomMetaDictionary } = dcmjs.data type DictionaryEntry = { @@ -25,13 +27,6 @@ export interface TagInfo { level: number } -export interface DicomTag { - name: string - vr: string - Value?: unknown[] - [key: string]: unknown -} - const PERSON_NAME_GROUP_KEYS = [ 'Alphabetic', 'Ideographic', @@ -130,16 +125,6 @@ function formatValue(val: unknown, vr?: string): string { return stringifyJsonScalar(val) } -export const formatTagValue = (tag: DicomTag): string => { - if (tag.Value === undefined || tag.Value === null) return '' - - if (Array.isArray(tag.Value)) { - return tag.Value.map((v) => formatValue(v, tag.vr)).join(', ') - } - - return formatValue(tag.Value, tag.vr) -} - /** Normalize to "(GGGG,EEEE)" for dcmjs dictionary lookup. */ function punctuateTagId(keyword: string): string | null { const eightHex = /^[0-9A-Fa-f]{8}$/ @@ -337,16 +322,8 @@ function processMetadataKeyword( return rowsForUnmappedKeyword(keyword, vrHint, value, depth) } -/** - * Processes DICOM metadata and returns a flattened array of tag information - * @param metadata - The DICOM metadata object to process - * @param depth - The current depth level for nested sequences (default: 0) - * @returns Array of processed tag information - */ -export function getRows( - metadata: Record, - depth = 0, -): TagInfo[] { +/** Tag rows of a dataset; sequences nest their items as children. */ +function getRows(metadata: Record, depth = 0): TagInfo[] { if (metadata === undefined || metadata === null) return [] const keywords = Object.keys(metadata).filter((key) => key !== '_vrMap') @@ -363,7 +340,6 @@ export function getRows( export function getSortedTags(metadata: Record): TagInfo[] { const tagList = getRows(metadata) - // Add bulkdataReferences as a special tag if it exists if ( metadata.bulkdataReferences !== undefined && metadata.bulkdataReferences !== null @@ -388,3 +364,279 @@ export function getSortedTags(metadata: Record): TagInfo[] { return tagList.sort((a, b) => a.tag.localeCompare(b.tag)) } + +export interface TagTreeNode { + key: string + tag: string + vr: string + keyword: string + value: string + children?: TagTreeNode[] +} + +/** Tag rows with keys unique along the sequence path, e.g. "00400560-1-0040A043" */ +export function buildTagTree(tags: TagInfo[], parentKey = ''): TagTreeNode[] { + return tags.map((tag, index) => { + const keyBase = tag.tag !== '' ? tag.tag.replace(/[(),]/g, '') : `${index}` + const key = parentKey !== '' ? `${parentKey}-${keyBase}` : keyBase + const node: TagTreeNode = { + key, + tag: tag.tag, + vr: tag.vr, + keyword: tag.keyword, + value: tag.value, + } + if (tag.children !== undefined && tag.children.length > 0) { + node.children = buildTagTree(tag.children, key) + } + return node + }) +} + +function nodeMatches(node: TagTreeNode, query: string): boolean { + return [node.tag, node.vr, node.keyword, node.value].some((field) => + (field ?? '').toString().toLowerCase().includes(query), + ) +} + +/** + * Keeps matching nodes and their ancestors. A matching node without matching + * descendants keeps all of its children. `matchedKeys` lists every kept + * ancestor/match so callers can expand the paths to the matches. + */ +export function filterTagTree( + data: TagTreeNode[], + query: string, +): { tree: TagTreeNode[]; matchedKeys: Set } { + const matchedKeys = new Set() + const normalized = query.trim().toLowerCase() + if (normalized === '') return { tree: data, matchedKeys } + + const filterNodes = (nodes: TagTreeNode[]): TagTreeNode[] => + nodes.flatMap((node): TagTreeNode[] => { + const children = + node.children !== undefined ? filterNodes(node.children) : [] + if (children.length > 0) { + matchedKeys.add(node.key) + return [{ ...node, children }] + } + if (nodeMatches(node, normalized)) { + matchedKeys.add(node.key) + return [node] + } + return [] + }) + + return { tree: filterNodes(data), matchedKeys } +} + +export function collectExpandableKeys(items: TagTreeNode[]): string[] { + return items.flatMap((item) => + item.children !== undefined && item.children.length > 0 + ? [item.key, ...collectExpandableKeys(item.children)] + : [], + ) +} + +/** Number of rendered rows given the expanded sequence keys */ +export function countRows( + items: TagTreeNode[], + expandedKeys: ReadonlySet, +): number { + return items.reduce( + (total, item) => + total + + 1 + + (item.children !== undefined && expandedKeys.has(item.key) + ? countRows(item.children, expandedKeys) + : 0), + 0, + ) +} + +function toSortableNumber(value: unknown): number | undefined { + if (value === undefined || value === null || value === '') return undefined + const numeric = Number(value) + return Number.isNaN(numeric) ? undefined : numeric +} + +/** Ascending numeric order; missing or non-numeric values sort last */ +function compareOptionalNumbers(a: unknown, b: unknown): number { + const left = toSortableNumber(a) + const right = toSortableNumber(b) + if (left === undefined && right === undefined) return 0 + if (left === undefined) return 1 + if (right === undefined) return -1 + return left - right +} + +export function sortInstancesByNumber( + images: readonly T[], +): T[] { + return [...images].sort((a, b) => + compareOptionalNumbers( + Reflect.get(a, 'InstanceNumber'), + Reflect.get(b, 'InstanceNumber'), + ), + ) +} + +/** Total pixel matrix size for WSI instances, else Rows/Columns */ +export function getInstanceDimensions( + metadata: Record | undefined, +): { columns?: number; rows?: number } | undefined { + if (metadata === undefined || metadata === null) return undefined + const columns = Number( + metadata.TotalPixelMatrixColumns ?? metadata.Columns ?? Number.NaN, + ) + const rows = Number( + metadata.TotalPixelMatrixRows ?? metadata.Rows ?? Number.NaN, + ) + return { + columns: Number.isFinite(columns) ? columns : undefined, + rows: Number.isFinite(rows) ? rows : undefined, + } +} + +export interface SeriesLike { + SeriesNumber?: string + SeriesDate?: string + SeriesTime?: string + SeriesDescription?: string + Modality?: string +} + +export function sortSeriesByNumber( + series: readonly T[], +): T[] { + return [...series].sort((a, b) => + compareOptionalNumbers(a.SeriesNumber, b.SeriesNumber), + ) +} + +/** "3 (SM): Slide 1" and the series date/time, e.g. "15 Jan 2024, 10:15" */ +export function getSeriesLabel(series: SeriesLike): { + label: string + description: string +} { + const { SeriesNumber = '', SeriesDescription = '', Modality = '' } = series + return { + label: `${SeriesNumber} (${Modality}): ${SeriesDescription}`, + description: formatStudyDateTime(series.SeriesDate, series.SeriesTime), + } +} + +/** Own enumerable fields of a dataset object, for the tag table */ +export function toTagRecord(dataset: object): Record { + return Object.fromEntries(Object.entries(dataset)) +} + +function readText(source: object, key: string): string | undefined { + const value: unknown = Reflect.get(source, key) + if (typeof value === 'string') return value + if (typeof value === 'number') return String(value) + return undefined +} + +/** One selectable series of the tag browser and its instances */ +export interface DisplaySet extends SeriesLike { + SeriesInstanceUID: string + images: readonly object[] +} + +export interface SlideImagesLike { + volumeImages?: readonly object[] + overviewImages?: readonly object[] + labelImages?: readonly object[] +} + +export interface StudySeriesLike { + SeriesInstanceUID: string + SeriesNumber?: number | string + SeriesDate?: string + SeriesTime?: string + SeriesDescription?: string + Modality?: string + instances?: readonly object[] +} + +/** + * One display set per SeriesInstanceUID, sorted by series number. Slide + * images come first (volume, overview and label images often share a series; + * duplicate SOP instances are dropped); study series without slide images + * are added after, using the series itself when it has no instances. + */ +export function buildDisplaySets( + slides: readonly SlideImagesLike[], + studySeries: readonly StudySeriesLike[] = [], +): DisplaySet[] { + const imagesBySeries = new Map< + string, + { images: object[]; sopInstanceUIDs: Set } + >() + for (const slide of slides) { + for (const images of [ + slide.volumeImages, + slide.overviewImages, + slide.labelImages, + ]) { + for (const image of images ?? []) { + const seriesUID = readText(image, 'SeriesInstanceUID') ?? '' + if (seriesUID === '') continue + let entry = imagesBySeries.get(seriesUID) + if (entry === undefined) { + entry = { images: [], sopInstanceUIDs: new Set() } + imagesBySeries.set(seriesUID, entry) + } + const sopUID = readText(image, 'SOPInstanceUID') ?? '' + if (sopUID !== '') { + if (entry.sopInstanceUIDs.has(sopUID)) continue + entry.sopInstanceUIDs.add(sopUID) + } + entry.images.push(image) + } + } + } + + const displaySets: DisplaySet[] = [] + for (const [seriesUID, { images }] of imagesBySeries) { + const first = images[0] + displaySets.push({ + SeriesInstanceUID: seriesUID, + SeriesNumber: readText(first, 'SeriesNumber'), + SeriesDate: readText(first, 'SeriesDate'), + SeriesTime: readText(first, 'SeriesTime'), + SeriesDescription: readText(first, 'SeriesDescription'), + Modality: readText(first, 'Modality'), + images, + }) + } + for (const series of studySeries) { + const seriesUID = series.SeriesInstanceUID + if (seriesUID === '' || imagesBySeries.has(seriesUID)) continue + displaySets.push({ + SeriesInstanceUID: seriesUID, + SeriesNumber: + series.SeriesNumber !== undefined + ? String(series.SeriesNumber) + : undefined, + SeriesDate: series.SeriesDate, + SeriesTime: series.SeriesTime, + SeriesDescription: series.SeriesDescription, + Modality: series.Modality, + images: + series.instances !== undefined && series.instances.length > 0 + ? series.instances + : [series], + }) + } + return sortSeriesByNumber(displaySets) +} + +/** Copy of `set` with `value` added, or removed if present */ +export function toggleSetValue(set: ReadonlySet, value: T): Set { + const next = new Set(set) + if (next.has(value)) next.delete(value) + else next.add(value) + return next +} diff --git a/src/components/Equipment.tsx b/src/components/Equipment.tsx index fb344211..9d5ec4e0 100644 --- a/src/components/Equipment.tsx +++ b/src/components/Equipment.tsx @@ -1,47 +1,29 @@ -// skipcq: JS-C1003 +/** skipcq: JS-C1003 */ import type * as dmv from 'dicom-microscopy-viewer' -import React from 'react' +import type React from 'react' -import Description from './Description' +import { formatMultiValue } from '../utils/displayFormat' +import { type KeyValueItem, SlimKeyValueGrid } from './slim/SlimKeyValueGrid' interface EquipmentProps { metadata?: dmv.metadata.VLWholeSlideMicroscopyImage } -/** - * React component representing a list of DICOM Equipment Entities. - */ -class Equipment extends React.Component> { - render(): React.ReactNode { - if (this.props.metadata === undefined) { - return null - } - const attributes = [ - { - name: 'Manufacturer', - value: this.props.metadata.Manufacturer, - }, - { - name: 'Model Name', - value: this.props.metadata.ManufacturerModelName, - }, - { - name: 'Device Serial Number', - value: this.props.metadata.DeviceSerialNumber, - }, - { - name: 'Software Versions', - value: this.props.metadata.SoftwareVersions, - }, - ] - if (this.props.metadata.InstitutionName != null) { - attributes.push({ - name: 'Institution Name', - value: this.props.metadata.InstitutionName, - }) - } - return +/** General Equipment module of the slide's volume image. */ +function Equipment({ metadata }: EquipmentProps): React.ReactElement | null { + if (metadata === undefined) { + return null } + const items: KeyValueItem[] = [ + { label: 'Manufacturer', value: metadata.Manufacturer }, + { label: 'Model', value: metadata.ManufacturerModelName }, + { label: 'Serial #', value: metadata.DeviceSerialNumber }, + { label: 'Software', value: formatMultiValue(metadata.SoftwareVersions) }, + ] + if (metadata.InstitutionName != null) { + items.push({ label: 'Institution', value: metadata.InstitutionName }) + } + return } export default Equipment diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx deleted file mode 100644 index 80458860..00000000 --- a/src/components/Footer.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Layout } from 'antd' -import React from 'react' - -interface FooterProps { - app: { - name: string - version: string - uid: string - organization?: string - } -} - -/** - * React component for the application footer. - */ -class Footer extends React.Component> { - render(): React.ReactNode { - return ( - - {this.props.app.organization} - - ) - } -} - -export default Footer diff --git a/src/components/Header.tsx b/src/components/Header.tsx deleted file mode 100644 index 675e7e5d..00000000 --- a/src/components/Header.tsx +++ /dev/null @@ -1,995 +0,0 @@ -import { - ApiOutlined, - BugOutlined, - CheckOutlined, - FileSearchOutlined, - InfoCircleOutlined, - InfoOutlined, - StopOutlined, - UnorderedListOutlined, - UserOutlined, -} from '@ant-design/icons' -import { - Col, - Collapse, - Dropdown, - Input, - Layout, - Modal, - Radio, - Row, - Space, - Tooltip, - Typography, -} from 'antd' -import type { RadioChangeEvent } from 'antd/es/radio' -import { detect } from 'detect-browser' -import React from 'react' -import { NavLink } from 'react-router-dom' -import { v4 as uuidv4 } from 'uuid' -import appPackageJson from '../../package.json' -import type { OidcSettings } from '../AppConfig' -import type { User } from '../auth' -import { - cacheOidcConfigInput, - getOidcConfigToApply, - isValidOidcConfig, - readCachedOidcConfigInput, -} from '../auth/oidcConfig' -import { SettingsButton } from '../contexts/SettingsContext' -import type DicomWebManager from '../DicomWebManager' -import NotificationMiddleware, { - NotificationMiddlewareEvents, -} from '../services/NotificationMiddleware' -import type { CustomError } from '../utils/CustomError' -import { type RouteComponentProps, withRouter } from '../utils/router' -import { - isGcpDicomStorePath, - isViewerPath, - parseSeriesInstanceUID, -} from '../utils/routes' -import { normalizeServerUrl } from '../utils/url' -import Button from './Button' -import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser' - -const { TextArea } = Input - -const aboutModalCopyTooltips: [React.ReactNode, React.ReactNode] = [ - 'Copy hash', - 'Copied!', -] - -const aboutModalStyles: Record = { - container: { - textAlign: 'center', - lineHeight: 1.6, - paddingRight: 25, - fontSize: '1rem', - }, - title: { - fontSize: '1.4rem', - fontWeight: 700, - marginBottom: 4, - }, - subtitle: { - fontSize: '1.15rem', - fontWeight: 600, - marginBottom: 12, - }, - link: { - display: 'inline-block', - marginBottom: 16, - }, - section: { - marginBottom: 12, - }, - label: { - fontWeight: 600, - display: 'block', - marginBottom: 4, - }, - bodyText: { - marginBottom: 4, - }, - code: { - display: 'inline-block', - wordBreak: 'break-all', - fontSize: '0.85rem', - }, -} - -/** - * Static count pill that avoids antd Badge → rc-motion `findDOMNode` - * (deprecated under React Strict Mode). - * - * Layout/CSS mirrors antd Badge (compact): wrapper `line-height: 1` so the - * header's 64px line-height cannot inflate the positioning context, and the - * count uses `top/right: 0` + `translate(50%, -50%)` to sit on the corner. - * Measured repro: without `line-height: 1`, a `top: -4` pill pins to y=0 and - * AppShell `overflow: hidden` crops it. - */ -function HeaderCountBadge({ - count, - color = '#ff4d4f', - zIndex, - /** Same meaning as antd Badge `offset`: [offsetX, offsetY] in px. */ - offset = [0, 0], - children, -}: { - count: number - color?: string - zIndex?: number - offset?: [number, number] - children?: React.ReactNode -}): JSX.Element { - const [offsetX, offsetY] = offset - const pill = - count > 0 ? ( - - {count > 99 ? '99+' : count} - - ) : null - - if (children == null) { - return <>{pill} - } - - return ( - - {children} - {pill} - - ) -} - -interface HeaderProps extends RouteComponentProps { - app: { - name: string - version: string - homepage: string - uid: string - organization?: string - } - user?: User - clients?: { [key: string]: DicomWebManager } - defaultClients?: { [key: string]: DicomWebManager } - showWorklistButton: boolean - onServerSelection: ({ - url, - oidc, - }: { - url: string - /** New settings, null to fall back to the deployment config */ - oidc?: OidcSettings | null - }) => void - onUserLogout?: () => void - showServerSelectionButton: boolean -} - -interface ExtendedCustomError extends CustomError { - source: string -} - -interface HeaderState { - selectedServerUrl?: string - isServerSelectionModalVisible: boolean - isServerSelectionDisabled: boolean - errorObj: ExtendedCustomError[] - errorCategory: string[] - warnings: string[] - serverSelectionMode: 'default' | 'custom' - /** False only when both custom logo.svg and favicon.ico fail. */ - showLogo: boolean - logoUrl: string - /** Optional OIDC config JSON string entered by user */ - oidcConfigInput: string - /** Whether the OIDC config JSON is valid */ - isOidcConfigValid: boolean -} - -/** - * React component for the application header. - */ -class Header extends React.Component { - constructor(props: HeaderProps) { - super(props) - const cachedServerUrl = window.localStorage - .getItem('slim_selected_server') - ?.trim() - const cachedMode = window.localStorage.getItem( - 'slim_server_selection_mode', - ) as 'default' | 'custom' | null - - const cachedOidcConfig = readCachedOidcConfigInput() - - this.state = { - errorObj: [], - errorCategory: [], - warnings: [], - selectedServerUrl: cachedServerUrl ?? '', - isServerSelectionModalVisible: false, - isServerSelectionDisabled: !this.isValidServerUrl(cachedServerUrl), - serverSelectionMode: - cachedMode === 'custom' && - cachedServerUrl !== null && - cachedServerUrl !== undefined && - cachedServerUrl !== '' - ? 'custom' - : 'default', - showLogo: true, - logoUrl: `${process.env.PUBLIC_URL}/logo.svg`, - oidcConfigInput: cachedOidcConfig, - isOidcConfigValid: isValidOidcConfig(cachedOidcConfig), - } - - const onErrorHandler = ({ - source, - error, - }: { - source: string - error: CustomError - }): void => { - this.setState((state) => ({ - ...state, - errorObj: [...state.errorObj, { ...error, source }], - errorCategory: [...state.errorCategory, error.type], - })) - } - - const onWarningHandler = (warning: string): void => { - this.setState((state) => ({ - ...state, - warnings: [...state.warnings, warning], - })) - } - - NotificationMiddleware.subscribe( - NotificationMiddlewareEvents.OnError, - onErrorHandler, - ) - - NotificationMiddleware.subscribe( - NotificationMiddlewareEvents.OnWarning, - onWarningHandler, - ) - } - - componentDidUpdate( - prevProps: Readonly, - prevState: Readonly, - ): void { - if ( - (prevState.warnings.length > 0 || prevState.errorObj.length > 0) && - this.props.location.pathname !== prevProps.location.pathname - ) { - this.setState({ - isServerSelectionModalVisible: false, - isServerSelectionDisabled: true, - errorObj: [], - errorCategory: [], - warnings: [], - }) - } - } - - private static readonly defaultLogoUrl = - `${process.env.PUBLIC_URL}/favicon.ico` - - handleLogoError = (): void => { - if (this.state.logoUrl !== Header.defaultLogoUrl) { - this.setState({ logoUrl: Header.defaultLogoUrl }) - return - } - this.setState({ showLogo: false }) - } - - /** - * public/logo.svg may be an empty Illustrator placeholder (viewBox only). - * Fall back to favicon.ico, the default Slim brand mark. - */ - handleLogoLoad = (event: React.SyntheticEvent): void => { - const src = event.currentTarget.currentSrc || event.currentTarget.src - if (!src.includes('logo.svg')) { - return - } - void fetch(src) - .then(async (response) => { - if (!response.ok) { - this.setState({ logoUrl: Header.defaultLogoUrl }) - return - } - const markup = await response.text() - const hasGraphic = - /<(?:path|rect|circle|ellipse|polygon|polyline|line|text|image|use|g)\b/i.test( - markup, - ) - if (!hasGraphic) { - this.setState({ logoUrl: Header.defaultLogoUrl }) - } - }) - .catch(() => { - this.setState({ logoUrl: Header.defaultLogoUrl }) - }) - } - - isValidServerUrl = (url: string | null | undefined): boolean => { - if (url == null || url === '') { - return false - } - const trimmedUrl = url.trim() - if (trimmedUrl === '') { - return false - } - if (trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://')) { - try { - const urlObj = new URL(trimmedUrl) - return urlObj.protocol.startsWith('http') && urlObj.pathname.length > 0 - } catch (_TypeError) { - return false - } - } - const pathNorm = trimmedUrl.startsWith('/') ? trimmedUrl : `/${trimmedUrl}` - return isGcpDicomStorePath(pathNorm) - } - - handleOidcConfigInput = ( - event: React.ChangeEvent, - ): void => { - const value = event.currentTarget.value - this.setState({ - oidcConfigInput: value, - isOidcConfigValid: isValidOidcConfig(value), - }) - } - - static handleUserMenuButtonClick(e: React.SyntheticEvent): void { - e.preventDefault() - } - - handleInfoButtonClick = (): void => { - const browser = detect() - const environment: { - browser: { - name?: string - version?: string - } - os: { - name?: string - } - } = { - browser: {}, - os: {}, - } - if (browser !== null && browser !== undefined) { - environment.browser = { - name: - browser.name !== null && browser.name !== undefined - ? browser.name - : undefined, - version: - browser.version !== null && browser.version !== undefined - ? browser.version - : undefined, - } - environment.os = { - name: - browser.os !== null && browser.os !== undefined - ? browser.os - : undefined, - } - } - - const slimCommit = - process.env.REACT_APP_GIT_SHA !== undefined && - process.env.REACT_APP_GIT_SHA !== '' - ? process.env.REACT_APP_GIT_SHA - : null - const viewerCommit = - process.env.REACT_APP_DMV_GIT_SHA !== undefined && - process.env.REACT_APP_DMV_GIT_SHA !== '' - ? process.env.REACT_APP_DMV_GIT_SHA - : null - const devDeps = appPackageJson.devDependencies as - | Record - | undefined - const deps = appPackageJson.dependencies as - | Record - | undefined - const viewerVersionRaw = - devDeps?.['dicom-microscopy-viewer'] ?? - deps?.['dicom-microscopy-viewer'] ?? - 'unknown' - const viewerVersion = viewerVersionRaw.replace(/^[^0-9]*/, '') - - const renderHashText = (hash: string | null): JSX.Element => { - if (hash == null) { - return ( - - unknown - - ) - } - return ( - - {hash} - - ) - } - - Modal.info({ - width: 480, - title: null, - centered: true, - content: ( -
- - - {this.props.app.name} - - - - {this.props.app.version} - - -
- - Commit Hash - - {renderHashText(slimCommit)} -
- -
- - - DICOM Microscopy Viewer - - - - Version {viewerVersion} - - {renderHashText(viewerCommit)} -
- -
- - Current Browser & OS - - - {environment.browser.name ?? 'Unknown'}{' '} - {environment.browser.version ?? ''} - - - {environment.os.name ?? 'Unknown OS'} - -
-
- ), - onOk: () => { - Modal.destroyAll?.() - }, - }) - } - - handleDicomTagBrowserButtonClick = (): void => { - const width = window.innerWidth - 200 - - const seriesInstanceUID = parseSeriesInstanceUID( - this.props.location.pathname, - ) - - Modal.info({ - title: 'DICOM Tag Browser', - width, - content: ( - - ), - onOk: () => { - Modal.destroyAll?.() - }, - }) - } - - handleDebugButtonClick = (): void => { - const errorMsgs: { - Authentication: string[] - Communication: string[] - EncodingDecoding: string[] - Visualization: string[] - } = { - Authentication: [], - Communication: [], - EncodingDecoding: [], - Visualization: [], - } - - type ObjectKey = keyof typeof errorMsgs - const errorNum = this.state.errorObj.length - - if (errorNum > 0) { - for (let i = 0; i < errorNum; i++) { - const category = this.state.errorCategory[i] as ObjectKey - errorMsgs[category].push( - `${this.state.errorObj[i].message as string} (Source: ${ - this.state.errorObj[i].source - })`, - ) - } - } - - const { Panel } = Collapse - - const showErrorCount = (errcount: number): JSX.Element => ( - - ) - - const showWarningCount = (warncount: number): JSX.Element => ( - 0 ? '#52c41a' : '#ff4d4f'} - /> - ) - - Modal.info({ - title: 'Debug Information\n (Check console for more information)', - width: 800, - content: ( - - -
    - {errorMsgs.Communication.map((e) => ( -
  1. {e}
  2. - ))} -
-
- -
    - {errorMsgs.EncodingDecoding.map((e) => ( -
  1. {e}
  2. - ))} -
-
- -
    - {errorMsgs.Visualization.map((e) => ( -
  1. {e}
  2. - ))} -
-
- -
    - {errorMsgs.Authentication.map((e) => ( -
  1. {e}
  2. - ))} -
-
- -
    - {this.state.warnings.map((warning) => ( -
  1. {warning}
  2. - ))} -
-
-
- ), - onOk: () => { - Modal.destroyAll?.() - }, - }) - } - - handleServerSelectionButtonClick = (): void => { - this.setState({ isServerSelectionModalVisible: true }) - } - - handleServerSelectionInput = ( - event: React.FormEvent, - ): void => { - const value = event.currentTarget.value.trim() - this.setState({ - selectedServerUrl: value, - isServerSelectionDisabled: !this.isValidServerUrl(value), - }) - } - - handleServerSelectionCancellation = (): void => { - const cachedServerUrl = window.localStorage - .getItem('slim_selected_server') - ?.trim() - const cachedOidcConfig = readCachedOidcConfigInput() - this.setState({ - serverSelectionMode: - cachedServerUrl !== null && - cachedServerUrl !== undefined && - cachedServerUrl !== '' - ? 'custom' - : 'default', - selectedServerUrl: cachedServerUrl ?? undefined, - isServerSelectionModalVisible: false, - isServerSelectionDisabled: !this.isValidServerUrl(cachedServerUrl), - oidcConfigInput: cachedOidcConfig, - isOidcConfigValid: isValidOidcConfig(cachedOidcConfig), - }) - } - - handleServerSelectionModeChange = (e: RadioChangeEvent): void => { - const mode = (e.target?.value ?? 'default') as 'default' | 'custom' - this.setState({ serverSelectionMode: mode }) - } - - handleServerSelection = (): void => { - /** Keep the modal open so an invalid entry cannot wipe the cached config */ - if (!this.state.isOidcConfigValid) { - return - } - - window.localStorage.setItem( - 'slim_server_selection_mode', - this.state.serverSelectionMode, - ) - - const oidcConfigToApply = getOidcConfigToApply( - this.state.oidcConfigInput, - readCachedOidcConfigInput(), - ) - /** Cache the config only once a selection is actually applied */ - const selectServer = (url: string): void => { - cacheOidcConfigInput(this.state.oidcConfigInput) - this.props.onServerSelection({ url, oidc: oidcConfigToApply }) - } - - if (this.state.serverSelectionMode === 'default') { - selectServer('') - this.setState({ - isServerSelectionModalVisible: false, - isServerSelectionDisabled: false, - }) - return - } - - const url = this.state.selectedServerUrl?.trim() - let closeModal = false - let resolvedUrl: string | undefined - if (url !== null && url !== undefined && url !== '') { - if (this.isValidServerUrl(url)) { - resolvedUrl = normalizeServerUrl(url) - selectServer(resolvedUrl) - closeModal = true - } - } - this.setState({ - isServerSelectionModalVisible: !closeModal, - isServerSelectionDisabled: !closeModal, - ...(closeModal && - resolvedUrl !== undefined && { - selectedServerUrl: resolvedUrl, - }), - }) - } - - render(): React.ReactNode { - let user = null - if (this.props.user !== undefined) { - const userMenuItems = [] - if (this.props.onUserLogout !== undefined) { - userMenuItems.push({ - label: 'Logout', - key: 'user-logout', - onClick: () => { - if (this.props.onUserLogout !== undefined) { - this.props.onUserLogout() - } - }, - }) - } - const userMenu = { items: userMenuItems } - user = ( - -
- Slim - - ) : null} - -
- {this.props.showServerSelectionButton ? urlInfo : ''} -
- - - - {worklistButton} - {infoButton} - {dicomTagBrowserButton} - {serverSelectionButton} - {debugButton} - - {user} - - - - - - - - Use default server - Use custom server - - - {this.state.serverSelectionMode === 'custom' && ( - - - ) : ( - - ) - } - /> - - )} - -
- - OIDC Configuration (optional) - - {`Example JSON format: -{ - "authority": "https://accounts.google.com", - "clientId": "your-client-id.apps.googleusercontent.com", - "scope": "email profile openid https://www.googleapis.com/auth/cloud-healthcare", - "grantType": "implicit" -}`} -
- } - overlayStyle={{ maxWidth: '450px' }} - > - - - -