diff --git a/import-export-schema/.gitignore b/import-export-schema/.gitignore index b49acfe7..3c8dfe07 100644 --- a/import-export-schema/.gitignore +++ b/import-export-schema/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +*.tsbuildinfo # Editor directories and files .vscode/* @@ -24,3 +25,5 @@ dist-ssr *.sw? .tmplr-preview + +AGENTS.md diff --git a/import-export-schema/README.md b/import-export-schema/README.md index c72e93d1..82676d3e 100644 --- a/import-export-schema/README.md +++ b/import-export-schema/README.md @@ -1,56 +1,110 @@ # DatoCMS Schema Import/Export Plugin -A powerful plugin for DatoCMS that enables seamless schema migration between projects through JSON import/export functionality. +Powerful, safe schema migration for DatoCMS. Export models/blocks and plugins as JSON, then import them into another project with guided conflict resolution. -## Features +## What it does -- Export single or multiple models/block models as JSON files -- Import models into different DatoCMS projects -- Smart conflict resolution with guided instructions -- Automatic plugin dependency detection and inclusion -- Safe import operations that preserve existing schema +- Builds dependency-aware exports: collects selected models, blocks, fieldsets, fields, and any referenced plugins while trimming validators that point to out-of-scope item types. +- Keeps exports portable: rewrites field appearances to rely only on bundled or built-in editors and downloads a prettified `export.json` when each task completes. +- Imports additively: compares the bundle against the target project, walks you through reuse/rename/skip decisions, and creates new entities without mutating existing ones unless you opt in. +- Restores editors safely: reinstalls plugin editors/addons when present, falling back to core editors so fields always remain valid in the destination project. +- Handles long tasks gracefully: surfaces cancellable overlays with stall notices, honors rate-limit throttling, and stops at the next safe checkpoint if you cancel mid-flight. +- Loads shared recipes: accepts `recipe_url` query parameters to pull an export from a URL so collaborators can hand off ready-to-import snapshots. -## Safety Features +## Where To Find It -As a security measure, this plugin is designed to never modify existing schema entities in the target project during JSON imports. It only adds new entities to the project, making the operation completely safe and non-destructive. +- Configuration > Export: start a new export, select multiple models/blocks, or export the entire current environment. +- Configuration > Import: upload an export file (or paste a recipe URL) and import safely into the current environment. +- From a model/block: in Schema, open a model/block, click the three dots beside the model/block name, and pick “Export as JSON…”—the plugin opens the Export page preloaded with that entity so you can jump straight to the graph. ## Installation -1. Navigate to your DatoCMS environment configuration -2. Go to the Plugins section -3. Search for "Schema Import/Export" -4. Click Install - -## Usage - -### Exporting Models - -1. In the Schema section, navigate to one of your models/block models -2. Select the "Export as JSON..." option -3. If the model/block model references other models/block models, you can decide to export them as well -4. Save the generated JSON file - -### Importing Models - -1. Navigate to your DatoCMS environment configuration -2. Go to the Import/Export section -3. Drop your JSON file -4. Follow the conflict resolution prompts if any appear -5. Confirm the import - -## Conflict Resolution - -When importing models, the plugin will: - -- Detect potential conflicts with existing schema -- Provide clear instructions for resolving each conflict -- Allow you to review changes before applying them - -## Dependencies - -The plugin automatically handles the following dependencies: - -- Required field plugins -- Block model relationships -- Field validations -- Field appearance settings +- In DatoCMS, open your project, go to Plugins, search for “Schema Import/Export”, then install. The plugin only requests `currentUserAccessToken`. + +## Export + +- Start from a model/block + - Open Schema, select a model/block. + - Click the three dots beside the model/block name and choose “Export as JSON…”. + - The Export page opens with that entity locked in the selection; inspect the graph/list, run “Select all dependencies” to pull in linked models/blocks/plugins, and undo it with “Unselect dependencies” if you change your mind. + - Start the export when ready; the long-task overlay shows progress/cancel options, and the prettified `export.json` downloads automatically once the task completes. + +- Start a new export (Schema > Export) + - The landing panel lets you either pick specific starting models/blocks or go straight to “Export entire schema”. + - Use the multi-select to seed the graph; the graph animates selections for small/medium schemas, while large schemas (>60 nodes) fall back to the list view with search, metrics (counts, components, cycles), and “Why included?” explanations. + - “Select all dependencies” adds related models/blocks/plugins in bulk and logs a notice showing how many were added; “Unselect dependencies” removes the auto-added ones. + - Plugin dependencies come from installed plugin lookups; if the CMA call fails you’ll see a warning so you know selections may be incomplete. + +- Export the entire schema (one click) + - Confirm the dialog to queue up every model, block, and plugin in the current environment. + - The overlay reports progress, handles cancellable requests, and falls back gracefully if the CMA throttles; the final file downloads as `export.json`. + +- After export + - You get a success notice (or cancellation/error messaging) plus the downloaded file; the selection stays in place so you can tweak it and run another export without leaving the page. + +## Import + +- Start an import (Schema > Import) + - Drag and drop an exported JSON file or use the “Select a JSON export file…” button; invalid JSON triggers an alert so you can retry. + - To hydrate directly from a shared recipe, append `?recipe_url=https://…` (optional `recipe_title=…`)—the plugin fetches it and switches to import mode automatically. + +- Resolve conflicts safely + - The plugin builds a conflict summary in the background with progress feedback; you can refresh it if the schema changes while you wait. + - For models/blocks: choose “Reuse existing” or “Rename” with inline validation for name/API key (preset suggestions are provided for fast renames). + - For plugins: choose “Reuse existing” or “Skip”. + - Use “Show only unresolved conflicts” to focus the list; entities marked “reuse” drop out of the graph/list so you stay focused on what will be created. + - The import graph mirrors the export graph for smaller selections and switches to the list view with metrics/search once the node count crosses the same 60-node threshold. + +- Run the import + - Imports are additive: new models/blocks/fields/fieldsets/plugins are created with fresh IDs, and existing assets are touched only when you explicitly reuse them. + - Field validators and appearances are remapped to the target project; missing plugin editors fall back to safe defaults and localized defaults expand to every locale in the target environment. + - The progress overlay includes a cancel affordance with the required warning dialog; if you cancel, the task stops at the next safe checkpoint. + +- After import + - Successful runs raise a notice and clear the loaded export; cancellations leave the file in place so you can try again, and failures keep the conflict form available for fixes. + +## Notes & Limits + +- Plugin detection: editor/addon plugins used by fields are included when “Select all dependencies” is used. If the installed plugin list cannot be fetched you’ll see a one-time banner (per session) so you know detection may be incomplete. +- Graph threshold: when the graph would exceed ~60 nodes the UI switches to the large-selection layout with search, metrics (counts/components/cycles), and “Why included?” reasoning instead of rendering an unreadable canvas. +- Rate limiting & throttling: long operations show a stall notice if progress pauses, and `ProjectSchema` throttles CMA calls by default (override with something like `localStorage.setItem('schemaThrottleMax', '8')`; valid values are 1–15 for local debugging). +- Appearance portability: if an editor plugin is not selected, that field falls back to a valid built‑in editor; addons are included only if selected or already installed. +- Debug logging: run `localStorage.setItem('schemaDebug', '1')` in the iframe console to enable detailed `debugLog` output. + +## Development Notes + +- Entry points: + - `src/main.tsx` registers plugin pages, schema dropdown shortcuts, and preserves environment-prefixed routing when navigating between Import/Export. + - `src/entrypoints/Config` uses `ctx.navigateTo` so config links jump directly to Schema, Import, or Export without reloading the iframe. +- Shared hooks: + - `useProjectSchema` memoizes the CMA client, caches item types/plugins/fields, and honors the `schemaThrottleMax` localStorage override. + - `useLongTask` tracks cancellable progress state shared by exports, imports, and conflict analysis. + - `useExportSelection` hydrates item types once and keeps the selection stable when the import page toggles between modes. + - `useExportGraph` assembles the React Flow graph/list data and streams progress updates for the preparation overlay. + - `useExportAllHandler` and `useSchemaExportTask` wrap `buildExportDoc`, download handling, progress overlays, and cancellation. + - `useConflictsBuilder` drives conflict analysis with `useLongTask`; `useRecipeLoader` watches `recipe_url` query params for shared exports. +- Shared UI: + - `TaskOverlayStack` + `TaskProgressOverlay` render cancellable overlays with `ProgressOverlay` stall detection. + - `GraphCanvas` and the Schema Overview components keep the export/import visualizations consistent, with large graph warnings gating heavy renders. +- Schema utilities: + - `ProjectSchema` provides cached lookups plus concurrency-limited `getItemTypeFieldsAndFieldsets` calls. + - `buildExportDoc` trims validators/appearances so exports stay self-contained; `buildImportDoc` + `importSchema` orchestrate plugin installs, item type creation, field migrations, and reorder passes. +- Local development: + - `npm run dev` starts Vite, `npm run build` runs `tsc -b` followed by `vite build`, `npm run analyze` builds with bundle analysis, and `npm run format` runs Biome in `--write` mode. + +## Export File Format + +- Version 2 (current): `{ version: '2', rootItemTypeId, entities: […] }` — preserves the explicit root model/block used to seed the export, to re-generate the export graph deterministically. +- Version 1 (legacy): `{ version: '1', entities: […] }` — still supported for import; the root is inferred from references. +- Field validators referencing models outside the selection are trimmed, and appearances are rewritten to include only allowed plugin editors/addons so the export remains self-contained. + +## Safety + +- Imports are additive and non‑destructive. The plugin never overwrites existing models/blocks or plugins. When conflicts are detected, you explicitly pick “Reuse existing” or “Rename”. + +## Troubleshooting + +- “Why did the graph disappear?” Large selections now show a warning instead of auto-rendering; click “Render it anyway” to view the full graph. +- “Fields lost their editor?” If you don’t include a custom editor plugin in the export/import, the plugin selects a safe, built‑in editor so the field remains valid in the target project. +- “Plugin dependencies were skipped?” Check for the banner warning about incomplete plugin detection and rerun “Select all dependencies” after reopening the page once the CMA call succeeds. +- “Cancel didn’t stop immediately?” The import/export pipeline stops at the next safe checkpoint; keep the overlay open until it confirms cancellation. diff --git a/import-export-schema/package-lock.json b/import-export-schema/package-lock.json index 464c3755..d8739fda 100644 --- a/import-export-schema/package-lock.json +++ b/import-export-schema/package-lock.json @@ -1,15 +1,14 @@ { "name": "datocms-plugin-schema-import-export", - "version": "0.1.7", + "version": "0.1.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "datocms-plugin-schema-import-export", - "version": "0.1.7", + "version": "0.1.16", "dependencies": { "@datocms/cma-client": "^3.4.5", - "@datocms/rest-api-events": "^3.4.3", "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", @@ -17,7 +16,6 @@ "@xyflow/react": "^12.3.6", "classnames": "^2.5.1", "d3-hierarchy": "^3.1.2", - "d3-timer": "^3.0.1", "datocms-plugin-sdk": "^2.0.13", "datocms-react-ui": "^2.0.13", "emoji-regex": "^10.4.0", @@ -25,16 +23,15 @@ "lodash-es": "^4.17.21", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-final-form": "^6.5.9", - "ts-easing": "^0.2.0" + "react-final-form": "^6.5.9" }, "devDependencies": { - "@types/d3-timer": "^3.0.2", + "@types/lodash-es": "^4.17.12", "@types/node": "^22.13.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", - "globals": "^15.9.0", + "postcss-preset-env": "^9.6.0", "typescript": "^5.5.3", "vite": "^5.4.1", "vite-plugin-svgr": "^4.3.0" @@ -84,6 +81,7 @@ "integrity": "sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -339,817 +337,1301 @@ "node": ">=6.9.0" } }, - "node_modules/@datocms/cma-client": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@datocms/cma-client/-/cma-client-3.4.5.tgz", - "integrity": "sha512-ddwqN1c0gNf6D79GjxkcZZXKqGk4541GTZfrpXUnU5H0NQJoh1avkCqaecaI9CybJClYwKmoEWgcXZYWjednCQ==", - "license": "MIT", - "dependencies": { - "@datocms/rest-client-utils": "^3.4.2", - "uuid": "^9.0.1" - } - }, - "node_modules/@datocms/rest-api-events": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@datocms/rest-api-events/-/rest-api-events-3.4.3.tgz", - "integrity": "sha512-F1zz0Pj1JqVIDjgl/zzNJ8zyFZ/Yjyhi4INeP0XsnrHfBy/pHff075iGTvNBXjMHzbP2DU8rR9WeNfXPCUTsLg==", + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.13.tgz", + "integrity": "sha512-MX0yLTwtZzr82sQ0zOjqimpZbzjMaK/h2pmlrLK7DCzlmiZLYFpoO94WmN1akRVo6ll/TdpHb53vihHLUMyvng==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "pusher-js": "^7.0.6" + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, - "node_modules/@datocms/rest-client-utils": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@datocms/rest-client-utils/-/rest-client-utils-3.4.2.tgz", - "integrity": "sha512-VjAtxySGH2c1qlZkJUnaRkujDiGAtoc5BtN1V42lvz35hFi/s/fkVOL40Ybr+lkIYsNtFdCPFaE5sW0tABHqaA==", - "license": "MIT", - "dependencies": { - "async-scheduler": "^1.4.4" + "node_modules/@csstools/color-helpers": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.2.1.tgz", + "integrity": "sha512-CEypeeykO9AN7JWkr1OEOQb0HRzZlPWGwV0Ya6DuVgFdDi6g3ma/cPZ5ZPZM4AWQikDpq/0llnGGlIL+j8afzw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": "^14 || ^16 || >=18" } }, - "node_modules/@emotion/babel-plugin": { - "version": "11.13.5", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", - "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "node_modules/@csstools/css-calc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.2.4.tgz", + "integrity": "sha512-tfOuvUQeo7Hz+FcuOd3LfXVp+342pnWUJ7D2y8NUpu1Ww6xnTbHLpz018/y6rtbHifJ3iIEf9ttxXd8KG7nL0Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/serialize": "^1.3.3", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/@emotion/cache": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", - "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "node_modules/@csstools/css-color-parser": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-2.0.5.tgz", + "integrity": "sha512-lRZSmtl+DSjok3u9hTWpmkxFZnz7stkbZxzKc08aDUsdrWwhSgWo8yq9rq9DaFUtbAyAq2xnH92fj01S+pwIww==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.9.0", - "@emotion/sheet": "^1.4.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "stylis": "4.2.0" + "@csstools/color-helpers": "^4.2.1", + "@csstools/css-calc": "^1.2.4" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, - "node_modules/@emotion/hash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", - "license": "MIT" - }, - "node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" - }, - "node_modules/@emotion/react": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", - "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", + "integrity": "sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.13.5", - "@emotion/cache": "^11.14.0", - "@emotion/serialize": "^1.3.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "hoist-non-react-statics": "^3.3.1" + "peer": true, + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "@csstools/css-tokenizer": "^2.4.1" } }, - "node_modules/@emotion/serialize": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", - "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "node_modules/@csstools/css-tokenizer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", + "integrity": "sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "dependencies": { - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.2", - "csstype": "^3.0.2" + "peer": true, + "engines": { + "node": "^14 || ^16 || >=18" } }, - "node_modules/@emotion/sheet": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", - "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", - "license": "MIT" - }, - "node_modules/@emotion/unitless": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", - "license": "MIT" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", - "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "node_modules/@csstools/media-query-list-parser": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz", + "integrity": "sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18" + }, "peerDependencies": { - "react": ">=16.8.0" + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, - "node_modules/@emotion/utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", - "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", - "license": "MIT" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@csstools/postcss-cascade-layers": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.6.tgz", + "integrity": "sha512-Xt00qGAQyqAODFiFEJNkTpSUz5VfYqnDLECdlA/Vv17nl/OIV5QfTRHGAXrBGG5YcJyHpJ+GF9gF/RZvOQz4oA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^3.1.1", + "postcss-selector-parser": "^6.0.13" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], + "node_modules/@csstools/postcss-color-function": { + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.19.tgz", + "integrity": "sha512-d1OHEXyYGe21G3q88LezWWx31ImEDdmINNDy0LyLNN9ChgN2bPxoubUPiHf9KmwypBMaHmNcMuA/WZOKdZk/Lg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], + "node_modules/@csstools/postcss-color-mix-function": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.19.tgz", + "integrity": "sha512-mLvQlMX+keRYr16AuvuV8WYKUwF+D0DiCqlBdvhQ0KYEtcQl9/is9Ssg7RcIys8x0jIn2h1zstS4izckdZj9wg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/postcss-content-alt-text": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-1.0.0.tgz", + "integrity": "sha512-SkHdj7EMM/57GVvSxSELpUg7zb5eAndBeuvGwFzYtU06/QXJ/h9fuK7wO5suteJzGhm3GDF/EWPCdWV2h1IGHQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], + "node_modules/@csstools/postcss-exponential-functions": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.9.tgz", + "integrity": "sha512-x1Avr15mMeuX7Z5RJUl7DmjhUtg+Amn5DZRD0fQ2TlTFTcJS8U1oxXQ9e5mA62S2RJgUU6db20CRoJyDvae2EQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^1.2.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-3.0.2.tgz", + "integrity": "sha512-E0xz2sjm4AMCkXLCFvI/lyl4XO6aN1NCSMMVEOngFDJ+k2rDwfr6NDjWljk1li42jiLNChVX+YFnmfGCigZKXw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.11.tgz", + "integrity": "sha512-KrHGsUPXRYxboXmJ9wiU/RzDM7y/5uIefLWKFSc36Pok7fxiPyvkSHO51kh+RLZS1W5hbqw9qaa6+tKpTSxa5g==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "4.0.20", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.20.tgz", + "integrity": "sha512-ZFl2JBHano6R20KB5ZrB8KdPM2pVK0u+/3cGQ2T8VubJq982I2LSOvQ4/VtxkAXjkPkk1rXt4AD1ni7UjTZ1Og==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], + "node_modules/@csstools/postcss-hwb-function": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.18.tgz", + "integrity": "sha512-3ifnLltR5C7zrJ+g18caxkvSRnu9jBBXCYgnBznRjxm6gQJGnnCO9H6toHfywNdNr/qkiVf2dymERPQLDnjLRQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], + "node_modules/@csstools/postcss-ic-unit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-3.0.7.tgz", + "integrity": "sha512-YoaNHH2wNZD+c+rHV02l4xQuDpfR8MaL7hD45iJyr+USwvr0LOheeytJ6rq8FN6hXBmEeoJBeXXgGmM8fkhH4g==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], + "node_modules/@csstools/postcss-initial": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-1.0.1.tgz", + "integrity": "sha512-wtb+IbUIrIf8CrN6MLQuFR7nlU5C7PwuebfeEXfjthUha1+XZj2RVi+5k/lukToA24sZkYAiSJfHM8uG/UZIdg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.8.tgz", + "integrity": "sha512-0aj591yGlq5Qac+plaWCbn5cpjs5Sh0daovYUKJUOMjIp70prGH/XPLp7QjxtbFXz3CTvb0H9a35dpEuIuUi3Q==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^3.1.1", + "postcss-selector-parser": "^6.0.13" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], + "node_modules/@csstools/postcss-light-dark-function": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-1.0.8.tgz", + "integrity": "sha512-x0UtpCyVnERsplUeoaY6nEtp1HxTf4lJjoK/ULEm40DraqFfUdUSt76yoOyX5rGY6eeOUOkurHyYlFHVKv/pew==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-2.0.1.tgz", + "integrity": "sha512-SsrWUNaXKr+e/Uo4R/uIsqJYt3DaggIh/jyZdhy/q8fECoJSKsSMr7nObSLdvoULB69Zb6Bs+sefEIoMG/YfOA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], + "node_modules/@csstools/postcss-logical-overflow": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-1.0.1.tgz", + "integrity": "sha512-Kl4lAbMg0iyztEzDhZuQw8Sj9r2uqFDcU1IPl+AAt2nue8K/f1i7ElvKtXkjhIAmKiy5h2EY8Gt/Cqg0pYFDCw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-1.0.1.tgz", + "integrity": "sha512-+kHamNxAnX8ojPCtV8WPcUP3XcqMFBSDuBuvT6MHgq7oX4IQxLIXKx64t7g9LiuJzE7vd06Q9qUYR6bh4YnGpQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/postcss-logical-resize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-2.0.1.tgz", + "integrity": "sha512-W5Gtwz7oIuFcKa5SmBjQ2uxr8ZoL7M2bkoIf0T1WeNqljMkBrfw1DDA8/J83k57NQ1kcweJEjkJ04pUkmyee3A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.11.tgz", + "integrity": "sha512-ElITMOGcjQtvouxjd90WmJRIw1J7KMP+M+O87HaVtlgOOlDt1uEPeTeii8qKGe2AiedEp0XOGIo9lidbiU2Ogg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/postcss-media-minmax": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.8.tgz", + "integrity": "sha512-KYQCal2i7XPNtHAUxCECdrC7tuxIWQCW+s8eMYs5r5PaAiVTeKwlrkRS096PFgojdNCmHeG0Cb7njtuNswNf+w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^1.2.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/media-query-list-parser": "^2.1.13" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.11.tgz", + "integrity": "sha512-YD6jrib20GRGQcnOu49VJjoAnQ/4249liuz7vTpy/JfgqQ1Dlc5eD4HPUMNLOw9CWey9E6Etxwf/xc/ZF8fECA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/media-query-list-parser": "^2.1.13" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], + "node_modules/@csstools/postcss-nested-calc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-3.0.2.tgz", + "integrity": "sha512-ySUmPyawiHSmBW/VI44+IObcKH0v88LqFe0d09Sb3w4B1qjkaROc6d5IA3ll9kjD46IIX/dbO5bwFN/swyoyZA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-3.0.2.tgz", + "integrity": "sha512-fCapyyT/dUdyPtrelQSIV+d5HqtTgnNP/BEG9IuhgXHt93Wc4CfC1bQ55GzKAjWrZbgakMQ7MLfCXEf3rlZJOw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/postcss-oklab-function": { + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.19.tgz", + "integrity": "sha512-e3JxXmxjU3jpU7TzZrsNqSX4OHByRC3XjItV3Ieo/JEQmLg5rdOL4lkv/1vp27gXemzfNt44F42k/pn0FpE21Q==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", - "license": "MIT", + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.3.0.tgz", + "integrity": "sha512-W2oV01phnILaRGYPmGFlL2MT/OgYjQDrL9sFlbdikMFi6oQkFki9B86XqEWR7HCsTZFVq7dbzr/o71B75TKkGg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@floating-ui/dom": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", - "license": "MIT", + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.19.tgz", + "integrity": "sha512-MxUMSNvio1WwuS6WRLlQuv6nNPXwIWUFzBBAvL/tBdWfiKjiJnAa6eSSN5gtaacSqUkQ/Ce5Z1OzLRfeaWhADA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.9" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", - "license": "MIT" - }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", - "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", - "license": "MIT", + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=6" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", - "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", - "license": "MIT", + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-3.0.1.tgz", + "integrity": "sha512-3ZFonK2gfgqg29gUJ2w7xVw2wFJ1eNWVDONjbzGkm73gJHVCYK5fnCqlLr+N+KbEfv2XbWAO0AaOJCFB6Fer6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.2" + "postcss-selector-parser": "^6.0.13" }, "engines": { - "node": ">=6" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", - "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", - "license": "(CC-BY-4.0 AND MIT)", + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.10.tgz", + "integrity": "sha512-MZwo0D0TYrQhT5FQzMqfy/nGZ28D1iFtpN7Su1ck5BPHS95+/Y5O9S4kEvo76f2YOsqwYcT8ZGehSI1TnzuX2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.2" + "@csstools/css-calc": "^1.2.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" }, "engines": { - "node": ">=6" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", - "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", - "license": "MIT", + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.7.tgz", + "integrity": "sha512-+cptcsM5r45jntU6VjotnkC9GteFR7BQBfZ5oW7inLCxj7AfLGAzMbZ60hKTP13AULVZBdxky0P8um0IBfLHVA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "prop-types": "^15.8.1" + "@csstools/color-helpers": "^4.2.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.3" + "postcss": "^8.4" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "license": "MIT", + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.10.tgz", + "integrity": "sha512-G9G8moTc2wiad61nY5HfvxLiM/myX0aYK4s1x8MQlPH29WDPxHQM7ghGgvv2qf2xH+rrXhztOmjGHJj4jsEqXw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@csstools/css-calc": "^1.2.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" }, "engines": { - "node": ">=6.0.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", + "node_modules/@csstools/postcss-unset-value": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-3.0.1.tgz", + "integrity": "sha512-dbDnZ2ja2U8mbPP0Hvmt2RMEGBiF1H7oY6HYSpjteXJGihYwgxgTr6KRbbJ/V6c+4wd51M+9980qG4gKVn5ttg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=6.0.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", + "node_modules/@csstools/selector-resolve-nested": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz", + "integrity": "sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=6.0.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "node_modules/@csstools/selector-specificity": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", + "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", - "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "node_modules/@csstools/utilities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-1.0.0.tgz", + "integrity": "sha512-tAgvZQe/t2mlvpNosA4+CkMiZ2azISW5WPAcdSalZlEjQvUfghHxfQcrCiK/7/CrfAWVxyM88kGFYO82heIGDg==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=14.0.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "postcss": "^8.4" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", - "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@datocms/cma-client": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/@datocms/cma-client/-/cma-client-3.4.5.tgz", + "integrity": "sha512-ddwqN1c0gNf6D79GjxkcZZXKqGk4541GTZfrpXUnU5H0NQJoh1avkCqaecaI9CybJClYwKmoEWgcXZYWjednCQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@datocms/rest-client-utils": "^3.4.2", + "uuid": "^9.0.1" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", - "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@datocms/rest-client-utils": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@datocms/rest-client-utils/-/rest-client-utils-3.4.2.tgz", + "integrity": "sha512-VjAtxySGH2c1qlZkJUnaRkujDiGAtoc5BtN1V42lvz35hFi/s/fkVOL40Ybr+lkIYsNtFdCPFaE5sW0tABHqaA==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "async-scheduler": "^1.4.4" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", - "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", - "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", - "cpu": [ - "x64" + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "aix" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", - "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", - "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", - "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ - "arm" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "android" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", - "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ - "arm" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", - "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", - "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -1157,55 +1639,152 @@ "license": "MIT", "optional": true, "os": [ - "linux" - ] + "freebsd" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", - "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ - "loong64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "freebsd" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", - "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ - "ppc64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", - "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ - "riscv64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", - "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -1214,12 +1793,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", - "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -1228,12 +1810,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", - "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -1241,13 +1826,50 @@ "license": "MIT", "optional": true, "os": [ - "linux" - ] + "netbsd" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", - "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -1256,12 +1878,15 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", - "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -1270,12 +1895,15 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", - "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -1284,1345 +1912,2626 @@ "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", + "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", + "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", + "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", + "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", + "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", + "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", + "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", + "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", + "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", + "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", + "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", + "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", + "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", + "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", + "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", + "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", + "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", + "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", + "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/gensync": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.4.tgz", + "integrity": "sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "22.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", + "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@xyflow/react": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.4.2.tgz", + "integrity": "sha512-AFJKVc/fCPtgSOnRst3xdYJwiEcUN9lDY7EO/YiRvFHYCJGgfzg+jpvZjkTOnBLGyrMJre9378pRxAc3fsR06A==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.50", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.50.tgz", + "integrity": "sha512-HVUZd4LlY88XAaldFh2nwVxDOcdIBxGpQ5txzwfJPf+CAjj2BfYug1fHs2p4yS7YO8H6A3EFJQovBE8YuHkAdg==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", + "license": "MIT" + }, + "node_modules/async-scheduler": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/async-scheduler/-/async-scheduler-1.4.4.tgz", + "integrity": "sha512-KVWlF6WKlUGJA8FOJYVVk7xDm3PxITWXp9hTeDsXMtUPvItQ2x6g2rIeNAa0TtAiiWvHJqhyA3wo+pj0rA7Ldg==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001734", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001734.tgz", + "integrity": "sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=10" } }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "node_modules/css-blank-pseudo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.2.tgz", + "integrity": "sha512-J/6m+lsqpKPqWHOifAFtKFeGLOzw3jR92rxQcwRUfA/eTuZzKfKlxOmYDx2+tqOPQAueNvBiY8WhAeHu5qNmTg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^6.0.13" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "node_modules/css-has-pseudo": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.5.tgz", + "integrity": "sha512-ZTv6RlvJJZKp32jPYnAJVhowDCrRrHUTAxsYSuUPBEDJjzws6neMnzkRblxtgmv1RgcV5dhH2gn7E3wA9Wt6lw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^3.1.1", + "postcss-selector-parser": "^6.0.13", + "postcss-value-parser": "^4.2.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "engines": { + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "node_modules/css-prefers-color-scheme": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-9.0.1.tgz", + "integrity": "sha512-iFit06ochwCKPRiWagbTa1OAWCvWWVdEnIFd8BaRrgO8YrrNh4RAWUQTFcYX5tdFZgFl1DJ3iiULchZyEbnF4g==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "postcss": "^8.4" } }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "node_modules/cssdb": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.3.1.tgz", + "integrity": "sha512-XnDRQMXucLueX92yDe0LPKupXetWoFOgawr4O4X41l5TltgK2NVbJJVDnnOywDYfW1sTJ28AcXGKOqdRKwCcmQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "bin": { + "cssesc": "bin/cssesc" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=4" } }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", - "dev": true, - "license": "MIT", + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=12" } }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", - "dev": true, - "license": "MIT", + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", "engines": { "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@svgr/babel-preset": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", - "dev": true, - "license": "MIT", + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", - "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", - "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", - "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", - "@svgr/babel-plugin-transform-svg-component": "8.0.0" + "d3-dispatch": "1 - 3", + "d3-selection": "3" }, "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=12" } }, - "node_modules/@svgr/core": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^8.1.3", - "snake-case": "^3.0.4" - }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "node": ">=12" } }, - "node_modules/@svgr/core/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", - "dev": true, - "license": "MIT", + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", "dependencies": { - "@babel/types": "^7.21.3", - "entities": "^4.4.0" + "d3-color": "1 - 3" }, "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "node": ">=12" } }, - "node_modules/@svgr/plugin-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "@svgr/hast-util-to-babel-ast": "8.0.0", - "svg-parser": "^2.0.4" - }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "peer": true, "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" + "node": ">=12" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" } }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "license": "MIT", + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", "dependencies": { - "@babel/types": "^7.0.0" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", - "dev": true, + "node_modules/datocms-plugin-sdk": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/datocms-plugin-sdk/-/datocms-plugin-sdk-2.0.13.tgz", + "integrity": "sha512-NU5Ghssnhnq+STCiGnNyVO5Hbv0gj2mX3mU0uHV2pZ1Xg5G1kGZiQnopz4Em4Y0sjxLysNkoAgIQ7T7cw7/Xrg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@datocms/cma-client": "*", + "@types/react": "^17.0.3", + "datocms-structured-text-utils": "^2.0.0", + "penpal": "^4.1.1" } }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "node_modules/datocms-plugin-sdk/node_modules/@types/react": { + "version": "17.0.83", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz", + "integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==", "license": "MIT", "dependencies": { - "@types/d3-selection": "*" + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" } }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "node_modules/datocms-plugin-sdk/node_modules/datocms-structured-text-utils": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/datocms-structured-text-utils/-/datocms-structured-text-utils-2.1.12.tgz", + "integrity": "sha512-tZtiPN/sEjl+4Z6igojPdap6Miy0Z6VO6Afor3vcyY/8PIwKVGbTsdd5trD+skWPCPRZVNzKpfYoAVziXWTL8Q==", "license": "MIT", "dependencies": { - "@types/d3-color": "*" + "array-flatten": "^3.0.0" } }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "node_modules/datocms-react-ui": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/datocms-react-ui/-/datocms-react-ui-2.0.13.tgz", + "integrity": "sha512-PaYYNgv/ubRUplIN+bYqmqM7pSGyqlP7dAdAGy2jjM9rvJok2VwooPwKqclveeKko9X7RJAiS8wBem3WzSHNKA==", "license": "MIT", "dependencies": { - "@types/d3-selection": "*" + "classnames": "^2.3.1", + "datocms-plugin-sdk": "^2.0.13", + "react-intersection-observer": "^8.31.0", + "react-select": "^5.2.1", + "scroll-into-view-if-needed": "^2.2.20" } }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "license": "MIT", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" } }, - "node_modules/@types/gensync": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.4.tgz", - "integrity": "sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==", + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.13.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", - "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "no-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "license": "MIT" + "node_modules/electron-to-chromium": { + "version": "1.5.97", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.97.tgz", + "integrity": "sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==", + "dev": true, + "license": "ISC" }, - "node_modules/@types/qs": { - "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "license": "MIT" }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, - "node_modules/@types/react": { - "version": "18.3.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", - "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "license": "MIT", "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" + "is-arrayish": "^0.2.1" } }, - "node_modules/@types/react-dom": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", - "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/@types/react-transition-group": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", - "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", - "peerDependencies": { - "@types/react": "*" + "engines": { + "node": ">=6" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "license": "MIT" + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@vitejs/plugin-react": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", - "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, + "license": "MIT" + }, + "node_modules/final-form": { + "version": "4.20.10", + "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.10.tgz", + "integrity": "sha512-TL48Pi1oNHeMOHrKv1bCJUrWZDcD3DIG6AGYVNOnyZPr7Bd/pStN0pL+lfzF5BNoj/FclaoiaLenk4XUIFVYng==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/core": "^7.26.0", - "@babel/plugin-transform-react-jsx-self": "^7.25.9", - "@babel/plugin-transform-react-jsx-source": "^7.25.9", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "@babel/runtime": "^7.10.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">=8" }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/final-form" } }, - "node_modules/@xyflow/react": { - "version": "12.4.2", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.4.2.tgz", - "integrity": "sha512-AFJKVc/fCPtgSOnRst3xdYJwiEcUN9lDY7EO/YiRvFHYCJGgfzg+jpvZjkTOnBLGyrMJre9378pRxAc3fsR06A==", + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, "license": "MIT", - "dependencies": { - "@xyflow/system": "0.0.50", - "classcat": "^5.0.3", - "zustand": "^4.4.0" + "engines": { + "node": "*" }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" } }, - "node_modules/@xyflow/system": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.50.tgz", - "integrity": "sha512-HVUZd4LlY88XAaldFh2nwVxDOcdIBxGpQ5txzwfJPf+CAjj2BfYug1fHs2p4yS7YO8H6A3EFJQovBE8YuHkAdg==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@types/d3-drag": "^3.0.7", - "@types/d3-selection": "^3.0.10", - "@types/d3-transition": "^3.0.8", - "@types/d3-zoom": "^3.0.8", - "d3-drag": "^3.0.0", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-flatten": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", - "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", - "license": "MIT" - }, - "node_modules/async-scheduler": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/async-scheduler/-/async-scheduler-1.4.4.tgz", - "integrity": "sha512-KVWlF6WKlUGJA8FOJYVVk7xDm3PxITWXp9hTeDsXMtUPvItQ2x6g2rIeNAa0TtAiiWvHJqhyA3wo+pj0rA7Ldg==", - "license": "MIT" + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, "engines": { - "node": ">=10", - "npm": ">=6" + "node": ">=6.9.0" } }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" + "function-bind": "^1.1.2" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 0.4" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001699", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz", - "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/classcat": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", - "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/compute-scroll-into-view": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", - "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "license": "MIT", "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=10" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/d3-color": { + "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "engines": { - "node": ">=12" - } + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", "dependencies": { - "d3-color": "1 - 3" + "js-tokens": "^3.0.0 || ^4.0.0" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" + "bin": { + "loose-envify": "cli.js" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" } }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" + "yallist": "^3.0.2" } }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=12" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/datocms-plugin-sdk": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/datocms-plugin-sdk/-/datocms-plugin-sdk-2.0.13.tgz", - "integrity": "sha512-NU5Ghssnhnq+STCiGnNyVO5Hbv0gj2mX3mU0uHV2pZ1Xg5G1kGZiQnopz4Em4Y0sjxLysNkoAgIQ7T7cw7/Xrg==", + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, "license": "MIT", "dependencies": { - "@datocms/cma-client": "*", - "@types/react": "^17.0.3", - "datocms-structured-text-utils": "^2.0.0", - "penpal": "^4.1.1" + "lower-case": "^2.0.2", + "tslib": "^2.0.3" } }, - "node_modules/datocms-plugin-sdk/node_modules/@types/react": { - "version": "17.0.83", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz", - "integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==", + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "^0.16", - "csstype": "^3.0.2" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/datocms-plugin-sdk/node_modules/datocms-structured-text-utils": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/datocms-structured-text-utils/-/datocms-structured-text-utils-2.1.12.tgz", - "integrity": "sha512-tZtiPN/sEjl+4Z6igojPdap6Miy0Z6VO6Afor3vcyY/8PIwKVGbTsdd5trD+skWPCPRZVNzKpfYoAVziXWTL8Q==", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", - "dependencies": { - "array-flatten": "^3.0.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/datocms-react-ui": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/datocms-react-ui/-/datocms-react-ui-2.0.13.tgz", - "integrity": "sha512-PaYYNgv/ubRUplIN+bYqmqM7pSGyqlP7dAdAGy2jjM9rvJok2VwooPwKqclveeKko9X7RJAiS8wBem3WzSHNKA==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "license": "MIT", "dependencies": { - "classnames": "^2.3.1", - "datocms-plugin-sdk": "^2.0.13", - "react-intersection-observer": "^8.31.0", - "react-select": "^5.2.1", - "scroll-into-view-if-needed": "^2.2.20" + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": ">=6.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "engines": { + "node": ">=8" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.97", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.97.tgz", - "integrity": "sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "node_modules/penpal": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/penpal/-/penpal-4.1.1.tgz", + "integrity": "sha512-6d1f8khVLyBz3DnhLztbfjJ7+ANxdXRM2l6awpnCdEtbrmse4AGTsELOvGuNY0SU7xZw7heGbP6IikVvaVTOWw==", "license": "MIT" }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=0.12" + "node": ">=12" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/postcss": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", + "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", + "peer": true, "dependencies": { - "is-arrayish": "^0.2.1" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/postcss-attribute-case-insensitive": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.3.tgz", + "integrity": "sha512-KHkmCILThWBRtg+Jn1owTnHPnFit4OkqS+eKiGEOPIGke54DCeYGJ6r0Fx/HjfE9M9kznApCLcU0DvnPchazMQ==", "dev": true, - "hasInstallScript": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "postcss-selector-parser": "^6.0.13" }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", "dev": true, "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=6" + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", + "node_modules/postcss-color-functional-notation": { + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.14.tgz", + "integrity": "sha512-dNUX+UH4dAozZ8uMHZ3CtCNYw8fyFAmqqdcyxMr7PEdM9jLXV19YscoYO0F25KqZYhmtWKQ+4tKrIZQrwzwg7A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, "engines": { - "node": ">=10" + "node": "^14 || ^16 || >=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "node_modules/postcss-color-hex-alpha": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.4.tgz", + "integrity": "sha512-XQZm4q4fNFqVCYMGPiBjcqDhuG7Ey2xrl99AnDJMyr5eDASsAGalndVgHZF8i97VFNy1GQeZc4q2ydagGmhelQ==", "dev": true, - "license": "MIT" - }, - "node_modules/final-form": { - "version": "4.20.10", - "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.10.tgz", - "integrity": "sha512-TL48Pi1oNHeMOHrKv1bCJUrWZDcD3DIG6AGYVNOnyZPr7Bd/pStN0pL+lfzF5BNoj/FclaoiaLenk4XUIFVYng==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "dependencies": { - "@babel/runtime": "^7.10.0" + "@csstools/utilities": "^1.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=8" + "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/final-form" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/postcss-color-rebeccapurple": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.3.tgz", + "integrity": "sha512-ruBqzEFDYHrcVq3FnW3XHgwRqVMrtEPLBtD7K2YmsLKVc2jbkxzzNEctJKsPCpDZ+LeMHLKRDoSShVefGc+CkQ==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/postcss-custom-media": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.8.tgz", + "integrity": "sha512-V1KgPcmvlGdxTel4/CyQtBJEFhMVpEmRGFrnVtgfGIHj5PJX9vO36eFBxKBeJn+aCDTed70cc+98Mz3J/uVdGQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.13", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/media-query-list-parser": "^2.1.13" + }, "engines": { - "node": ">=6.9.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/globals": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", - "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "node_modules/postcss-custom-properties": { + "version": "13.3.12", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.12.tgz", + "integrity": "sha512-oPn/OVqONB2ZLNqN185LDyaVByELAA/u3l2CS2TS16x2j2XsmV4kd8U49+TMxmUsEU9d8fB/I10E6U7kB0L1BA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.13", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/utilities": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=18" + "node": "^14 || ^16 || >=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/postcss-custom-selectors": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.12.tgz", + "integrity": "sha512-ctIoprBMJwByYMGjXG0F7IT2iMF2hnamQ+aWZETyBM0aAlyaYdVZTeUkk8RB+9h9wP+NdN3f01lfvKl2ZSqC0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "@csstools/cascade-layer-name-parser": "^1.0.13", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "license": "MIT", + "node_modules/postcss-dir-pseudo-class": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-8.0.1.tgz", + "integrity": "sha512-uULohfWBBVoFiZXgsQA24JV6FdKIidQ+ZqxOouhWwdE+qJlALbkS5ScB43ZTjPK+xUZZhlaO/NjfCt5h4IKUfw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "postcss-selector-parser": "^6.0.13" }, "engines": { - "node": ">=6" + "node": "^14 || ^16 || >=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", + "node_modules/postcss-double-position-gradients": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.7.tgz", + "integrity": "sha512-1xEhjV9u1s4l3iP5lRt1zvMjI/ya8492o9l/ivcxHhkO3nOz16moC4JpMxDUGrOs4R3hX+KWT7gKoV842cwRgg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "hasown": "^2.0.2" + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">= 0.4" + "node": "^14 || ^16 || >=18" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/postcss-focus-visible": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-9.0.1.tgz", + "integrity": "sha512-N2VQ5uPz3Z9ZcqI5tmeholn4d+1H14fKXszpjogZIrFbhaq0zNAtq8sAnw6VLiqGbL8YBzsnu7K9bBkTqaRimQ==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "argparse": "^2.0.1" + "postcss-selector-parser": "^6.0.13" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "node_modules/postcss-focus-within": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-8.0.1.tgz", + "integrity": "sha512-NFU3xcY/xwNaapVb+1uJ4n23XImoC86JNwkY/uduytSl2s9Ekc2EpzmRR63+ExitnW3Mab3Fba/wRPCT5oDILA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^6.0.13" }, "engines": { - "node": ">=6" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", "dev": true, "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "node_modules/postcss-gap-properties": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-5.0.1.tgz", + "integrity": "sha512-k2z9Cnngc24c0KF4MtMuDdToROYqGMMUQGcE6V0odwjHyOHtaDBlLeRBV70y9/vF7KIbShrTRZ70JjsI1BZyWw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": "^14 || ^16 || >=18" }, - "bin": { - "loose-envify": "cli.js" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "node_modules/postcss-image-set-function": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-6.0.3.tgz", + "integrity": "sha512-i2bXrBYzfbRzFnm+pVuxVePSTCRiNmlfssGI4H0tJQvDue+yywXwUxe68VyzXs7cGtMaH6MCLY6IbCShrSroCw==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "tslib": "^2.0.3" + "@csstools/utilities": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/postcss-lab-function": { + "version": "6.0.19", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.19.tgz", + "integrity": "sha512-vwln/mgvFrotJuGV8GFhpAOu9iGf3pvTBr6dLPDmUcqVD5OsQpEFyQMAFTxSxWXGEzBj6ld4pZ/9GDfEpXvo0g==", "dev": true, - "license": "ISC", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "yallist": "^3.0.2" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "node_modules/postcss-logical": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-7.0.1.tgz", + "integrity": "sha512-8GwUQZE0ri0K0HJHkDv87XOLC8DE0msc+HoWLeKdtjDZEwpZ5xuK3QdV6FhmHSQW40LPkg43QzvATRAI3LsRkg==", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/ai" + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" } ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "node_modules/postcss-nesting": { + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.5.tgz", + "integrity": "sha512-N1NgI1PDCiAGWPTYrwqm8wpjv0bgDmkYHH72pNsqTCv9CObxjxftdYu6AKtGN+pnJa7FQjMm3v4sp8QJbFsYdQ==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" + "@csstools/selector-resolve-nested": "^1.1.0", + "@csstools/selector-specificity": "^3.1.1", + "postcss-selector-parser": "^6.1.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "node_modules/postcss-opacity-percentage": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-2.0.0.tgz", + "integrity": "sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ==", "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.2" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", + "node_modules/postcss-overflow-shorthand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-5.0.1.tgz", + "integrity": "sha512-XzjBYKLd1t6vHsaokMV9URBt2EwC9a7nDhpQpjoPk2HRTSQfokPfyAS/Q7AOrzUu6q+vp/GnrDBGuj/FCaRqrQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "callsites": "^3.0.0" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=6" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-9.0.1.tgz", + "integrity": "sha512-JfL+paQOgRQRMoYFc2f73pGuG/Aw3tt4vYMR6UA3cWVMxivviPTnMFnFTczUJOA4K2Zga6xgQVE+PcLs64WC8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=8" + "node": "^14 || ^16 || >=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", + "node_modules/postcss-preset-env": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.6.0.tgz", + "integrity": "sha512-Lxfk4RYjUdwPCYkc321QMdgtdCP34AeI94z+/8kVmqnTIlD4bMRQeGcMZgwz8BxHrzQiFXYIR5d7k/9JMs2MEA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-cascade-layers": "^4.0.6", + "@csstools/postcss-color-function": "^3.0.19", + "@csstools/postcss-color-mix-function": "^2.0.19", + "@csstools/postcss-content-alt-text": "^1.0.0", + "@csstools/postcss-exponential-functions": "^1.0.9", + "@csstools/postcss-font-format-keywords": "^3.0.2", + "@csstools/postcss-gamut-mapping": "^1.0.11", + "@csstools/postcss-gradients-interpolation-method": "^4.0.20", + "@csstools/postcss-hwb-function": "^3.0.18", + "@csstools/postcss-ic-unit": "^3.0.7", + "@csstools/postcss-initial": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^4.0.8", + "@csstools/postcss-light-dark-function": "^1.0.8", + "@csstools/postcss-logical-float-and-clear": "^2.0.1", + "@csstools/postcss-logical-overflow": "^1.0.1", + "@csstools/postcss-logical-overscroll-behavior": "^1.0.1", + "@csstools/postcss-logical-resize": "^2.0.1", + "@csstools/postcss-logical-viewport-units": "^2.0.11", + "@csstools/postcss-media-minmax": "^1.1.8", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.11", + "@csstools/postcss-nested-calc": "^3.0.2", + "@csstools/postcss-normalize-display-values": "^3.0.2", + "@csstools/postcss-oklab-function": "^3.0.19", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/postcss-relative-color-syntax": "^2.0.19", + "@csstools/postcss-scope-pseudo-class": "^3.0.1", + "@csstools/postcss-stepped-value-functions": "^3.0.10", + "@csstools/postcss-text-decoration-shorthand": "^3.0.7", + "@csstools/postcss-trigonometric-functions": "^3.0.10", + "@csstools/postcss-unset-value": "^3.0.1", + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.1", + "css-blank-pseudo": "^6.0.2", + "css-has-pseudo": "^6.0.5", + "css-prefers-color-scheme": "^9.0.1", + "cssdb": "^8.1.0", + "postcss-attribute-case-insensitive": "^6.0.3", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^6.0.14", + "postcss-color-hex-alpha": "^9.0.4", + "postcss-color-rebeccapurple": "^9.0.3", + "postcss-custom-media": "^10.0.8", + "postcss-custom-properties": "^13.3.12", + "postcss-custom-selectors": "^7.1.12", + "postcss-dir-pseudo-class": "^8.0.1", + "postcss-double-position-gradients": "^5.0.7", + "postcss-focus-visible": "^9.0.1", + "postcss-focus-within": "^8.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^5.0.1", + "postcss-image-set-function": "^6.0.3", + "postcss-lab-function": "^6.0.19", + "postcss-logical": "^7.0.1", + "postcss-nesting": "^12.1.5", + "postcss-opacity-percentage": "^2.0.0", + "postcss-overflow-shorthand": "^5.0.1", + "postcss-page-break": "^3.0.4", + "postcss-place": "^9.0.1", + "postcss-pseudo-class-any-link": "^9.0.2", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^7.0.2" + }, "engines": { - "node": ">=8" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/penpal": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/penpal/-/penpal-4.1.1.tgz", - "integrity": "sha512-6d1f8khVLyBz3DnhLztbfjJ7+ANxdXRM2l6awpnCdEtbrmse4AGTsELOvGuNY0SU7xZw7heGbP6IikVvaVTOWw==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "node_modules/postcss-pseudo-class-any-link": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.2.tgz", + "integrity": "sha512-HFSsxIqQ9nA27ahyfH37cRWGk3SYyQLpk0LiWw/UGMV4VKT5YG2ONee4Pz/oFesnK0dn2AjcyequDbIjKJgB0g==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^6.0.13" + }, "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/postcss": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", - "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-7.0.2.tgz", + "integrity": "sha512-/SSxf/90Obye49VZIfc0ls4H0P6i6V1iHv0pzZH8SdgvZOPFkF37ef1r5cyWcMflJSFJ5bfuoluTnFnBBFiuSA==", "dev": true, "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "github", + "url": "https://github.com/sponsors/csstools" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "opencollective", + "url": "https://opencollective.com/csstools" } ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "postcss-selector-parser": "^6.0.13" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -2634,28 +4543,12 @@ "react-is": "^16.13.1" } }, - "node_modules/pusher-js": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-7.6.0.tgz", - "integrity": "sha512-5CJ7YN5ZdC24E0ETraCU5VYFv0IY5ziXhrS0gS5+9Qrro1E4M1lcZhtr9H1H+6jNSLj1LKKAgcLeE1EH9GxMlw==", - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "4.17.28", - "@types/node": "^14.14.31", - "tweetnacl": "^1.0.3" - } - }, - "node_modules/pusher-js/node_modules/@types/node": { - "version": "14.18.63", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", - "license": "MIT" - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -2668,6 +4561,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -2912,12 +4806,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ts-easing": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", - "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==", - "license": "Unlicense" - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2925,18 +4813,13 @@ "dev": true, "license": "0BSD" }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "license": "Unlicense" - }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2949,6 +4832,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -3005,6 +4889,13 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -3024,6 +4915,7 @@ "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/import-export-schema/package.json b/import-export-schema/package.json index cb45aa37..12cae243 100644 --- a/import-export-schema/package.json +++ b/import-export-schema/package.json @@ -3,7 +3,7 @@ "description": "Export one or multiple models/block models as JSON files for reimport into another DatoCMS project.", "homepage": "https://github.com/datocms/plugins", "private": false, - "version": "0.1.14", + "version": "0.1.20", "author": "Stefano Verna ", "type": "module", "keywords": [ @@ -26,12 +26,12 @@ "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", + "analyze": "ANALYZE=true npm run build", "prepublishOnly": "npm run build", "format": "npx @biomejs/biome check --unsafe --write ." }, "dependencies": { "@datocms/cma-client": "^3.4.5", - "@datocms/rest-api-events": "^3.4.3", "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", @@ -47,18 +47,18 @@ "lodash-es": "^4.17.21", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-final-form": "^6.5.9", - "ts-easing": "^0.2.0" + "react-final-form": "^6.5.9" }, "devDependencies": { - "@types/d3-timer": "^3.0.2", + "@types/lodash-es": "^4.17.12", "@types/node": "^22.13.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", - "globals": "^15.9.0", + "postcss-preset-env": "^9.6.0", "typescript": "^5.5.3", "vite": "^5.4.1", "vite-plugin-svgr": "^4.3.0" - } + }, + "packageManager": "pnpm@10.21.0+sha512.da3337267e400fdd3d479a6c68079ac6db01d8ca4f67572083e722775a796788a7a9956613749e000fac20d424b594f7a791a5f4e2e13581c5ef947f26968a40" } diff --git a/import-export-schema/pnpm-lock.yaml b/import-export-schema/pnpm-lock.yaml new file mode 100644 index 00000000..345b3959 --- /dev/null +++ b/import-export-schema/pnpm-lock.yaml @@ -0,0 +1,3092 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@datocms/cma-client': + specifier: ^3.4.5 + version: 3.4.5 + '@fortawesome/fontawesome-svg-core': + specifier: ^6.7.2 + version: 6.7.2 + '@fortawesome/free-solid-svg-icons': + specifier: ^6.7.2 + version: 6.7.2 + '@fortawesome/react-fontawesome': + specifier: ^0.2.2 + version: 0.2.6(@fortawesome/fontawesome-svg-core@6.7.2)(react@18.3.1) + '@types/d3-hierarchy': + specifier: ^3.1.7 + version: 3.1.7 + '@xyflow/react': + specifier: ^12.3.6 + version: 12.9.2(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: + specifier: ^2.5.1 + version: 2.5.1 + d3-hierarchy: + specifier: ^3.1.2 + version: 3.1.2 + d3-timer: + specifier: ^3.0.1 + version: 3.0.1 + datocms-plugin-sdk: + specifier: ^2.0.13 + version: 2.0.15 + datocms-react-ui: + specifier: ^2.0.13 + version: 2.0.15(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + emoji-regex: + specifier: ^10.4.0 + version: 10.6.0 + final-form: + specifier: ^4.20.10 + version: 4.20.10 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-final-form: + specifier: ^6.5.9 + version: 6.5.9(final-form@4.20.10)(react@18.3.1) + devDependencies: + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/node': + specifier: ^22.13.1 + version: 22.19.0 + '@types/react': + specifier: ^18.3.3 + version: 18.3.26 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.26) + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.7.0(vite@5.4.21(@types/node@22.19.0)) + postcss-preset-env: + specifier: ^9.6.0 + version: 9.6.0(postcss@8.5.6) + typescript: + specifier: ^5.5.3 + version: 5.9.3 + vite: + specifier: ^5.4.1 + version: 5.4.21(@types/node@22.19.0) + vite-plugin-svgr: + specifier: ^4.3.0 + version: 4.5.0(rollup@4.53.2)(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.0)) + +packages: + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@csstools/cascade-layer-name-parser@1.0.13': + resolution: {integrity: sha512-MX0yLTwtZzr82sQ0zOjqimpZbzjMaK/h2pmlrLK7DCzlmiZLYFpoO94WmN1akRVo6ll/TdpHb53vihHLUMyvng==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.7.1 + '@csstools/css-tokenizer': ^2.4.1 + + '@csstools/color-helpers@4.2.1': + resolution: {integrity: sha512-CEypeeykO9AN7JWkr1OEOQb0HRzZlPWGwV0Ya6DuVgFdDi6g3ma/cPZ5ZPZM4AWQikDpq/0llnGGlIL+j8afzw==} + engines: {node: ^14 || ^16 || >=18} + + '@csstools/css-calc@1.2.4': + resolution: {integrity: sha512-tfOuvUQeo7Hz+FcuOd3LfXVp+342pnWUJ7D2y8NUpu1Ww6xnTbHLpz018/y6rtbHifJ3iIEf9ttxXd8KG7nL0Q==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.7.1 + '@csstools/css-tokenizer': ^2.4.1 + + '@csstools/css-color-parser@2.0.5': + resolution: {integrity: sha512-lRZSmtl+DSjok3u9hTWpmkxFZnz7stkbZxzKc08aDUsdrWwhSgWo8yq9rq9DaFUtbAyAq2xnH92fj01S+pwIww==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.7.1 + '@csstools/css-tokenizer': ^2.4.1 + + '@csstools/css-parser-algorithms@2.7.1': + resolution: {integrity: sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-tokenizer': ^2.4.1 + + '@csstools/css-tokenizer@2.4.1': + resolution: {integrity: sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==} + engines: {node: ^14 || ^16 || >=18} + + '@csstools/media-query-list-parser@2.1.13': + resolution: {integrity: sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.7.1 + '@csstools/css-tokenizer': ^2.4.1 + + '@csstools/postcss-cascade-layers@4.0.6': + resolution: {integrity: sha512-Xt00qGAQyqAODFiFEJNkTpSUz5VfYqnDLECdlA/Vv17nl/OIV5QfTRHGAXrBGG5YcJyHpJ+GF9gF/RZvOQz4oA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-color-function@3.0.19': + resolution: {integrity: sha512-d1OHEXyYGe21G3q88LezWWx31ImEDdmINNDy0LyLNN9ChgN2bPxoubUPiHf9KmwypBMaHmNcMuA/WZOKdZk/Lg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-color-mix-function@2.0.19': + resolution: {integrity: sha512-mLvQlMX+keRYr16AuvuV8WYKUwF+D0DiCqlBdvhQ0KYEtcQl9/is9Ssg7RcIys8x0jIn2h1zstS4izckdZj9wg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-content-alt-text@1.0.0': + resolution: {integrity: sha512-SkHdj7EMM/57GVvSxSELpUg7zb5eAndBeuvGwFzYtU06/QXJ/h9fuK7wO5suteJzGhm3GDF/EWPCdWV2h1IGHQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-exponential-functions@1.0.9': + resolution: {integrity: sha512-x1Avr15mMeuX7Z5RJUl7DmjhUtg+Amn5DZRD0fQ2TlTFTcJS8U1oxXQ9e5mA62S2RJgUU6db20CRoJyDvae2EQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-font-format-keywords@3.0.2': + resolution: {integrity: sha512-E0xz2sjm4AMCkXLCFvI/lyl4XO6aN1NCSMMVEOngFDJ+k2rDwfr6NDjWljk1li42jiLNChVX+YFnmfGCigZKXw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-gamut-mapping@1.0.11': + resolution: {integrity: sha512-KrHGsUPXRYxboXmJ9wiU/RzDM7y/5uIefLWKFSc36Pok7fxiPyvkSHO51kh+RLZS1W5hbqw9qaa6+tKpTSxa5g==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-gradients-interpolation-method@4.0.20': + resolution: {integrity: sha512-ZFl2JBHano6R20KB5ZrB8KdPM2pVK0u+/3cGQ2T8VubJq982I2LSOvQ4/VtxkAXjkPkk1rXt4AD1ni7UjTZ1Og==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-hwb-function@3.0.18': + resolution: {integrity: sha512-3ifnLltR5C7zrJ+g18caxkvSRnu9jBBXCYgnBznRjxm6gQJGnnCO9H6toHfywNdNr/qkiVf2dymERPQLDnjLRQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-ic-unit@3.0.7': + resolution: {integrity: sha512-YoaNHH2wNZD+c+rHV02l4xQuDpfR8MaL7hD45iJyr+USwvr0LOheeytJ6rq8FN6hXBmEeoJBeXXgGmM8fkhH4g==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-initial@1.0.1': + resolution: {integrity: sha512-wtb+IbUIrIf8CrN6MLQuFR7nlU5C7PwuebfeEXfjthUha1+XZj2RVi+5k/lukToA24sZkYAiSJfHM8uG/UZIdg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-is-pseudo-class@4.0.8': + resolution: {integrity: sha512-0aj591yGlq5Qac+plaWCbn5cpjs5Sh0daovYUKJUOMjIp70prGH/XPLp7QjxtbFXz3CTvb0H9a35dpEuIuUi3Q==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-light-dark-function@1.0.8': + resolution: {integrity: sha512-x0UtpCyVnERsplUeoaY6nEtp1HxTf4lJjoK/ULEm40DraqFfUdUSt76yoOyX5rGY6eeOUOkurHyYlFHVKv/pew==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-float-and-clear@2.0.1': + resolution: {integrity: sha512-SsrWUNaXKr+e/Uo4R/uIsqJYt3DaggIh/jyZdhy/q8fECoJSKsSMr7nObSLdvoULB69Zb6Bs+sefEIoMG/YfOA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-overflow@1.0.1': + resolution: {integrity: sha512-Kl4lAbMg0iyztEzDhZuQw8Sj9r2uqFDcU1IPl+AAt2nue8K/f1i7ElvKtXkjhIAmKiy5h2EY8Gt/Cqg0pYFDCw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-overscroll-behavior@1.0.1': + resolution: {integrity: sha512-+kHamNxAnX8ojPCtV8WPcUP3XcqMFBSDuBuvT6MHgq7oX4IQxLIXKx64t7g9LiuJzE7vd06Q9qUYR6bh4YnGpQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-resize@2.0.1': + resolution: {integrity: sha512-W5Gtwz7oIuFcKa5SmBjQ2uxr8ZoL7M2bkoIf0T1WeNqljMkBrfw1DDA8/J83k57NQ1kcweJEjkJ04pUkmyee3A==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-viewport-units@2.0.11': + resolution: {integrity: sha512-ElITMOGcjQtvouxjd90WmJRIw1J7KMP+M+O87HaVtlgOOlDt1uEPeTeii8qKGe2AiedEp0XOGIo9lidbiU2Ogg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-media-minmax@1.1.8': + resolution: {integrity: sha512-KYQCal2i7XPNtHAUxCECdrC7tuxIWQCW+s8eMYs5r5PaAiVTeKwlrkRS096PFgojdNCmHeG0Cb7njtuNswNf+w==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11': + resolution: {integrity: sha512-YD6jrib20GRGQcnOu49VJjoAnQ/4249liuz7vTpy/JfgqQ1Dlc5eD4HPUMNLOw9CWey9E6Etxwf/xc/ZF8fECA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-nested-calc@3.0.2': + resolution: {integrity: sha512-ySUmPyawiHSmBW/VI44+IObcKH0v88LqFe0d09Sb3w4B1qjkaROc6d5IA3ll9kjD46IIX/dbO5bwFN/swyoyZA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-normalize-display-values@3.0.2': + resolution: {integrity: sha512-fCapyyT/dUdyPtrelQSIV+d5HqtTgnNP/BEG9IuhgXHt93Wc4CfC1bQ55GzKAjWrZbgakMQ7MLfCXEf3rlZJOw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-oklab-function@3.0.19': + resolution: {integrity: sha512-e3JxXmxjU3jpU7TzZrsNqSX4OHByRC3XjItV3Ieo/JEQmLg5rdOL4lkv/1vp27gXemzfNt44F42k/pn0FpE21Q==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-progressive-custom-properties@3.3.0': + resolution: {integrity: sha512-W2oV01phnILaRGYPmGFlL2MT/OgYjQDrL9sFlbdikMFi6oQkFki9B86XqEWR7HCsTZFVq7dbzr/o71B75TKkGg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-relative-color-syntax@2.0.19': + resolution: {integrity: sha512-MxUMSNvio1WwuS6WRLlQuv6nNPXwIWUFzBBAvL/tBdWfiKjiJnAa6eSSN5gtaacSqUkQ/Ce5Z1OzLRfeaWhADA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-scope-pseudo-class@3.0.1': + resolution: {integrity: sha512-3ZFonK2gfgqg29gUJ2w7xVw2wFJ1eNWVDONjbzGkm73gJHVCYK5fnCqlLr+N+KbEfv2XbWAO0AaOJCFB6Fer6A==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-stepped-value-functions@3.0.10': + resolution: {integrity: sha512-MZwo0D0TYrQhT5FQzMqfy/nGZ28D1iFtpN7Su1ck5BPHS95+/Y5O9S4kEvo76f2YOsqwYcT8ZGehSI1TnzuX2g==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-text-decoration-shorthand@3.0.7': + resolution: {integrity: sha512-+cptcsM5r45jntU6VjotnkC9GteFR7BQBfZ5oW7inLCxj7AfLGAzMbZ60hKTP13AULVZBdxky0P8um0IBfLHVA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-trigonometric-functions@3.0.10': + resolution: {integrity: sha512-G9G8moTc2wiad61nY5HfvxLiM/myX0aYK4s1x8MQlPH29WDPxHQM7ghGgvv2qf2xH+rrXhztOmjGHJj4jsEqXw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-unset-value@3.0.1': + resolution: {integrity: sha512-dbDnZ2ja2U8mbPP0Hvmt2RMEGBiF1H7oY6HYSpjteXJGihYwgxgTr6KRbbJ/V6c+4wd51M+9980qG4gKVn5ttg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/selector-resolve-nested@1.1.0': + resolution: {integrity: sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss-selector-parser: ^6.0.13 + + '@csstools/selector-specificity@3.1.1': + resolution: {integrity: sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss-selector-parser: ^6.0.13 + + '@csstools/utilities@1.0.0': + resolution: {integrity: sha512-tAgvZQe/t2mlvpNosA4+CkMiZ2azISW5WPAcdSalZlEjQvUfghHxfQcrCiK/7/CrfAWVxyM88kGFYO82heIGDg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@datocms/cma-client@3.4.5': + resolution: {integrity: sha512-ddwqN1c0gNf6D79GjxkcZZXKqGk4541GTZfrpXUnU5H0NQJoh1avkCqaecaI9CybJClYwKmoEWgcXZYWjednCQ==} + + '@datocms/rest-client-utils@3.4.2': + resolution: {integrity: sha512-VjAtxySGH2c1qlZkJUnaRkujDiGAtoc5BtN1V42lvz35hFi/s/fkVOL40Ybr+lkIYsNtFdCPFaE5sW0tABHqaA==} + + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@fortawesome/fontawesome-common-types@6.7.2': + resolution: {integrity: sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==} + engines: {node: '>=6'} + + '@fortawesome/fontawesome-svg-core@6.7.2': + resolution: {integrity: sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==} + engines: {node: '>=6'} + + '@fortawesome/free-solid-svg-icons@6.7.2': + resolution: {integrity: sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==} + engines: {node: '>=6'} + + '@fortawesome/react-fontawesome@0.2.6': + resolution: {integrity: sha512-mtBFIi1UsYQo7rYonYFkjgYKGoL8T+fEH6NGUpvuqtY3ytMsAoDaPo5rk25KuMtKDipY4bGYM/CkmCHA1N3FUg==} + peerDependencies: + '@fortawesome/fontawesome-svg-core': ~1 || ~6 || ~7 + react: ^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.53.2': + resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.2': + resolution: {integrity: sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.2': + resolution: {integrity: sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.2': + resolution: {integrity: sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.2': + resolution: {integrity: sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.2': + resolution: {integrity: sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.2': + resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.2': + resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.2': + resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.2': + resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.2': + resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.2': + resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.2': + resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.2': + resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.2': + resolution: {integrity: sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.2': + resolution: {integrity: sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.2': + resolution: {integrity: sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.2': + resolution: {integrity: sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==} + cpu: [x64] + os: [win32] + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.20': + resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + + '@types/node@22.19.0': + resolution: {integrity: sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react-transition-group@4.4.12': + resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} + peerDependencies: + '@types/react': '*' + + '@types/react@17.0.89': + resolution: {integrity: sha512-I98SaDCar5lvEYl80ClRIUztH/hyWHR+I2f+5yTVp/MQ205HgYkA2b5mVdry/+nsEIrf8I65KA5V/PASx68MsQ==} + + '@types/react@18.3.26': + resolution: {integrity: sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==} + + '@types/scheduler@0.16.8': + resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@xyflow/react@12.9.2': + resolution: {integrity: sha512-Xr+LFcysHCCoc5KRHaw+FwbqbWYxp9tWtk1mshNcqy25OAPuaKzXSdqIMNOA82TIXF/gFKo0Wgpa6PU7wUUVqw==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@xyflow/system@0.0.72': + resolution: {integrity: sha512-WBI5Aau0fXTXwxHPzceLNS6QdXggSWnGjDtj/gG669crApN8+SCmEtkBth1m7r6pStNo/5fI9McEi7Dk0ymCLA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-flatten@3.0.0: + resolution: {integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==} + + async-scheduler@1.4.4: + resolution: {integrity: sha512-KVWlF6WKlUGJA8FOJYVVk7xDm3PxITWXp9hTeDsXMtUPvItQ2x6g2rIeNAa0TtAiiWvHJqhyA3wo+pj0rA7Ldg==} + + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + baseline-browser-mapping@2.8.25: + resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==} + hasBin: true + + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + 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==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + css-blank-pseudo@6.0.2: + resolution: {integrity: sha512-J/6m+lsqpKPqWHOifAFtKFeGLOzw3jR92rxQcwRUfA/eTuZzKfKlxOmYDx2+tqOPQAueNvBiY8WhAeHu5qNmTg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + css-has-pseudo@6.0.5: + resolution: {integrity: sha512-ZTv6RlvJJZKp32jPYnAJVhowDCrRrHUTAxsYSuUPBEDJjzws6neMnzkRblxtgmv1RgcV5dhH2gn7E3wA9Wt6lw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + css-prefers-color-scheme@9.0.1: + resolution: {integrity: sha512-iFit06ochwCKPRiWagbTa1OAWCvWWVdEnIFd8BaRrgO8YrrNh4RAWUQTFcYX5tdFZgFl1DJ3iiULchZyEbnF4g==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + cssdb@8.4.2: + resolution: {integrity: sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + datocms-plugin-sdk@2.0.15: + resolution: {integrity: sha512-MXuOo8i4Ksw6TIY6YYpb8czukp2/e9pKv/TXYXKIglRBtP2q62xfsP5F+JCDRSM5VOdR8hwGQU9QkDHjHJoR8A==} + + datocms-react-ui@2.0.15: + resolution: {integrity: sha512-NX4Ju2gJDQNXZC2fQQDFZ/ros9zAEU4NMFYoCX73wy32sbIFlBt1NtjawpBl/cB5akq8BLMW3WVj0dsskpLpsA==} + + datocms-structured-text-utils@2.1.12: + resolution: {integrity: sha512-tZtiPN/sEjl+4Z6igojPdap6Miy0Z6VO6Afor3vcyY/8PIwKVGbTsdd5trD+skWPCPRZVNzKpfYoAVziXWTL8Q==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + electron-to-chromium@1.5.249: + resolution: {integrity: sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + final-form@4.20.10: + resolution: {integrity: sha512-TL48Pi1oNHeMOHrKv1bCJUrWZDcD3DIG6AGYVNOnyZPr7Bd/pStN0pL+lfzF5BNoj/FclaoiaLenk4XUIFVYng==} + engines: {node: '>=8'} + + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + 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==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + 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==} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + penpal@4.1.1: + resolution: {integrity: sha512-6d1f8khVLyBz3DnhLztbfjJ7+ANxdXRM2l6awpnCdEtbrmse4AGTsELOvGuNY0SU7xZw7heGbP6IikVvaVTOWw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss-attribute-case-insensitive@6.0.3: + resolution: {integrity: sha512-KHkmCILThWBRtg+Jn1owTnHPnFit4OkqS+eKiGEOPIGke54DCeYGJ6r0Fx/HjfE9M9kznApCLcU0DvnPchazMQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-clamp@4.1.0: + resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==} + engines: {node: '>=7.6.0'} + peerDependencies: + postcss: ^8.4.6 + + postcss-color-functional-notation@6.0.14: + resolution: {integrity: sha512-dNUX+UH4dAozZ8uMHZ3CtCNYw8fyFAmqqdcyxMr7PEdM9jLXV19YscoYO0F25KqZYhmtWKQ+4tKrIZQrwzwg7A==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-color-hex-alpha@9.0.4: + resolution: {integrity: sha512-XQZm4q4fNFqVCYMGPiBjcqDhuG7Ey2xrl99AnDJMyr5eDASsAGalndVgHZF8i97VFNy1GQeZc4q2ydagGmhelQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-color-rebeccapurple@9.0.3: + resolution: {integrity: sha512-ruBqzEFDYHrcVq3FnW3XHgwRqVMrtEPLBtD7K2YmsLKVc2jbkxzzNEctJKsPCpDZ+LeMHLKRDoSShVefGc+CkQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-custom-media@10.0.8: + resolution: {integrity: sha512-V1KgPcmvlGdxTel4/CyQtBJEFhMVpEmRGFrnVtgfGIHj5PJX9vO36eFBxKBeJn+aCDTed70cc+98Mz3J/uVdGQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-custom-properties@13.3.12: + resolution: {integrity: sha512-oPn/OVqONB2ZLNqN185LDyaVByELAA/u3l2CS2TS16x2j2XsmV4kd8U49+TMxmUsEU9d8fB/I10E6U7kB0L1BA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-custom-selectors@7.1.12: + resolution: {integrity: sha512-ctIoprBMJwByYMGjXG0F7IT2iMF2hnamQ+aWZETyBM0aAlyaYdVZTeUkk8RB+9h9wP+NdN3f01lfvKl2ZSqC0g==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-dir-pseudo-class@8.0.1: + resolution: {integrity: sha512-uULohfWBBVoFiZXgsQA24JV6FdKIidQ+ZqxOouhWwdE+qJlALbkS5ScB43ZTjPK+xUZZhlaO/NjfCt5h4IKUfw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-double-position-gradients@5.0.7: + resolution: {integrity: sha512-1xEhjV9u1s4l3iP5lRt1zvMjI/ya8492o9l/ivcxHhkO3nOz16moC4JpMxDUGrOs4R3hX+KWT7gKoV842cwRgg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-focus-visible@9.0.1: + resolution: {integrity: sha512-N2VQ5uPz3Z9ZcqI5tmeholn4d+1H14fKXszpjogZIrFbhaq0zNAtq8sAnw6VLiqGbL8YBzsnu7K9bBkTqaRimQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-focus-within@8.0.1: + resolution: {integrity: sha512-NFU3xcY/xwNaapVb+1uJ4n23XImoC86JNwkY/uduytSl2s9Ekc2EpzmRR63+ExitnW3Mab3Fba/wRPCT5oDILA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-font-variant@5.0.0: + resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==} + peerDependencies: + postcss: ^8.1.0 + + postcss-gap-properties@5.0.1: + resolution: {integrity: sha512-k2z9Cnngc24c0KF4MtMuDdToROYqGMMUQGcE6V0odwjHyOHtaDBlLeRBV70y9/vF7KIbShrTRZ70JjsI1BZyWw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-image-set-function@6.0.3: + resolution: {integrity: sha512-i2bXrBYzfbRzFnm+pVuxVePSTCRiNmlfssGI4H0tJQvDue+yywXwUxe68VyzXs7cGtMaH6MCLY6IbCShrSroCw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-lab-function@6.0.19: + resolution: {integrity: sha512-vwln/mgvFrotJuGV8GFhpAOu9iGf3pvTBr6dLPDmUcqVD5OsQpEFyQMAFTxSxWXGEzBj6ld4pZ/9GDfEpXvo0g==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-logical@7.0.1: + resolution: {integrity: sha512-8GwUQZE0ri0K0HJHkDv87XOLC8DE0msc+HoWLeKdtjDZEwpZ5xuK3QdV6FhmHSQW40LPkg43QzvATRAI3LsRkg==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-nesting@12.1.5: + resolution: {integrity: sha512-N1NgI1PDCiAGWPTYrwqm8wpjv0bgDmkYHH72pNsqTCv9CObxjxftdYu6AKtGN+pnJa7FQjMm3v4sp8QJbFsYdQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-opacity-percentage@2.0.0: + resolution: {integrity: sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.2 + + postcss-overflow-shorthand@5.0.1: + resolution: {integrity: sha512-XzjBYKLd1t6vHsaokMV9URBt2EwC9a7nDhpQpjoPk2HRTSQfokPfyAS/Q7AOrzUu6q+vp/GnrDBGuj/FCaRqrQ==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-page-break@3.0.4: + resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==} + peerDependencies: + postcss: ^8 + + postcss-place@9.0.1: + resolution: {integrity: sha512-JfL+paQOgRQRMoYFc2f73pGuG/Aw3tt4vYMR6UA3cWVMxivviPTnMFnFTczUJOA4K2Zga6xgQVE+PcLs64WC8Q==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-preset-env@9.6.0: + resolution: {integrity: sha512-Lxfk4RYjUdwPCYkc321QMdgtdCP34AeI94z+/8kVmqnTIlD4bMRQeGcMZgwz8BxHrzQiFXYIR5d7k/9JMs2MEA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-pseudo-class-any-link@9.0.2: + resolution: {integrity: sha512-HFSsxIqQ9nA27ahyfH37cRWGk3SYyQLpk0LiWw/UGMV4VKT5YG2ONee4Pz/oFesnK0dn2AjcyequDbIjKJgB0g==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-replace-overflow-wrap@4.0.0: + resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==} + peerDependencies: + postcss: ^8.0.3 + + postcss-selector-not@7.0.2: + resolution: {integrity: sha512-/SSxf/90Obye49VZIfc0ls4H0P6i6V1iHv0pzZH8SdgvZOPFkF37ef1r5cyWcMflJSFJ5bfuoluTnFnBBFiuSA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-final-form@6.5.9: + resolution: {integrity: sha512-x3XYvozolECp3nIjly+4QqxdjSSWfcnpGEL5K8OBT6xmGrq5kBqbA6+/tOqoom9NwqIPPbxPNsOViFlbKgowbA==} + peerDependencies: + final-form: ^4.20.4 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + react-intersection-observer@8.34.0: + resolution: {integrity: sha512-TYKh52Zc0Uptp5/b4N91XydfSGKubEhgZRtcg1rhTKABXijc4Sdr1uTp5lJ8TN27jwUsdXxjHXtHa0kPj704sw==} + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-select@5.10.2: + resolution: {integrity: sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + rollup@4.53.2: + resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + 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==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-isomorphic-layout-effect@1.2.1: + resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + vite-plugin-svgr@4.5.0: + resolution: {integrity: sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==} + peerDependencies: + vite: '>=2.6.0' + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + +snapshots: + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.27.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/runtime@7.28.4': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@csstools/cascade-layer-name-parser@1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1)': + dependencies: + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + + '@csstools/color-helpers@4.2.1': {} + + '@csstools/css-calc@1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1)': + dependencies: + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + + '@csstools/css-color-parser@2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1)': + dependencies: + '@csstools/color-helpers': 4.2.1 + '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + + '@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1)': + dependencies: + '@csstools/css-tokenizer': 2.4.1 + + '@csstools/css-tokenizer@2.4.1': {} + + '@csstools/media-query-list-parser@2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1)': + dependencies: + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + + '@csstools/postcss-cascade-layers@4.0.6(postcss@8.5.6)': + dependencies: + '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + '@csstools/postcss-color-function@3.0.19(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-color-mix-function@2.0.19(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-content-alt-text@1.0.0(postcss@8.5.6)': + dependencies: + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-exponential-functions@1.0.9(postcss@8.5.6)': + dependencies: + '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + postcss: 8.5.6 + + '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.5.6)': + dependencies: + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + postcss: 8.5.6 + + '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-hwb-function@3.0.18(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-ic-unit@3.0.7(postcss@8.5.6)': + dependencies: + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-initial@1.0.1(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + + '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.5.6)': + dependencies: + '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + '@csstools/postcss-light-dark-function@1.0.8(postcss@8.5.6)': + dependencies: + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + + '@csstools/postcss-logical-overflow@1.0.1(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + + '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + + '@csstools/postcss-logical-resize@2.0.1(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.5.6)': + dependencies: + '@csstools/css-tokenizer': 2.4.1 + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-media-minmax@1.1.8(postcss@8.5.6)': + dependencies: + '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + postcss: 8.5.6 + + '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.5.6)': + dependencies: + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + postcss: 8.5.6 + + '@csstools/postcss-nested-calc@3.0.2(postcss@8.5.6)': + dependencies: + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-oklab-function@3.0.19(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.5.6)': + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.5.6)': + dependencies: + '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + postcss: 8.5.6 + + '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.5.6)': + dependencies: + '@csstools/color-helpers': 4.2.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.5.6)': + dependencies: + '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + postcss: 8.5.6 + + '@csstools/postcss-unset-value@3.0.1(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + + '@csstools/selector-resolve-nested@1.1.0(postcss-selector-parser@6.1.2)': + dependencies: + postcss-selector-parser: 6.1.2 + + '@csstools/selector-specificity@3.1.1(postcss-selector-parser@6.1.2)': + dependencies: + postcss-selector-parser: 6.1.2 + + '@csstools/utilities@1.0.0(postcss@8.5.6)': + dependencies: + postcss: 8.5.6 + + '@datocms/cma-client@3.4.5': + dependencies: + '@datocms/rest-client-utils': 3.4.2 + uuid: 9.0.1 + + '@datocms/rest-client-utils@3.4.2': + dependencies: + async-scheduler: 1.4.4 + + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/runtime': 7.28.4 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.28.4 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.26 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.1.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/unitless@0.10.0': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@fortawesome/fontawesome-common-types@6.7.2': {} + + '@fortawesome/fontawesome-svg-core@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + + '@fortawesome/free-solid-svg-icons@6.7.2': + dependencies: + '@fortawesome/fontawesome-common-types': 6.7.2 + + '@fortawesome/react-fontawesome@0.2.6(@fortawesome/fontawesome-svg-core@6.7.2)(react@18.3.1)': + dependencies: + '@fortawesome/fontawesome-svg-core': 6.7.2 + prop-types: 15.8.1 + react: 18.3.1 + + '@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/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/pluginutils@5.3.0(rollup@4.53.2)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.53.2 + + '@rollup/rollup-android-arm-eabi@4.53.2': + optional: true + + '@rollup/rollup-android-arm64@4.53.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.2': + optional: true + + '@rollup/rollup-darwin-x64@4.53.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.2': + optional: true + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + + '@svgr/babel-preset@8.1.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.5) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.5) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.5) + + '@svgr/core@8.1.0(typescript@5.9.3)': + dependencies: + '@babel/core': 7.28.5 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.9.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.28.5 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': + dependencies: + '@babel/core': 7.28.5 + '@svgr/babel-preset': 8.1.0(@babel/core@7.28.5) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@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.28.5 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/d3-color@3.1.3': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/estree@1.0.8': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.20 + + '@types/lodash@4.17.20': {} + + '@types/node@22.19.0': + dependencies: + undici-types: 6.21.0 + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.26)': + dependencies: + '@types/react': 18.3.26 + + '@types/react-transition-group@4.4.12(@types/react@18.3.26)': + dependencies: + '@types/react': 18.3.26 + + '@types/react@17.0.89': + dependencies: + '@types/prop-types': 15.7.15 + '@types/scheduler': 0.16.8 + csstype: 3.1.3 + + '@types/react@18.3.26': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.1.3 + + '@types/scheduler@0.16.8': {} + + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@22.19.0))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21(@types/node@22.19.0) + transitivePeerDependencies: + - supports-color + + '@xyflow/react@12.9.2(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@xyflow/system': 0.0.72 + classcat: 5.0.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zustand: 4.5.7(@types/react@18.3.26)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.72': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + + argparse@2.0.1: {} + + array-flatten@3.0.0: {} + + async-scheduler@1.4.4: {} + + autoprefixer@10.4.21(postcss@8.5.6): + dependencies: + browserslist: 4.27.0 + caniuse-lite: 1.0.30001754 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.28.4 + cosmiconfig: 7.1.0 + resolve: 1.22.11 + + baseline-browser-mapping@2.8.25: {} + + browserslist@4.27.0: + dependencies: + baseline-browser-mapping: 2.8.25 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.249 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.27.0) + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001754: {} + + classcat@5.0.5: {} + + classnames@2.5.1: {} + + compute-scroll-into-view@1.0.20: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + 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.2 + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + + css-blank-pseudo@6.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + css-has-pseudo@6.0.5(postcss@8.5.6): + dependencies: + '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + css-prefers-color-scheme@9.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + cssdb@8.4.2: {} + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + datocms-plugin-sdk@2.0.15: + dependencies: + '@datocms/cma-client': 3.4.5 + '@types/react': 17.0.89 + datocms-structured-text-utils: 2.1.12 + penpal: 4.1.1 + + datocms-react-ui@2.0.15(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + classnames: 2.5.1 + datocms-plugin-sdk: 2.0.15 + react-intersection-observer: 8.34.0(react@18.3.1) + react-select: 5.10.2(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + scroll-into-view-if-needed: 2.2.31 + transitivePeerDependencies: + - '@types/react' + - react + - react-dom + - supports-color + + datocms-structured-text-utils@2.1.12: + dependencies: + array-flatten: 3.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.28.4 + csstype: 3.1.3 + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + electron-to-chromium@1.5.249: {} + + emoji-regex@10.6.0: {} + + entities@4.5.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + estree-walker@2.0.2: {} + + final-form@4.20.10: + dependencies: + '@babel/runtime': 7.28.4 + + find-root@1.1.0: {} + + fraction.js@4.3.7: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + is-arrayish@0.2.1: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json5@2.2.3: {} + + lines-and-columns@1.2.4: {} + + lodash-es@4.17.21: {} + + 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 + + memoize-one@6.0.0: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-releases@2.0.27: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + penpal@4.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss-attribute-case-insensitive@6.0.3(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-clamp@4.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-color-functional-notation@6.0.14(postcss@8.5.6): + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + postcss-color-hex-alpha@9.0.4(postcss@8.5.6): + dependencies: + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-color-rebeccapurple@9.0.3(postcss@8.5.6): + dependencies: + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-custom-media@10.0.8(postcss@8.5.6): + dependencies: + '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + postcss: 8.5.6 + + postcss-custom-properties@13.3.12(postcss@8.5.6): + dependencies: + '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-custom-selectors@7.1.12(postcss@8.5.6): + dependencies: + '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-dir-pseudo-class@8.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-double-position-gradients@5.0.7(postcss@8.5.6): + dependencies: + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-focus-visible@9.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-focus-within@8.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-font-variant@5.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-gap-properties@5.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-image-set-function@6.0.3(postcss@8.5.6): + dependencies: + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-lab-function@6.0.19(postcss@8.5.6): + dependencies: + '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) + '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) + '@csstools/css-tokenizer': 2.4.1 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/utilities': 1.0.0(postcss@8.5.6) + postcss: 8.5.6 + + postcss-logical@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-nesting@12.1.5(postcss@8.5.6): + dependencies: + '@csstools/selector-resolve-nested': 1.1.0(postcss-selector-parser@6.1.2) + '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-opacity-percentage@2.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-overflow-shorthand@5.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-page-break@3.0.4(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-place@9.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + postcss-preset-env@9.6.0(postcss@8.5.6): + dependencies: + '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.5.6) + '@csstools/postcss-color-function': 3.0.19(postcss@8.5.6) + '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.5.6) + '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.5.6) + '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.5.6) + '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.5.6) + '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.5.6) + '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.5.6) + '@csstools/postcss-hwb-function': 3.0.18(postcss@8.5.6) + '@csstools/postcss-ic-unit': 3.0.7(postcss@8.5.6) + '@csstools/postcss-initial': 1.0.1(postcss@8.5.6) + '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.5.6) + '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.5.6) + '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.5.6) + '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.5.6) + '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.5.6) + '@csstools/postcss-logical-resize': 2.0.1(postcss@8.5.6) + '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.5.6) + '@csstools/postcss-media-minmax': 1.1.8(postcss@8.5.6) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.5.6) + '@csstools/postcss-nested-calc': 3.0.2(postcss@8.5.6) + '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.5.6) + '@csstools/postcss-oklab-function': 3.0.19(postcss@8.5.6) + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.5.6) + '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.5.6) + '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.5.6) + '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.5.6) + '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.5.6) + '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.5.6) + '@csstools/postcss-unset-value': 3.0.1(postcss@8.5.6) + autoprefixer: 10.4.21(postcss@8.5.6) + browserslist: 4.27.0 + css-blank-pseudo: 6.0.2(postcss@8.5.6) + css-has-pseudo: 6.0.5(postcss@8.5.6) + css-prefers-color-scheme: 9.0.1(postcss@8.5.6) + cssdb: 8.4.2 + postcss: 8.5.6 + postcss-attribute-case-insensitive: 6.0.3(postcss@8.5.6) + postcss-clamp: 4.1.0(postcss@8.5.6) + postcss-color-functional-notation: 6.0.14(postcss@8.5.6) + postcss-color-hex-alpha: 9.0.4(postcss@8.5.6) + postcss-color-rebeccapurple: 9.0.3(postcss@8.5.6) + postcss-custom-media: 10.0.8(postcss@8.5.6) + postcss-custom-properties: 13.3.12(postcss@8.5.6) + postcss-custom-selectors: 7.1.12(postcss@8.5.6) + postcss-dir-pseudo-class: 8.0.1(postcss@8.5.6) + postcss-double-position-gradients: 5.0.7(postcss@8.5.6) + postcss-focus-visible: 9.0.1(postcss@8.5.6) + postcss-focus-within: 8.0.1(postcss@8.5.6) + postcss-font-variant: 5.0.0(postcss@8.5.6) + postcss-gap-properties: 5.0.1(postcss@8.5.6) + postcss-image-set-function: 6.0.3(postcss@8.5.6) + postcss-lab-function: 6.0.19(postcss@8.5.6) + postcss-logical: 7.0.1(postcss@8.5.6) + postcss-nesting: 12.1.5(postcss@8.5.6) + postcss-opacity-percentage: 2.0.0(postcss@8.5.6) + postcss-overflow-shorthand: 5.0.1(postcss@8.5.6) + postcss-page-break: 3.0.4(postcss@8.5.6) + postcss-place: 9.0.1(postcss@8.5.6) + postcss-pseudo-class-any-link: 9.0.2(postcss@8.5.6) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.5.6) + postcss-selector-not: 7.0.2(postcss@8.5.6) + + postcss-pseudo-class-any-link@9.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-replace-overflow-wrap@4.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-not@7.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-final-form@6.5.9(final-form@4.20.10)(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.4 + final-form: 4.20.10 + react: 18.3.1 + + react-intersection-observer@8.34.0(react@18.3.1): + dependencies: + react: 18.3.1 + + react-is@16.13.1: {} + + react-refresh@0.17.0: {} + + react-select@5.10.2(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.4 + '@emotion/cache': 11.14.0 + '@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1) + '@floating-ui/dom': 1.7.4 + '@types/react-transition-group': 4.4.12(@types/react@18.3.26) + memoize-one: 6.0.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + use-isomorphic-layout-effect: 1.2.1(@types/react@18.3.26)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - supports-color + + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.4 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + resolve-from@4.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup@4.53.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.2 + '@rollup/rollup-android-arm64': 4.53.2 + '@rollup/rollup-darwin-arm64': 4.53.2 + '@rollup/rollup-darwin-x64': 4.53.2 + '@rollup/rollup-freebsd-arm64': 4.53.2 + '@rollup/rollup-freebsd-x64': 4.53.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.2 + '@rollup/rollup-linux-arm-musleabihf': 4.53.2 + '@rollup/rollup-linux-arm64-gnu': 4.53.2 + '@rollup/rollup-linux-arm64-musl': 4.53.2 + '@rollup/rollup-linux-loong64-gnu': 4.53.2 + '@rollup/rollup-linux-ppc64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-gnu': 4.53.2 + '@rollup/rollup-linux-riscv64-musl': 4.53.2 + '@rollup/rollup-linux-s390x-gnu': 4.53.2 + '@rollup/rollup-linux-x64-gnu': 4.53.2 + '@rollup/rollup-linux-x64-musl': 4.53.2 + '@rollup/rollup-openharmony-arm64': 4.53.2 + '@rollup/rollup-win32-arm64-msvc': 4.53.2 + '@rollup/rollup-win32-ia32-msvc': 4.53.2 + '@rollup/rollup-win32-x64-gnu': 4.53.2 + '@rollup/rollup-win32-x64-msvc': 4.53.2 + fsevents: 2.3.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + semver@6.3.1: {} + + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + source-map-js@1.2.1: {} + + source-map@0.5.7: {} + + stylis@4.2.0: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + svg-parser@2.0.4: {} + + tslib@2.8.1: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + update-browserslist-db@1.1.4(browserslist@4.27.0): + dependencies: + browserslist: 4.27.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-isomorphic-layout-effect@1.2.1(@types/react@18.3.26)(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.26 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + uuid@9.0.1: {} + + vite-plugin-svgr@4.5.0(rollup@4.53.2)(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.0)): + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.53.2) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) + vite: 5.4.21(@types/node@22.19.0) + transitivePeerDependencies: + - rollup + - supports-color + - typescript + + vite@5.4.21(@types/node@22.19.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.53.2 + optionalDependencies: + '@types/node': 22.19.0 + fsevents: 2.3.3 + + yallist@3.1.1: {} + + yaml@1.10.2: {} + + zustand@4.5.7(@types/react@18.3.26)(react@18.3.1): + dependencies: + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + react: 18.3.1 diff --git a/import-export-schema/postcss.config.cjs b/import-export-schema/postcss.config.cjs new file mode 100644 index 00000000..393892c4 --- /dev/null +++ b/import-export-schema/postcss.config.cjs @@ -0,0 +1,12 @@ +// Enable modern CSS features (including native nesting) for broader browser support +// without changing authoring style. +module.exports = { + plugins: [ + require('postcss-preset-env')({ + stage: 1, + features: { + 'nesting-rules': true, + }, + }), + ], +}; diff --git a/import-export-schema/src/components/BlankSlate.tsx b/import-export-schema/src/components/BlankSlate.tsx new file mode 100644 index 00000000..c0e15e7e --- /dev/null +++ b/import-export-schema/src/components/BlankSlate.tsx @@ -0,0 +1,24 @@ +import type { ReactNode } from 'react'; + +type Props = { + title: ReactNode; + body: ReactNode; + footer?: ReactNode; +}; + +/** + * Lightweight wrapper around the shared blank-slate markup so pages can focus on content. + */ +export function BlankSlate({ title, body, footer }: Props) { + return ( +
+
+
{title}
+
{body}
+
+ {footer ? ( +
{footer}
+ ) : null} +
+ ); +} diff --git a/import-export-schema/src/components/ExportLandingPanel.tsx b/import-export-schema/src/components/ExportLandingPanel.tsx new file mode 100644 index 00000000..4a0aa3bf --- /dev/null +++ b/import-export-schema/src/components/ExportLandingPanel.tsx @@ -0,0 +1,49 @@ +import { Button } from 'datocms-react-ui'; + +interface Props { + onSelectModels: () => void; + onExportAll: () => void | Promise; + exportAllDisabled: boolean; + title?: string; + description?: string; + selectLabel?: string; + exportAllLabel?: string; +} + +/** + * First step of the export flow that offers the quick actions without surfacing + * the detailed selection UI. + */ +export function ExportLandingPanel({ + onSelectModels, + onExportAll, + exportAllDisabled, + title = 'Start a new export', + description = 'Choose how you want to start the export process.', + selectLabel = 'Export select models', + exportAllLabel = 'Export entire schema', +}: Props) { + return ( +
+
{title}
+
+

{description}

+
+ + +
+
+
+ ); +} diff --git a/import-export-schema/src/components/ExportSelectionPanel.tsx b/import-export-schema/src/components/ExportSelectionPanel.tsx new file mode 100644 index 00000000..b4323bed --- /dev/null +++ b/import-export-schema/src/components/ExportSelectionPanel.tsx @@ -0,0 +1,105 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import { Button, SelectField } from 'datocms-react-ui'; +import { useMemo } from 'react'; + +type MultiOption = { label: string; value: string }; +type SelectGroup = { + label?: string; + options: readonly OptionType[]; +}; + +type Props = { + selectId: string; + itemTypes?: SchemaTypes.ItemType[]; + selectedIds: string[]; + onSelectedIdsChange: (ids: string[]) => void; + onStart: () => void; + onBack: () => void; + startDisabled: boolean; + title?: string; + description?: string; + selectLabel?: string; + startLabel?: string; + backLabel?: string; +}; + +/** + * Secondary step of the export flow that lets editors pick targeted models/blocks + * before jumping into the dependency graph. + */ +export function ExportSelectionPanel({ + selectId, + itemTypes, + selectedIds, + onSelectedIdsChange, + onStart, + onBack, + startDisabled, + title = 'Select models to export', + description = 'Choose the models and blocks you want to inspect. You can refine the selection on the next screen.', + selectLabel = 'Starting models/blocks', + startLabel = 'Export selection', + backLabel = 'Back', +}: Props) { + const options = useMemo( + () => + (itemTypes ?? []).map((itemType) => ({ + value: itemType.id, + label: `${itemType.attributes.name}${ + itemType.attributes.modular_block ? ' (Block)' : '' + }`, + })), + [itemTypes], + ); + + // React-Select expects objects; keep them memoized so the control stays controlled. + const value = useMemo( + () => options.filter((option) => selectedIds.includes(option.value)), + [options, selectedIds], + ); + + return ( +
+
{title}
+
+

{description}

+
+
+ > + id={selectId} + name="export-initial-model" + label={selectLabel} + selectInputProps={{ + isMulti: true, + isClearable: true, + isDisabled: !itemTypes, + options, + placeholder: 'Choose models/blocks…', + }} + value={value} + onChange={(multi) => + onSelectedIdsChange( + Array.isArray(multi) + ? multi.map((option) => option.value) + : [], + ) + } + /> +
+
+ + +
+
+
+
+ ); +} diff --git a/import-export-schema/src/components/GraphCanvas.tsx b/import-export-schema/src/components/GraphCanvas.tsx new file mode 100644 index 00000000..88e69f5b --- /dev/null +++ b/import-export-schema/src/components/GraphCanvas.tsx @@ -0,0 +1,44 @@ +import type { NodeMouseHandler, NodeTypes } from '@xyflow/react'; +import { Background, ReactFlow } from '@xyflow/react'; +import type { AppNode, Graph } from '@/utils/graph/types'; + +type Props = { + graph: Graph; + nodeTypes: NodeTypes; + edgeTypes: Parameters[0]['edgeTypes']; + onNodeClick?: NodeMouseHandler | NodeMouseHandler; + style?: React.CSSProperties; + fitView?: boolean; +}; + +/** + * Shared React Flow canvas configuration to keep export/import graphs consistent. + */ +export function GraphCanvas({ + graph, + nodeTypes, + edgeTypes, + onNodeClick, + style, + fitView = true, +}: Props) { + return ( + + + + ); +} diff --git a/import-export-schema/src/components/ItemTypeNodeRenderer.tsx b/import-export-schema/src/components/ItemTypeNodeRenderer.tsx index b091e8fd..3f4479d2 100644 --- a/import-export-schema/src/components/ItemTypeNodeRenderer.tsx +++ b/import-export-schema/src/components/ItemTypeNodeRenderer.tsx @@ -1,4 +1,3 @@ -import { Schema } from '@/utils/icons'; import type { SchemaTypes } from '@datocms/cma-client'; import { Handle, @@ -10,8 +9,9 @@ import { useStore, } from '@xyflow/react'; import classNames from 'classnames'; -import { sortBy } from 'lodash-es'; +import sortBy from 'lodash-es/sortBy'; import { useState } from 'react'; +import { Schema } from '@/utils/icons'; import { Field } from '../components/Field'; export type ItemTypeNode = Node< @@ -23,10 +23,17 @@ export type ItemTypeNode = Node< 'itemType' >; +/** + * Renders a fieldset summary inside the item-type tooltip, keeping field ordering in sync + * with their schema positions. + */ function Fieldset({ fieldset, allFields, -}: { fieldset: SchemaTypes.Fieldset; allFields: SchemaTypes.Field[] }) { +}: { + fieldset: SchemaTypes.Fieldset; + allFields: SchemaTypes.Field[]; +}) { return (
{fieldset.attributes.title}
@@ -36,7 +43,7 @@ function Fieldset({ (f) => f.relationships.fieldset.data?.id === fieldset.id, ), 'attributes.position', - ).map((field) => ( + ).map((field: SchemaTypes.Field) => ( ))}
@@ -44,8 +51,13 @@ function Fieldset({ ); } +// Show extra metadata once the canvas is sufficiently zoomed in. const zoomSelector = (s: ReactFlowState) => s.transform[2] >= 0.8; +/** + * Node renderer used by React Flow to display a DatoCMS model/block with a hoverable + * field list and API key details that show when zoomed in. + */ export function ItemTypeNodeRenderer({ data: { itemType, fields, fieldsets }, className, @@ -72,27 +84,26 @@ export function ItemTypeNodeRenderer({ isVisible={isTooltipVisible} className="tooltip" > - {fields.length + fieldsets.length === 0 ? ( - <>No fields - ) : ( - sortBy( - [ - ...fields.filter((e) => !e.relationships.fieldset.data), - ...fieldsets, - ], - 'attributes.position', - ).map((fieldOrFieldset) => - fieldOrFieldset.type === 'field' ? ( - - ) : ( -
- ), - ) - )} + {fields.length + fieldsets.length === 0 + ? 'No fields' + : sortBy( + [ + ...fields.filter((e) => !e.relationships.fieldset.data), + ...fieldsets, + ], + 'attributes.position', + ).map( + (fieldOrFieldset: SchemaTypes.Field | SchemaTypes.Fieldset) => + fieldOrFieldset.type === 'field' ? ( + + ) : ( +
+ ), + )}
setTooltipVisible(true)} onMouseLeave={() => setTooltipVisible(false)} > diff --git a/import-export-schema/src/components/ProgressOverlay.tsx b/import-export-schema/src/components/ProgressOverlay.tsx new file mode 100644 index 00000000..6c5f3027 --- /dev/null +++ b/import-export-schema/src/components/ProgressOverlay.tsx @@ -0,0 +1,130 @@ +import { Button } from 'datocms-react-ui'; +import type { PropsWithChildren } from 'react'; +import ProgressStallNotice from './ProgressStallNotice'; + +type CancelProps = { + label: string; + intent?: 'negative' | 'muted'; + disabled?: boolean; + onCancel: () => void; + size?: 's' | 'm'; +}; + +type ProgressData = { + label?: string; + done?: number; + total?: number; + percentOverride?: number; +}; + +type Props = { + title: string; + subtitle?: string; + progress: ProgressData; + stallCurrent?: number | undefined; + ariaLabel: string; + cancel?: CancelProps; + overlayZIndex?: number; +}; + +// Ensure the progress bar always renders with a minimal width when progress is unknown. +function clampPercent(value: number | undefined): number { + if (typeof value !== 'number' || Number.isNaN(value)) return 0.1; + return Math.min(1, Math.max(0, value)); +} + +function resolvePercent(progress: ProgressData): number { + if (typeof progress.percentOverride === 'number') { + return clampPercent(progress.percentOverride); + } + if ( + typeof progress.done === 'number' && + typeof progress.total === 'number' && + progress.total > 0 + ) { + return clampPercent(progress.done / progress.total); + } + return 0.1; +} + +/** + * Fullscreen overlay that shows a determinate progress bar, optional stall warning, + * and cancel affordance while long-running tasks execute. + */ +export function ProgressOverlay({ + title, + subtitle, + progress, + stallCurrent, + ariaLabel, + cancel, + overlayZIndex = 9999, +}: PropsWithChildren) { + const percent = resolvePercent(progress); + const totalText = + typeof progress.total === 'number' && progress.total > 0 + ? `${progress.done ?? 0} / ${progress.total}` + : ''; + + return ( +
+
+
{title}
+ {subtitle ? ( +
{subtitle}
+ ) : null} + +
0 + ? progress.total + : undefined + } + aria-valuenow={ + typeof progress.total === 'number' && progress.total > 0 + ? progress.done + : undefined + } + > +
+
+
+
{progress.label ?? ''}
+
{totalText}
+
+ + {cancel ? ( +
+ +
+ ) : null} +
+
+ ); +} diff --git a/import-export-schema/src/components/ProgressStallNotice.tsx b/import-export-schema/src/components/ProgressStallNotice.tsx new file mode 100644 index 00000000..bf2b274c --- /dev/null +++ b/import-export-schema/src/components/ProgressStallNotice.tsx @@ -0,0 +1,68 @@ +import { useEffect, useRef, useState } from 'react'; + +type Props = { + // Current completed unit (eg. done/finished). Pass undefined to disable. + current: number | undefined; + // Minimum time without progress before showing the notice. + thresholdMs?: number; + // Optional custom message. + message?: string; +}; + +/** + * Surface a gentle warning when long-running tasks appear stuck, while avoiding + * false positives during the initial idle period. + */ +export default function ProgressStallNotice({ + current, + thresholdMs = 8000, + message = 'We made too many requests in a short time. Progress may look paused and should resume automatically in a few seconds.', +}: Props) { + const [stalled, setStalled] = useState(false); + const lastValueRef = useRef(undefined); + const lastChangeAtRef = useRef(undefined); + const hasStartedRef = useRef(false); + + // Track progress changes + useEffect(() => { + if (typeof current !== 'number') { + // Reset when disabled/hidden + lastValueRef.current = undefined; + lastChangeAtRef.current = undefined; + hasStartedRef.current = false; + setStalled(false); + return; + } + + if (lastValueRef.current !== current) { + lastValueRef.current = current; + lastChangeAtRef.current = Date.now(); + if (current > 0) { + hasStartedRef.current = true; + } + // Any change clears a stall + setStalled(false); + } + }, [current]); + + // Timer to detect stalls + useEffect(() => { + const id = window.setInterval(() => { + if (!hasStartedRef.current) return; // don't warn before any real progress + if (typeof lastChangeAtRef.current !== 'number') return; + const since = Date.now() - lastChangeAtRef.current; + if (since >= thresholdMs) { + setStalled(true); + } + }, 500); + return () => window.clearInterval(id); + }, [thresholdMs]); + + if (!stalled) return null; + + return ( +
+ {message} +
+ ); +} diff --git a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/Collapsible.tsx b/import-export-schema/src/components/SchemaOverview/Collapsible.tsx similarity index 58% rename from import-export-schema/src/entrypoints/ImportPage/ConflictsManager/Collapsible.tsx rename to import-export-schema/src/components/SchemaOverview/Collapsible.tsx index fb647324..23d5c4ea 100644 --- a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/Collapsible.tsx +++ b/import-export-schema/src/components/SchemaOverview/Collapsible.tsx @@ -1,25 +1,33 @@ import type { SchemaTypes } from '@datocms/cma-client'; import { + faCircleExclamation, faCaretRight as faCollapsed, faCaretDown as faExpanded, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import { type ReactNode, useContext, useEffect, useRef } from 'react'; -import { SelectedEntityContext } from '../SelectedEntityContext'; +import { SelectedEntityContext } from './SelectedEntityContext'; type Props = { entity: SchemaTypes.ItemType | SchemaTypes.Plugin; invalid?: boolean; + hasConflict?: boolean; title: ReactNode; children: ReactNode; + className?: string; }; +/** + * Accordion-style wrapper that also syncs with the graph selection context. + */ export default function Collapsible({ entity, invalid, + hasConflict = false, title, children, + className, }: Props) { const elRef = useRef(null); @@ -46,18 +54,36 @@ export default function Collapsible({ 'conflict', isSelected && 'conflict--selected', invalid && 'conflict--invalid', + hasConflict && 'conflict--has-conflict', + className, )} ref={elRef} > -
- {title} -
-
+ + {title} + {hasConflict ? ( + + + Conflict + + ) : null} + +
{children} -
+
); } diff --git a/import-export-schema/src/components/SchemaOverview/SelectedEntityContext.tsx b/import-export-schema/src/components/SchemaOverview/SelectedEntityContext.tsx new file mode 100644 index 00000000..3ab853fc --- /dev/null +++ b/import-export-schema/src/components/SchemaOverview/SelectedEntityContext.tsx @@ -0,0 +1,17 @@ +// Provides a shared context for the schema overview to track the currently +// selected entity and expose a setter used by graph interactions. +import type { SchemaTypes } from '@datocms/cma-client'; +import { createContext } from 'react'; + +type SelectedEntityContextValue = { + entity?: SchemaTypes.ItemType | SchemaTypes.Plugin; + set: ( + entity: SchemaTypes.ItemType | SchemaTypes.Plugin | undefined, + zoomIn?: boolean, + ) => void; +}; + +export const SelectedEntityContext = createContext({ + entity: undefined, + set: () => {}, +}); diff --git a/import-export-schema/src/components/TaskOverlayStack.tsx b/import-export-schema/src/components/TaskOverlayStack.tsx new file mode 100644 index 00000000..2301b5b9 --- /dev/null +++ b/import-export-schema/src/components/TaskOverlayStack.tsx @@ -0,0 +1,28 @@ +import { + TaskProgressOverlay, + type TaskProgressOverlayProps, +} from '@/components/TaskProgressOverlay'; + +// Small wrapper that lets import/export flows list every long-running task overlay they +// need (single export, mass export, import) without repeating overlay wiring at each call. + +type OverlayConfig = TaskProgressOverlayProps & { id?: string | number }; + +type Props = { + items: OverlayConfig[]; +}; + +/** + * Render a list of long-task overlays while keeping individual config definitions concise. + * Centralizes the overlay stack so the entrypoints simply hand us the configs for any + * concurrent export/import tasks instead of juggling multiple ``s. + */ +export function TaskOverlayStack({ items }: Props) { + return ( + <> + {items.map(({ id, ...config }, index) => ( + + ))} + + ); +} diff --git a/import-export-schema/src/components/TaskProgressOverlay.tsx b/import-export-schema/src/components/TaskProgressOverlay.tsx new file mode 100644 index 00000000..9060c854 --- /dev/null +++ b/import-export-schema/src/components/TaskProgressOverlay.tsx @@ -0,0 +1,77 @@ +import { ProgressOverlay } from '@/components/ProgressOverlay'; +import type { + LongTaskProgress, + LongTaskState, + UseLongTaskResult, +} from '@/shared/tasks/useLongTask'; + +// Adapter that presents a running `useLongTask` instance inside the shared modal overlay UI +// used throughout the import/export flows. + +type CancelOptions = { + label: string; + onCancel: () => void | Promise; + intent?: 'negative' | 'muted'; + disabled?: boolean; +}; + +export type TaskProgressOverlayProps = { + task: UseLongTaskResult; + title: string; + subtitle: string | ((state: LongTaskState) => string); + ariaLabel: string; + overlayZIndex?: number; + progressLabel?: (progress: LongTaskProgress, state: LongTaskState) => string; + percentOverride?: number; + cancel?: (state: LongTaskState) => CancelOptions | undefined; +}; + +/** + * Convenience wrapper over the shared modal overlay component so long-running imports and + * exports surface consistent progress UI. Callers pass their `useLongTask` handle plus + * lightweight callbacks for dynamic subtitles, labels, or cancel behavior. + */ +export function TaskProgressOverlay({ + task, + title, + subtitle, + ariaLabel, + overlayZIndex, + progressLabel, + percentOverride, + cancel, +}: TaskProgressOverlayProps) { + if (task.state.status !== 'running') { + // The overlay only renders while the task is active; once it resolves the modal + // disappears so the page can show completion state instead. + return null; + } + + const state = task.state; + const progress = state.progress; + // Subtitle/label hooks let the overlay speak to the current step (eg "Fetching records") + // without duplicating formatting logic where the task is started. + const resolvedSubtitle = + typeof subtitle === 'function' ? subtitle(state) : subtitle; + const label = progressLabel ? progressLabel(progress, state) : progress.label; + const cancelProps = cancel?.(state); + + return ( + + ); +} diff --git a/import-export-schema/src/entrypoints/Config/index.tsx b/import-export-schema/src/entrypoints/Config/index.tsx index 9241a9bf..99170a59 100644 --- a/import-export-schema/src/entrypoints/Config/index.tsx +++ b/import-export-schema/src/entrypoints/Config/index.tsx @@ -6,6 +6,7 @@ type Props = { ctx: RenderConfigScreenCtx; }; +/** Lightweight anchor that uses the plugin navigation API instead of full page loads. */ function Link({ href, children }: { href: string; children: ReactNode }) { const ctx = useCtx(); @@ -22,9 +23,14 @@ function Link({ href, children }: { href: string; children: ReactNode }) { ); } +/** Configuration screen shown in Settings → Plugins. */ export function Config({ ctx }: Props) { - const schemaUrl = `${ctx.isEnvironmentPrimary ? '' : `/environments/${ctx.environment}`}/schema`; - const pageUrl = `${ctx.isEnvironmentPrimary ? '' : `/environments/${ctx.environment}`}/configuration/p/${ctx.plugin.id}/pages/import-export`; + const environmentPath = ctx.isEnvironmentPrimary + ? '' + : `/environments/${ctx.environment}`; + const schemaUrl = `${environmentPath}/schema`; + const importUrl = `${environmentPath}/configuration/p/${ctx.plugin.id}/pages/import`; + const exportUrl = `${environmentPath}/configuration/p/${ctx.plugin.id}/pages/export`; return ( @@ -41,12 +47,18 @@ export function Config({ ctx }: Props) {
  • - Need to import some models/blocks from an already generated export? - Go to the{' '} - - Schema > Import/Export section + To import models/blocks from an exported JSON, go to the{' '} + + Schema > Import {' '} - on the sidebar. + page in the sidebar. +
  • +
  • + To export a selection or the entire schema, go to the{' '} + + Schema > Export + {' '} + page.
  • diff --git a/import-export-schema/src/entrypoints/ExportHome/index.tsx b/import-export-schema/src/entrypoints/ExportHome/index.tsx new file mode 100644 index 00000000..8d93db28 --- /dev/null +++ b/import-export-schema/src/entrypoints/ExportHome/index.tsx @@ -0,0 +1,269 @@ +/** + * Export navigation flow: + * + * ┌──────────┐ onSelectModels ┌────────────┐ onStart (with selection) ┌──────────┐ + * │ Landing │ ──────────────────▶ │ Selection │ ─────────────────────────────▶│ Graph │ + * └──────────┘ onExportAll runs └────────────┘ onBack ⟲ Landing └──────────┘ + */ +import { ReactFlowProvider } from '@xyflow/react'; +import type { RenderPageCtx } from 'datocms-plugin-sdk'; +import { Canvas } from 'datocms-react-ui'; +import { useId, useState } from 'react'; +import { ExportLandingPanel } from '@/components/ExportLandingPanel'; +import { ExportSelectionPanel } from '@/components/ExportSelectionPanel'; +import { TaskOverlayStack } from '@/components/TaskOverlayStack'; +import { useExportAllHandler } from '@/shared/hooks/useExportAllHandler'; +import { useExportSelection } from '@/shared/hooks/useExportSelection'; +import { useProjectSchema } from '@/shared/hooks/useProjectSchema'; +import { useSchemaExportTask } from '@/shared/hooks/useSchemaExportTask'; +import { + type UseLongTaskResult, + useLongTask, +} from '@/shared/tasks/useLongTask'; +import ExportInner from '../ExportPage/Inner'; + +type Props = { + ctx: RenderPageCtx; +}; + +type ExportView = 'landing' | 'selection' | 'graph'; + +type OverlayItems = Parameters[0]['items']; + +type BuildOverlayItemsArgs = { + exportAllTask: UseLongTaskResult; + exportPreparingTask: UseLongTaskResult; + exportSelectionTask: UseLongTaskResult; + exportPreparingPercent: number; +}; + +// Keep overlay wiring centralized so the JSX tree stays readable and we can reuse +// the same overlay definitions if we ever need them elsewhere (e.g. tests). +function buildOverlayItems({ + exportAllTask, + exportPreparingTask, + exportSelectionTask, + exportPreparingPercent, +}: BuildOverlayItemsArgs): OverlayItems { + return [ + { + id: 'export-all', + task: exportAllTask, + title: 'Exporting entire schema', + subtitle: 'Sit tight, we’re gathering models, blocks, and plugins…', + ariaLabel: 'Export in progress', + progressLabel: (progress) => progress.label ?? 'Loading project schema…', + cancel: () => ({ + label: 'Cancel export', + intent: exportAllTask.state.cancelRequested ? 'muted' : 'negative', + disabled: exportAllTask.state.cancelRequested, + onCancel: () => exportAllTask.controller.requestCancel(), + }), + }, + { + id: 'export-preparing', + task: exportPreparingTask, + title: 'Preparing export', + subtitle: 'Sit tight, we’re setting up your models, blocks, and plugins…', + ariaLabel: 'Preparing export', + progressLabel: (progress) => progress.label ?? 'Preparing export…', + percentOverride: exportPreparingPercent, + }, + { + id: 'export-selection', + task: exportSelectionTask, + title: 'Exporting selection', + subtitle: 'Sit tight, we’re gathering models, blocks, and plugins…', + ariaLabel: 'Export in progress', + progressLabel: (progress) => progress.label ?? 'Preparing export…', + cancel: () => ({ + label: 'Cancel export', + intent: exportSelectionTask.state.cancelRequested + ? 'muted' + : 'negative', + disabled: exportSelectionTask.state.cancelRequested, + onCancel: () => exportSelectionTask.controller.requestCancel(), + }), + }, + ]; +} + +/** + * Landing page for the export workflow. Guides the user from the initial action + * choice into the detailed graph view while coordinating the long-running tasks. + */ +export default function ExportHome({ ctx }: Props) { + // ----- Schema + selection state ----- + // Seed the selection autocomplete and schema data used by every subview; we + // establish this once so each panel can rely on the same shared data. + const exportInitialSelectId = useId(); + const projectSchema = useProjectSchema(ctx); + + const { + allItemTypes, + selectedIds: exportInitialItemTypeIds, + selectedItemTypes: exportInitialItemTypes, + setSelectedIds: setExportInitialItemTypeIds, + } = useExportSelection({ schema: projectSchema }); + + const [view, setView] = useState('landing'); + + // ----- Long-running tasks ----- + // The export flow manipulates three distinct tasks (export all, prepare graph, + // targeted export). + const exportAllTask = useLongTask(); + const exportPreparingTask = useLongTask(); + const { task: exportSelectionTask, runExport: runSelectionExport } = + useSchemaExportTask({ + schema: projectSchema, + ctx, + }); + + // Smoothed percent for preparing overlay to avoid jitter and changing max + const [exportPreparingPercent, setExportPreparingPercent] = useState(0.1); + + const runExportAll = useExportAllHandler({ + ctx, + schema: projectSchema, + task: exportAllTask.controller, + }); + + // Determine once whether the user has made a selection and keep the root item + // handy so downstream callbacks stay simple. + const hasSelection = exportInitialItemTypeIds.length > 0; + const rootItemTypeId = hasSelection ? exportInitialItemTypeIds[0] : undefined; + + const handleLandingSelect = () => { + setView('selection'); + }; + + const handleBackToLanding = () => { + setView('landing'); + }; + + const handleStartSelection = () => { + if (!hasSelection) { + return; + } + + exportPreparingTask.controller.start({ + label: 'Preparing export…', + }); + setExportPreparingPercent(0.1); + setView('graph'); + }; + + const handleGraphPrepared = () => { + setExportPreparingPercent(1); + exportPreparingTask.controller.complete({ + label: 'Graph prepared', + }); + }; + + const handlePrepareProgress = (progress: { + done: number; + total: number; + label: string; + phase?: 'scan' | 'build'; + }) => { + if (exportPreparingTask.state.status !== 'running') { + exportPreparingTask.controller.start(progress); + } else { + exportPreparingTask.controller.setProgress(progress); + } + + // When the task cannot provide a total, gently advance toward 25% so the + // overlay feels alive; otherwise map the true progress into the remaining + // 75% so the visual bar always pushes forward. This may seem like a hack, + // but it's the best way to keep the overlay feeling alive while the task is running. + const hasFixedTotal = (progress.total ?? 0) > 0; + const raw = hasFixedTotal ? progress.done / progress.total : 0; + + if (!hasFixedTotal) { + setExportPreparingPercent((prev) => + Math.min(0.25, Math.max(prev, prev + 0.02)), + ); + } else { + const mapped = 0.25 + raw * 0.75; + setExportPreparingPercent((prev) => Math.max(prev, Math.min(1, mapped))); + } + }; + + const handleCloseGraph = () => { + setView('selection'); + setExportPreparingPercent(0.1); + exportPreparingTask.controller.reset(); + }; + + const handleSelectionExport = ( + itemTypeIds: string[], + pluginIds: string[], + ) => { + if (!rootItemTypeId) { + return; + } + + void runSelectionExport({ + rootItemTypeId, + itemTypeIds, + pluginIds, + }); + }; + + // The view map condenses our conditional rendering into a single lookup and + // keeps each subview’s JSX colocated with the handlers it consumes. + const viewContent: Record = { + landing: ( + + ), + selection: ( + + ), + graph: ( + + ), + }; + + // Precompute overlay config outside the JSX so the render tree remains easy to + // scan and future overlays only require extending this list. + const overlayItems = buildOverlayItems({ + exportAllTask, + exportPreparingTask, + exportSelectionTask, + exportPreparingPercent, + }); + + return ( + + +
    +
    +
    {viewContent[view]}
    +
    +
    +
    + + {/* Blocking overlay while exporting all */} + +
    + ); +} diff --git a/import-export-schema/src/entrypoints/ExportPage/DependencyActionsPanel.module.css b/import-export-schema/src/entrypoints/ExportPage/DependencyActionsPanel.module.css new file mode 100644 index 00000000..23868e9a --- /dev/null +++ b/import-export-schema/src/entrypoints/ExportPage/DependencyActionsPanel.module.css @@ -0,0 +1,5 @@ +.actions { + display: flex; + gap: 8px; + align-items: center; +} diff --git a/import-export-schema/src/entrypoints/ExportPage/DependencyActionsPanel.tsx b/import-export-schema/src/entrypoints/ExportPage/DependencyActionsPanel.tsx new file mode 100644 index 00000000..437d5c2a --- /dev/null +++ b/import-export-schema/src/entrypoints/ExportPage/DependencyActionsPanel.tsx @@ -0,0 +1,59 @@ +import { faFileExport } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Panel } from '@xyflow/react'; +import { Button, Spinner } from 'datocms-react-ui'; +import styles from './DependencyActionsPanel.module.css'; + +type Props = { + selectingDependencies: boolean; + areAllDependenciesSelected: boolean; + selectedItemCount: number; + onSelectAllDependencies: () => void; + onUnselectAllDependencies: () => void; + onExport: () => void; +}; + +/** + * Sticky controls rendered over the graph to handle dependency selection + export CTA. + */ +export function DependencyActionsPanel({ + selectingDependencies, + areAllDependenciesSelected, + selectedItemCount, + onSelectAllDependencies, + onUnselectAllDependencies, + onExport, +}: Props) { + return ( + +
    + + {selectingDependencies && } + + +
    +
    + ); +} diff --git a/import-export-schema/src/entrypoints/ExportPage/ExportItemTypeNodeRenderer.tsx b/import-export-schema/src/entrypoints/ExportPage/ExportItemTypeNodeRenderer.tsx index 45427e23..800da24d 100644 --- a/import-export-schema/src/entrypoints/ExportPage/ExportItemTypeNodeRenderer.tsx +++ b/import-export-schema/src/entrypoints/ExportPage/ExportItemTypeNodeRenderer.tsx @@ -1,25 +1,34 @@ +import type { NodeProps } from '@xyflow/react'; +import classNames from 'classnames'; +import { useContext } from 'react'; import { type ItemTypeNode, ItemTypeNodeRenderer, } from '@/components/ItemTypeNodeRenderer'; +import { SelectedEntityContext } from '@/components/SchemaOverview/SelectedEntityContext'; import { EntitiesToExportContext } from '@/entrypoints/ExportPage/EntitiesToExportContext'; -import type { NodeProps } from '@xyflow/react'; -import { useContext } from 'react'; +/** + * Highlights item-type nodes that fall outside the export selection. + */ export function ExportItemTypeNodeRenderer(props: NodeProps) { const { itemType } = props.data; const entitiesToExport = useContext(EntitiesToExportContext); + const selectedEntityContext = useContext(SelectedEntityContext); + + const excluded = + entitiesToExport && !entitiesToExport.itemTypeIds.includes(itemType.id); + const isFocused = selectedEntityContext.entity === itemType; return ( ); } diff --git a/import-export-schema/src/entrypoints/ExportPage/ExportPluginNodeRenderer.tsx b/import-export-schema/src/entrypoints/ExportPage/ExportPluginNodeRenderer.tsx index f73c959a..86132876 100644 --- a/import-export-schema/src/entrypoints/ExportPage/ExportPluginNodeRenderer.tsx +++ b/import-export-schema/src/entrypoints/ExportPage/ExportPluginNodeRenderer.tsx @@ -1,16 +1,21 @@ +import type { NodeProps } from '@xyflow/react'; +import classNames from 'classnames'; +import { useContext } from 'react'; import { type PluginNode, PluginNodeRenderer, } from '@/components/PluginNodeRenderer'; +import { SelectedEntityContext } from '@/components/SchemaOverview/SelectedEntityContext'; import { EntitiesToExportContext } from '@/entrypoints/ExportPage/EntitiesToExportContext'; -import type { NodeProps } from '@xyflow/react'; -import classNames from 'classnames'; -import { useContext } from 'react'; +/** + * Wraps the generic plugin renderer to flag nodes that are outside the current export selection. + */ export function ExportPluginNodeRenderer(props: NodeProps) { const { plugin } = props.data; const entitiesToExport = useContext(EntitiesToExportContext); + const selectedEntityContext = useContext(SelectedEntityContext); return ( ) { className={classNames( entitiesToExport && !entitiesToExport.pluginIds.includes(plugin.id) && - 'app-node__excluded-from-export', + 'app-node--excluded', + selectedEntityContext.entity === plugin && 'app-node--focused', )} /> ); diff --git a/import-export-schema/src/entrypoints/ExportPage/ExportSchema.ts b/import-export-schema/src/entrypoints/ExportPage/ExportSchema.ts index 3c153379..ed9e4c90 100644 --- a/import-export-schema/src/entrypoints/ExportPage/ExportSchema.ts +++ b/import-export-schema/src/entrypoints/ExportPage/ExportSchema.ts @@ -1,11 +1,21 @@ +/** + * 'bulk exports' widened this helper so importer/graph code could reuse + * the same normalized view of a schema export. v2 exports optionally declare multiple root + * item types, but v1 files assumed a single implicit root; the coalescing logic keeps + * both behaviours working so older exports still import cleanly. + */ +import type { SchemaTypes } from '@datocms/cma-client'; +import get from 'lodash-es/get'; import { findLinkedItemTypeIds } from '@/utils/datocms/schema'; import { isDefined } from '@/utils/isDefined'; import type { ExportDoc } from '@/utils/types'; -import type { SchemaTypes } from '@datocms/cma-client'; -import { get } from 'lodash-es'; +/** + * Normalizes an export document into easy-to-query maps, helping both import and graph builders. + */ export class ExportSchema { public rootItemType: SchemaTypes.ItemType; + public rootItemTypes: SchemaTypes.ItemType[]; public itemTypesById: Map; public pluginsById: Map; public fieldsById: Map; @@ -16,55 +26,82 @@ export class ExportSchema { for (const itemType of exportDoc.entities.filter( (e): e is SchemaTypes.ItemType => e.type === 'item_type', )) { - this.itemTypesById.set(itemType.id, itemType); + // Normalize ID to string to avoid number/string key mismatches + this.itemTypesById.set(String(itemType.id), itemType); } this.pluginsById = new Map(); for (const plugin of exportDoc.entities.filter( (e): e is SchemaTypes.Plugin => e.type === 'plugin', )) { - this.pluginsById.set(plugin.id, plugin); + this.pluginsById.set(String(plugin.id), plugin); } this.fieldsById = new Map(); for (const field of exportDoc.entities.filter( (e): e is SchemaTypes.Field => e.type === 'field', )) { - this.fieldsById.set(field.id, field); + this.fieldsById.set(String(field.id), field); } this.fieldsetsById = new Map(); for (const fieldset of exportDoc.entities.filter( (e): e is SchemaTypes.Fieldset => e.type === 'fieldset', )) { - this.fieldsetsById.set(fieldset.id, fieldset); + this.fieldsetsById.set(String(fieldset.id), fieldset); } - if (exportDoc.version === '1') { - const targetItemTypeIds = new Set(); - - for (const field of this.fields) { - const itemTypeId = field.relationships.item_type.data.id; - for (const linkedItemTypeId of findLinkedItemTypeIds(field)) { - if (linkedItemTypeId !== itemTypeId) { - targetItemTypeIds.add(linkedItemTypeId); - } + // Compute roots by inspecting field link targets (no incoming edges) + const targetItemTypeIds = new Set(); + for (const field of this.fields) { + const itemTypeId = String(field.relationships.item_type.data.id); + for (const linkedItemTypeId of findLinkedItemTypeIds(field)) { + if (String(linkedItemTypeId) !== itemTypeId) { + targetItemTypeIds.add(String(linkedItemTypeId)); } } + } - const rootItemTypes = this.itemTypes.filter( - (itemType) => !targetItemTypeIds.has(itemType.id), - ); + this.rootItemTypes = this.itemTypes.filter( + (itemType) => !targetItemTypeIds.has(itemType.id), + ); - if (rootItemTypes.length !== 1) { + if (exportDoc.version === '1') { + if (this.rootItemTypes.length !== 1) { throw new Error( 'This export file was generated by an older version of this plugin, and it is invalid because the initial model/block model cannot be determined. Please update to the most recent version of the plugin and export your schema once more.', ); } - - this.rootItemType = rootItemTypes[0]; + this.rootItemType = this.rootItemTypes[0]; } else { - this.rootItemType = this.getItemTypeById(exportDoc.rootItemTypeId); + // Prefer explicit root in v2 for backward compatibility; fall back to computed roots + const explicitRootId = exportDoc.rootItemTypeId; + if (explicitRootId) { + this.rootItemType = this.getItemTypeById(String(explicitRootId)); + // Ensure it exists in the list, even if incoming edge analysis differs + if ( + !this.rootItemTypes.find( + (it) => String(it.id) === String(explicitRootId), + ) + ) { + this.rootItemTypes = [this.rootItemType, ...this.rootItemTypes]; + } + } else if (this.rootItemTypes.length > 0) { + this.rootItemType = this.rootItemTypes[0]; + } else { + // Fallback: pick any item type present + const any = this.itemTypes[0]; + if (!any) { + throw new Error('Invalid export: missing item types'); + } + this.rootItemType = any; + this.rootItemTypes = [any]; + } + } + + this.rootItemTypes = this.ensureRootCoverage(this.rootItemTypes); + if (!this.rootItemType && this.rootItemTypes.length > 0) { + this.rootItemType = this.rootItemTypes[0]; } } @@ -85,7 +122,7 @@ export class ExportSchema { } getItemTypeById(itemTypeId: string) { - const itemType = this.itemTypesById.get(itemTypeId); + const itemType = this.itemTypesById.get(String(itemTypeId)); if (!itemType) { throw new Error('Not existing'); @@ -95,7 +132,7 @@ export class ExportSchema { } getPluginById(pluginId: string) { - const plugin = this.pluginsById.get(pluginId); + const plugin = this.pluginsById.get(String(pluginId)); if (!plugin) { throw new Error('Not existing'); @@ -108,8 +145,8 @@ export class ExportSchema { return ( get(itemType, 'relationships.fields.data', []) as Array<{ id: string }> ) - .map((f) => f.id) - .map((fid) => this.fieldsById.get(fid)) + .map((f) => String(f.id)) + .map((fid) => this.fieldsById.get(String(fid))) .filter(isDefined); } @@ -119,8 +156,98 @@ export class ExportSchema { id: string; }> ) - .map((fs) => fs.id) - .map((fsid) => this.fieldsetsById.get(fsid)) + .map((fs) => String(fs.id)) + .map((fsid) => this.fieldsetsById.get(String(fsid))) .filter(isDefined); } + + private ensureRootCoverage( + initialRoots: SchemaTypes.ItemType[], + ): SchemaTypes.ItemType[] { + if (this.itemTypes.length === 0) { + return []; + } + + const adjacency = new Map>(); + + const ensureNeighbors = (id: string) => { + const key = String(id); + if (!adjacency.has(key)) { + adjacency.set(key, new Set()); + } + return adjacency.get(key)!; + }; + + for (const itemType of this.itemTypes) { + const currentId = String(itemType.id); + const neighbors = ensureNeighbors(currentId); + const fields = this.getItemTypeFields(itemType); + + for (const field of fields) { + for (const rawLinkedId of findLinkedItemTypeIds(field)) { + const linkedId = String(rawLinkedId); + if (!this.itemTypesById.has(linkedId) || linkedId === currentId) { + continue; + } + neighbors.add(linkedId); + ensureNeighbors(linkedId).add(currentId); + } + } + } + + const visited = new Set(); + const result: SchemaTypes.ItemType[] = []; + const resultIds = new Set(); + + const visitFrom = (startId: string) => { + const queue: string[] = [startId]; + while (queue.length > 0) { + const current = String(queue.shift()!); + if (visited.has(current)) { + continue; + } + visited.add(current); + const neighbors = adjacency.get(current); + if (!neighbors) { + continue; + } + for (const neighbor of neighbors) { + if (!visited.has(neighbor)) { + queue.push(neighbor); + } + } + } + }; + + const addSeed = (itemType: SchemaTypes.ItemType | undefined) => { + if (!itemType) return; + const id = String(itemType.id); + if (!resultIds.has(id)) { + resultIds.add(id); + result.push(itemType); + } + visitFrom(id); + }; + + for (const root of initialRoots) { + addSeed(root); + } + + if (result.length === 0) { + const fallbackRoot = + this.rootItemType ?? + this.itemTypesById.values().next().value ?? + undefined; + addSeed(fallbackRoot); + } + + for (const itemType of this.itemTypes) { + const id = String(itemType.id); + if (!visited.has(id)) { + addSeed(itemType); + } + } + + return result; + } } diff --git a/import-export-schema/src/entrypoints/ExportPage/ExportSchemaOverview.tsx b/import-export-schema/src/entrypoints/ExportPage/ExportSchemaOverview.tsx new file mode 100644 index 00000000..b9c9966a --- /dev/null +++ b/import-export-schema/src/entrypoints/ExportPage/ExportSchemaOverview.tsx @@ -0,0 +1,407 @@ +/** + * Renders the export-side right sidebar schema overview, grouping selected/unselected + * models and plugins from the dependency graph so editors can confirm what the + * bulk export will include. Also drives the "only selected" toggle and per-entity + * summaries reused across import/export review flows. + */ +import type { SchemaTypes } from '@datocms/cma-client'; +import classNames from 'classnames'; +import { Spinner, SwitchInput } from 'datocms-react-ui'; +import { useMemo, useState } from 'react'; +import type { ItemTypeNode } from '@/components/ItemTypeNodeRenderer'; +import type { PluginNode } from '@/components/PluginNodeRenderer'; +import Collapsible from '@/components/SchemaOverview/Collapsible'; +import { getTextWithoutRepresentativeEmojiAndPadding } from '@/utils/emojiAgnosticSorter'; +import type { Graph } from '@/utils/graph/types'; + +const localeAwareCollator = new Intl.Collator(undefined, { + sensitivity: 'base', + numeric: true, +}); + +type ItemTypeEntry = { + itemType: SchemaTypes.ItemType; + selected: boolean; +}; + +type PluginEntry = { + plugin: SchemaTypes.Plugin; + selected: boolean; +}; + +type ItemTypeBuckets = { + selected: ItemTypeEntry[]; + unselected: ItemTypeEntry[]; +}; + +type GroupedItemTypes = { + models: ItemTypeBuckets; + blocks: ItemTypeBuckets; +}; + +type PluginBuckets = { + selected: PluginEntry[]; + unselected: PluginEntry[]; +}; + +type Props = { + graph?: Graph; + selectedItemTypeIds: string[]; + selectedPluginIds: string[]; +}; + +function sortEntriesByDisplayName( + entries: T[], + getName: (entry: T) => string, +) { + return [...entries].sort((a, b) => + localeAwareCollator.compare(getName(a), getName(b)), + ); +} + +function isItemTypeNode(node: Graph['nodes'][number]): node is ItemTypeNode { + return node.type === 'itemType'; +} + +function isPluginNode(node: Graph['nodes'][number]): node is PluginNode { + return node.type === 'plugin'; +} + +function renderItemTypeEntry(entry: ItemTypeEntry) { + const { + itemType: { + attributes: { name, modular_block: isBlock }, + }, + } = entry; + + const className = entry.selected + ? 'schema-overview__item schema-overview__item--selected' + : 'schema-overview__item schema-overview__item--unselected'; + + return ( + +

    + This {isBlock ? 'block model' : 'model'} is currently{' '} + {entry.selected ? 'selected for export.' : 'not part of this export.'} +

    +

    + {entry.selected + ? 'The exported schema JSON will include this ' + : 'Select it '} + {entry.selected ? '.' : ' from the graph to include it.'} +

    +
    + ); +} + +function renderPluginEntry(entry: PluginEntry) { + const { + plugin: { + attributes: { name }, + }, + } = entry; + + const className = entry.selected + ? 'schema-overview__item schema-overview__item--selected' + : 'schema-overview__item schema-overview__item--unselected'; + + return ( + +

    + {name} is{' '} + {entry.selected ? 'selected for export.' : 'not selected yet.'} +

    +

    + {entry.selected + ? 'The exported recipe will include this plugin entry.' + : 'Choose this plugin in the dependency graph if it should be exported.'} +

    +
    + ); +} + +function renderItemTypeGroup( + title: string, + entries: ItemTypeEntry[], + keyPrefix: string, +) { + if (entries.length === 0) { + return null; + } + + return ( +
    +
    + {title} ({entries.length}) +
    +
    + {entries.map((entry) => renderItemTypeEntry(entry))} +
    +
    + ); +} + +function renderPluginGroup( + title: string, + entries: PluginEntry[], + keyPrefix: string, +) { + if (entries.length === 0) { + return null; + } + + return ( +
    +
    + {title} ({entries.length}) +
    +
    + {entries.map((entry) => renderPluginEntry(entry))} +
    +
    + ); +} + +function SchemaOverviewCategory({ + title, + groups, + className, +}: { + title: string; + groups: Array; + className?: string; +}) { + const filteredGroups = groups.filter((group): group is JSX.Element => + Boolean(group), + ); + if (filteredGroups.length === 0) { + return null; + } + + return ( +
    +
    {title}
    + {filteredGroups} +
    + ); +} + +export function ExportSchemaOverview({ + graph, + selectedItemTypeIds, + selectedPluginIds, +}: Props) { + const [showOnlySelected, setShowOnlySelected] = useState(false); + const groupedItemTypes = useMemo(() => { + const empty: GroupedItemTypes = { + models: { selected: [], unselected: [] }, + blocks: { selected: [], unselected: [] }, + }; + + if (!graph) { + return empty; + } + + const entries = graph.nodes.filter(isItemTypeNode).map((node) => ({ + itemType: node.data.itemType, + selected: selectedItemTypeIds.includes(node.data.itemType.id), + })); + + for (const entry of entries) { + // TypeScript work-around to keep strong typing during population. + const bucketRef = entry.itemType.attributes.modular_block + ? empty.blocks + : empty.models; + + if (entry.selected) { + bucketRef.selected.push(entry); + } else { + bucketRef.unselected.push(entry); + } + } + + const sortItemTypes = (items: ItemTypeEntry[]) => + sortEntriesByDisplayName(items, (entry) => + getTextWithoutRepresentativeEmojiAndPadding( + entry.itemType.attributes.name, + ), + ); + + return { + models: { + selected: sortItemTypes(empty.models.selected), + unselected: sortItemTypes(empty.models.unselected), + }, + blocks: { + selected: sortItemTypes(empty.blocks.selected), + unselected: sortItemTypes(empty.blocks.unselected), + }, + }; + }, [graph, selectedItemTypeIds]); + + const pluginBuckets = useMemo(() => { + const empty: PluginBuckets = { selected: [], unselected: [] }; + + if (!graph) { + return empty; + } + + for (const node of graph.nodes.filter(isPluginNode)) { + const entry: PluginEntry = { + plugin: node.data.plugin, + selected: selectedPluginIds.includes(node.data.plugin.id), + }; + + if (entry.selected) { + empty.selected.push(entry); + } else { + empty.unselected.push(entry); + } + } + + const sortPlugins = (items: PluginEntry[]) => + sortEntriesByDisplayName(items, (entry) => + getTextWithoutRepresentativeEmojiAndPadding( + entry.plugin.attributes.name, + ), + ); + + return { + selected: sortPlugins(empty.selected), + unselected: sortPlugins(empty.unselected), + }; + }, [graph, selectedPluginIds]); + + const selectedCount = + groupedItemTypes.models.selected.length + + groupedItemTypes.blocks.selected.length + + pluginBuckets.selected.length; + const unselectedCount = + groupedItemTypes.models.unselected.length + + groupedItemTypes.blocks.unselected.length + + pluginBuckets.unselected.length; + + if (!graph) { + return ( +
    +
    +
    + Schema overview +
    +
    +
    +
    + +
    +
    +
    + ); + } + + const selectedGroups = [ + renderItemTypeGroup( + 'Models', + groupedItemTypes.models.selected, + 'selected-models', + ), + renderItemTypeGroup( + 'Block models', + groupedItemTypes.blocks.selected, + 'selected-blocks', + ), + renderPluginGroup('Plugins', pluginBuckets.selected, 'selected-plugins'), + ]; + + const unselectedGroups = showOnlySelected + ? [] + : [ + renderItemTypeGroup( + 'Models', + groupedItemTypes.models.unselected, + 'unselected-models', + ), + renderItemTypeGroup( + 'Block models', + groupedItemTypes.blocks.unselected, + 'unselected-blocks', + ), + renderPluginGroup( + 'Plugins', + pluginBuckets.unselected, + 'unselected-plugins', + ), + ]; + + return ( +
    +
    +
    Schema overview
    +
    + {selectedCount} selected + + {unselectedCount} not selected +
    +
    +
    + +
    +
    + {selectedCount === 0 && unselectedCount === 0 ? ( +
    +

    + Nothing to show yet — pick a model or plugin to build the export + graph. +

    +
    + ) : null} + + + +
    +
    + ); +} diff --git a/import-export-schema/src/entrypoints/ExportPage/Inner.tsx b/import-export-schema/src/entrypoints/ExportPage/Inner.tsx index d9985d84..c23775e4 100644 --- a/import-export-schema/src/entrypoints/ExportPage/Inner.tsx +++ b/import-export-schema/src/entrypoints/ExportPage/Inner.tsx @@ -1,76 +1,204 @@ -import type { ProjectSchema } from '@/utils/ProjectSchema'; import type { SchemaTypes } from '@datocms/cma-client'; -import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'; +import { faXmark } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { - Background, type NodeMouseHandler, type NodeTypes, - Panel, - ReactFlow, + useReactFlow, } from '@xyflow/react'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; import '@xyflow/react/dist/style.css'; -import { type AppNode, type Graph, edgeTypes } from '@/utils/graph/types'; import type { RenderPageCtx } from 'datocms-plugin-sdk'; -import { - Button, - Toolbar, - ToolbarStack, - ToolbarTitle, - useCtx, -} from 'datocms-react-ui'; -import { without } from 'lodash-es'; -import { useCallback, useEffect, useState } from 'react'; +import { Button, Spinner, useCtx } from 'datocms-react-ui'; +import without from 'lodash-es/without'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { GraphCanvas } from '@/components/GraphCanvas'; +import { SelectedEntityContext } from '@/components/SchemaOverview/SelectedEntityContext'; +import { GRAPH_NODE_THRESHOLD } from '@/shared/constants/graph'; +import { debugLog } from '@/utils/debug'; +import { expandSelectionWithDependencies } from '@/utils/graph/dependencies'; +import { type AppNode, edgeTypes } from '@/utils/graph/types'; +import { DependencyActionsPanel } from './DependencyActionsPanel'; import { EntitiesToExportContext } from './EntitiesToExportContext'; import { ExportItemTypeNodeRenderer } from './ExportItemTypeNodeRenderer'; import { ExportPluginNodeRenderer } from './ExportPluginNodeRenderer'; -import { buildGraphFromSchema } from './buildGraphFromSchema'; -import { useAnimatedNodes } from './useAnimatedNodes'; +import { ExportSchemaOverview } from './ExportSchemaOverview'; +import { useExportGraph } from './useExportGraph'; +// Map React Flow node types to their respective renderer components. const nodeTypes: NodeTypes = { itemType: ExportItemTypeNodeRenderer, plugin: ExportPluginNodeRenderer, }; type Props = { - initialItemType: SchemaTypes.ItemType; + initialItemTypes: SchemaTypes.ItemType[]; schema: ProjectSchema; onExport: (itemTypeIds: string[], pluginIds: string[]) => void; + onClose?: () => void; + onGraphPrepared?: () => void; + onPrepareProgress?: (update: { + done: number; + total: number; + label: string; + phase?: 'scan' | 'build'; + }) => void; + installedPluginIds?: Set; + onSelectingDependenciesChange?: (busy: boolean) => void; }; -export default function Inner({ initialItemType, schema, onExport }: Props) { +/** + * Presents the export graph, wiring selection state, dependency resolution, and + * export call-outs. For large selections it warns before rendering the full canvas. + */ +export default function Inner({ + initialItemTypes, + schema, + onExport, + onClose, + onGraphPrepared, + onPrepareProgress, + installedPluginIds, + onSelectingDependenciesChange, +}: Props) { const ctx = useCtx(); + const { fitBounds, fitView } = useReactFlow(); + + // Track the current selection while ensuring initial models stay checked. + const [selectedItemTypeIds, setSelectedItemTypeIds] = useState( + initialItemTypes.map((it) => it.id), + ); + const [selectedPluginIds, setSelectedPluginIds] = useState([]); + const [selectingDependencies, setSelectingDependencies] = useState(false); + // Remember which dependencies were auto-selected so we can undo the action later. + const [autoSelectedDependencies, setAutoSelectedDependencies] = useState<{ + itemTypeIds: Set; + pluginIds: Set; + }>({ itemTypeIds: new Set(), pluginIds: new Set() }); + const [focusedEntity, setFocusedEntity] = useState< + SchemaTypes.ItemType | SchemaTypes.Plugin | undefined + >(undefined); + const [forceRenderGraph, setForceRenderGraph] = useState(false); + const [pendingZoomEntity, setPendingZoomEntity] = useState< + SchemaTypes.ItemType | SchemaTypes.Plugin | null | undefined + >(undefined); + + const { graph, error, refresh } = useExportGraph({ + initialItemTypes, + selectedItemTypeIds, + schema, + onPrepareProgress, + onGraphPrepared, + installedPluginIds, + }); + + const resolvedInstalledPluginIds = useMemo(() => { + if (installedPluginIds && installedPluginIds.size > 0) { + return installedPluginIds; + } + if (!graph) return undefined; + const discovered = new Set( + graph.nodes + .filter((node) => node.type === 'plugin') + .map((node) => (node.type === 'plugin' ? node.data.plugin.id : '')), + ); + return discovered.size > 0 ? discovered : undefined; + }, [installedPluginIds, graph]); - const [graph, setGraph] = useState(); + const handleClose = useCallback(() => { + if (onClose) { + onClose(); + return; + } + ctx.navigateTo( + `${ctx.isEnvironmentPrimary ? '' : `/environments/${ctx.environment}`}/configuration/p/${ctx.plugin.id}/pages/export`, + ); + }, [ctx, onClose]); - const [selectedItemTypeIds, setSelectedItemTypeIds] = useState([ - initialItemType.id, + // Overlay is controlled by parent; we signal prepared after each build + + // Keep selection in sync if the parent changes the initial set of item types + useEffect(() => { + const mustHave = new Set(initialItemTypes.map((it) => it.id)); + setSelectedItemTypeIds((prev) => { + const next = new Set(prev); + for (const id of mustHave) next.add(id); + return Array.from(next); + }); + }, [ + initialItemTypes + .map((it) => it.id) + .sort() + .join('-'), ]); - const [selectedPluginIds, setSelectedPluginIds] = useState([]); + const graphTooLarge = !!graph && graph.nodes.length > GRAPH_NODE_THRESHOLD; + useEffect(() => { + if (!graphTooLarge && forceRenderGraph) { + setForceRenderGraph(false); + } + }, [graphTooLarge, forceRenderGraph]); + + const showGraph = !!graph && (!graphTooLarge || forceRenderGraph); useEffect(() => { - async function run() { - const graph = await buildGraphFromSchema({ - initialItemType, - selectedItemTypeIds, - schema, - }); + if (!showGraph || pendingZoomEntity === undefined || !graph) { + return; + } - setGraph(graph); + if (pendingZoomEntity === null) { + fitView({ duration: 800 }); + setPendingZoomEntity(undefined); + return; } - run(); - }, [initialItemType, selectedItemTypeIds.sort().join('-'), schema]); + const node = graph.nodes.find((node) => + pendingZoomEntity.type === 'plugin' + ? node.type === 'plugin' && node.data.plugin.id === pendingZoomEntity.id + : node.type === 'itemType' && + node.data.itemType.id === pendingZoomEntity.id, + ); - const animatedNodes = useAnimatedNodes(graph ? graph.nodes : [], { - animationDuration: 300, - }); + if (!node) { + setPendingZoomEntity(undefined); + return; + } + + fitBounds( + { x: node.position.x, y: node.position.y, width: 200, height: 200 }, + { duration: 800, padding: 1 }, + ); + setPendingZoomEntity(undefined); + }, [fitBounds, fitView, graph, pendingZoomEntity, showGraph]); + + const graphNodes = useMemo(() => { + if (!showGraph || !graph) { + return []; + } + return graph.nodes; + }, [graph, showGraph]); + + const handleSelectEntity = useCallback( + ( + newEntity: SchemaTypes.ItemType | SchemaTypes.Plugin | undefined, + zoomIn = false, + ) => { + setFocusedEntity(newEntity); + + if (!zoomIn) { + return; + } + + setPendingZoomEntity(newEntity ?? null); + }, + [graphTooLarge], + ); const onNodeClick: NodeMouseHandler = useCallback( (_, node) => { if (node.type === 'itemType') { - if (node.id === initialItemType.id) { + setFocusedEntity(node.data.itemType); + if (initialItemTypes.some((it) => `itemType--${it.id}` === node.id)) { return; } @@ -82,6 +210,7 @@ export default function Inner({ initialItemType, schema, onExport }: Props) { } if (node.type === 'plugin') { + setFocusedEntity(node.data.plugin); setSelectedPluginIds((old) => old.includes(node.data.plugin.id) ? without(old, node.data.plugin.id) @@ -89,73 +218,305 @@ export default function Inner({ initialItemType, schema, onExport }: Props) { ); } }, - [initialItemType.id], + [ + initialItemTypes + .map((it) => it.id) + .sort() + .join('-'), + ], ); - if (!graph) { - return null; - } + const handleSelectAllDependencies = useCallback(async () => { + setSelectingDependencies(true); + onSelectingDependenciesChange?.(true); + try { + // Ensure any preparation overlay is hidden during dependency selection + onGraphPrepared?.(); + + const warnedKey = 'exportPluginIdsWarned'; + if (!resolvedInstalledPluginIds && typeof window !== 'undefined') { + try { + const alreadyWarned = + window.sessionStorage.getItem(warnedKey) === '1'; + if (!alreadyWarned) { + void ctx.notice( + 'Plugin dependency detection may be incomplete (installed plugin list unavailable).', + ); + window.sessionStorage.setItem(warnedKey, '1'); + } + } catch {} + } + + debugLog('SelectAllDependencies start', { + selectedItemTypeCount: selectedItemTypeIds.length, + selectedPluginCount: selectedPluginIds.length, + installedPluginIds: resolvedInstalledPluginIds + ? Array.from(resolvedInstalledPluginIds).slice(0, 5) + : 'unknown', + }); + + const expansion = expandSelectionWithDependencies({ + graph, + seedItemTypeIds: selectedItemTypeIds, + seedPluginIds: selectedPluginIds, + installedPluginIds: resolvedInstalledPluginIds, + }); + + const { addedItemTypeIds, addedPluginIds } = expansion; + + setSelectedItemTypeIds(Array.from(expansion.itemTypeIds)); + setSelectedPluginIds(Array.from(expansion.pluginIds)); + setAutoSelectedDependencies({ + itemTypeIds: new Set(addedItemTypeIds), + pluginIds: new Set(addedPluginIds), + }); + + debugLog('SelectAllDependencies done', { + itemTypeCount: expansion.itemTypeIds.size, + pluginCount: expansion.pluginIds.size, + samplePluginIds: Array.from(expansion.pluginIds).slice(0, 5), + }); + + void ctx.notice( + `Selected dependencies: +${addedItemTypeIds.length} models, +${addedPluginIds.length} plugins`, + ); + } finally { + setSelectingDependencies(false); + // Do not lift overlay suppression here; let onGraphPrepared re-enable it + } + }, [ + ctx, + graph, + onGraphPrepared, + onSelectingDependenciesChange, + resolvedInstalledPluginIds, + selectedItemTypeIds, + selectedPluginIds, + ]); + + const handleUnselectAllDependencies = useCallback(() => { + // Remove dependencies previously auto-selected; fallback to none + const toRemoveItemTypeIds = autoSelectedDependencies.itemTypeIds; + const toRemovePluginIds = autoSelectedDependencies.pluginIds; + + if (toRemoveItemTypeIds.size === 0 && toRemovePluginIds.size === 0) { + // No recorded auto-selected deps; nothing to do + void ctx.notice('No dependencies to unselect'); + return; + } + + const removedModelsCount = selectedItemTypeIds.filter((id) => + toRemoveItemTypeIds.has(id), + ).length; + const removedPluginsCount = selectedPluginIds.filter((id) => + toRemovePluginIds.has(id), + ).length; + + setSelectedItemTypeIds((prev) => + prev.filter((id) => !toRemoveItemTypeIds.has(id)), + ); + setSelectedPluginIds((prev) => + prev.filter((id) => !toRemovePluginIds.has(id)), + ); + setAutoSelectedDependencies({ + itemTypeIds: new Set(), + pluginIds: new Set(), + }); + void ctx.notice( + `Unselected dependencies: -${removedModelsCount} models, -${removedPluginsCount} plugins`, + ); + }, [autoSelectedDependencies, ctx, selectedItemTypeIds, selectedPluginIds]); + + // Determine if all dependencies are already selected to flip the CTA label. + const areAllDependenciesSelected = useMemo(() => { + try { + const expansion = expandSelectionWithDependencies({ + graph, + seedItemTypeIds: selectedItemTypeIds, + seedPluginIds: selectedPluginIds, + installedPluginIds: resolvedInstalledPluginIds, + }); + return ( + expansion.addedItemTypeIds.length === 0 && + expansion.addedPluginIds.length === 0 + ); + } catch { + return false; + } + }, [ + graph, + resolvedInstalledPluginIds, + selectedItemTypeIds, + selectedPluginIds, + ]); return ( -
    - - - Export {initialItemType.attributes.name} -
    - - - +
    -
    - - {graph && ( - +
    + {!graph && !error ? ( + + ) : error ? ( +
    - - - +
    + ) : ( +
    +
    +
    - Export {selectedItemTypeIds.length} elements as JSON - - - +
    + +
    + + {showGraph ? ( + <> + + + onExport(selectedItemTypeIds, selectedPluginIds) + } + /> + + ) : graph ? ( +
    +
    + This graph has {graph.nodes.length} nodes. Trying to + render it may slow down your browser. +
    + +
    + ) : null} +
    +
    +
    +
    +
    + +
    +
    +
    )} - -
    +
    +
    ); diff --git a/import-export-schema/src/entrypoints/ExportPage/buildExportDoc.ts b/import-export-schema/src/entrypoints/ExportPage/buildExportDoc.ts index 5cc60f8f..2651e76d 100644 --- a/import-export-schema/src/entrypoints/ExportPage/buildExportDoc.ts +++ b/import-export-schema/src/entrypoints/ExportPage/buildExportDoc.ts @@ -1,21 +1,32 @@ -import type { ProjectSchema } from '@/utils/ProjectSchema'; -import { - defaultAppearanceForFieldType, - isHardcodedEditor, -} from '@/utils/datocms/fieldTypeInfo'; +import cloneDeep from 'lodash-es/cloneDeep'; +import get from 'lodash-es/get'; +import intersection from 'lodash-es/intersection'; +import set from 'lodash-es/set'; +import { ensureExportableAppearance } from '@/utils/datocms/appearance'; import { validatorsContainingBlocks, validatorsContainingLinks, } from '@/utils/datocms/schema'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; import type { ExportDocV2 } from '@/utils/types'; -import { cloneDeep, get, intersection, set } from 'lodash-es'; +type BuildExportDocOptions = { + onProgress?: (label: string) => void; + shouldCancel?: () => boolean; +}; + +/** + * Assemble an export document tailored to the selected item types and plugins, trimming + * validators and appearances so the payload is self-contained. + */ export default async function buildExportDoc( schema: ProjectSchema, initialItemTypeId: string, itemTypeIdsToExport: string[], pluginIdsToExport: string[], + options: BuildExportDocOptions = {}, ): Promise { + const { onProgress, shouldCancel } = options; const doc: ExportDocV2 = { version: '2', rootItemTypeId: initialItemTypeId, @@ -23,24 +34,32 @@ export default async function buildExportDoc( }; for (const pluginId of pluginIdsToExport) { + if (shouldCancel?.()) throw new Error('Export cancelled'); const plugin = await schema.getPluginById(pluginId); doc.entities.push(plugin); + onProgress?.(`Plugin: ${plugin.attributes.name}`); } for (const itemTypeIdToExport of itemTypeIdsToExport) { + if (shouldCancel?.()) throw new Error('Export cancelled'); const itemTypeToExport = await schema.getItemTypeById(itemTypeIdToExport); + onProgress?.(`Model/Block: ${itemTypeToExport.attributes.name}`); const [fields, fieldsets] = await schema.getItemTypeFieldsAndFieldsets(itemTypeToExport); + if (shouldCancel?.()) throw new Error('Export cancelled'); + onProgress?.(`Fields/Fieldsets for ${itemTypeToExport.attributes.name}`); doc.entities.push(itemTypeToExport); for (const fieldset of fieldsets) { + if (shouldCancel?.()) throw new Error('Export cancelled'); doc.entities.push(fieldset); } for (const field of fields) { + if (shouldCancel?.()) throw new Error('Export cancelled'); const exportableField = cloneDeep(field); const validators = [ @@ -58,6 +77,7 @@ export default async function buildExportDoc( validator, ) as string[]; + // Drop links to models outside the export selection so the document stays valid. set( exportableField.attributes.validators, validator, @@ -65,20 +85,11 @@ export default async function buildExportDoc( ); } - field.attributes.appeareance = undefined; - - if ( - !(await isHardcodedEditor(field.attributes.appearance.editor)) && - !pluginIdsToExport.includes(field.attributes.appearance.editor) - ) { - exportableField.attributes.appearance = - await defaultAppearanceForFieldType(field.attributes.field_type); - } - - exportableField.attributes.appearance.addons = - field.attributes.appearance.addons.filter((addon) => - pluginIdsToExport.includes(addon.id), - ); + // Remove appearance references to non-exported plugins/media. + exportableField.attributes.appearance = await ensureExportableAppearance( + field, + pluginIdsToExport, + ); doc.entities.push(exportableField); } diff --git a/import-export-schema/src/entrypoints/ExportPage/buildGraphFromSchema.ts b/import-export-schema/src/entrypoints/ExportPage/buildGraphFromSchema.ts index 7985fea6..f23a3273 100644 --- a/import-export-schema/src/entrypoints/ExportPage/buildGraphFromSchema.ts +++ b/import-export-schema/src/entrypoints/ExportPage/buildGraphFromSchema.ts @@ -1,229 +1,37 @@ -import type { ItemTypeNode } from '@/components/ItemTypeNodeRenderer'; -import type { PluginNode } from '@/components/PluginNodeRenderer'; -import type { ProjectSchema } from '@/utils/ProjectSchema'; -import { - findLinkedItemTypeIds, - findLinkedPluginIds, -} from '@/utils/datocms/schema'; -import { buildHierarchyNodes } from '@/utils/graph/buildHierarchyNodes'; -import { rebuildGraphWithPositionsFromHierarchy } from '@/utils/graph/rebuildGraphWithPositionsFromHierarchy'; -import type { AppEdge, Graph } from '@/utils/graph/types'; import type { SchemaTypes } from '@datocms/cma-client'; -import { MarkerType } from '@xyflow/react'; -import { find, sortBy } from 'lodash-es'; +import { buildGraph } from '@/utils/graph/buildGraph'; +import type { Graph, SchemaProgressUpdate } from '@/utils/graph/types'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; +import { ProjectSchemaSource } from '@/utils/schema/ProjectSchemaSource'; type Options = { - initialItemType: SchemaTypes.ItemType; + initialItemTypes: SchemaTypes.ItemType[]; selectedItemTypeIds: string[]; schema: ProjectSchema; + onProgress?: (update: SchemaProgressUpdate) => void; + installedPluginIds?: Set; }; -type QueueItem = SchemaTypes.ItemType | SchemaTypes.Plugin; - +/** + * Lightweight wrapper that adapts the current project schema into the shared + * `buildGraph` helper so the export view can render a dependency graph. + */ export async function buildGraphFromSchema({ - initialItemType, + initialItemTypes, selectedItemTypeIds, schema, + onProgress, + installedPluginIds, }: Options): Promise { - const graph: Graph = { nodes: [], edges: [] }; - - const queue: QueueItem[][] = [[initialItemType]]; - const processedNodes = new Set(); - - // Process each level of the graph - while (queue.length > 0) { - const currentLevelItems = queue.shift(); - - if (!currentLevelItems) { - throw new Error('Unexpected error: currentLevelItemTypes is undefined'); - } - - const nextLevelQueue = new Set(); - - // Process all nodes at the current level in parallel - await Promise.all( - currentLevelItems.map(async (itemTypeOrPlugin) => { - // Skip if already processed - if (processedNodes.has(itemTypeOrPlugin)) { - return; - } - - // Mark as processed - processedNodes.add(itemTypeOrPlugin); - - if (itemTypeOrPlugin.type === 'item_type') { - const itemType = itemTypeOrPlugin; - - // Process fields and collect child nodes - const [fields, fieldsets] = - await schema.getItemTypeFieldsAndFieldsets(itemType); - - // Add current node to graph - graph.nodes.push(buildItemTypeNode(itemType, fields, fieldsets)); - - if (!selectedItemTypeIds.includes(itemType.id)) { - return; - } - - const [edges, linkedItemTypeIds, linkedPluginIds] = - await buildEdgesForItemType(itemType, fields, initialItemType); - - graph.edges.push(...edges); - - // Process all item types in parallel - await Promise.all([ - ...Array.from(linkedItemTypeIds).map(async (linkedItemTypeId) => { - const linkedItemType = - await schema.getItemTypeById(linkedItemTypeId); - - if (!processedNodes.has(linkedItemType)) { - nextLevelQueue.add(linkedItemType); - } - }), - ...Array.from(linkedPluginIds).map(async (linkedPluginId) => { - const linkedPlugin = await schema.getPluginById(linkedPluginId); - - if (!processedNodes.has(linkedPlugin)) { - nextLevelQueue.add(linkedPlugin); - } - }), - ]); - } else { - const plugin = itemTypeOrPlugin; - - // Add current node to graph - graph.nodes.push(buildPluginNode(plugin)); - } - }), - ); - - // If we have nodes for the next level, add them to the queue - if (nextLevelQueue.size > 0) { - queue.push([...nextLevelQueue]); - } - } - - const sortedGraph = deterministicGraphSort(graph); - - if (sortedGraph.nodes.length === 0) { - return sortedGraph; - } - - const hierarchy = buildHierarchyNodes(sortedGraph, selectedItemTypeIds); - - for (const hierarchyNode of hierarchy.descendants()) { - const innerNode = hierarchy.data; - - if (innerNode.type === 'itemType') { - hierarchyNode.children = selectedItemTypeIds.includes( - innerNode.data.itemType.id, - ) - ? hierarchyNode.children - : undefined; - } - } - - return rebuildGraphWithPositionsFromHierarchy(hierarchy, sortedGraph.edges); -} - -export async function buildEdgesForItemType( - itemType: SchemaTypes.ItemType, - fields: SchemaTypes.Field[], - rootItemType: SchemaTypes.ItemType, -) { - const edges: AppEdge[] = []; - const linkedItemTypeIds = new Set(); - const linkedPluginIds = new Set(); - - for (const field of fields) { - for (const linkedItemTypeId of await findLinkedItemTypeIds(field)) { - if (linkedItemTypeId === rootItemType.id) { - continue; - } - - const id = `toItemType--${itemType.id}->${linkedItemTypeId}`; - linkedItemTypeIds.add(linkedItemTypeId); - - const edge = find(edges, { id }); - - if (edge) { - edge.data!.fields.push(field); - } else { - edges.push({ - id, - source: `itemType--${itemType.id}`, - target: `itemType--${linkedItemTypeId}`, - type: 'field', - data: { fields: [field] }, - markerEnd: { type: MarkerType.ArrowClosed }, - }); - } - } - - for (const linkedPluginId of await findLinkedPluginIds(field)) { - const id = `toPlugin--${itemType.id}->${linkedPluginId}`; - const edge = find(edges, { id }); - linkedPluginIds.add(linkedPluginId); - - if (edge) { - edge.data!.fields.push(field); - } else { - edges.push({ - id, - source: `itemType--${itemType.id}`, - target: `plugin--${linkedPluginId}`, - type: 'field', - data: { fields: [field] }, - markerEnd: { type: MarkerType.ArrowClosed }, - }); - } - } - } - - return [edges, linkedItemTypeIds, linkedPluginIds] as const; + const source = new ProjectSchemaSource(schema, { + installedPluginIds, + }); + return buildGraph({ + source, + initialItemTypes, + selectedItemTypeIds, + onProgress, + }); } -export function buildPluginNode(plugin: SchemaTypes.Plugin): PluginNode { - return { - id: `plugin--${plugin.id}`, - position: { - x: 0, - y: 0, - }, - type: 'plugin', - data: { - plugin, - }, - }; -} - -export function buildItemTypeNode( - itemType: SchemaTypes.ItemType, - fields: SchemaTypes.Field[], - fieldsets: SchemaTypes.Fieldset[], -): ItemTypeNode { - return { - id: `itemType--${itemType.id}`, - position: { - x: 0, - y: 0, - }, - type: 'itemType', - data: { - itemType, - fields, - fieldsets, - }, - }; -} - -export function deterministicGraphSort(graph: Graph) { - return { - nodes: sortBy(graph.nodes, [ - 'type', - 'data.itemType.attributes.api_key', - 'data.itemType.attributes.name', - ]), - edges: graph.edges, - }; -} +export type { SchemaTypes }; diff --git a/import-export-schema/src/entrypoints/ExportPage/index.tsx b/import-export-schema/src/entrypoints/ExportPage/index.tsx index acf9669c..bf59b20a 100644 --- a/import-export-schema/src/entrypoints/ExportPage/index.tsx +++ b/import-export-schema/src/entrypoints/ExportPage/index.tsx @@ -1,53 +1,224 @@ -import { ProjectSchema } from '@/utils/ProjectSchema'; -import { downloadJSON } from '@/utils/downloadJson'; -import { type SchemaTypes, buildClient } from '@datocms/cma-client'; +import type { SchemaTypes } from '@datocms/cma-client'; import { ReactFlowProvider } from '@xyflow/react'; import type { RenderPageCtx } from 'datocms-plugin-sdk'; import { Canvas, Spinner } from 'datocms-react-ui'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { ProgressOverlay } from '@/components/ProgressOverlay'; +import { useProjectSchema } from '@/shared/hooks/useProjectSchema'; +import { useSchemaExportTask } from '@/shared/hooks/useSchemaExportTask'; +import { useLongTask } from '@/shared/tasks/useLongTask'; +import { debugLog } from '@/utils/debug'; import Inner from './Inner'; -import buildExportDoc from './buildExportDoc'; type Props = { ctx: RenderPageCtx; initialItemTypeId: string; }; +type PreparingPhase = 'scan' | 'build'; + +type PrepareProgressUpdate = { + done: number; + total: number; + label: string; + phase?: PreparingPhase; +}; + +const INITIAL_PREPARING_PERCENT = 0.1; +const PREPARING_HEARTBEAT_CAP = 0.88; +const PREPARING_HEARTBEAT_INCREMENT = 0.015; +const PREPARING_HEARTBEAT_INTERVAL_MS = 250; + +const PREPARING_PHASE_CONFIG: Record< + PreparingPhase, + { + range: { min: number; max: number }; + cap: number; + } +> = { + scan: { range: { min: 0.05, max: 0.85 }, cap: PREPARING_HEARTBEAT_CAP }, + build: { range: { min: 0.85, max: 1 }, cap: 1 }, +}; + +// Clamp helper keeps derived percentages inside their designated bounds. +function clamp(value: number, min: number, max: number) { + if (value < min) return min; + if (value > max) return max; + return value; +} + +/** + * Map a phase-specific progress update into the smoothed overlay percentage. + */ +function mapPreparingProgressPercent( + phase: PreparingPhase, + done: number | undefined, + total: number | undefined, + previous: number, +) { + if (typeof done !== 'number' || typeof total !== 'number' || total <= 0) { + return previous; + } + + const config = PREPARING_PHASE_CONFIG[phase]; + const safeDone = clamp(done, 0, total); + const normalized = clamp(safeDone / total, 0, 1); + const { range, cap } = config; + const mapped = range.min + normalized * (range.max - range.min); + + return Math.max(previous, clamp(mapped, range.min, cap)); +} + +/** + * Export entry loaded from the DatoCMS sidebar when a single model kicks off the flow. + * Fetches schema resources, shows progress overlays, and renders the main graph view. + */ export default function ExportPage({ ctx, initialItemTypeId }: Props) { + const schema = useProjectSchema(ctx); + const [initialItemType, setInitialItemType] = useState< SchemaTypes.ItemType | undefined >(); + const [suppressPreparingOverlay, setSuppressPreparingOverlay] = + useState(false); + const [preparingPhase, setPreparingPhase] = useState('scan'); + const preparingTask = useLongTask(); + const { task: exportTask, runExport } = useSchemaExportTask({ + schema, + ctx, + }); + + const preparingProgress = preparingTask.state.progress; + const exportProgress = exportTask.state.progress; - const schema = useMemo(() => { - const client = buildClient({ - apiToken: ctx.currentUserAccessToken!, - environment: ctx.environment, - }); - return new ProjectSchema(client); - }, [ctx.currentUserAccessToken, ctx.environment]); + const preparingHasTotals = + typeof preparingProgress.total === 'number' && preparingProgress.total > 0; + const isPreparingRunning = preparingTask.state.status === 'running'; + const shouldShowPreparingOverlay = + isPreparingRunning && !suppressPreparingOverlay; + // Smoothed visual progress percentage for the preparing overlay. + // We map the initial scanning phase to 0–25%, then determinate build to 25–100%. + const [preparingPercent, setPreparingPercent] = useState( + INITIAL_PREPARING_PERCENT, + ); + // Heartbeat fallback: gently move during scan if no numeric totals arrive useEffect(() => { - async function run() { - const itemType = await schema.getItemTypeById(initialItemTypeId); - setInitialItemType(itemType); + const heartbeatActive = + shouldShowPreparingOverlay && + preparingPhase === 'scan' && + !preparingHasTotals; + if (!heartbeatActive) { + return; } - run(); - }, [schema, initialItemTypeId]); + const id = window.setInterval(() => { + setPreparingPercent((prev) => + Math.min(PREPARING_HEARTBEAT_CAP, prev + PREPARING_HEARTBEAT_INCREMENT), + ); + }, PREPARING_HEARTBEAT_INTERVAL_MS); - async function handleExport(itemTypeIds: string[], pluginIds: string[]) { - const exportDoc = await buildExportDoc( - schema, - initialItemTypeId, - itemTypeIds, - pluginIds, - ); - downloadJSON(exportDoc, { fileName: 'export.json', prettify: true }); - ctx.notice('Export completed with success!'); - ctx.navigateTo( - `${ctx.isEnvironmentPrimary ? '' : `/environments/${ctx.environment}`}/configuration/p/${ctx.plugin.id}/pages/import-export`, - ); - } + return () => window.clearInterval(id); + }, [preparingPhase, preparingHasTotals, shouldShowPreparingOverlay]); + + // Preload installed plugin IDs once to avoid network calls during selection + const [installedPluginIds, setInstalledPluginIds] = useState< + Set | undefined + >(); + // Pre-fetch plugin IDs so dependency expansion can quickly filter installed extensions. + useEffect(() => { + let active = true; + (async () => { + try { + const plugins = await schema.getAllPlugins(); + if (active) setInstalledPluginIds(new Set(plugins.map((p) => p.id))); + } catch (_) { + // ignore; selection will just skip plugin dependencies when unknown + } + })(); + return () => { + active = false; + }; + }, [schema]); + + const [lastPreparedForId, setLastPreparedForId] = useState< + string | undefined + >(undefined); + // Kick off the initial graph build the first time we enter for a content model. + useEffect(() => { + let active = true; + + async function ensureInitialItemType() { + try { + const itemType = await schema.getItemTypeById(initialItemTypeId); + if (!active) return; + + setInitialItemType(itemType); + + if (lastPreparedForId === initialItemTypeId) { + return; + } + + debugLog('ExportPage preparing start', { initialItemTypeId }); + preparingTask.controller.start({ label: 'Preparing export…' }); + setPreparingPhase('scan'); + setPreparingPercent(INITIAL_PREPARING_PERCENT); + setLastPreparedForId(initialItemTypeId); + } catch (error) { + if (active) { + debugLog('ExportPage failed to load initial item type', error); + } + } + } + + ensureInitialItemType(); + + return () => { + active = false; + }; + }, [schema, initialItemTypeId, lastPreparedForId, preparingTask.controller]); + + const handlePrepareProgress = useCallback( + (progress: PrepareProgressUpdate) => { + if (preparingTask.state.status !== 'running') { + preparingTask.controller.start(progress); + } else { + preparingTask.controller.setProgress(progress); + } + + const phase = progress.phase ?? 'scan'; + setPreparingPhase(phase); + setPreparingPercent((prev) => + mapPreparingProgressPercent(phase, progress.done, progress.total, prev), + ); + }, + [preparingTask.controller, preparingTask.state.status], + ); + + const handleGraphPrepared = useCallback(() => { + debugLog('ExportPage graph prepared'); + setPreparingPercent(1); + preparingTask.controller.complete({ label: 'Graph prepared' }); + setSuppressPreparingOverlay(false); + setPreparingPhase('build'); + }, [preparingTask.controller]); + + const handleExport = useCallback( + (itemTypeIds: string[], pluginIds: string[]) => + runExport({ + rootItemTypeId: initialItemTypeId, + itemTypeIds, + pluginIds, + }), + [initialItemTypeId, runExport], + ); + + // Hide the preparing overlay while dependency batches recompute to avoid flicker. + const handleSelectingDependenciesChange = useCallback((busy: boolean) => { + if (busy) { + setSuppressPreparingOverlay(true); + } + }, []); if (!initialItemType) { return ( @@ -64,11 +235,48 @@ export default function ExportPage({ ctx, initialItemTypeId }: Props) { + {shouldShowPreparingOverlay && ( + + )} + {exportTask.state.status === 'running' && ( + exportTask.controller.requestCancel(), + }} + /> + )} ); } diff --git a/import-export-schema/src/entrypoints/ExportPage/styles.module.css b/import-export-schema/src/entrypoints/ExportPage/styles.module.css deleted file mode 100644 index 81e37de8..00000000 --- a/import-export-schema/src/entrypoints/ExportPage/styles.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.inspector { - margin-top: var(--spacing-l); -} diff --git a/import-export-schema/src/entrypoints/ExportPage/useAnimatedNodes.tsx b/import-export-schema/src/entrypoints/ExportPage/useAnimatedNodes.tsx deleted file mode 100644 index c17a3164..00000000 --- a/import-export-schema/src/entrypoints/ExportPage/useAnimatedNodes.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { AppNode } from '@/utils/graph/types'; -import { useReactFlow } from '@xyflow/react'; -import { timer } from 'd3-timer'; -import { useEffect, useState } from 'react'; -import { easing } from 'ts-easing'; - -export function useAnimatedNodes( - initialNodes: AppNode[], - { animationDuration = 300 } = {}, -) { - const [nodes, setNodes] = useState(initialNodes); - const { getNode } = useReactFlow(); - - useEffect(() => { - const wantedPositionChanges = initialNodes.map((initialNode) => { - const currentNode = getNode(initialNode.id); - - return { - id: initialNode.id, - from: - (currentNode == null ? undefined : currentNode.position) ?? - initialNode.position, - to: initialNode.position, - node: initialNode, - }; - }); - - const t = timer((elapsed) => { - const percentElapsed = easing.inOutCubic(elapsed / animationDuration); - - const movedNodes = wantedPositionChanges.map(({ node, from, to }) => ({ - ...node, - position: { - x: from.x + (to.x - from.x) * percentElapsed, - y: from.y + (to.y - from.y) * percentElapsed, - }, - })); - - setNodes(movedNodes); - - if (elapsed > animationDuration) { - setNodes(initialNodes); - t.stop(); - } - }); - - return () => t.stop(); - }, [initialNodes, getNode, animationDuration]); - - return nodes; -} diff --git a/import-export-schema/src/entrypoints/ExportPage/useExportGraph.ts b/import-export-schema/src/entrypoints/ExportPage/useExportGraph.ts new file mode 100644 index 00000000..2cde7c0b --- /dev/null +++ b/import-export-schema/src/entrypoints/ExportPage/useExportGraph.ts @@ -0,0 +1,94 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import { useEffect, useRef, useState } from 'react'; +import { debugLog } from '@/utils/debug'; +import type { Graph, SchemaProgressUpdate } from '@/utils/graph/types'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; +import { buildGraphFromSchema } from './buildGraphFromSchema'; + +type Options = { + initialItemTypes: SchemaTypes.ItemType[]; + selectedItemTypeIds: string[]; + schema: ProjectSchema; + onPrepareProgress?: (update: SchemaProgressUpdate) => void; + onGraphPrepared?: () => void; + installedPluginIds?: Set; +}; + +/** + * Builds the export dependency graph whenever the selection or schema changes, + * surfacing progress callbacks and exposing a manual `refresh` helper. + */ +export function useExportGraph({ + initialItemTypes, + selectedItemTypeIds, + schema, + onPrepareProgress, + onGraphPrepared, + installedPluginIds, +}: Options) { + const [graph, setGraph] = useState(); + const [error, setError] = useState(); + const [refreshKey, setRefreshKey] = useState(0); + const prepareProgressRef = useRef(onPrepareProgress); + const graphPreparedRef = useRef(onGraphPrepared); + + useEffect(() => { + prepareProgressRef.current = onPrepareProgress; + }, [onPrepareProgress]); + + useEffect(() => { + graphPreparedRef.current = onGraphPrepared; + }, [onGraphPrepared]); + + useEffect(() => { + // Avoid setting state after unmount or when inputs change mid-build. + let cancelled = false; + async function run() { + try { + setError(undefined); + debugLog('ExportGraph build start', { + selectedItemTypeCount: selectedItemTypeIds.length, + }); + const nextGraph = await buildGraphFromSchema({ + initialItemTypes, + selectedItemTypeIds, + schema, + onProgress: prepareProgressRef.current, + installedPluginIds, + }); + if (cancelled) return; + setGraph(nextGraph); + debugLog('ExportGraph build complete', { + nodeCount: nextGraph.nodes.length, + edgeCount: nextGraph.edges.length, + }); + graphPreparedRef.current?.(); + } catch (err) { + if (cancelled) return; + console.error('Error building export graph:', err); + setError(err as Error); + graphPreparedRef.current?.(); + } + } + run(); + return () => { + cancelled = true; + }; + }, [ + initialItemTypes + .map((it) => it.id) + .sort() + .join('-'), + selectedItemTypeIds.slice().sort().join('-'), + schema, + refreshKey, + installedPluginIds, + ]); + + return { + graph, + error, + // Trigger a rebuild (for example after an intermittent API failure). + refresh: () => setRefreshKey((key) => key + 1), + }; +} diff --git a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/ItemTypeConflict.tsx b/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/ItemTypeConflict.tsx index f1673d25..5a890ee9 100644 --- a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/ItemTypeConflict.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/ItemTypeConflict.tsx @@ -1,105 +1,149 @@ import type { SchemaTypes } from '@datocms/cma-client'; -import { useReactFlow } from '@xyflow/react'; import { SelectField, TextField } from 'datocms-react-ui'; +import { useId } from 'react'; import { Field } from 'react-final-form'; -import type { GroupBase } from 'react-select'; +import Collapsible from '@/components/SchemaOverview/Collapsible'; import { useResolutionStatusForItemType } from '../ResolutionsForm'; -import Collapsible from './Collapsible'; type Option = { label: string; value: string }; +type SelectGroup = { + label?: string; + options: readonly OptionType[]; +}; type Props = { exportItemType: SchemaTypes.ItemType; - projectItemType: SchemaTypes.ItemType; + projectItemType?: SchemaTypes.ItemType; }; +/** + * Renders the resolution UI for a conflicting model/block, including rename inputs. + */ export function ItemTypeConflict({ exportItemType, projectItemType }: Props) { + const selectId = useId(); + const nameId = useId(); + const apiKeyId = useId(); const fieldPrefix = `itemType-${exportItemType.id}`; - const resolution = useResolutionStatusForItemType(exportItemType.id)!; - const node = useReactFlow().getNode(`itemType--${exportItemType.id}`); + const resolution = useResolutionStatusForItemType(exportItemType.id); const exportType = exportItemType.attributes.modular_block ? 'block' : 'model'; - const projectType = projectItemType.attributes.modular_block + const projectType = projectItemType?.attributes.modular_block ? 'block' : 'model'; - const options: Option[] = [ - { label: `Import ${exportType} using a different name`, value: 'rename' }, - ]; + const resolutionValues = resolution?.values; + const resolutionStrategy = resolutionValues?.strategy; + + const resolutionStrategyIsRename = resolutionStrategy === 'rename'; + const resolutionStrategyIsReuseExisting = + resolutionStrategy === 'reuseExisting'; + + const renameReady = + resolutionStrategyIsRename && + !!resolutionValues?.name && + !!resolutionValues?.apiKey && + !resolution?.invalid; + + const reuseReady = resolutionStrategyIsReuseExisting && !resolution?.invalid; + + const conflictResolved = + Boolean(projectItemType) && (renameReady || reuseReady); - if ( - exportItemType.attributes.modular_block === - projectItemType.attributes.modular_block - ) { + const hasConflict = Boolean(projectItemType) && !conflictResolved; + + // Base strategy options; reuse is only valid for matching model/block types. + const options: Option[] = []; + + if (projectItemType) { options.push({ - label: `Reuse the existing ${exportType}`, - value: 'reuseExisting', + label: `Import ${exportType} using a different name`, + value: 'rename', }); - } - if (!node) { - return null; + if ( + exportItemType.attributes.modular_block === + projectItemType.attributes.modular_block + ) { + options.push({ + label: `Reuse the existing ${exportType}`, + value: 'reuseExisting', + }); + } } + const isInvalid = hasConflict && Boolean(resolution?.invalid); + return ( -

    - The project already has a {projectType} called{' '} - - {projectItemType.attributes.name} - {' '} - ({projectItemType.attributes.api_key}). -

    - - {({ input, meta: { error } }) => ( - > - {...input} - id="fieldTypes" - label="To resolve this conflict:" - selectInputProps={{ - options, - }} - value={options.find((ft) => input.value.includes(ft.value))} - onChange={(option) => input.onChange(option ? option.value : null)} - error={error} - /> - )} - - {resolution.values.strategy === 'rename' && ( + {projectItemType ? ( <> -
    - - {({ input, meta: { error } }) => ( - - )} - -
    -
    - - {({ input, meta: { error } }) => ( - - )} - -
    +

    + The project already has a {projectType} called{' '} + + {projectItemType.attributes.name} + {' '} + ({projectItemType.attributes.api_key}). +

    + + {({ input, meta: { error } }) => ( + > + {...input} + id={selectId} + label="To resolve this conflict:" + selectInputProps={{ + options, + }} + value={ + options.find((option) => input.value === option.value) ?? null + } + onChange={(option) => + input.onChange(option ? option.value : null) + } + placeholder="Select..." + error={error} + /> + )} + + {resolutionStrategyIsRename && ( + <> +
    + + {({ input, meta: { error } }) => ( + + )} + +
    +
    + + {({ input, meta: { error } }) => ( + + )} + +
    + + )} + ) : ( +

    No conflicts detected for this name and api key.

    )}
    ); diff --git a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/PluginConflict.tsx b/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/PluginConflict.tsx index ccd93916..c25e2a9f 100644 --- a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/PluginConflict.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/PluginConflict.tsx @@ -1,12 +1,15 @@ import type { SchemaTypes } from '@datocms/cma-client'; -import { useReactFlow } from '@xyflow/react'; import { SelectField } from 'datocms-react-ui'; +import { useId } from 'react'; import { Field } from 'react-final-form'; -import type { GroupBase } from 'react-select'; +import Collapsible from '@/components/SchemaOverview/Collapsible'; import { useResolutionStatusForPlugin } from '../ResolutionsForm'; -import Collapsible from './Collapsible'; type Option = { label: string; value: string }; +type SelectGroup = { + label?: string; + options: readonly OptionType[]; +}; const options: Option[] = [ { @@ -18,43 +21,60 @@ const options: Option[] = [ type Props = { exportPlugin: SchemaTypes.Plugin; - projectPlugin: SchemaTypes.Plugin; + projectPlugin?: SchemaTypes.Plugin; }; +/** Presents resolution choices for plugin conflicts (reuse vs. skip). */ export function PluginConflict({ exportPlugin, projectPlugin }: Props) { + const selectId = useId(); const fieldPrefix = `plugin-${exportPlugin.id}`; - const resolution = useResolutionStatusForPlugin(exportPlugin.id)!; - const node = useReactFlow().getNode(`plugin--${exportPlugin.id}`); + const resolution = useResolutionStatusForPlugin(exportPlugin.id); + + const strategy = resolution?.values?.strategy; + const hasValidResolution = Boolean( + !resolution?.invalid && + (strategy === 'reuseExisting' || strategy === 'skip'), + ); - if (!node) { - return null; - } + const hasConflict = Boolean(projectPlugin) && !hasValidResolution; return ( -

    - The project already has the plugin{' '} - {projectPlugin.attributes.name}. -

    - - {({ input, meta: { error } }) => ( - > - {...input} - id="fieldTypes" - label="To resolve this conflict:" - selectInputProps={{ - options, - }} - value={options.find((ft) => input.value.includes(ft.value))} - onChange={(option) => input.onChange(option ? option.value : null)} - error={error} - /> - )} - + {projectPlugin ? ( + <> +

    + The project already has the plugin{' '} + {projectPlugin.attributes.name}. +

    + + {({ input, meta: { error } }) => ( + > + {...input} + id={selectId} + label="To resolve this conflict:" + selectInputProps={{ + options, + }} + value={ + options.find((option) => input.value === option.value) ?? null + } + onChange={(option) => + input.onChange(option ? option.value : null) + } + placeholder="Select..." + error={error} + /> + )} + + + ) : ( +

    No conflicts detected for this name or URL.

    + )}
    ); } diff --git a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/buildConflicts.ts b/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/buildConflicts.ts index 1f81b094..68a7c17c 100644 --- a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/buildConflicts.ts +++ b/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/buildConflicts.ts @@ -1,18 +1,29 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import keyBy from 'lodash-es/keyBy'; import type { ExportSchema } from '@/entrypoints/ExportPage/ExportSchema'; import type { ProjectSchema } from '@/utils/ProjectSchema'; -import type { SchemaTypes } from '@datocms/cma-client'; -import { keyBy } from 'lodash-es'; export type Conflicts = { plugins: Record; itemTypes: Record; }; +/** + * Compare the export snapshot against the project and identify models/plugins that collide + * by name, API key, or URL. + */ export default async function buildConflicts( exportSchema: ExportSchema, projectSchema: ProjectSchema, + onProgress?: (p: { done: number; total: number; label: string }) => void, ) { + let done = 0; + const total = 2 + exportSchema.itemTypes.length + exportSchema.plugins.length; + + onProgress?.({ done, total, label: 'Loading models…' }); const projectItemTypes = await projectSchema.getAllItemTypes(); + done += 1; + onProgress?.({ done, total, label: 'Loading plugins…' }); const projectItemTypesByName = keyBy(projectItemTypes, 'attributes.name'); const projectItemTypesByApiKey = keyBy( projectItemTypes, @@ -20,6 +31,8 @@ export default async function buildConflicts( ); const projectPlugins = await projectSchema.getAllPlugins(); + done += 1; + onProgress?.({ done, total, label: 'Scanning item types…' }); const projectPluginsByName = keyBy(projectPlugins, 'attributes.name'); const projectPluginsByUrl = keyBy(projectPlugins, 'attributes.url'); @@ -31,18 +44,27 @@ export default async function buildConflicts( projectItemTypesByApiKey[itemType.attributes.api_key]; if (conflictingItemType) { - conflicts.itemTypes[itemType.id] = conflictingItemType; + conflicts.itemTypes[String(itemType.id)] = conflictingItemType; } + done += 1; + onProgress?.({ + done, + total, + label: `Item type: ${itemType.attributes.name}`, + }); } + onProgress?.({ done, total, label: 'Scanning plugins…' }); for (const plugin of exportSchema.plugins) { const conflictingPlugin = projectPluginsByUrl[plugin.attributes.url] || projectPluginsByName[plugin.attributes.name]; if (conflictingPlugin) { - conflicts.plugins[plugin.id] = conflictingPlugin; + conflicts.plugins[String(plugin.id)] = conflictingPlugin; } + done += 1; + onProgress?.({ done, total, label: `Plugin: ${plugin.attributes.name}` }); } return conflicts; diff --git a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/index.tsx b/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/index.tsx index 94a5dddd..ab9260ba 100644 --- a/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/index.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/ConflictsManager/index.tsx @@ -1,144 +1,440 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import type { RenderPageCtx } from 'datocms-plugin-sdk'; +import { Button, SwitchInput } from 'datocms-react-ui'; +import get from 'lodash-es/get'; +import { useContext, useId, useMemo, useState } from 'react'; +import { useFormState } from 'react-final-form'; import type { ExportSchema } from '@/entrypoints/ExportPage/ExportSchema'; import { getTextWithoutRepresentativeEmojiAndPadding } from '@/utils/emojiAgnosticSorter'; -import { Button, Toolbar, ToolbarStack, ToolbarTitle } from 'datocms-react-ui'; -import { defaults, groupBy, map, mapValues, sortBy } from 'lodash-es'; -import { useContext } from 'react'; -import { useFormState } from 'react-final-form'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; import { ConflictsContext } from './ConflictsContext'; import { ItemTypeConflict } from './ItemTypeConflict'; import { PluginConflict } from './PluginConflict'; +// Collator keeps alphabetical/numeric ordering stable regardless of locale accents. +const localeAwareCollator = new Intl.Collator(undefined, { + sensitivity: 'base', + numeric: true, +}); + +function sortEntriesByDisplayName(items: T[], getName: (item: T) => string) { + // Clone to avoid mutating callers and sort using the locale-aware collator. + return [...items].sort((a, b) => + localeAwareCollator.compare(getName(a), getName(b)), + ); +} + type Props = { exportSchema: ExportSchema; + schema: ProjectSchema; + // ctx is currently unused; keep for future enhancements + ctx?: RenderPageCtx; }; -export default function ConflictsManager({ exportSchema }: Props) { +/** + * Organizes detected conflicts by type, wiring them into the resolutions form. + */ +export default function ConflictsManager({ + exportSchema, + schema: _schema, +}: Props) { const conflicts = useContext(ConflictsContext); - const { submitting, valid } = useFormState(); + const [showOnlyConflicts, setShowOnlyConflicts] = useState(false); + const toggleId = useId(); + // Track submission state for enabling/disabling the final CTA. + const formState = useFormState({ + subscription: { + submitting: true, + valid: true, + validating: true, + values: true, + errors: true, + }, + }); + const { + submitting, + valid, + validating, + values: formValues = {}, + errors: formErrors = {}, + } = formState as { + submitting: boolean; + valid: boolean; + validating: boolean; + values: Record; + errors: Record; + }; - if (!conflicts) { - return null; - } + type ItemTypeEntry = { + exportItemType: SchemaTypes.ItemType; + projectItemType?: SchemaTypes.ItemType; + }; + + type PluginEntry = { + exportPlugin: SchemaTypes.Plugin; + projectPlugin?: SchemaTypes.Plugin; + }; + + const itemTypesByCategory = useMemo(() => { + const empty: Record<'blocks' | 'models', ItemTypeEntry[]> = { + blocks: [], + models: [], + }; + + if (!conflicts) { + return empty; + } - const noPotentialConflicts = - Object.keys(conflicts.itemTypes).length === 0 && - Object.keys(conflicts.plugins).length === 0; + const entries: ItemTypeEntry[] = exportSchema.itemTypes.map( + (exportItemType) => ({ + exportItemType, + projectItemType: + conflicts.itemTypes[String(exportItemType.id)] ?? undefined, + }), + ); - const groupedItemTypes = defaults( - mapValues( - groupBy( - map(conflicts.itemTypes, (projectItemType, exportItemTypeId) => { - const exportItemType = exportSchema.getItemTypeById(exportItemTypeId); - return { exportItemTypeId, exportItemType, projectItemType }; + const grouped = entries.reduce< + Record<'blocks' | 'models', ItemTypeEntry[]> + >( + (accumulator, entry) => { + const key: 'blocks' | 'models' = entry.exportItemType.attributes + .modular_block + ? 'blocks' + : 'models'; + accumulator[key].push(entry); + return accumulator; + }, + { blocks: [], models: [] }, + ); + + const sortByUnresolvedThenName = (items: ItemTypeEntry[]) => + sortEntriesByDisplayName( + [...items].sort((a, b) => { + // Unresolved first + const aUnresolved = isItemTypeConflictUnresolved( + a.exportItemType, + a.projectItemType, + ); + const bUnresolved = isItemTypeConflictUnresolved( + b.exportItemType, + b.projectItemType, + ); + if (aUnresolved !== bUnresolved) { + return aUnresolved ? -1 : 1; + } + // Then any remaining conflicts (already resolved) before non-conflicts + const aHasConflict = Boolean(a.projectItemType); + const bHasConflict = Boolean(b.projectItemType); + if (aHasConflict !== bHasConflict) { + return aHasConflict ? -1 : 1; + } + return 0; }), - ({ exportItemType }) => - exportItemType?.attributes.modular_block ? 'blocks' : 'models', - ), - (group) => - sortBy(group, ({ exportItemType }) => + (entry) => getTextWithoutRepresentativeEmojiAndPadding( - exportItemType.attributes.name, + entry.exportItemType.attributes.name, ), - ), - ), - { blocks: [], models: [] }, + ); + + return { + blocks: sortByUnresolvedThenName(grouped.blocks), + models: sortByUnresolvedThenName(grouped.models), + }; + }, [conflicts, exportSchema, formValues, formErrors]); + + // Deterministic sorting keeps plugin ordering stable between renders. + const pluginEntries = useMemo(() => { + if (!conflicts) { + return []; + } + + const entries: PluginEntry[] = exportSchema.plugins.map((exportPlugin) => ({ + exportPlugin, + projectPlugin: conflicts.plugins[String(exportPlugin.id)] ?? undefined, + })); + + const unresolvedFirst = [...entries].sort((a, b) => { + const aUnresolved = isPluginConflictUnresolved( + a.exportPlugin, + a.projectPlugin, + ); + const bUnresolved = isPluginConflictUnresolved( + b.exportPlugin, + b.projectPlugin, + ); + if (aUnresolved !== bUnresolved) { + return aUnresolved ? -1 : 1; + } + const aHasConflict = Boolean(a.projectPlugin); + const bHasConflict = Boolean(b.projectPlugin); + if (aHasConflict !== bHasConflict) { + return aHasConflict ? -1 : 1; + } + return 0; + }); + + return sortEntriesByDisplayName(unresolvedFirst, (entry) => + getTextWithoutRepresentativeEmojiAndPadding( + entry.exportPlugin.attributes.name, + ), + ); + }, [conflicts, exportSchema, formValues, formErrors]); + + // Returns true while an item-type conflict still needs user input. + function isItemTypeConflictUnresolved( + exportItemType: SchemaTypes.ItemType, + projectItemType?: SchemaTypes.ItemType, + ) { + if (!projectItemType) { + return false; + } + + const fieldPrefix = `itemType-${exportItemType.id}`; + const strategy = get(formValues, [fieldPrefix, 'strategy']); + const hasErrors = Boolean(get(formErrors, [fieldPrefix])); + + if (!strategy) { + return true; + } + + if (hasErrors) { + return true; + } + + if (strategy === 'rename') { + const name = get(formValues, [fieldPrefix, 'name']); + const apiKey = get(formValues, [fieldPrefix, 'apiKey']); + return !(name && apiKey); + } + + if (strategy === 'reuseExisting') { + return false; + } + + return true; + } + + // Plugins require no extra inputs beyond strategy selection. + function isPluginConflictUnresolved( + exportPlugin: SchemaTypes.Plugin, + projectPlugin?: SchemaTypes.Plugin, + ) { + if (!projectPlugin) { + return false; + } + + const fieldPrefix = `plugin-${exportPlugin.id}`; + const strategy = get(formValues, [fieldPrefix, 'strategy']); + const hasErrors = Boolean(get(formErrors, [fieldPrefix])); + + if (!strategy) { + return true; + } + + if (hasErrors) { + return true; + } + + return false; + } + + // Toggle in place filters the list down to unresolved conflicts when requested. + const visibleModels = itemTypesByCategory.models.filter( + ({ exportItemType, projectItemType }) => + showOnlyConflicts + ? isItemTypeConflictUnresolved(exportItemType, projectItemType) + : true, + ); + + const visibleBlocks = itemTypesByCategory.blocks.filter( + ({ exportItemType, projectItemType }) => + showOnlyConflicts + ? isItemTypeConflictUnresolved(exportItemType, projectItemType) + : true, + ); + + const visiblePlugins = pluginEntries.filter( + ({ exportPlugin, projectPlugin }) => + showOnlyConflicts + ? isPluginConflictUnresolved(exportPlugin, projectPlugin) + : true, + ); + + if (!conflicts) { + return null; + } + + // Always count every conflict to show accurate totals even when filtered. + const itemTypeConflictCount = + itemTypesByCategory.blocks.filter(({ projectItemType }) => projectItemType) + .length + + itemTypesByCategory.models.filter(({ projectItemType }) => projectItemType) + .length; + const pluginConflictCount = pluginEntries.filter( + ({ projectPlugin }) => projectPlugin, + ).length; + + const hasConflicts = itemTypeConflictCount > 0 || pluginConflictCount > 0; + + const unresolvedModelConflicts = itemTypesByCategory.models.some( + ({ exportItemType, projectItemType }) => + isItemTypeConflictUnresolved(exportItemType, projectItemType), + ); + + const unresolvedBlockConflicts = itemTypesByCategory.blocks.some( + ({ exportItemType, projectItemType }) => + isItemTypeConflictUnresolved(exportItemType, projectItemType), ); - const sortedPlugins = sortBy( - map(conflicts.plugins, (projectPlugin, exportPluginId) => { - const exportPlugin = exportSchema.getPluginById(exportPluginId); - return { exportPluginId, exportPlugin, projectPlugin }; - }), - ({ exportPlugin }) => exportPlugin.attributes.name, + const unresolvedPluginConflicts = pluginEntries.some( + ({ exportPlugin, projectPlugin }) => + isPluginConflictUnresolved(exportPlugin, projectPlugin), ); + const hasUnresolvedConflicts = + unresolvedModelConflicts || + unresolvedBlockConflicts || + unresolvedPluginConflicts; + + // When there are no conflicts at all, do not block the CTA on form + // validation state — there is nothing to validate and the button should + // be immediately clickable. Only gate on form validity/validation when + // actual conflicts exist. + const proceedDisabled = + submitting || + (hasConflicts && (validating || !valid || hasUnresolvedConflicts)); + const proceedTooltip = + hasConflicts && (hasUnresolvedConflicts || !valid) + ? 'Select how to resolve the conflicts before proceeding' + : undefined; + return (
    - - - Import conflicts -
    - - +
    +
    Schema overview
    + +
    -
    - {noPotentialConflicts ? ( -

    - No conflicts have been found with existing schema of this project! -

    - ) : ( -

    - Some conflicts exist with the current schema in this project. - Before importing, we need to determine how to handle them. + {!hasConflicts && ( +

    +

    + All set — no conflicting models, blocks, or plugins were found in + this import.

    - )} -
    +
    + )} -
    - {groupedItemTypes.models.length > 0 && ( -
    -
    Models
    -
    - {groupedItemTypes.models.map( - ({ exportItemTypeId, exportItemType, projectItemType }) => ( - - ), - )} -
    -
    - )} - - {groupedItemTypes.blocks.length > 0 && ( -
    -
    - Block models -
    -
    - {groupedItemTypes.blocks.map( - ({ exportItemTypeId, exportItemType, projectItemType }) => ( - - ), - )} -
    -
    - )} - - {sortedPlugins.length > 0 && ( -
    -
    Plugins
    -
    - {sortedPlugins.map( - ({ exportPluginId, exportPlugin, projectPlugin }) => ( - - ), - )} -
    -
    - )} -
    + {(() => { + type SectionKey = 'models' | 'blocks' | 'plugins'; + const baseOrder: SectionKey[] = ['models', 'blocks', 'plugins']; + const unresolvedByKey: Record = { + models: unresolvedModelConflicts, + blocks: unresolvedBlockConflicts, + plugins: unresolvedPluginConflicts, + }; + const sectionOrder = [...baseOrder].sort((a, b) => { + if (unresolvedByKey[a] !== unresolvedByKey[b]) { + return unresolvedByKey[a] ? -1 : 1; + } + return baseOrder.indexOf(a) - baseOrder.indexOf(b); + }); + + return sectionOrder.map((section) => { + if (section === 'models' && visibleModels.length > 0) { + return ( +
    +
    + Models ({visibleModels.length}) +
    +
    + {visibleModels.map(({ exportItemType, projectItemType }) => ( + + ))} +
    +
    + ); + } + + if (section === 'blocks' && visibleBlocks.length > 0) { + return ( +
    +
    + Block models ({visibleBlocks.length}) +
    +
    + {visibleBlocks.map(({ exportItemType, projectItemType }) => ( + + ))} +
    +
    + ); + } + + if (section === 'plugins' && visiblePlugins.length > 0) { + return ( +
    +
    + Plugins ({visiblePlugins.length}) +
    +
    + {visiblePlugins.map(({ exportPlugin, projectPlugin }) => ( + + ))} +
    +
    + ); + } + return null; + }); + })()}
    - + {/* Left slot intentionally empty for layout parity with Export flow */} +
    +
    + +

    The import will never alter any existing elements in the schema.

    diff --git a/import-export-schema/src/entrypoints/ImportPage/FileDropZone.tsx b/import-export-schema/src/entrypoints/ImportPage/FileDropZone.tsx index 48cb0ca0..20204bdd 100644 --- a/import-export-schema/src/entrypoints/ImportPage/FileDropZone.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/FileDropZone.tsx @@ -1,37 +1,52 @@ -import type { ExportDoc } from '@/utils/types'; import { faFolderOpen } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import type { RenderPageCtx } from 'datocms-plugin-sdk'; import { Button, useCtx } from 'datocms-react-ui'; import type React from 'react'; import { type ReactNode, useCallback, useRef, useState } from 'react'; +import type { ExportDoc } from '@/utils/types'; type Props = { onJsonDrop: (filename: string, exportDoc: ExportDoc) => void; children: (button: ReactNode) => ReactNode; }; +/** + * Handles drag-and-drop and manual file selection, validating JSON before delegating upwards. + */ export default function FileDropZone({ onJsonDrop, children }: Props) { const ctx = useCtx(); const fileInputRef = useRef(null); + // Track nested dragenter/leaves so moving over children does not cancel pending state + const dragDepthRef = useRef(0); const [pendingDrop, setPendingDrop] = useState(false); - const handleDragOver = useCallback((e: React.DragEvent) => { + const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }, []); - const handleDragEnter = useCallback((e: React.DragEvent) => { + const handleDragEnter = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); - setPendingDrop(true); + // Increment depth on every dragenter (including children) + dragDepthRef.current += 1; + // Only show pending if a file is being dragged + const hasFiles = Array.from(e.dataTransfer?.types ?? []).includes('Files'); + if (hasFiles) { + setPendingDrop(true); + } }, []); - const handleDragLeave = useCallback((e: React.DragEvent) => { + const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); - setPendingDrop(false); + // Decrement depth; only clear pending when leaving root entirely + dragDepthRef.current = Math.max(0, dragDepthRef.current - 1); + if (dragDepthRef.current === 0) { + setPendingDrop(false); + } }, []); const handleFileSelection = useCallback( @@ -55,7 +70,7 @@ export default function FileDropZone({ onJsonDrop, children }: Props) { const json = JSON.parse(result) as ExportDoc; onJsonDrop(file.name, json); - } catch (err) { + } catch (_err) { ctx.alert('Invalid JSON format'); } }; @@ -65,10 +80,12 @@ export default function FileDropZone({ onJsonDrop, children }: Props) { ); const handleDrop = useCallback( - (e: React.DragEvent) => { + (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); + // Reset depth and pending state on drop + dragDepthRef.current = 0; setPendingDrop(false); const file = e.dataTransfer.files[0]; @@ -96,12 +113,13 @@ export default function FileDropZone({ onJsonDrop, children }: Props) { ); return ( -
    {children( <> @@ -122,6 +140,6 @@ export default function FileDropZone({ onJsonDrop, children }: Props) { , )} -
    + ); } diff --git a/import-export-schema/src/entrypoints/ImportPage/GraphEntitiesContext.tsx b/import-export-schema/src/entrypoints/ImportPage/GraphEntitiesContext.tsx new file mode 100644 index 00000000..115c08ed --- /dev/null +++ b/import-export-schema/src/entrypoints/ImportPage/GraphEntitiesContext.tsx @@ -0,0 +1,11 @@ +import { createContext } from 'react'; + +type GraphEntitiesContextValue = { + hasItemTypeNode: (id: string) => boolean; + hasPluginNode: (id: string) => boolean; +}; + +export const GraphEntitiesContext = createContext({ + hasItemTypeNode: () => false, + hasPluginNode: () => false, +}); diff --git a/import-export-schema/src/entrypoints/ImportPage/ImportItemTypeNodeRenderer.tsx b/import-export-schema/src/entrypoints/ImportPage/ImportItemTypeNodeRenderer.tsx index 41e94e89..50390323 100644 --- a/import-export-schema/src/entrypoints/ImportPage/ImportItemTypeNodeRenderer.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/ImportItemTypeNodeRenderer.tsx @@ -1,14 +1,17 @@ +import type { NodeProps } from '@xyflow/react'; +import classNames from 'classnames'; +import { useContext } from 'react'; import { type ItemTypeNode, ItemTypeNodeRenderer, } from '@/components/ItemTypeNodeRenderer'; +import { SelectedEntityContext } from '@/components/SchemaOverview/SelectedEntityContext'; import { ConflictsContext } from '@/entrypoints/ImportPage/ConflictsManager/ConflictsContext'; import { useResolutionStatusForItemType } from '@/entrypoints/ImportPage/ResolutionsForm'; -import { SelectedEntityContext } from '@/entrypoints/ImportPage/SelectedEntityContext'; -import type { NodeProps } from '@xyflow/react'; -import classNames from 'classnames'; -import { useContext } from 'react'; +/** + * Renders import graph item-type nodes, overlaying conflict and resolution state styling. + */ export function ImportItemTypeNodeRenderer(props: NodeProps) { const { itemType } = props.data; @@ -36,8 +39,8 @@ export function ImportItemTypeNodeRenderer(props: NodeProps) { apiKey={resolutionNewApiKey || itemType.attributes.api_key} className={classNames( unresolvedConflict && 'app-node--conflict', - resolutionStrategyIsReuseExisting && 'app-node__excluded-from-export', - selectedEntityContext.entity === itemType && 'app-node__focused', + resolutionStrategyIsReuseExisting && 'app-node--excluded', + selectedEntityContext.entity === itemType && 'app-node--focused', )} /> ); diff --git a/import-export-schema/src/entrypoints/ImportPage/ImportPluginNodeRenderer.tsx b/import-export-schema/src/entrypoints/ImportPage/ImportPluginNodeRenderer.tsx index d82574bf..e6d3dab7 100644 --- a/import-export-schema/src/entrypoints/ImportPage/ImportPluginNodeRenderer.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/ImportPluginNodeRenderer.tsx @@ -1,12 +1,12 @@ +import type { NodeProps } from '@xyflow/react'; +import classNames from 'classnames'; +import { useContext } from 'react'; import { type PluginNode, PluginNodeRenderer, } from '@/components/PluginNodeRenderer'; +import { SelectedEntityContext } from '@/components/SchemaOverview/SelectedEntityContext'; import { ConflictsContext } from '@/entrypoints/ImportPage/ConflictsManager/ConflictsContext'; -import { SelectedEntityContext } from '@/entrypoints/ImportPage/SelectedEntityContext'; -import type { NodeProps } from '@xyflow/react'; -import classNames from 'classnames'; -import { useContext } from 'react'; import { useResolutionStatusForPlugin } from './ResolutionsForm'; export function ImportPluginNodeRenderer(props: NodeProps) { @@ -21,8 +21,8 @@ export function ImportPluginNodeRenderer(props: NodeProps) { {...props} className={classNames( conflict && resolution?.invalid && 'app-node--conflict', - conflict && !resolution?.invalid && 'app-node__excluded-from-export', - selectedEntityContext.entity === plugin && 'app-node__focused', + conflict && !resolution?.invalid && 'app-node--excluded', + selectedEntityContext.entity === plugin && 'app-node--focused', )} /> ); diff --git a/import-export-schema/src/entrypoints/ImportPage/ImportWorkflow.tsx b/import-export-schema/src/entrypoints/ImportPage/ImportWorkflow.tsx new file mode 100644 index 00000000..5b6d4b1d --- /dev/null +++ b/import-export-schema/src/entrypoints/ImportPage/ImportWorkflow.tsx @@ -0,0 +1,77 @@ +import type { RenderPageCtx } from 'datocms-plugin-sdk'; +import { Spinner } from 'datocms-react-ui'; +import { BlankSlate } from '@/components/BlankSlate'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; +import type { ExportDoc } from '@/utils/types'; +import type { ExportSchema } from '../ExportPage/ExportSchema'; +import type { Conflicts } from './ConflictsManager/buildConflicts'; +import { ConflictsContext } from './ConflictsManager/ConflictsContext'; +import FileDropZone from './FileDropZone'; +import { Inner } from './Inner'; +import ResolutionsForm, { type Resolutions } from './ResolutionsForm'; + +type Props = { + ctx: RenderPageCtx; + projectSchema: ProjectSchema; + exportSchema: [string, ExportSchema] | undefined; + loadingRecipe: boolean; + conflicts: Conflicts | undefined; + onDrop: (filename: string, doc: ExportDoc) => Promise; + onImport: (resolutions: Resolutions) => Promise; +}; + +/** + * Encapsulates the import-side UX, from file drop through conflict resolution. + */ +export function ImportWorkflow({ + ctx, + projectSchema, + exportSchema, + loadingRecipe, + conflicts, + onDrop, + onImport, +}: Props) { + return ( + + {(button) => { + if (exportSchema) { + if (!conflicts) { + return ; + } + + return ( + + + + + + ); + } + + if (loadingRecipe) { + return ; + } + + return ( + +

    + Drag and drop your exported JSON file here, or click the + button to select one from your computer. +

    + {button} + + } + /> + ); + }} +
    + ); +} diff --git a/import-export-schema/src/entrypoints/ImportPage/Inner.tsx b/import-export-schema/src/entrypoints/ImportPage/Inner.tsx index f7c7812f..496cceac 100644 --- a/import-export-schema/src/entrypoints/ImportPage/Inner.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/Inner.tsx @@ -1,5 +1,6 @@ -import { type AppNode, type Graph, edgeTypes } from '@/utils/graph/types'; import type { SchemaTypes } from '@datocms/cma-client'; +import { faXmark } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Background, type NodeMouseHandler, @@ -7,16 +8,21 @@ import { ReactFlow, useReactFlow, } from '@xyflow/react'; -import { VerticalSplit } from 'datocms-react-ui'; -import { useCallback, useEffect, useState } from 'react'; +import { Button } from 'datocms-react-ui'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { SelectedEntityContext } from '@/components/SchemaOverview/SelectedEntityContext'; +import { GRAPH_NODE_THRESHOLD } from '@/shared/constants/graph'; +import { type AppNode, edgeTypes, type Graph } from '@/utils/graph/types'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; import type { ExportSchema } from '../ExportPage/ExportSchema'; +import { buildGraphFromExportDoc } from './buildGraphFromExportDoc'; import ConflictsManager from './ConflictsManager'; +import { GraphEntitiesContext } from './GraphEntitiesContext'; import { ImportItemTypeNodeRenderer } from './ImportItemTypeNodeRenderer'; import { ImportPluginNodeRenderer } from './ImportPluginNodeRenderer'; import { useSkippedItemsAndPluginIds } from './ResolutionsForm'; -import { SelectedEntityContext } from './SelectedEntityContext'; -import { buildGraphFromExportDoc } from './buildGraphFromExportDoc'; +// Map React Flow node types to the dedicated renderers for import graphs. const nodeTypes: NodeTypes = { itemType: ImportItemTypeNodeRenderer, plugin: ImportPluginNodeRenderer, @@ -24,19 +30,26 @@ const nodeTypes: NodeTypes = { type Props = { exportSchema: ExportSchema; + schema: ProjectSchema; + ctx: import('datocms-plugin-sdk').RenderPageCtx; }; -export function Inner({ exportSchema }: Props) { - const { fitBounds, fitView } = useReactFlow(); +/** + * Displays the import graph, helps the user inspect potential conflicts, and keeps + * the selection in sync with the conflict resolution form. + */ +export function Inner({ exportSchema, schema, ctx: _ctx }: Props) { + const { fitBounds, fitView, setNodes, setEdges } = useReactFlow(); const { skippedItemTypeIds, skippedPluginIds } = useSkippedItemsAndPluginIds(); - useEffect(() => { - setTimeout(() => fitView(), 100); - }, []); - const [graph, setGraph] = useState(); + const [forceRenderGraph, setForceRenderGraph] = useState(false); + const [pendingZoomEntity, setPendingZoomEntity] = useState< + SchemaTypes.ItemType | SchemaTypes.Plugin | null | undefined + >(undefined); + // Rebuild the graph when the export document or skip lists change. useEffect(() => { async function run() { setGraph(await buildGraphFromExportDoc(exportSchema, skippedItemTypeIds)); @@ -61,60 +74,208 @@ export function Inner({ exportSchema }: Props) { ); }, []); - function handleSelectEntity( - newEntity: undefined | SchemaTypes.ItemType | SchemaTypes.Plugin, - zoomIn?: boolean, - ) { - setSelectedEntity(newEntity); - - if (zoomIn && graph) { - if (newEntity) { - const node = graph.nodes.find((node) => - newEntity.type === 'plugin' - ? node.type === 'plugin' && node.data.plugin.id === newEntity.id - : node.type === 'itemType' && - node.data.itemType.id === newEntity.id, - )!; - - fitBounds( - { x: node.position.x, y: node.position.y, width: 200, height: 200 }, - { duration: 800, padding: 1 }, - ); - } else { - fitView({ duration: 800 }); + const requestCancel = useCallback(() => { + window.dispatchEvent(new CustomEvent('import:request-cancel')); + }, []); + + const totalPotentialNodes = + exportSchema.itemTypes.length + exportSchema.plugins.length; + + const graphTooLarge = + !!graph && + (graph.nodes.length > GRAPH_NODE_THRESHOLD || + totalPotentialNodes > GRAPH_NODE_THRESHOLD); + + useEffect(() => { + if (!graphTooLarge && forceRenderGraph) { + setForceRenderGraph(false); + } + }, [graphTooLarge, forceRenderGraph]); + + // Prefer the interactive graph for small/medium selections; allow manual override otherwise. + const showGraph = !!graph && (!graphTooLarge || forceRenderGraph); + + useEffect(() => { + if (!graph) { + setNodes([]); + setEdges([]); + return; + } + + if (!showGraph) { + setNodes(graph.nodes); + setEdges(graph.edges); + } + }, [graph, showGraph, setNodes, setEdges]); + + const graphEntitySets = useMemo(() => { + const itemTypeIds = new Set(); + const pluginIds = new Set(); + + if (graph) { + for (const node of graph.nodes) { + if (node.type === 'itemType') { + itemTypeIds.add(node.data.itemType.id); + } + if (node.type === 'plugin') { + pluginIds.add(node.data.plugin.id); + } } } - } + + return { itemTypeIds, pluginIds }; + }, [graph]); + + useEffect(() => { + if (!showGraph || pendingZoomEntity === undefined || !graph) { + return; + } + + if (pendingZoomEntity === null) { + fitView({ duration: 800 }); + setPendingZoomEntity(undefined); + return; + } + + const node = graph.nodes.find((node) => + pendingZoomEntity.type === 'plugin' + ? node.type === 'plugin' && node.data.plugin.id === pendingZoomEntity.id + : node.type === 'itemType' && + node.data.itemType.id === pendingZoomEntity.id, + ); + + if (!node) { + setPendingZoomEntity(undefined); + return; + } + + fitBounds( + { x: node.position.x, y: node.position.y, width: 200, height: 200 }, + { duration: 800, padding: 1 }, + ); + setPendingZoomEntity(undefined); + }, [fitBounds, fitView, graph, pendingZoomEntity, showGraph]); + + // Zoom the viewport to the full graph once React Flow has mounted and the graph is visible. + useEffect(() => { + if (!showGraph) { + return; + } + const timeout = window.setTimeout(() => fitView(), 100); + return () => window.clearTimeout(timeout); + }, [fitView, showGraph, graph?.nodes.length]); + + const handleSelectEntity = useCallback( + ( + newEntity: undefined | SchemaTypes.ItemType | SchemaTypes.Plugin, + zoomIn?: boolean, + ) => { + setSelectedEntity(newEntity); + + if (!zoomIn) { + return; + } + + setPendingZoomEntity(newEntity ?? null); + }, + [graphTooLarge], + ); return ( - graphEntitySets.itemTypeIds.has(id), + hasPluginNode: (id) => graphEntitySets.pluginIds.has(id), + }} > - -
    - {graph && ( - setSelectedEntity(undefined)} - onNodeClick={onNodeClick} + +
    +
    +
    - - - )} -
    -
    - +
    + +
    + {graph && showGraph && ( + setSelectedEntity(undefined)} + onNodeClick={onNodeClick} + > + + + )} + {graph && !showGraph && ( +
    +
    + This graph has {graph.nodes.length} nodes. Trying to render + it may slow down your browser. +
    + +
    + )} +
    +
    +
    +
    + +
    +
    - -
    + + ); } diff --git a/import-export-schema/src/entrypoints/ImportPage/ResolutionsForm.tsx b/import-export-schema/src/entrypoints/ImportPage/ResolutionsForm.tsx index 4884ae6a..72709ce7 100644 --- a/import-export-schema/src/entrypoints/ImportPage/ResolutionsForm.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/ResolutionsForm.tsx @@ -1,8 +1,9 @@ -import type { ProjectSchema } from '@/utils/ProjectSchema'; -import { useNodes, useReactFlow } from '@xyflow/react'; -import { get, keyBy, set } from 'lodash-es'; +import get from 'lodash-es/get'; +import keyBy from 'lodash-es/keyBy'; +import set from 'lodash-es/set'; import { type ReactNode, useContext, useMemo } from 'react'; import { Form as FormHandler, useFormState } from 'react-final-form'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; import { ConflictsContext } from './ConflictsManager/ConflictsContext'; export type ItemTypeConflictResolutionRename = { @@ -39,6 +40,7 @@ type Props = { onSubmit: (values: Resolutions) => void; }; +// Mirrors the platform validation rules plus common reserved identifiers. function isValidApiKey(apiKey: string) { if (!apiKey.match(/^[a-z](([a-z0-9]|_(?![_0-9]))*[a-z0-9])$/)) { return false; @@ -64,15 +66,12 @@ function isValidApiKey(apiKey: string) { return true; } +/** + * Hosts the conflict resolution form and exposes helpers for components to read state. + */ export default function ResolutionsForm({ schema, children, onSubmit }: Props) { const conflicts = useContext(ConflictsContext); - const { getNode } = useReactFlow(); - - // we need this to re-render this component everytime the nodes change, and - // revalidate the form! - useNodes(); - const initialValues = useMemo( () => conflicts @@ -89,6 +88,7 @@ export default function ResolutionsForm({ schema, children, onSubmit }: Props) { `itemType-${id}`, { strategy: null, + // Suggest sensible rename defaults to speed up resolution. name: `${projectItemType.attributes.name} (Import)`, apiKey: `${projectItemType.attributes.api_key}_import`, }, @@ -100,7 +100,7 @@ export default function ResolutionsForm({ schema, children, onSubmit }: Props) { [conflicts], ); - function handleSubmit(values: FormValues) { + async function handleSubmit(values: FormValues) { const resolutions: Resolutions = { itemTypes: {}, plugins: {} }; if (!conflicts) { @@ -108,29 +108,21 @@ export default function ResolutionsForm({ schema, children, onSubmit }: Props) { } for (const pluginId of Object.keys(conflicts.plugins)) { - if (!getNode(`plugin--${pluginId}`)) { - continue; - } - const result = get(values, [`plugin-${pluginId}`]) as PluginValues; - - resolutions.plugins[pluginId] = { - strategy: result.strategy as 'reuseExisting' | 'skip', - }; + if (result?.strategy) { + resolutions.plugins[pluginId] = { + strategy: result.strategy as 'reuseExisting' | 'skip', + }; + } } for (const itemTypeId of Object.keys(conflicts.itemTypes)) { - if (!getNode(`itemType--${itemTypeId}`)) { - continue; - } - const fieldPrefix = `itemType-${itemTypeId}`; - const result = get(values, fieldPrefix) as ItemTypeValues; - if (result.strategy === 'reuseExisting') { + if (result?.strategy === 'reuseExisting') { resolutions.itemTypes[itemTypeId] = { strategy: 'reuseExisting' }; - } else { + } else if (result?.strategy === 'rename') { resolutions.itemTypes[itemTypeId] = { strategy: 'rename', apiKey: result.apiKey!, @@ -139,7 +131,7 @@ export default function ResolutionsForm({ schema, children, onSubmit }: Props) { } } - onSubmit(resolutions); + await onSubmit(resolutions); } if (!conflicts) { @@ -149,64 +141,87 @@ export default function ResolutionsForm({ schema, children, onSubmit }: Props) { return ( initialValues={initialValues} - validate={async (values) => { + validate={(values) => { const errors: Record = {}; if (!conflicts) { return {}; } - const projectItemTypes = await schema.getAllItemTypes(); - const itemTypesByName = keyBy(projectItemTypes, 'attributes.name'); - const itemTypesByApiKey = keyBy(projectItemTypes, 'attributes.api_key'); + const pluginIds = Object.keys(conflicts.plugins); + const itemTypeIds = Object.keys(conflicts.itemTypes); - for (const pluginId of Object.keys(conflicts.plugins)) { - if (!getNode(`plugin--${pluginId}`)) { - continue; - } + // No conflicts at all → nothing to validate; return synchronously. + if (pluginIds.length === 0 && itemTypeIds.length === 0) { + return {}; + } + // Synchronous required checks for strategies across plugins/item types. + for (const pluginId of pluginIds) { const fieldPrefix = `plugin-${pluginId}`; - if (!get(values, [fieldPrefix, 'strategy'])) { set(errors, [fieldPrefix, 'strategy'], 'Required!'); } } - for (const itemTypeId of Object.keys(conflicts.itemTypes)) { - if (!getNode(`itemType--${itemTypeId}`)) { - continue; - } - + let hasRename = false; + for (const itemTypeId of itemTypeIds) { const fieldPrefix = `itemType-${itemTypeId}`; - const strategy = get(values, [fieldPrefix, 'strategy']); - if (!strategy) { set(errors, [fieldPrefix, 'strategy'], 'Required!'); } - if (strategy === 'rename') { + hasRename = true; const name = get(values, [fieldPrefix, 'name']); - if (!name) { set(errors, [fieldPrefix, 'name'], 'Required!'); - } else if (name in itemTypesByName) { - set(errors, [fieldPrefix, 'name'], 'Already used in project!'); } - const apiKey = get(values, [fieldPrefix, 'apiKey']); - if (!apiKey) { set(errors, [fieldPrefix, 'apiKey'], 'Required!'); } else if (!isValidApiKey(apiKey)) { set(errors, [fieldPrefix, 'apiKey'], 'Invalid format'); - } else if (apiKey in itemTypesByApiKey) { - set(errors, [fieldPrefix, 'apiKey'], 'Already used in project!'); } } } - return errors; + // If there are no rename validations to check against the project + // (or there were only required/format errors), return synchronously to + // avoid toggling Final Form's `validating` flag. + if (!hasRename) { + return errors; + } + + // Only now perform the async lookup needed to check for collisions + // against existing project item types. + return (async () => { + const projectItemTypes = await schema.getAllItemTypes(); + const itemTypesByName = keyBy(projectItemTypes, 'attributes.name'); + const itemTypesByApiKey = keyBy( + projectItemTypes, + 'attributes.api_key', + ); + + for (const itemTypeId of itemTypeIds) { + const fieldPrefix = `itemType-${itemTypeId}`; + const strategy = get(values, [fieldPrefix, 'strategy']); + if (strategy !== 'rename') continue; + + const name = get(values, [fieldPrefix, 'name']); + if (name && name in itemTypesByName) { + set(errors, [fieldPrefix, 'name'], 'Already used in project!'); + } + const apiKey = get(values, [fieldPrefix, 'apiKey']); + if (apiKey) { + if (apiKey in itemTypesByApiKey) { + set(errors, [fieldPrefix, 'apiKey'], 'Already used in project!'); + } + } + } + + return errors; + })(); }} onSubmit={handleSubmit} > @@ -215,6 +230,9 @@ export default function ResolutionsForm({ schema, children, onSubmit }: Props) { ); } +/** + * Convenience hook for grabbing validity + values for a specific item type row. + */ export function useResolutionStatusForItemType(itemTypeId: string) { const state = useFormState(); @@ -233,6 +251,7 @@ export function useResolutionStatusForItemType(itemTypeId: string) { }; } +/** Same as above but for plugin conflicts. */ export function useResolutionStatusForPlugin(pluginId: string) { const state = useFormState(); @@ -251,6 +270,9 @@ export function useResolutionStatusForPlugin(pluginId: string) { }; } +/** + * Derive which entities are being reused so the graph/list views can hide them. + */ export function useSkippedItemsAndPluginIds() { const conflicts = useContext(ConflictsContext); const formState = useFormState(); diff --git a/import-export-schema/src/entrypoints/ImportPage/SelectedEntityContext.tsx b/import-export-schema/src/entrypoints/ImportPage/SelectedEntityContext.tsx deleted file mode 100644 index 264c0403..00000000 --- a/import-export-schema/src/entrypoints/ImportPage/SelectedEntityContext.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type { SchemaTypes } from '@datocms/cma-client'; -import { createContext } from 'react'; - -type Context = { - entity: undefined | SchemaTypes.ItemType | SchemaTypes.Plugin; - set: ( - newEntity: undefined | SchemaTypes.ItemType | SchemaTypes.Plugin, - zoomIn?: boolean, - ) => void; -}; - -export const SelectedEntityContext = createContext({ - entity: undefined, - set: () => {}, -}); diff --git a/import-export-schema/src/entrypoints/ImportPage/buildGraphFromExportDoc.ts b/import-export-schema/src/entrypoints/ImportPage/buildGraphFromExportDoc.ts index 5adcd0c8..3c8b6140 100644 --- a/import-export-schema/src/entrypoints/ImportPage/buildGraphFromExportDoc.ts +++ b/import-export-schema/src/entrypoints/ImportPage/buildGraphFromExportDoc.ts @@ -1,89 +1,17 @@ -import { buildHierarchyNodes } from '@/utils/graph/buildHierarchyNodes'; -import { rebuildGraphWithPositionsFromHierarchy } from '@/utils/graph/rebuildGraphWithPositionsFromHierarchy'; +import { buildGraph } from '@/utils/graph/buildGraph'; import type { Graph } from '@/utils/graph/types'; -import type { SchemaTypes } from '@datocms/cma-client'; +import { ExportSchemaSource } from '@/utils/schema/ExportSchemaSource'; import type { ExportSchema } from '../ExportPage/ExportSchema'; -import { - buildEdgesForItemType, - buildItemTypeNode, - buildPluginNode, - deterministicGraphSort, -} from '../ExportPage/buildGraphFromSchema'; - -type QueueItem = SchemaTypes.ItemType | SchemaTypes.Plugin; export async function buildGraphFromExportDoc( exportSchema: ExportSchema, itemTypeIdsToSkip: string[], ): Promise { - const graph: Graph = { nodes: [], edges: [] }; - const queue: QueueItem[][] = [[exportSchema.rootItemType]]; - const processedNodes = new Set(); - - // Process each level of the graph - while (queue.length > 0) { - const currentLevelItems = queue.shift(); - - if (!currentLevelItems) { - throw new Error('Unexpected error: currentLevelItemTypes is undefined'); - } - - const nextLevelQueue = new Set(); - - for (const itemTypeOrPlugin of currentLevelItems) { - if (processedNodes.has(itemTypeOrPlugin)) { - continue; - } - - processedNodes.add(itemTypeOrPlugin); - - if (itemTypeOrPlugin.type === 'item_type') { - const itemType = itemTypeOrPlugin; - - const fields = exportSchema.getItemTypeFields(itemType); - const fieldsets = exportSchema.getItemTypeFieldsets(itemType); - - graph.nodes.push(buildItemTypeNode(itemType, fields, fieldsets)); - - if (itemTypeIdsToSkip.includes(itemType.id)) { - continue; - } - - const [edges, linkedItemTypeIds, linkedPluginIds] = - await buildEdgesForItemType( - itemType, - fields, - exportSchema.rootItemType, - ); - - graph.edges.push(...edges); - - for (const linkedItemTypeId of linkedItemTypeIds) { - const linkedItemType = - exportSchema.itemTypesById.get(linkedItemTypeId)!; - nextLevelQueue.add(linkedItemType); - } - - for (const linkedPluginId of linkedPluginIds) { - const linkedPlugin = exportSchema.pluginsById.get(linkedPluginId)!; - - nextLevelQueue.add(linkedPlugin); - } - } else { - const plugin = itemTypeOrPlugin; - - // Add current node to graph - graph.nodes.push(buildPluginNode(plugin)); - } - } - - // If we have nodes for the next level, add them to the queue - if (nextLevelQueue.size > 0) { - queue.push([...nextLevelQueue]); - } - } - - const sortedGraph = deterministicGraphSort(graph); - const hierarchy = buildHierarchyNodes(sortedGraph); - return rebuildGraphWithPositionsFromHierarchy(hierarchy, sortedGraph.edges); + // Convert the static export document into the graph format expected by React Flow. + const source = new ExportSchemaSource(exportSchema); + return buildGraph({ + source, + initialItemTypes: exportSchema.rootItemTypes, + itemTypeIdsToSkip, + }); } diff --git a/import-export-schema/src/entrypoints/ImportPage/buildImportDoc.ts b/import-export-schema/src/entrypoints/ImportPage/buildImportDoc.ts index 341b8c2e..10fab709 100644 --- a/import-export-schema/src/entrypoints/ImportPage/buildImportDoc.ts +++ b/import-export-schema/src/entrypoints/ImportPage/buildImportDoc.ts @@ -1,8 +1,8 @@ +import type { SchemaTypes } from '@datocms/cma-client'; import { findLinkedItemTypeIds, findLinkedPluginIds, } from '@/utils/datocms/schema'; -import type { SchemaTypes } from '@datocms/cma-client'; import type { ExportSchema } from '../ExportPage/ExportSchema'; import type { Conflicts } from './ConflictsManager/buildConflicts'; import type { @@ -28,6 +28,9 @@ export type ImportDoc = { }; }; +/** + * Walk the export graph while honoring conflict resolutions, producing a document for import. + */ export async function buildImportDoc( exportSchema: ExportSchema, conflicts: Conflicts, @@ -44,7 +47,8 @@ export async function buildImportDoc( }, }; - const queue: QueueItem[][] = [[exportSchema.rootItemType]]; + // Breadth-first traversal keeps dependencies ordered for creation. + const queue: QueueItem[][] = [exportSchema.rootItemTypes]; const processedNodes = new Set(); // Process each level of the graph @@ -93,7 +97,10 @@ export async function buildImportDoc( nextLevelQueue.add(linkedItemType); } - for (const linkedPluginId of await findLinkedPluginIds(field)) { + for (const linkedPluginId of findLinkedPluginIds( + field, + new Set(Array.from(exportSchema.pluginsById.keys())), + )) { const linkedPlugin = exportSchema.pluginsById.get(linkedPluginId)!; nextLevelQueue.add(linkedPlugin); diff --git a/import-export-schema/src/entrypoints/ImportPage/importSchema.ts b/import-export-schema/src/entrypoints/ImportPage/importSchema.ts index c8898b19..32261856 100644 --- a/import-export-schema/src/entrypoints/ImportPage/importSchema.ts +++ b/import-export-schema/src/entrypoints/ImportPage/importSchema.ts @@ -1,224 +1,418 @@ -import { - defaultAppearanceForFieldType, - isHardcodedEditor, -} from '@/utils/datocms/fieldTypeInfo'; +import { type Client, generateId, type SchemaTypes } from '@datocms/cma-client'; +import find from 'lodash-es/find'; +import get from 'lodash-es/get'; +import isEqual from 'lodash-es/isEqual'; +import omit from 'lodash-es/omit'; +import pick from 'lodash-es/pick'; +import set from 'lodash-es/set'; +import sortBy from 'lodash-es/sortBy'; +import { mapAppearanceToProject } from '@/utils/datocms/appearance'; import { validatorsContainingBlocks, validatorsContainingLinks, } from '@/utils/datocms/schema'; -import { type Client, type SchemaTypes, generateId } from '@datocms/cma-client'; -import { find, get, isEqual, omit, pick, set, sortBy } from 'lodash-es'; +import { debugLog } from '@/utils/debug'; import type { ImportDoc } from './buildImportDoc'; -// biome-ignore lint/suspicious/noExplicitAny: Doesn't work with unknown :( -type PromiseGeneratorFn = (...args: any[]) => Promise; +/** Convenience helper to surface clearer errors when an ID mapping is missing. */ +function getOrThrow(map: Map, key: K, context: string): V { + const value = map.get(key); + if (value === undefined) { + throw new Error(`Missing mapping for ${String(key)} in ${context}`); + } + return value; +} -export type ImportProgress = { total: number; finished: number }; +export type ImportProgress = { + total: number; + finished: number; + label?: string; +}; -export default async function importSchema( - importDoc: ImportDoc, - client: Client, - updateProgress: (progress: ImportProgress) => void, -) { - // const [client, unsubscribe] = await withEventsSubscription(rawClient); +export type ImportResult = { + itemTypeIdByExportId: Record; + fieldIdByExportId: Record; + fieldsetIdByExportId: Record; + pluginIdByExportId: Record; +}; - let total = 0; - let finished = 0; +type ProgressUpdate = (progress: ImportProgress) => void; - function track(promiseGeneratorFn: T): T { - return (async (...args: Parameters) => { - total += 1; - updateProgress({ total, finished }); - try { - const result = await promiseGeneratorFn(...args); - return result; - } finally { - finished += 1; - updateProgress({ total, finished }); - } - }) as T; +type ShouldCancel = () => boolean; + +/** + * Reports task progress while guarding against cancellation between steps. + */ +class ProgressTracker { + private finished = 0; + + constructor( + private readonly total: number, + private readonly update: ProgressUpdate, + private readonly shouldCancel: ShouldCancel, + ) {} + + checkCancel() { + if (this.shouldCancel()) { + throw new Error('Import cancelled'); + } } - const { locales } = await client.site.find(); + private report(label?: string) { + this.update({ total: this.total, finished: this.finished, label }); + } - const itemTypeIdMappings: Map = new Map(); - const fieldIdMappings: Map = new Map(); - const fieldsetIdMappings: Map = new Map(); - const pluginIdMappings: Map = new Map(); + async run( + labelForCall: (...args: TArgs) => string, + fn: (...args: TArgs) => Promise, + ...args: TArgs + ): Promise { + let label: string | undefined; + try { + this.checkCancel(); + label = labelForCall(...args); + this.report(label); + const result = await fn(...args); + this.checkCancel(); + return result; + } finally { + this.finished += 1; + this.report(label); + } + } +} + +type ImportMappings = { + itemTypeIds: Map; + fieldIds: Map; + fieldsetIds: Map; + pluginIds: Map; +}; + +type ImportContext = { + client: Client; + tracker: ProgressTracker; + locales: string[]; + importDoc: ImportDoc; + mappings: ImportMappings; +}; + +/** + * Pre-generate project-side IDs for every entity that will be created during import. + */ +function prepareMappings(importDoc: ImportDoc): ImportMappings { + const itemTypeIds = new Map(); + const fieldIds = new Map(); + const fieldsetIds = new Map(); + const pluginIds = new Map(); for (const toCreate of importDoc.itemTypes.entitiesToCreate) { - itemTypeIdMappings.set(toCreate.entity.id, generateId()); + itemTypeIds.set(toCreate.entity.id, generateId()); for (const field of toCreate.fields) { - fieldIdMappings.set(field.id, generateId()); + fieldIds.set(field.id, generateId()); } for (const fieldset of toCreate.fieldsets) { - fieldsetIdMappings.set(fieldset.id, generateId()); + fieldsetIds.set(fieldset.id, generateId()); } } for (const [exportId, projectId] of Object.entries( importDoc.itemTypes.idsToReuse, )) { - itemTypeIdMappings.set(exportId, projectId); + itemTypeIds.set(exportId, projectId); } - for (const toCreate of importDoc.plugins.entitiesToCreate) { - pluginIdMappings.set(toCreate.id, generateId()); + for (const plugin of importDoc.plugins.entitiesToCreate) { + pluginIds.set(plugin.id, generateId()); } for (const [exportId, projectId] of Object.entries( importDoc.plugins.idsToReuse, )) { - pluginIdMappings.set(exportId, projectId); + pluginIds.set(exportId, projectId); + } + + return { itemTypeIds, fieldIds, fieldsetIds, pluginIds }; +} + +/** + * Concurrency-limited map that respects cancellation signals between iterations. + */ +async function pMap( + items: readonly T[], + limit: number, + iteratee: (item: T, index: number) => Promise, + checkCancel: () => void, +): Promise { + const results: R[] = new Array(items.length); + let nextIndex = 0; + let error: unknown = null; + + async function worker() { + while (true) { + if (error) return; + const current = nextIndex; + if (current >= items.length) return; + nextIndex += 1; + try { + checkCancel(); + const result = await iteratee(items[current], current); + results[current] = result; + } catch (err) { + error = err; + return; + } + } } - // Create new plugins - await Promise.all( - importDoc.plugins.entitiesToCreate.map( - track(async (plugin) => { - const data: SchemaTypes.PluginCreateSchema['data'] = { - type: 'plugin', - id: pluginIdMappings.get(plugin.id), - attributes: plugin.attributes.package_name - ? pick(plugin.attributes, ['package_name']) - : plugin.meta.version === '2' - ? omit(plugin.attributes, ['parameters']) - : omit(plugin.attributes, [ - 'parameter_definitions', - 'field_types', - 'plugin_type', - 'parameters', - ]), - }; - - try { - console.log('Creating plugin', data); - const { data: plugin } = await client.plugins.rawCreate({ data }); - - if (!isEqual(plugin.attributes.parameters, {})) { - try { - await client.plugins.update(pluginIdMappings.get(plugin.id)!, { - parameters: plugin.attributes.parameters, - }); - } catch (e) { - // NOP - // Legacy plugin parameters might be invalid + const workers = Array.from( + { length: Math.max(1, Math.min(limit, items.length)) }, + () => worker(), + ); + await Promise.all(workers); + if (error) throw error; + return results; +} + +/** + * Install any plugins bundled with the export before creating linked entities. + */ +async function createPluginsPhase(context: ImportContext) { + const { + client, + tracker, + importDoc: { + plugins: { entitiesToCreate: pluginsToCreate }, + }, + mappings: { pluginIds }, + } = context; + + await pMap( + pluginsToCreate, + 4, + (plugin) => + tracker.run( + (p: SchemaTypes.Plugin) => + `Creating plugin: ${ + p.attributes.name || p.attributes.package_name || p.id + }`, + async (p: SchemaTypes.Plugin) => { + const data: SchemaTypes.PluginCreateSchema['data'] = { + type: 'plugin', + id: pluginIds.get(p.id), + attributes: p.attributes.package_name + ? pick(p.attributes, ['package_name']) + : p.meta.version === '2' + ? omit(p.attributes, ['parameters']) + : omit(p.attributes, [ + 'parameter_definitions', + 'field_types', + 'plugin_type', + 'parameters', + ]), + }; + + try { + debugLog('Creating plugin', data); + const { data: created } = await client.plugins.rawCreate({ data }); + + if (!isEqual(created.attributes.parameters, {})) { + try { + await client.plugins.update(created.id, { + parameters: created.attributes.parameters, + }); + } catch { + // ignore invalid legacy parameters + } } + debugLog('Created plugin', created); + } catch (error) { + console.error('Failed to create plugin', data, error); } - console.log('Created plugin', plugin); - } catch (e) { - console.error('Failed to create plugin', data, e); - } - }), - ), + }, + plugin, + ), + () => tracker.checkCancel(), ); +} - // Create new item types - const createdItemTypes = await Promise.all( - importDoc.itemTypes.entitiesToCreate.map( - track(async (toCreate) => { - const data: SchemaTypes.ItemTypeCreateSchema['data'] = { - type: 'item_type', - id: itemTypeIdMappings.get(toCreate.entity.id), - attributes: omit(toCreate.entity.attributes, [ - 'has_singleton_item', - 'ordering_direction', - 'ordering_meta', - ]), - }; - - try { - if (toCreate.rename) { - data.attributes.name = toCreate.rename.name; - data.attributes.api_key = toCreate.rename.apiKey; +/** + * Create item types (models and blocks) and return the freshly created records. + */ +async function createItemTypesPhase( + context: ImportContext, +): Promise> { + const { + client, + tracker, + importDoc: { + itemTypes: { entitiesToCreate: itemTypesToCreate }, + }, + mappings: { itemTypeIds }, + } = context; + + return pMap( + itemTypesToCreate, + 3, + (toCreate) => + tracker.run( + (t: ImportDoc['itemTypes']['entitiesToCreate'][number]) => + `Creating ${t.entity.attributes.modular_block ? 'block' : 'model'}: ${ + t.rename?.name || t.entity.attributes.name + }`, + async (t: ImportDoc['itemTypes']['entitiesToCreate'][number]) => { + const data: SchemaTypes.ItemTypeCreateSchema['data'] = { + type: 'item_type', + id: itemTypeIds.get(t.entity.id), + attributes: omit(t.entity.attributes, [ + 'has_singleton_item', + 'ordering_direction', + 'ordering_meta', + ]), + }; + + if (t.rename) { + data.attributes.name = t.rename.name; + data.attributes.api_key = t.rename.apiKey; } - console.log('Creating item type', data); - const { data: itemType } = await client.itemTypes.rawCreate({ data }); - console.log('Created item type', itemType); - - return itemType; - } catch (e) { - console.error('Failed to create item type', data, e); - } - }), - ), + try { + debugLog('Creating item type', data); + const { data: created } = await client.itemTypes.rawCreate({ + data, + }); + debugLog('Created item type', created); + return created; + } catch (error) { + console.error('Failed to create item type', data, error); + return undefined; + } + }, + toCreate, + ), + () => tracker.checkCancel(), ); +} - // Create fields and fieldsets - await Promise.all( - importDoc.itemTypes.entitiesToCreate.map( - async ({ entity: { id: itemTypeId }, fields, fieldsets }) => { - await Promise.all( - fieldsets.map( - track(async (fieldset) => { +/** + * Create fieldsets and fields for each item type, respecting dependencies and validators. + */ +async function createFieldsetsAndFieldsPhase(context: ImportContext) { + const { + client, + tracker, + locales, + importDoc: { + itemTypes: { entitiesToCreate: itemTypesToCreate }, + }, + mappings, + } = context; + + await pMap( + itemTypesToCreate, + 2, + async ({ entity, fields, fieldsets }) => { + const itemTypeId = entity.id; + + await pMap( + fieldsets, + 4, + (fieldset) => + tracker.run( + (_fs: SchemaTypes.Fieldset) => + `Creating fieldset in ${entity.attributes.name}`, + async (fs: SchemaTypes.Fieldset) => { const data: SchemaTypes.FieldsetCreateSchema['data'] = { - ...omit(fieldset, ['relationships']), - id: fieldsetIdMappings.get(fieldset.id), + ...omit(fs, ['relationships']), + id: mappings.fieldsetIds.get(fs.id), }; try { - console.log('Creating fieldset', data); - - const { data: fieldset } = await client.fieldsets.rawCreate( - itemTypeIdMappings.get(itemTypeId)!, - { - data, - }, + debugLog('Creating fieldset', data); + const itemTypeProjectId = getOrThrow( + mappings.itemTypeIds, + itemTypeId, + 'fieldset create', ); - - console.log('Created fieldset', fieldset); - } catch (e) { - console.error('Failed to create fieldset', data, e); + const { data: created } = await client.fieldsets.rawCreate( + itemTypeProjectId, + { data }, + ); + debugLog('Created fieldset', created); + } catch (error) { + console.error('Failed to create fieldset', data, error); } - }), + }, + fieldset, ), - ); + () => tracker.checkCancel(), + ); - const nonSlugFields = fields.filter( - (field) => field.attributes.field_type !== 'slug', - ); + const nonSlugFields = fields.filter( + (field) => field.attributes.field_type !== 'slug', + ); - await Promise.all( - nonSlugFields.map( - track((field) => - importField(field, { + await pMap( + nonSlugFields, + 6, + (field) => + tracker.run( + (f: SchemaTypes.Field) => + `Creating field ${f.attributes.label || f.attributes.api_key} in ${entity.attributes.name}`, + (f: SchemaTypes.Field) => + importField(f, { client, locales, - fieldIdMappings, - pluginIdMappings, - fieldsetIdMappings, - itemTypeIdMappings, + mappings, }), - ), + field, ), - ); + () => tracker.checkCancel(), + ); - const slugFields = fields.filter( - (field) => field.attributes.field_type === 'slug', - ); + const slugFields = fields.filter( + (field) => field.attributes.field_type === 'slug', + ); - await Promise.all( - slugFields.map( - track((field) => - importField(field, { + await pMap( + slugFields, + 4, + (field) => + tracker.run( + (f: SchemaTypes.Field) => + `Creating field ${f.attributes.label || f.attributes.api_key} in ${entity.attributes.name}`, + (f: SchemaTypes.Field) => + importField(f, { client, locales, - fieldIdMappings, - pluginIdMappings, - fieldsetIdMappings, - itemTypeIdMappings, + mappings, }), - ), + field, ), - ); - }, - ), + () => tracker.checkCancel(), + ); + }, + () => tracker.checkCancel(), ); +} - // Finalize new item types +/** + * Apply relationship and ordering metadata that requires created field IDs. + */ +async function finalizeItemTypesPhase( + context: ImportContext, + createdItemTypes: Array, +) { + const { + client, + tracker, + importDoc: { + itemTypes: { entitiesToCreate: itemTypesToCreate }, + }, + mappings, + } = context; const relationshipsToUpdate = [ 'ordering_field', @@ -228,149 +422,270 @@ export default async function importSchema( 'presentation_title_field', 'presentation_image_field', ] as const; - const attributesToUpdate = ['ordering_direction', 'ordering_meta']; - await Promise.all( - importDoc.itemTypes.entitiesToCreate.map( - track(async (toCreate) => { - const id = itemTypeIdMappings.get(toCreate.entity.id)!; - const createdItemType = find(createdItemTypes, { id })!; - - const data: SchemaTypes.ItemTypeUpdateSchema['data'] = { - type: 'item_type', - id, - attributes: pick(toCreate.entity.attributes, attributesToUpdate), - relationships: relationshipsToUpdate.reduce( - (acc, relationshipName) => { - const handle = get( - toCreate.entity, - `relationships.${relationshipName}.data`, - ); - - return { - ...acc, - [relationshipName]: { - data: handle - ? { - type: 'field', - id: fieldIdMappings.get(handle.id)!, - } - : null, - }, - }; - }, - {} as NonNullable< - SchemaTypes.ItemTypeUpdateSchema['data']['relationships'] - >, - ), - }; - - try { - console.log( - data.relationships, - pick(createdItemType.attributes, attributesToUpdate), - pick(createdItemType.relationships, relationshipsToUpdate), + await pMap( + itemTypesToCreate, + 3, + (toCreate) => + tracker.run( + (t: ImportDoc['itemTypes']['entitiesToCreate'][number]) => + `Finalizing ${t.entity.attributes.modular_block ? 'block' : 'model'}: ${ + t.rename?.name || t.entity.attributes.name + }`, + async (t: ImportDoc['itemTypes']['entitiesToCreate'][number]) => { + const id = getOrThrow( + mappings.itemTypeIds, + t.entity.id, + 'finalize item type', ); - if ( - !isEqual( - data.relationships, - pick(createdItemType.relationships, relationshipsToUpdate), - ) || - !isEqual( - data.attributes, - pick(createdItemType.attributes, attributesToUpdate), - ) - ) { - console.log('Finalizing item type', data); - const { data: updatedItemType } = await client.itemTypes.rawUpdate( - id, - { data }, - ); - console.log('Finalized item type', updatedItemType); + const createdItemType = find(createdItemTypes, { id }); + if (!createdItemType) { + throw new Error(`Item type not found after creation: ${id}`); } - } catch (e) { - console.error('Failed to finalize item type', data, e); - } - }), - ), - ); - // Reorder fields and fieldsets - await Promise.all( - importDoc.itemTypes.entitiesToCreate.map( - track(async ({ entity: itemType, fields, fieldsets }) => { - const allEntities = [...fieldsets, ...fields]; + const data: SchemaTypes.ItemTypeUpdateSchema['data'] = { + type: 'item_type', + id, + attributes: pick(t.entity.attributes, attributesToUpdate), + relationships: relationshipsToUpdate.reduce( + (acc, relationshipName) => { + const handle = get( + t.entity, + `relationships.${relationshipName}.data`, + ); - if (allEntities.length <= 1) { - return; - } + return { + ...acc, + [relationshipName]: { + data: handle + ? { + type: 'field', + id: getOrThrow( + mappings.fieldIds, + handle.id, + 'finalize relationships', + ), + } + : null, + }, + }; + }, + {} as NonNullable< + SchemaTypes.ItemTypeUpdateSchema['data']['relationships'] + >, + ), + }; + + try { + debugLog('Finalize diff snapshot', { + relationships: data.relationships, + currentAttributes: pick( + createdItemType.attributes, + attributesToUpdate, + ), + currentRelationships: pick( + createdItemType.relationships, + relationshipsToUpdate, + ), + }); + if ( + !isEqual( + data.relationships, + pick(createdItemType.relationships, relationshipsToUpdate), + ) || + !isEqual( + data.attributes, + pick(createdItemType.attributes, attributesToUpdate), + ) + ) { + debugLog('Finalizing item type', data); + const { data: updatedItemType } = + await client.itemTypes.rawUpdate(id, { data }); + debugLog('Finalized item type', updatedItemType); + } + } catch (error) { + console.error('Failed to finalize item type', data, error); + } + }, + toCreate, + ), + () => tracker.checkCancel(), + ); +} - try { - console.log( - 'Reordering fields/fieldsets for item type', - itemTypeIdMappings.get(itemType.id)!, - ); - for (const entity of sortBy(allEntities, [ - 'attributes', - 'position', - ])) { - if (entity.type === 'fieldset') { - await client.fieldsets.update( - fieldsetIdMappings.get(entity.id)!, - { - position: entity.attributes.position, - }, - ); - } else { - await client.fields.update(fieldIdMappings.get(entity.id)!, { - position: entity.attributes.position, - }); +/** + * Restore the original ordering for fieldsets and fields to match the export. + */ +async function reorderEntitiesPhase(context: ImportContext) { + const { + client, + tracker, + importDoc: { + itemTypes: { entitiesToCreate: itemTypesToCreate }, + }, + mappings, + } = context; + + await pMap( + itemTypesToCreate, + 3, + (obj) => + tracker.run( + (o: ImportDoc['itemTypes']['entitiesToCreate'][number]) => { + const { entity } = o; + return `Reordering fields/fieldsets for ${entity.attributes.name}`; + }, + async (o: ImportDoc['itemTypes']['entitiesToCreate'][number]) => { + const { entity: itemType, fields, fieldsets } = o; + const allEntities = [...fieldsets, ...fields]; + + if (allEntities.length <= 1) { + return; + } + + try { + debugLog('Reordering fields/fieldsets for item type', { + itemTypeId: getOrThrow( + mappings.itemTypeIds, + itemType.id, + 'reorder start log', + ), + }); + for (const entity of sortBy(allEntities, [ + 'attributes', + 'position', + ])) { + tracker.checkCancel(); + if (entity.type === 'fieldset') { + await client.fieldsets.update( + getOrThrow( + mappings.fieldsetIds, + entity.id, + 'fieldset reorder', + ), + { + position: entity.attributes.position, + }, + ); + } else { + await client.fields.update( + getOrThrow(mappings.fieldIds, entity.id, 'field reorder'), + { + position: entity.attributes.position, + }, + ); + } } + debugLog('Reordered fields/fieldsets for item type', { + itemTypeId: getOrThrow( + mappings.itemTypeIds, + itemType.id, + 'reorder log', + ), + }); + } catch (error) { + console.error('Failed to reorder fields/fieldsets', error); } - console.log( - 'Reordered fields/fieldsets for item type', - itemTypeIdMappings.get(itemType.id)!, - ); - } catch (e) { - console.error('Failed to reorder fields/fieldsets', e); - } - }), - ), + }, + obj, + ), + () => tracker.checkCancel(), + ); +} + +export default async function importSchema( + importDoc: ImportDoc, + client: Client, + updateProgress: ProgressUpdate, + opts?: { shouldCancel?: ShouldCancel }, +): Promise { + const shouldCancel = opts?.shouldCancel ?? (() => false); + + const pluginCreates = importDoc.plugins.entitiesToCreate.length; + const itemTypeCreates = importDoc.itemTypes.entitiesToCreate.length; + const fieldsetCreates = importDoc.itemTypes.entitiesToCreate.reduce( + (acc, it) => acc + it.fieldsets.length, + 0, + ); + const fieldCreates = importDoc.itemTypes.entitiesToCreate.reduce( + (acc, it) => acc + it.fields.length, + 0, ); + const finalizeUpdates = itemTypeCreates; + const reorderBatches = itemTypeCreates; + + const total = + pluginCreates + + itemTypeCreates + + fieldsetCreates + + fieldCreates + + finalizeUpdates + + reorderBatches; + + const tracker = new ProgressTracker(total, updateProgress, shouldCancel); + + tracker.checkCancel(); + const { locales } = await client.site.find(); + tracker.checkCancel(); + + const mappings = prepareMappings(importDoc); + const context: ImportContext = { + client, + tracker, + locales, + importDoc, + mappings, + }; - // unsubscribe(); + await createPluginsPhase(context); + const createdItemTypes = await createItemTypesPhase(context); + await createFieldsetsAndFieldsPhase(context); + await finalizeItemTypesPhase(context, createdItemTypes); + await reorderEntitiesPhase(context); + + return { + itemTypeIdByExportId: Object.fromEntries(mappings.itemTypeIds), + fieldIdByExportId: Object.fromEntries(mappings.fieldIds), + fieldsetIdByExportId: Object.fromEntries(mappings.fieldsetIds), + pluginIdByExportId: Object.fromEntries(mappings.pluginIds), + }; } type ImportFieldOptions = { client: Client; locales: string[]; - fieldIdMappings: Map; - fieldsetIdMappings: Map; - itemTypeIdMappings: Map; - pluginIdMappings: Map; + mappings: ImportMappings; }; +/** + * Create a single field in the target project, translating validators and appearance. + */ async function importField( field: SchemaTypes.Field, - { - client, - locales, - fieldIdMappings, - fieldsetIdMappings, - itemTypeIdMappings, - pluginIdMappings, - }: ImportFieldOptions, + { client, locales, mappings }: ImportFieldOptions, ) { + const nextAppearance = await mapAppearanceToProject( + field, + mappings.pluginIds, + ); + const data: SchemaTypes.FieldCreateSchema['data'] = { ...field, - id: fieldIdMappings.get(field.id), + id: mappings.fieldIds.get(field.id), + attributes: { + ...field.attributes, + }, relationships: { fieldset: { data: field.relationships.fieldset.data ? { type: 'fieldset', - id: fieldsetIdMappings.get(field.relationships.fieldset.data.id)!, + id: getOrThrow( + mappings.fieldsetIds, + field.relationships.fieldset.data.id, + 'field appearance fieldset mapping', + ), } : null, }, @@ -379,12 +694,12 @@ async function importField( const validators = [ ...validatorsContainingLinks.filter( - (i) => i.field_type === field.attributes.field_type, + (item) => item.field_type === field.attributes.field_type, ), ...validatorsContainingBlocks.filter( - (i) => i.field_type === field.attributes.field_type, + (item) => item.field_type === field.attributes.field_type, ), - ].map((i) => i.validator); + ].map((item) => item.validator); for (const validator of validators) { const fieldLinkedItemTypeIds = get( @@ -394,39 +709,36 @@ async function importField( const newIds: string[] = []; - for (const fieldLinkedItemTypeId of fieldLinkedItemTypeIds) { - if (itemTypeIdMappings.has(fieldLinkedItemTypeId)) { - newIds.push(itemTypeIdMappings.get(fieldLinkedItemTypeId)!); - } + for (const fieldLinkedItemTypeId of fieldLinkedItemTypeIds ?? []) { + const mapped = mappings.itemTypeIds.get(fieldLinkedItemTypeId); + if (mapped) newIds.push(mapped); } - set(data.attributes.validators!, validator, newIds); + const validatorsContainer = (data.attributes.validators ?? {}) as Record< + string, + unknown + >; + set(validatorsContainer, validator, newIds); + data.attributes.validators = + validatorsContainer as typeof data.attributes.validators; } const slugTitleFieldValidator = field.attributes.validators .slug_title_field as undefined | { title_field_id: string }; if (slugTitleFieldValidator) { - data.attributes.validators!.slug_title_field = { - title_field_id: fieldIdMappings.get( - slugTitleFieldValidator.title_field_id, - )!, + const mapped = getOrThrow( + mappings.fieldIds, + slugTitleFieldValidator.title_field_id, + 'slug title field', + ); + (data.attributes.validators as Record).slug_title_field = { + title_field_id: mapped, }; } - data.attributes.appeareance = undefined; - - if (!(await isHardcodedEditor(field.attributes.appearance.editor))) { - if (pluginIdMappings.has(field.attributes.appearance.editor)) { - data.attributes.appearance!.editor = pluginIdMappings.get( - field.attributes.appearance.editor, - )!; - } else { - data.attributes.appearance = await defaultAppearanceForFieldType( - field.attributes.field_type, - ); - } - } + delete (data.attributes as { appearance?: unknown }).appearance; + delete (data.attributes as { appeareance?: unknown }).appeareance; if (field.attributes.localized) { const oldDefaultValues = field.attributes.default_value as Record< @@ -434,25 +746,27 @@ async function importField( unknown >; data.attributes.default_value = Object.fromEntries( - locales.map((locale) => [locale, oldDefaultValues[locale] || null]), + locales.map((locale) => [locale, oldDefaultValues?.[locale] ?? null]), ); } - data.attributes.appearance!.addons = field.attributes.appearance.addons - .filter((addon) => pluginIdMappings.has(addon.id)) - .map((addon) => ({ ...addon, id: pluginIdMappings.get(addon.id)! })); + data.attributes.appearance = nextAppearance; try { - console.log('Creating field', data); + debugLog('Creating field', data); + const itemTypeProjectId = getOrThrow( + mappings.itemTypeIds, + field.relationships.item_type.data.id, + 'field create', + ); const { data: createdField } = await client.fields.rawCreate( - itemTypeIdMappings.get(field.relationships.item_type.data.id)!, + itemTypeProjectId, { data, }, ); - console.log('Created field', createdField); - } catch (e) { - console.log('failed to create field', data, e); - throw e; + debugLog('Created field', createdField); + } catch (error) { + console.error('Failed to create field', data, error); } } diff --git a/import-export-schema/src/entrypoints/ImportPage/index.tsx b/import-export-schema/src/entrypoints/ImportPage/index.tsx index 49a8fb66..593d4593 100644 --- a/import-export-schema/src/entrypoints/ImportPage/index.tsx +++ b/import-export-schema/src/entrypoints/ImportPage/index.tsx @@ -1,256 +1,349 @@ -import { ProjectSchema } from '@/utils/ProjectSchema'; -import type { ExportDoc } from '@/utils/types'; -import { buildClient } from '@datocms/cma-client'; -import { faXmark } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ReactFlowProvider } from '@xyflow/react'; import type { RenderPageCtx } from 'datocms-plugin-sdk'; +import { Canvas } from 'datocms-react-ui'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { TaskOverlayStack } from '@/components/TaskOverlayStack'; +import { useConflictsBuilder } from '@/shared/hooks/useConflictsBuilder'; +import { useProjectSchema } from '@/shared/hooks/useProjectSchema'; import { - Button, - Canvas, - Spinner, - Toolbar, - ToolbarStack, - ToolbarTitle, -} from 'datocms-react-ui'; -import { useEffect, useMemo, useState } from 'react'; + type UseLongTaskResult, + useLongTask, +} from '@/shared/tasks/useLongTask'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; +import type { ExportDoc } from '@/utils/types'; import { ExportSchema } from '../ExportPage/ExportSchema'; -import { ConflictsContext } from './ConflictsManager/ConflictsContext'; -import buildConflicts, { - type Conflicts, -} from './ConflictsManager/buildConflicts'; -import FileDropZone from './FileDropZone'; -import { Inner } from './Inner'; -import ResolutionsForm, { type Resolutions } from './ResolutionsForm'; import { buildImportDoc } from './buildImportDoc'; -import importSchema, { type ImportProgress } from './importSchema'; +import { ImportWorkflow } from './ImportWorkflow'; +import importSchema from './importSchema'; +import type { Resolutions } from './ResolutionsForm'; +import { useRecipeLoader } from './useRecipeLoader'; + type Props = { ctx: RenderPageCtx; }; -export function ImportPage({ ctx }: Props) { - const params = new URLSearchParams(ctx.location.search); - const recipeUrl = params.get('recipe_url'); - const recipeTitle = params.get('recipe_title'); - const [loadingRecipeByUrl, setLoadingRecipeByUrl] = useState(false); - - useEffect(() => { - async function run() { - if (!recipeUrl) { - return; - } - - try { - setLoadingRecipeByUrl(true); - const uri = new URL(recipeUrl); - - const response = await fetch(recipeUrl); - const body = await response.json(); - - const schema = new ExportSchema(body as ExportDoc); - setExportSchema([ - recipeTitle || uri.pathname.split('/').pop()!, - schema, - ]); - } finally { - setLoadingRecipeByUrl(false); - } - } - - run(); - }, [recipeUrl]); +type ImportModeState = { + exportSchema: [string, ExportSchema] | undefined; + conflicts: ReturnType['conflicts']; + loadingRecipe: boolean; + handleDrop: (filename: string, doc: ExportDoc) => Promise; + handleImport: (resolutions: Resolutions) => Promise; + importTask: UseLongTaskResult; + conflictsTask: UseLongTaskResult; +}; +/** + * Encapsulates the import tab lifecycle: loading shared recipes, reacting to file drops, + * resolving conflicts, and driving the long-running CMA import task. + */ +function useImportMode({ + ctx, + projectSchema, +}: { + ctx: RenderPageCtx; + projectSchema: ProjectSchema; +}): ImportModeState { + const importTask = useLongTask(); + const conflictsTask = useLongTask(); const [exportSchema, setExportSchema] = useState< [string, ExportSchema] | undefined >(); - const [importProgress, setImportProgress] = useState< - ImportProgress | undefined - >(undefined); + const { conflicts, setConflicts } = useConflictsBuilder({ + exportSchema: exportSchema?.[1], + projectSchema, + task: conflictsTask.controller, + }); - async function handleDrop(filename: string, doc: ExportDoc) { - try { - const schema = new ExportSchema(doc); - setExportSchema([filename, schema]); - } catch (e) { - console.error(e); - ctx.alert(e instanceof Error ? e.message : 'Invalid export file!'); - } - } + const client = projectSchema.client; - const client = useMemo( - () => - buildClient({ - apiToken: ctx.currentUserAccessToken!, - environment: ctx.environment, - }), - [ctx.currentUserAccessToken, ctx.environment], + const handleRecipeLoaded = useCallback( + ({ label, schema }: { label: string; schema: ExportSchema }) => { + setExportSchema([label, schema]); + }, + [], ); - const projectSchema = useMemo(() => new ProjectSchema(client), [client]); + const handleRecipeError = useCallback( + (error: unknown) => { + console.error('Failed to load shared export recipe', error); + ctx.alert('Could not load the shared export recipe.'); + }, + [ctx], + ); - const [conflicts, setConflicts] = useState(); + const { loading: loadingRecipe } = useRecipeLoader(ctx, handleRecipeLoaded, { + onError: handleRecipeError, + }); - useEffect(() => { - async function run() { - if (!exportSchema) { - return; + const handleDrop = useCallback( + async (filename: string, doc: ExportDoc) => { + try { + const schema = new ExportSchema(doc); + setExportSchema([filename, schema]); + } catch (error) { + console.error(error); + ctx.alert( + error instanceof Error ? error.message : 'Invalid export file!', + ); } - setConflicts(await buildConflicts(exportSchema[1], projectSchema)); - } + }, + [ctx], + ); - run(); - }, [exportSchema, projectSchema]); + const handleImport = useCallback( + async (resolutions: Resolutions) => { + if (!exportSchema || !conflicts) { + throw new Error('Invariant'); + } - async function handleImport(resolutions: Resolutions) { - if (!exportSchema || !conflicts) { - throw new Error('Invariant'); - } + try { + importTask.controller.start({ + done: 0, + total: 1, + label: 'Preparing import…', + }); - try { - setImportProgress({ finished: 0, total: 1 }); + const importDoc = await buildImportDoc( + exportSchema[1], + conflicts, + resolutions, + ); - const importDoc = await buildImportDoc( - exportSchema[1], - conflicts, - resolutions, - ); + await importSchema( + importDoc, + client, + (progress) => { + if (!importTask.controller.isCancelRequested()) { + importTask.controller.setProgress({ + done: progress.finished, + total: progress.total, + label: progress.label, + }); + } + }, + { + shouldCancel: () => importTask.controller.isCancelRequested(), + }, + ); - await importSchema(importDoc, client, setImportProgress); + if (importTask.controller.isCancelRequested()) { + throw new Error('Import cancelled'); + } - ctx.notice('Import completed successfully!'); - ctx.navigateTo( - `${ctx.isEnvironmentPrimary ? '' : `/environments/${ctx.environment}`}/configuration/p/${ctx.plugin.id}/pages/import-export`, + importTask.controller.complete({ + done: importTask.state.progress.total, + total: importTask.state.progress.total, + label: 'Import completed', + }); + ctx.notice('Import completed successfully.'); + setExportSchema(undefined); + setConflicts(undefined); + } catch (error) { + console.error(error); + if (error instanceof Error && error.message === 'Import cancelled') { + importTask.controller.complete({ label: 'Import cancelled' }); + ctx.notice('Import canceled'); + } else { + importTask.controller.fail(error); + ctx.alert('Import could not be completed successfully.'); + } + } finally { + importTask.controller.reset(); + } + }, + [ + client, + conflicts, + ctx, + exportSchema, + importTask, + setConflicts, + setExportSchema, + ], + ); + + useEffect(() => { + const handleCancelRequest = async () => { + if (!exportSchema) return; + const result = await ctx.openConfirm({ + title: 'Cancel the import?', + content: `Do you really want to cancel the import process of "${exportSchema[0]}"?`, + choices: [ + { + label: 'Yes, cancel the import', + value: 'yes', + intent: 'negative', + }, + ], + cancel: { + label: 'Nevermind', + value: false, + intent: 'positive', + }, + }); + + if (result === 'yes') { + setExportSchema(undefined); + } + }; + + window.addEventListener( + 'import:request-cancel', + handleCancelRequest as unknown as EventListener, + ); + return () => { + window.removeEventListener( + 'import:request-cancel', + handleCancelRequest as unknown as EventListener, ); + }; + }, [ctx, exportSchema, setExportSchema]); + + return { + exportSchema, + conflicts, + loadingRecipe, + handleDrop, + handleImport, + importTask, + conflictsTask, + }; +} + +type OverlayItemsArgs = { + ctx: RenderPageCtx; + exportSchema: [string, ExportSchema] | undefined; + importTask: UseLongTaskResult; + conflictsTask: UseLongTaskResult; +}; + +/** + * Coalesces the overlay stack configuration used by `TaskOverlayStack`. Keeping the builder + * in one place clarifies which long tasks participate in the UI at any moment. + */ +function useOverlayItems({ + ctx, + exportSchema, + importTask, + conflictsTask, +}: OverlayItemsArgs) { + return useMemo( + () => [ + buildImportOverlay(ctx, importTask, exportSchema), + buildConflictsOverlay(conflictsTask), + ], + [ctx, exportSchema, importTask, conflictsTask], + ); +} + +/** + * Unified import entrypoint rendered inside the Schema sidebar page. Handles file drops + * and conflict resolution for schema imports. + */ +export function ImportPage({ ctx }: Props) { + const projectSchema = useProjectSchema(ctx); + const importMode = useImportMode({ ctx, projectSchema }); - setImportProgress(undefined); - setExportSchema(undefined); - } catch (e) { - console.error(e); - ctx.alert('Import could not be completed successfully.'); - } - } + const overlayItems = useOverlayItems({ + ctx, + exportSchema: importMode.exportSchema, + importTask: importMode.importTask, + conflictsTask: importMode.conflictsTask, + }); return ( - {importProgress ? ( +
    - - - Import in progress -
    - -
    -
    -
    -
    -
    -
    - - Import in progress: please do not close the window or change - section! 🙏 -
    -
    +
    - ) : ( - -
    - {exportSchema ? ( - - - 📄 Import "{exportSchema[0]}" -
    - - - - ) : ( - - - Schema Import/Export -
    - - - )} -
    - - {(button) => - exportSchema ? ( - conflicts ? ( - - - - - - ) : ( - - ) - ) : loadingRecipeByUrl ? ( - - ) : ( -
    -
    -
    - Upload your schema export file -
    + -
    -

    - Drag and drop your exported JSON file here, or click - the button to select one from your computer. -

    - {button} -
    -
    -
    - 💡 Need to export your schema? Go to one of your models - or blocks and choose "Export as JSON". -
    -
    - ) - } -
    -
    -
    - - )} + ); } + +type OverlayConfig = Parameters[0]['items'][number]; + +/** + * Overlay shown while the CMA import runs. Allows cancelling with a confirmation when an + * export recipe is currently loaded. + */ +function buildImportOverlay( + ctx: RenderPageCtx, + importTask: UseLongTaskResult, + exportSchema: [string, ExportSchema] | undefined, +): OverlayConfig { + return { + id: 'import', + task: importTask, + title: 'Import in progress', + subtitle: (state) => + state.cancelRequested + ? 'Cancelling import…' + : 'Sit tight, we’re applying models, fields, and plugins…', + ariaLabel: 'Import in progress', + progressLabel: (progress, state) => + state.cancelRequested + ? 'Stopping at next safe point…' + : (progress.label ?? ''), + cancel: () => ({ + label: 'Cancel import', + intent: importTask.state.cancelRequested ? 'muted' : 'negative', + disabled: importTask.state.cancelRequested, + onCancel: async () => { + if (!exportSchema) return; + const result = await ctx.openConfirm({ + title: 'Cancel import in progress?', + content: + 'Stopping now can leave partial changes in your project. Some models or blocks may be created without relationships, some fields or fieldsets may already exist, and plugin installations or editor settings may be incomplete. You can run the import again to finish or manually clean up. Are you sure you want to cancel?', + choices: [ + { + label: 'Yes, cancel the import', + value: 'yes', + intent: 'negative', + }, + ], + cancel: { + label: 'Nevermind', + value: false, + intent: 'positive', + }, + }); + + if (result === 'yes') { + importTask.controller.requestCancel(); + } + }, + }), + }; +} + +/** + * Overlay used while conflicts between project and recipe are resolved. + */ +function buildConflictsOverlay( + conflictsTask: UseLongTaskResult, +): OverlayConfig { + return { + id: 'conflicts', + task: conflictsTask, + title: 'Preparing import', + subtitle: 'Sit tight, we’re scanning your export against the project…', + ariaLabel: 'Preparing import', + progressLabel: (progress) => progress.label ?? 'Preparing import…', + overlayZIndex: 9998, + }; +} diff --git a/import-export-schema/src/entrypoints/ImportPage/useRecipeLoader.ts b/import-export-schema/src/entrypoints/ImportPage/useRecipeLoader.ts new file mode 100644 index 00000000..ae710ad6 --- /dev/null +++ b/import-export-schema/src/entrypoints/ImportPage/useRecipeLoader.ts @@ -0,0 +1,74 @@ +import type { RenderPageCtx } from 'datocms-plugin-sdk'; +import { useEffect, useState } from 'react'; +import type { ExportDoc } from '@/utils/types'; +import { ExportSchema } from '../ExportPage/ExportSchema'; + +type RecipeLoaderResult = { + loading: boolean; +}; + +type RecipeLoadedCallback = (payload: { + label: string; + schema: ExportSchema; +}) => void; + +type RecipeLoaderOptions = { + onError?: (error: unknown) => void; +}; + +/** + * Watches the page URL for the optional recipe parameters and hydrates an export schema + * when present, exposing a simple loading flag to the caller. + */ +export function useRecipeLoader( + ctx: RenderPageCtx, + onLoaded: RecipeLoadedCallback, + { onError }: RecipeLoaderOptions = {}, +): RecipeLoaderResult { + const [loading, setLoading] = useState(false); + const locationSearch = ctx.location.search; + + useEffect(() => { + const params = new URLSearchParams(locationSearch); + const recipeUrlValue = params.get('recipe_url'); + if (!recipeUrlValue) { + return; + } + const recipeUrl = recipeUrlValue; + + const recipeTitle = params.get('recipe_title'); + let cancelled = false; + + async function run() { + try { + setLoading(true); + const response = await fetch(recipeUrl); + const body = (await response.json()) as ExportDoc; + const schema = new ExportSchema(body); + if (cancelled) return; + const parsedUrl = new URL(recipeUrl); + const fallbackName = + parsedUrl.pathname.split('/').pop() || 'Imported schema'; + onLoaded({ + label: recipeTitle || fallbackName, + schema, + }); + } catch (error) { + if (!cancelled) { + onError?.(error); + console.error('Failed to load recipe export', error); + } + } finally { + if (!cancelled) setLoading(false); + } + } + + void run(); + + return () => { + cancelled = true; + }; + }, [locationSearch, onLoaded, onError]); + + return { loading }; +} diff --git a/import-export-schema/src/icons/fieldgroup-text.svg b/import-export-schema/src/icons/fieldgroup-text.svg deleted file mode 100644 index e226c4ac..00000000 --- a/import-export-schema/src/icons/fieldgroup-text.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/import-export-schema/src/icons/fielgroup-location.svg b/import-export-schema/src/icons/fielgroup-location.svg deleted file mode 100644 index 5edbe475..00000000 --- a/import-export-schema/src/icons/fielgroup-location.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/import-export-schema/src/index.css b/import-export-schema/src/index.css index 405d7b90..a584615a 100644 --- a/import-export-schema/src/index.css +++ b/import-export-schema/src/index.css @@ -2,11 +2,26 @@ svg { display: block; } +:root { + --overlay-gradient: linear-gradient( + 180deg, + rgba(250, 252, 255, 0.96), + rgba(245, 247, 255, 0.96) + ); +} + html, -body { +body, +#root { height: 100%; } +html, +body { + margin: 0; + padding: 0; +} + .dropzone, .export-wrapper { position: absolute; @@ -17,7 +32,7 @@ body { } .dropzone--pending { - &:before { + &::before { content: ""; position: absolute; top: 20px; @@ -48,12 +63,14 @@ body { cursor: pointer; border: 1px solid rgb(var(--color-components)); background: color-mix(in srgb, rgb(var(--color-components)), white 94%); +} +.app-node__body { &:hover { - box-shadow: 0 0 0 3px transparent, 0 0 0 3px - rgb(var(--color-components), 40%); + box-shadow: + 0 0 0 3px transparent, + 0 0 0 3px rgb(var(--color-components), 40%); } - * { text-overflow: ellipsis; overflow: hidden; @@ -109,8 +126,16 @@ body { opacity: 0.2; } -.app-node__focused .app-node__body { - box-shadow: 0 0 0 3px transparent, 0 0 0 3px rgb(var(--color-components), 50%); +/* New standardized modifier class; keep legacy for backward compatibility */ +.app-node--excluded { + opacity: 0.2; +} + +.app-node__focused .app-node__body, +.app-node--focused .app-node__body { + box-shadow: + 0 0 0 3px transparent, + 0 0 0 3px rgb(var(--color-components), 50%); } .tooltip { @@ -119,9 +144,12 @@ body { border-radius: 10px; /* http://smoothshadows.com/#djEsMSw2LDAuMTIsNzgsMCwwLCMwMzA3MTIsI2YzZjRmNiwjZmZmZmZmLDI%3D */ box-shadow: - 0px 0px 2px rgba(3, 7, 18, 0.02), 0px 0px 9px rgba(3, 7, 18, 0.04), - 0px 0px 20px rgba(3, 7, 18, 0.06), 0px 0px 35px rgba(3, 7, 18, 0.08), - 0px 0px 54px rgba(3, 7, 18, 0.1), 0px 0px 78px rgba(3, 7, 18, 0.12); + 0px 0px 2px rgba(3, 7, 18, 0.02), + 0px 0px 9px rgba(3, 7, 18, 0.04), + 0px 0px 20px rgba(3, 7, 18, 0.06), + 0px 0px 35px rgba(3, 7, 18, 0.08), + 0px 0px 54px rgba(3, 7, 18, 0.1), + 0px 0px 78px rgba(3, 7, 18, 0.12); display: flex; flex-direction: column; @@ -193,8 +221,10 @@ code { display: flex; align-items: center; justify-content: center; +} - &:before { +.fieldEdge { + &::before { display: block; content: ""; width: 10px; @@ -202,7 +232,6 @@ code { border-radius: 100px; background: #999; } - .fieldEdge__tooltip { display: none; position: absolute; @@ -213,21 +242,17 @@ code { backdrop-filter: blur(2px); padding: 10px; } - &:hover { - &:before { + &::before { background: #555; } - .fieldEdge__tooltip { display: block; } } - .field { font-size: 10px; } - .field__icon { min-width: 16px; min-height: 16px; @@ -246,8 +271,25 @@ code { flex-direction: column; } +/* Ensure export page never scrolls at the outer container */ +.page--export { + overflow: hidden; +} + .page__toolbar { - min-height: 65px; + /* Keep toolbar compact so content sits higher */ + min-height: 44px; + padding: 6px var(--spacing-l); + display: flex; + align-items: center; + border-bottom: none; /* remove separator line */ + box-shadow: none; /* ensure no shadow line */ +} + +/* Also remove any inner toolbar border/shadow if present */ +.page__toolbar > * { + border-bottom: none; + box-shadow: none; } .page__toolbar__title { @@ -262,9 +304,519 @@ code { overflow: auto; } +/* Center the main blank-slate body on the page */ +.blank-slate { + height: 100%; +} + +/* Ensure centering fills the available content area in both pages and dropzones */ +.page__content > .blank-slate, +.dropzone > .blank-slate { + position: absolute; + inset: 0; +} + +/* Center only the main square, regardless of tips above/below */ +.blank-slate__body { + position: absolute; + top: 35%; + left: 50%; + transform: translate(-50%, -50%); +} + +.blank-slate__body__outside { + position: absolute; + left: 50%; + bottom: 20px; + transform: translateX(-50%); + text-align: center; +} + +/* Generic list resets for clean summaries */ +.list--plain { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: 8px; /* consistent vertical rhythm */ +} + +/* Neutralize ad-hoc margins for items inside plain lists */ +.list--plain > li { + margin: 0; +} + +/* When using connection cards inside plain lists, rely on list gap for spacing */ +:where(.list--plain) .connection-card { + margin: 0; +} + +/* Default row styling for simple list items (not cards) */ +/* Non-card, non-link items styled as simple rows */ +.list--plain > li:not(.connection-card):not(.list-item--link) { + padding: 8px 10px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: #fff; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + /* Allow contents to shrink and ellipsize within the tile */ + min-width: 0; +} + +/* Ensure direct children can shrink and truncate text properly */ +.list--plain > li:not(.connection-card):not(.list-item--link) > * { + min-width: 0; + flex: 1 1 auto; +} + +.list--plain > li:not(.connection-card):not(.list-item--link):hover { + background: rgba(0, 0, 0, 0.02); +} + +/* Linked items: move row chrome to the anchor for full-click area */ +.list--plain > li.list-item--link { + padding: 0; + border: 0; + background: transparent; + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +:where(.list--plain) .list-item--link > a { + display: block; + flex: 1 1 auto; + min-width: 0; + padding: 8px 10px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: #fff; + color: inherit; + text-decoration: none; + box-sizing: border-box; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + z-index: 1; + pointer-events: auto; + cursor: pointer; +} + +:where(.list--plain) .list-item--link > a:hover { + background: rgba(0, 0, 0, 0.02); +} + +/* Slim variant for compact summaries */ +.list--slim { + gap: 4px; +} + +.list--slim > li:not(.connection-card):not(.list-item--link) { + padding: 6px 8px; + border-radius: 6px; +} + +.list--plain code { + background: #f6f7f9; + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 1px 6px; +} + +.list--dashed { + list-style: none; + padding: 0; + margin: 0; +} + +.list--dashed li { + position: relative; + padding-left: 12px; +} + +.list--dashed li::before { + content: "–"; + position: absolute; + left: 0; + color: var(--light-body-color); +} + +/* Summary toolbar meta and chips */ +.summary-toolbar__meta { + display: inline-flex; + gap: 6px; + align-items: center; + flex-wrap: wrap; +} + +.chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 10px; + border: 1px solid var(--border-color); + border-radius: 999px; + background: #fff; + font-size: 12px; + color: var(--base-body-color); +} + +.chip--muted { + color: var(--light-body-color); +} + +/* Make button-based chips look like pills, not default grey buttons */ +button.chip { + appearance: none; + background: #fff; + border: 1px solid var(--border-color); + border-radius: 999px; + color: var(--base-body-color); + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + font: inherit; + font-size: 12px; + line-height: 1; + padding: 3px 10px; +} + +button.chip:hover { + background: rgba(0, 0, 0, 0.02); +} + +/* Active/pressed state for toggle chips */ +button.chip.is-active, +button.chip[aria-pressed="true"] { + background: var(--accent-color); + color: #fff; + border-color: var(--accent-color); +} + +/* Softer variant still works on buttons */ +button.chip.chip--soft { + background: rgba(59, 130, 246, 0.06); + border-color: rgba(59, 130, 246, 0.25); + color: color-mix(in srgb, #3b82f6, black 35%); +} + +button.chip.chip--muted { + color: var(--light-body-color); +} + +/* Accessibility focus ring */ +button.chip:focus-visible { + outline: 2px solid var(--accent-color); + outline-offset: 2px; +} + +/* Connections UI */ +.summary { + background: linear-gradient(180deg, #fafcff, #f5f7ff); +} + +.summary__section { + padding: 16px; + display: grid; + gap: 12px; +} + +.summary__title { + font-weight: 700; + letter-spacing: -0.2px; +} + +.summary__grid { + display: grid; + /* Fluid columns that adapt from 1 → n nicely */ + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--spacing-m, 12px); + align-items: stretch; /* equal height tiles per row */ + grid-auto-flow: row dense; +} + +/* Avoid overflow on long content inside tiles */ +.summary__grid > * { + min-width: 0; +} + +.surface { + border: 1px solid var(--border-color); + border-radius: 8px; + background: #fff; + padding: 12px; +} + +/* New summary layout: left nav + right content */ +.summary__layout { + display: grid; + grid-template-columns: 260px 1fr; + gap: var(--spacing-m, 12px); + align-items: start; +} + +.summary__nav { + display: grid; + gap: 6px; + position: sticky; + top: 8px; +} + +.summary__nav__item { + appearance: none; + border: 1px solid var(--border-color); + background: #fff; + border-radius: 8px; + padding: 8px 10px; + width: 100%; + text-align: left; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + cursor: pointer; +} + +.summary__nav__item.is-active { + border-color: var(--accent-color); + background: color-mix(in srgb, var(--accent-color), white 94%); +} + +.summary__nav__label { + font-weight: 600; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.summary__content { + min-width: 0; +} + +.summary__content__title { + font-weight: 700; + margin-bottom: 8px; +} + +@media (max-width: 900px) { + .summary__layout { + grid-template-columns: 1fr; + } + .summary__nav { + position: relative; + top: auto; + display: flex; + gap: 8px; + overflow-x: auto; + padding-bottom: 4px; + } + .summary__nav__item { + flex: 0 0 auto; + min-width: 160px; + } +} + +.box__title { + font-weight: 600; +} + +.box__meta { + color: var(--light-body-color); +} + +.collapsible__toggle { + display: grid; + grid-template-columns: 20px 1fr; + align-items: center; + gap: 8px; + width: 100%; + background: transparent; /* keep surface white */ + border: 0; + padding: 8px 10px; + border-radius: 6px; + cursor: pointer; + text-align: left; + position: relative; /* ensure stacking context so content doesn't overlay */ + z-index: 0; +} + +.collapsible__toggle:hover { + background: transparent; +} + +.collapsible__title { + font-weight: 600; +} + +.collapsible__content { + margin-top: 8px; + position: relative; + z-index: 0; +} + +.chip--soft { + background: color-mix(in srgb, var(--accent-color, #4f46e5), white 92%); + border-color: color-mix(in srgb, var(--accent-color, #4f46e5), white 70%); + color: var(--accent-color, #4f46e5); +} + +.connections { + display: grid; + gap: 12px; +} + +.connections__toolbar { + display: grid; + grid-template-columns: minmax(260px, 420px) 1fr auto; + align-items: end; + gap: 10px; + border-bottom: 1px solid var(--border-color); + padding-bottom: 8px; +} + +.connections__spacer { + width: 100%; +} + +.connections__actions { + display: grid; + gap: 6px; + align-items: end; + justify-items: end; +} + +.connections__count { + font-size: 12px; + color: var(--light-body-color); +} + +.button-group { + display: inline-flex; + gap: 8px; +} + +.connection-card { + margin: 0 0 8px 0; +} + +.connection-card__container { + border: 1px solid var(--border-color); + border-radius: 8px; + background: #fff; + overflow: hidden; + transition: + box-shadow 160ms ease, + border-color 160ms ease, + transform 160ms ease; +} + +.connection-card__toggle { + display: grid; + grid-template-columns: 20px 1fr auto; + align-items: center; + gap: 8px; + width: 100%; + background: transparent; + border: 0; + padding: 10px 12px; + cursor: pointer; + text-align: left; + transition: background-color 150ms ease; +} + +.connection-card__toggle:hover { + background: rgba(0, 0, 0, 0.02); +} + +.connection-card__toggle:focus-visible { + outline: none; + box-shadow: inset 0 0 0 2px var(--accent-color, #4f46e5); + border-radius: 6px; +} + +.connection-card__toggle[aria-expanded="true"] { + background: rgba(0, 0, 0, 0.02); + border-bottom: 1px solid var(--border-color); +} + +.caret { + display: inline-block; + width: 14px; + text-align: center; + color: var(--light-body-color); +} + +.connection-card__title { + font-weight: 600; +} + +.connection-card__apikey { + color: #666; + font-weight: 400; +} + +.connection-card__counts { + display: inline-flex; + gap: 6px; + align-items: center; +} + +.connection-card__body { + /* Align content with the start of the title column: 12 (container padding) + 20 (caret) + 8 (gap) */ + padding: var(--spacing-l, 16px) var(--spacing-l, 16px) var(--spacing-l, 16px) + calc(12px + 20px + 8px); + border-top: 1px solid var(--border-color); + background: #fafafa; +} + +/* Improve readability of lists within card body */ +.connection-card__body .list--dashed li { + margin: 4px 0; +} + +.connection-card__body code { + background: #f6f7f9; + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 1px 6px; +} + +/* Subtle elevation when open/hover */ +.connection-card__container.is-open, +.connection-card__container:hover { + border-color: color-mix(in srgb, var(--accent-color, #4f46e5), white 60%); + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06); +} + +/* Export page: prevent outer content scrolling; only inner panes scroll */ +.page--export > .page__content { + overflow: hidden; + overflow-y: hidden; + min-height: 0; + padding: 0; +} + +.page--export .export-wrapper { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + min-height: 0; +} + .page__actions { border-top: 1px solid var(--border-color); padding: var(--spacing-l); + display: grid; + gap: var(--spacing-s); } .blank-slate { @@ -273,19 +825,29 @@ code { left: 0; width: 100%; height: 100%; + box-sizing: border-box; /* prevent padding from causing overflow */ display: flex; flex-direction: column; gap: 15px; align-items: center; - justify-content: center; + /* Prefer near-top layout with a bit of breathing room */ + justify-content: flex-start; + padding-top: var(--spacing-l); +} + +/* Ensure blank slates on export never create outer scrollbars */ +.page--export .blank-slate { + overflow: hidden; } .blank-slate__body { border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); border-radius: 4px; - max-width: 600px; + width: 750px; padding: var(--spacing-xxl); + /* Ensure some breathing room from toolbar/top */ + margin-top: var(--spacing-xl); } .blank-slate__body__title { @@ -301,8 +863,42 @@ code { color: var(--light-body-color); } +/* Export selection panel */ +.export-selector { + display: grid; + grid-template-columns: 1fr; + gap: var(--spacing-m); + align-items: start; + min-width: 420px; +} + +.export-selector__field { + width: 100%; +} + +.export-landing__actions { + display: grid; + gap: var(--spacing-m); +} + +.export-landing__actions button { + width: 100%; +} + +.export-selector__actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-s); +} + +.export-selector__actions button { + width: 100%; +} + .import__graph, -.import__details { +.import__details, +.export__graph, +.export__details { position: absolute; top: 0; left: 0; @@ -311,17 +907,38 @@ code { overflow: auto; } -.import__details { +.import__details, +.export__details { border-left: 1px solid var(--border-color); box-sizing: border-box; + background: #fff; + /* Slightly tighter padding for redesigned conflicts UI */ +} + +.import__details .page__content { + padding: 0 var(--spacing-l) var(--spacing-xl); + box-sizing: border-box; +} + +.import__details .conflicts-manager__group__content { + display: grid; + gap: var(--spacing-m); +} + +.import__graph-close, +.export__graph-close { + position: absolute; + top: 16px; + right: 16px; + z-index: 5; } .conflict { border-bottom: 1px solid var(--border-color); &.conflict--selected { - border-top: 8px solid var(--border-color); - border-bottom: 8px solid var(--border-color); + border-top: 4px solid var(--border-color); + border-bottom: 4px solid var(--border-color); } &:first-child { @@ -329,33 +946,318 @@ code { } } +.conflict--has-conflict { + border: 1px solid rgba(217, 48, 37, 0.22); + border-radius: 8px; + background: rgba(217, 48, 37, 0.04); +} + +.conflict--has-conflict.conflict--selected { + border-top: 1px solid rgba(217, 48, 37, 0.22); + border-bottom: 1px solid rgba(217, 48, 37, 0.22); + background: rgba(217, 48, 37, 0.06); +} + +.conflict--has-conflict:first-child { + border-top: 1px solid rgba(217, 48, 37, 0.22); +} + .conflict__title { - font-weight: bold; - padding: var(--spacing-m) var(--spacing-l); + font-weight: 700; + appearance: none; + background: transparent; + border: 0; + width: 100%; + text-align: left; cursor: pointer; display: flex; gap: 10px; align-items: center; + color: var(--base-body-color); + padding: 10px 12px; +} - svg { - color: var(--base-body-color); - } +.conflict--has-conflict .conflict__title { + color: color-mix(in srgb, var(--base-body-color), #6d1f16 35%); +} - &:hover { - background: var(--light-bg-color); - } +.conflict__title__text { + flex: 1; + min-width: 0; + display: inline-flex; + align-items: center; + gap: 6px; + font-weight: inherit; +} + +.conflict__badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 600; + color: #a0312a; + background-color: rgba(217, 48, 37, 0.12); +} + +.conflict__badge svg { + width: 12px; + height: 12px; +} - .conflict--selected & { - border-bottom: 1px solid var(--border-color); +.conflict__content { + padding: 12px 16px; /* reduce inner padding for a tighter look */ +} + +.schema-overview__summary { + margin-top: 10px; + font-size: 12px; + color: var(--light-body-color); + display: inline-flex; + align-items: center; + gap: 6px; +} + +.schema-overview__title { + display: inline-flex; + align-items: center; + gap: 6px; +} + +.schema-overview__title__name { + font-weight: inherit; +} + +.schema-overview__title__meta { + color: var(--light-body-color); + font-size: 12px; +} + +.schema-overview__status { + font-size: 11px; + font-weight: 600; + border-radius: 999px; + padding: 2px 8px; + text-transform: none; + letter-spacing: 0.01em; +} + +.schema-overview__status--selected { + color: #0f5132; + background-color: rgba(25, 135, 84, 0.18); +} + +.schema-overview__status--unselected { + color: #4f4f4f; + background-color: rgba(79, 79, 79, 0.12); +} + +.schema-overview__category { + margin: 24px 0; + padding-top: 12px; +} + +.schema-overview__category--selected { + border-bottom: 1px solid var(--border-color); + padding-bottom: 16px; + margin-bottom: 24px; +} + +.schema-overview__category__title { + font-weight: 700; + font-size: var(--font-size-m); + padding: 0 var(--spacing-l); + color: var(--base-body-color); + margin-bottom: 6px; +} + +.schema-overview__item { + border: 1px solid var(--border-color); + border-radius: 10px; + margin: 6px var(--spacing-l); + background: #fff; + transition: + border-color 0.2s ease, + background-color 0.2s ease; +} + +.import__details .schema-overview__item.conflict { + border-bottom: none; + border-top: none; +} + +.import__details .schema-overview__item.conflict:first-child { + border-top: none; +} + +.import__details .schema-overview__item.conflict + .conflict { + border-top: none; +} + +.schema-overview__item .conflict__title { + padding: 12px 14px; +} + +.schema-overview__item .conflict__content { + padding: 12px 18px 16px; +} + +.schema-overview__item--selected { + background: rgba(25, 135, 84, 0.08); + border-color: rgba(25, 135, 84, 0.35); +} + +.schema-overview__item--unselected { + background: #fff; +} + +.schema-overview__item--unselected .conflict__title { + color: var(--base-body-color); +} + +.export__details .schema-overview__item { + border: 1px solid var(--border-color); +} + +.export__details .schema-overview__item.schema-overview__item--selected { + border-color: rgba(25, 135, 84, 0.35); +} + +.export__details .schema-overview__item.schema-overview__item--unselected { + border-color: var(--border-color); +} + +/* Pretty export overlay styles */ +.export-overlay__card { + background: #fff; + border: 1px solid var(--border-color); + border-radius: 14px; + box-shadow: 0 18px 60px rgba(0, 0, 0, 0.12); + padding: 28px 30px; + width: 720px; + max-width: 92vw; +} + +.progress-overlay { + position: fixed; + inset: 0; + background: var(--overlay-gradient); + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 16px; +} + +.export-overlay__title { + font-weight: 800; + font-size: 22px; + letter-spacing: -0.01em; + margin-bottom: 10px; +} + +.export-overlay__subtitle { + color: var(--light-body-color); + margin-bottom: 18px; + font-size: 14px; +} + +.export-overlay__bar { + height: 14px; + /* Lighter track to increase contrast with the fill */ + background: color-mix(in srgb, var(--accent-color, #4f46e5), white 96%); + border-radius: 999px; + overflow: hidden; + position: relative; + margin-bottom: 12px; +} + +.export-overlay__bar__fill { + position: relative; + height: 100%; + /* Solid base using project accent color */ + background-color: var(--accent-color, #4f46e5); + transition: width 180ms ease; + overflow: hidden; +} + +/* Independent shimmer overlay to avoid jitter during width updates */ +.export-overlay__bar__fill::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: -50%; + width: 50%; + pointer-events: none; + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.28) 45%, + rgba(255, 255, 255, 0.42) 50%, + rgba(255, 255, 255, 0.28) 55%, + rgba(255, 255, 255, 0) 100% + ); + animation: exportBarShimmer 1.4s linear infinite; + will-change: transform; +} + +@keyframes exportBarShimmer { + 0% { + transform: translateX(0%); } + 100% { + transform: translateX(300%); + } +} - .conflict--invalid & { - color: var(--alert-color); +@keyframes exportBarShift { + 0% { + background-position: 0% 0; + } + 100% { + background-position: -200% 0; } } -.conflict__content { - padding: var(--spacing-l); +.export-overlay__meta { + display: flex; + justify-content: space-between; + font-size: 13px; + color: var(--light-body-color); +} + +.export-overlay__steps { + margin-top: 12px; + font-size: 13px; + color: var(--base-body-color); +} + +.export-overlay__step { + display: flex; + align-items: center; + gap: 8px; + opacity: 0.9; +} + +.export-overlay__step::before { + content: ""; + display: inline-block; + width: 6px; + height: 6px; + border-radius: 999px; + background: #3b82f6; +} + +/* Tiny helper note shown when progress stalls due to rate-limiting */ +.rate-limit-notice { + margin-top: 8px; + font-size: 12px; + color: var(--light-body-color); + text-align: center; } .form__item { @@ -363,7 +1265,7 @@ code { } .conflicts-manager__actions { - padding: var(--spacing-l); + padding: var(--spacing-m) var(--spacing-l) var(--spacing-s); } .conflicts-manager__actions__reassurance { @@ -377,16 +1279,16 @@ code { .conflicts-manager__group { margin: 20px 0; - &:first-child { margin-top: 0; } } .conflicts-manager__group__title { - font-weight: bold; - font-size: var(--font-size-l); - padding: var(--spacing-m) var(--spacing-l); + font-weight: 700; + font-size: var(--font-size-m); + padding: 8px var(--spacing-l); + color: var(--light-body-color); } .no-text-wrap { @@ -427,9 +1329,12 @@ form { border: 3px solid white; /* http://smoothshadows.com/#djEsMSw2LDAuMTIsNzgsMCwwLCMwMzA3MTIsI2YzZjRmNiwjZmZmZmZmLDI%3D */ box-shadow: - 0px 0px 2px rgba(3, 7, 18, 0.02), 0px 0px 9px rgba(3, 7, 18, 0.04), - 0px 0px 20px rgba(3, 7, 18, 0.06), 0px 0px 35px rgba(3, 7, 18, 0.08), - 0px 0px 54px rgba(3, 7, 18, 0.1), 0px 0px 78px rgba(3, 7, 18, 0.12); + 0px 0px 2px rgba(3, 7, 18, 0.02), + 0px 0px 9px rgba(3, 7, 18, 0.04), + 0px 0px 20px rgba(3, 7, 18, 0.06), + 0px 0px 35px rgba(3, 7, 18, 0.08), + 0px 0px 54px rgba(3, 7, 18, 0.1), + 0px 0px 78px rgba(3, 7, 18, 0.12); } .progress__meter__track { @@ -447,7 +1352,6 @@ form { a { color: var(--accent-color); } - ul { padding: 0 0 0 20px; } diff --git a/import-export-schema/src/main.tsx b/import-export-schema/src/main.tsx index 0286d276..42042a9a 100644 --- a/import-export-schema/src/main.tsx +++ b/import-export-schema/src/main.tsx @@ -2,10 +2,20 @@ import { connect } from 'datocms-plugin-sdk'; import 'datocms-react-ui/styles.css'; import '@xyflow/react/dist/style.css'; import './index.css'; +import { Spinner } from 'datocms-react-ui'; +import { lazy, Suspense } from 'react'; import { render } from '@/utils/render'; -import { Config } from './entrypoints/Config'; -import ExportPage from './entrypoints/ExportPage'; -import { ImportPage } from './entrypoints/ImportPage'; + +// Lazy-load entrypoints so the iframe boots with only the shared shell. +// Each page chunk loads on demand, keeping initial bundles lighter and first render quicker. +const LazyConfig = lazy(() => + import('./entrypoints/Config').then((m) => ({ default: m.Config })), +); +const LazyExportHome = lazy(() => import('./entrypoints/ExportHome')); +const LazyExportPage = lazy(() => import('./entrypoints/ExportPage')); +const LazyImportPage = lazy(() => + import('./entrypoints/ImportPage').then((m) => ({ default: m.ImportPage })), +); connect({ schemaItemTypeDropdownActions() { @@ -18,9 +28,13 @@ connect({ ]; }, async executeSchemaItemTypeDropdownAction(_id, itemType, ctx) { - ctx.navigateTo( - `${ctx.isEnvironmentPrimary ? '' : `/environments/${ctx.environment}`}/configuration/p/${ctx.plugin.id}/pages/import-export?itemTypeId=${itemType.id}`, - ); + const environmentPrefix = ctx.isEnvironmentPrimary + ? '' + : `/environments/${ctx.environment}`; + const exportPagePath = `/configuration/p/${ctx.plugin.id}/pages/export`; + const navigateUrl = `${environmentPrefix}${exportPagePath}?itemTypeId=${itemType.id}`; + + ctx.navigateTo(navigateUrl); }, settingsAreaSidebarItemGroups() { return [ @@ -28,25 +42,62 @@ connect({ label: 'Schema', items: [ { - label: 'Import/Export', + label: 'Import', icon: 'file-import', - pointsTo: { pageId: 'import-export' }, + pointsTo: { pageId: 'import' }, + }, + { + label: 'Export', + icon: 'file-export', + pointsTo: { pageId: 'export' }, }, ], }, ]; }, - renderPage(_id, ctx) { + renderPage(pageId, ctx) { + // All page renders may include an itemTypeId query param when navigating from a schema dropdown. const params = new URLSearchParams(ctx.location.search); const itemTypeId = params.get('itemTypeId'); - if (!itemTypeId) { - return render(); + if (pageId === 'import') { + // Direct navigation to the import page always boots the import screen in import-only mode. + return render( + }> + + , + ); } - return render(); + if (pageId === 'export') { + if (itemTypeId) { + // Export triggered from a specific item type skips the landing step and hydrates the export page. + return render( + }> + + , + ); + } + // Bare export navigation shows the landing page so the user can choose what to export. + return render( + }> + + , + ); + } + + // Unknown page IDs fall back to the import screen to preserve legacy deep links. + return render( + }> + + , + ); }, renderConfigScreen(ctx) { - return render(); + return render( + }> + + , + ); }, }); diff --git a/import-export-schema/src/shared/constants/graph.ts b/import-export-schema/src/shared/constants/graph.ts new file mode 100644 index 00000000..9599de33 --- /dev/null +++ b/import-export-schema/src/shared/constants/graph.ts @@ -0,0 +1 @@ +export const GRAPH_NODE_THRESHOLD = 60; diff --git a/import-export-schema/src/shared/hooks/useCmaClient.ts b/import-export-schema/src/shared/hooks/useCmaClient.ts new file mode 100644 index 00000000..99a79a3a --- /dev/null +++ b/import-export-schema/src/shared/hooks/useCmaClient.ts @@ -0,0 +1,26 @@ +import type { Client, ClientConfigOptions } from '@datocms/cma-client'; +import type { RenderConfigScreenCtx, RenderPageCtx } from 'datocms-plugin-sdk'; +import { useMemo } from 'react'; +import { createCmaClient } from '@/utils/createCmaClient'; + +type AuthCtx = + | Pick + | Pick; + +type UseCmaClientOptions = { + overrides?: Partial; +}; + +/** + * Returns a memoized CMA client that only changes when auth info changes. + * Consumers should keep `overrides` stable to avoid needless re-instantiation. + */ +export function useCmaClient( + ctx: AuthCtx, + { overrides }: UseCmaClientOptions = {}, +): Client { + return useMemo( + () => createCmaClient(ctx, overrides), + [ctx.currentUserAccessToken, ctx.environment, overrides], + ); +} diff --git a/import-export-schema/src/shared/hooks/useConflictsBuilder.ts b/import-export-schema/src/shared/hooks/useConflictsBuilder.ts new file mode 100644 index 00000000..6b64eda3 --- /dev/null +++ b/import-export-schema/src/shared/hooks/useConflictsBuilder.ts @@ -0,0 +1,62 @@ +import { useCallback, useEffect, useState } from 'react'; +import type { ExportSchema } from '@/entrypoints/ExportPage/ExportSchema'; +import buildConflicts, { + type Conflicts, +} from '@/entrypoints/ImportPage/ConflictsManager/buildConflicts'; +import type { LongTaskController } from '@/shared/tasks/useLongTask'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; + +/** + * Builds the import conflict summary in the background while providing a + * reusable `refresh` helper and progress reporting via `LongTask`. + */ +export function useConflictsBuilder({ + exportSchema, + projectSchema, + task, +}: { + exportSchema: ExportSchema | undefined; + projectSchema: ProjectSchema; + task: LongTaskController; +}) { + const [conflicts, setConflicts] = useState(); + const [refreshKey, setRefreshKey] = useState(0); + + // Rebuild conflicts whenever the export document, schema, or refresh key changes. + useEffect(() => { + let cancelled = false; + async function run() { + if (!exportSchema) { + setConflicts(undefined); + return; + } + try { + task.start({ done: 0, total: 1, label: 'Preparing import…' }); + const result = await buildConflicts( + exportSchema, + projectSchema, + (p) => { + if (!cancelled) { + task.setProgress(p); + } + }, + ); + if (cancelled) return; + setConflicts(result); + } finally { + if (!cancelled) { + task.complete({ label: 'Conflicts ready' }); + task.reset(); + } + } + } + run(); + return () => { + cancelled = true; + }; + }, [exportSchema, projectSchema, task, refreshKey]); + + const refresh = useCallback(() => setRefreshKey((key) => key + 1), []); + + return { conflicts, setConflicts, refresh }; +} diff --git a/import-export-schema/src/shared/hooks/useExportAllHandler.ts b/import-export-schema/src/shared/hooks/useExportAllHandler.ts new file mode 100644 index 00000000..8afa567a --- /dev/null +++ b/import-export-schema/src/shared/hooks/useExportAllHandler.ts @@ -0,0 +1,86 @@ +import type { RenderPageCtx } from 'datocms-plugin-sdk'; +import { useCallback } from 'react'; +import buildExportDoc from '@/entrypoints/ExportPage/buildExportDoc'; +import type { LongTaskController } from '@/shared/tasks/useLongTask'; +import { downloadJSON } from '@/utils/downloadJson'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; + +type Options = { + ctx: RenderPageCtx; + schema: ProjectSchema; + task: LongTaskController; +}; + +/** + * Returns a memoized handler that exports the entire schema with confirmation + progress. + */ +export function useExportAllHandler({ ctx, schema, task }: Options) { + return useCallback(async () => { + try { + const confirmation = await ctx.openConfirm({ + title: 'Export entire schema?', + content: + 'This will export all models, block models, and plugins in the current environment as a single JSON file.', + choices: [ + { + label: 'Export everything', + value: 'export', + intent: 'positive', + }, + ], + cancel: { label: 'Cancel', value: false }, + }); + if (confirmation !== 'export') { + return; + } + + task.start({ label: 'Preparing export…' }); + const allTypes = await schema.getAllItemTypes(); + const allPlugins = await schema.getAllPlugins(); + if (!allTypes.length) { + task.reset(); + ctx.alert('No item types found in this environment.'); + return; + } + // Use the first regular model as root to match older exports; fall back to any. + const preferredRoot = + allTypes.find((t) => !t.attributes.modular_block) || allTypes[0]; + const total = allPlugins.length + allTypes.length * 2; + task.setProgress({ done: 0, total, label: 'Preparing export…' }); + let done = 0; + const exportDoc = await buildExportDoc( + schema, + preferredRoot.id, + allTypes.map((t) => t.id), + allPlugins.map((p) => p.id), + { + onProgress: (label: string) => { + done += 1; + task.setProgress({ done, total, label }); + }, + shouldCancel: () => task.isCancelRequested(), + }, + ); + if (task.isCancelRequested()) { + throw new Error('Export cancelled'); + } + downloadJSON(exportDoc, { + fileName: 'export.json', + prettify: true, + }); + task.complete({ done: total, total, label: 'Export completed' }); + ctx.notice('Export completed successfully.'); + } catch (error) { + console.error('Export-all failed', error); + if (error instanceof Error && error.message === 'Export cancelled') { + task.complete({ label: 'Export cancelled' }); + ctx.notice('Export canceled'); + } else { + task.fail(error); + ctx.alert('Could not export the current schema. Please try again.'); + } + } finally { + task.reset(); + } + }, [ctx, schema, task]); +} diff --git a/import-export-schema/src/shared/hooks/useExportSelection.ts b/import-export-schema/src/shared/hooks/useExportSelection.ts new file mode 100644 index 00000000..9a3da015 --- /dev/null +++ b/import-export-schema/src/shared/hooks/useExportSelection.ts @@ -0,0 +1,78 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import { useEffect, useMemo, useState } from 'react'; +import { isDefined } from '@/utils/isDefined'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; + +type UseExportSelectionOptions = { + schema: ProjectSchema; + enabled?: boolean; +}; + +type UseExportSelectionResult = { + allItemTypes?: SchemaTypes.ItemType[]; + selectedIds: string[]; + selectedItemTypes: SchemaTypes.ItemType[]; + setSelectedIds: (ids: string[]) => void; +}; + +/** + * Fetches item types and keeps a derived selection list in sync with the schema client. + */ +export function useExportSelection({ + schema, + enabled = true, +}: UseExportSelectionOptions): UseExportSelectionResult { + const [allItemTypes, setAllItemTypes] = useState(); + const [selectedIds, setSelectedIds] = useState([]); + const [selectedItemTypes, setSelectedItemTypes] = useState< + SchemaTypes.ItemType[] + >([]); + + useEffect(() => { + if (!enabled) { + return; + } + + let cancelled = false; + async function load() { + const types = await schema.getAllItemTypes(); + if (!cancelled) { + setAllItemTypes(types); + } + } + + void load(); + + return () => { + cancelled = true; + }; + }, [schema, enabled]); + + const itemTypesById = useMemo(() => { + if (!allItemTypes) { + return undefined; + } + return new Map(allItemTypes.map((it) => [it.id, it])); + }, [allItemTypes]); + + useEffect(() => { + if (!enabled) { + setSelectedItemTypes([]); + return; + } + if (!itemTypesById) { + return; + } + + setSelectedItemTypes( + selectedIds.map((id) => itemTypesById.get(id)).filter(isDefined), + ); + }, [enabled, itemTypesById, selectedIds.join('-')]); + + return { + allItemTypes, + selectedIds, + selectedItemTypes, + setSelectedIds, + }; +} diff --git a/import-export-schema/src/shared/hooks/useProjectSchema.ts b/import-export-schema/src/shared/hooks/useProjectSchema.ts new file mode 100644 index 00000000..1164e6da --- /dev/null +++ b/import-export-schema/src/shared/hooks/useProjectSchema.ts @@ -0,0 +1,33 @@ +import type { Client, ClientConfigOptions } from '@datocms/cma-client'; +import type { RenderConfigScreenCtx, RenderPageCtx } from 'datocms-plugin-sdk'; +import { useMemo } from 'react'; +import { ProjectSchema } from '@/utils/ProjectSchema'; +import { useCmaClient } from './useCmaClient'; + +type AuthCtx = + | Pick + | Pick; + +type UseProjectSchemaOptions = { + clientOverrides?: Partial; + existingClient?: Client; +}; + +/** + * Provides a memoized ProjectSchema instance keyed by CMA client identity. + * If `existingClient` is passed, the hook will wrap that client instead of + * creating a new one. The resulting schema caches API calls internally, so + * sharing the instance across the component tree avoids redundant requests. + */ +export function useProjectSchema( + ctx: AuthCtx, + options: UseProjectSchemaOptions = {}, +): ProjectSchema { + const resolvedClient = useCmaClient(ctx, { + overrides: options.clientOverrides, + }); + + const targetClient = options.existingClient ?? resolvedClient; + + return useMemo(() => new ProjectSchema(targetClient), [targetClient]); +} diff --git a/import-export-schema/src/shared/hooks/useSchemaExportTask.ts b/import-export-schema/src/shared/hooks/useSchemaExportTask.ts new file mode 100644 index 00000000..25eef348 --- /dev/null +++ b/import-export-schema/src/shared/hooks/useSchemaExportTask.ts @@ -0,0 +1,97 @@ +import type { RenderPageCtx } from 'datocms-plugin-sdk'; +import { useCallback } from 'react'; +import buildExportDoc from '@/entrypoints/ExportPage/buildExportDoc'; +import { useLongTask } from '@/shared/tasks/useLongTask'; +import { downloadJSON } from '@/utils/downloadJson'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; + +type RunExportArgs = { + rootItemTypeId: string; + itemTypeIds: string[]; + pluginIds: string[]; + fileName?: string; +}; + +type UseSchemaExportTaskOptions = { + schema: ProjectSchema; + ctx: RenderPageCtx; + defaultFileName?: string; +}; + +type SchemaExportTask = { + runExport: (args: RunExportArgs) => Promise; + task: ReturnType; +}; + +/** + * Shared helper that wraps export doc building with progress + cancellation handling. + */ +export function useSchemaExportTask({ + schema, + ctx, + defaultFileName = 'export.json', +}: UseSchemaExportTaskOptions): SchemaExportTask { + const task = useLongTask(); + + const runExport = useCallback( + async ({ + rootItemTypeId, + itemTypeIds, + pluginIds, + fileName, + }: RunExportArgs) => { + try { + const total = pluginIds.length + itemTypeIds.length * 2; + task.controller.start({ + done: 0, + total, + label: 'Preparing export…', + }); + let done = 0; + + const exportDoc = await buildExportDoc( + schema, + rootItemTypeId, + itemTypeIds, + pluginIds, + { + onProgress: (label: string) => { + done += 1; + task.controller.setProgress({ done, total, label }); + }, + shouldCancel: () => task.controller.isCancelRequested(), + }, + ); + + if (task.controller.isCancelRequested()) { + throw new Error('Export cancelled'); + } + + downloadJSON(exportDoc, { + fileName: fileName ?? defaultFileName, + prettify: true, + }); + task.controller.complete({ + done: total, + total, + label: 'Export completed', + }); + ctx.notice('Export completed successfully.'); + } catch (error) { + console.error('Schema export failed', error); + if (error instanceof Error && error.message === 'Export cancelled') { + task.controller.complete({ label: 'Export cancelled' }); + ctx.notice('Export canceled'); + } else { + task.controller.fail(error); + ctx.alert('Could not complete the export. Please try again.'); + } + } finally { + task.controller.reset(); + } + }, + [ctx, defaultFileName, schema, task.controller], + ); + + return { runExport, task }; +} diff --git a/import-export-schema/src/shared/tasks/useLongTask.ts b/import-export-schema/src/shared/tasks/useLongTask.ts new file mode 100644 index 00000000..f240554b --- /dev/null +++ b/import-export-schema/src/shared/tasks/useLongTask.ts @@ -0,0 +1,119 @@ +import { useMemo, useRef, useState } from 'react'; + +export type LongTaskStatus = + | 'idle' + | 'running' + | 'cancelling' + | 'completed' + | 'failed'; + +export type LongTaskProgress = { + label?: string; + done?: number; + total?: number; +}; + +export type LongTaskState = { + status: LongTaskStatus; + cancelRequested: boolean; + progress: LongTaskProgress; + error?: Error; +}; + +const initialState: LongTaskState = { + status: 'idle', + cancelRequested: false, + progress: {}, + error: undefined, +}; + +export type LongTaskController = { + start(progress?: LongTaskProgress): void; + setProgress(update: LongTaskProgress): void; + complete(progress?: LongTaskProgress): void; + fail(error: unknown): void; + requestCancel(): void; + reset(): void; + isCancelRequested(): boolean; +}; + +function toError(error: unknown): Error { + if (error instanceof Error) return error; + return new Error(typeof error === 'string' ? error : 'Unknown error'); +} + +function mergeProgress( + prev: LongTaskProgress, + update: LongTaskProgress, +): LongTaskProgress { + return { + ...prev, + ...update, + }; +} + +export type UseLongTaskResult = { + state: LongTaskState; + controller: LongTaskController; +}; + +/** + * Manage long-running async tasks (imports, exports, etc.) with progress + cancel support. + */ +export function useLongTask(): UseLongTaskResult { + const [state, setState] = useState(initialState); + const cancelRequestedRef = useRef(false); + + const controller = useMemo(() => { + return { + start(progress) { + cancelRequestedRef.current = false; + setState({ + status: 'running', + cancelRequested: false, + progress: progress ?? {}, + error: undefined, + }); + }, + setProgress(update) { + setState((prev) => ({ + ...prev, + progress: mergeProgress(prev.progress, update), + })); + }, + complete(progress) { + setState((prev) => ({ + status: 'completed', + cancelRequested: prev.cancelRequested, + progress: mergeProgress(prev.progress, progress ?? {}), + error: undefined, + })); + }, + fail(error) { + setState((prev) => ({ + status: 'failed', + cancelRequested: prev.cancelRequested, + progress: prev.progress, + error: toError(error), + })); + }, + requestCancel() { + cancelRequestedRef.current = true; + setState((prev) => ({ + ...prev, + status: prev.status === 'running' ? 'cancelling' : prev.status, + cancelRequested: true, + })); + }, + reset() { + cancelRequestedRef.current = false; + setState(initialState); + }, + isCancelRequested() { + return cancelRequestedRef.current; + }, + }; + }, []); + + return { state, controller }; +} diff --git a/import-export-schema/src/types/lodash-es.d.ts b/import-export-schema/src/types/lodash-es.d.ts new file mode 100644 index 00000000..3b593500 --- /dev/null +++ b/import-export-schema/src/types/lodash-es.d.ts @@ -0,0 +1,4 @@ +declare module 'lodash-es' { + export * from 'lodash'; + export { default } from 'lodash'; +} diff --git a/import-export-schema/src/utils/ProjectSchema.ts b/import-export-schema/src/utils/ProjectSchema.ts index be8389a8..25338b69 100644 --- a/import-export-schema/src/utils/ProjectSchema.ts +++ b/import-export-schema/src/utils/ProjectSchema.ts @@ -1,6 +1,8 @@ import type { Client, SchemaTypes } from '@datocms/cma-client'; -import { groupBy } from 'lodash-es'; +/** + * Thin caching layer around the CMA client that smooths out rate limits and provides lookups. + */ export class ProjectSchema { public client: Client; private itemTypesPromise: Promise | null = null; @@ -12,9 +14,50 @@ export class ProjectSchema { private fieldsByItemType: Map = new Map(); private fieldsetsByItemType: Map = new Map(); private alreadyFetchedRelatedFields: Map = new Map(); + // In-flight promises to prevent duplicate requests per item type + private fieldsPromisesByItemType: Map> = + new Map(); + private fieldsetsPromisesByItemType: Map< + string, + Promise + > = new Map(); + + // Simple throttle to avoid hitting 429 when many models are selected + // Keep concurrency conservative: DatoCMS rate-limits bursty calls + // If needed, make this configurable later via constructor param + private throttleMax = 2; + private throttleActive = 0; + private throttleQueue: Array<() => void> = []; constructor(client: Client) { this.client = client; + try { + // Allow overriding throttle via localStorage for large schemas + const raw = + typeof window !== 'undefined' + ? window.localStorage?.getItem?.('schemaThrottleMax') + : undefined; + const parsed = raw ? parseInt(raw, 10) : NaN; + if (!Number.isNaN(parsed) && parsed > 0 && parsed < 16) { + this.throttleMax = parsed; + } + } catch { + // ignore + } + } + + private async withThrottle(fn: () => Promise): Promise { + if (this.throttleActive >= this.throttleMax) { + await new Promise((resolve) => this.throttleQueue.push(resolve)); + } + this.throttleActive += 1; + try { + return await fn(); + } finally { + this.throttleActive -= 1; + const next = this.throttleQueue.shift(); + if (next) next(); + } } private async loadItemTypes(): Promise { @@ -89,7 +132,7 @@ export class ProjectSchema { const itemType = this.itemTypesByName.get(name); if (!itemType) { - throw new Error(`Item type with API key '${name}' not found`); + throw new Error(`Item type with name '${name}' not found`); } return itemType; @@ -120,14 +163,20 @@ export class ProjectSchema { async getItemTypeFieldsAndFieldsets( itemType: SchemaTypes.ItemType, ): Promise<[SchemaTypes.Field[], SchemaTypes.Fieldset[]]> { - if ( - !itemType.attributes.modular_block && - !this.fieldsetsByItemType.get(itemType.id) - ) { - const { data: fieldsets } = await this.client.fieldsets.rawList( - itemType.id, - ); - this.fieldsetsByItemType.set(itemType.id, fieldsets); + if (!itemType.attributes.modular_block) { + if (!this.fieldsetsByItemType.get(itemType.id)) { + let promise = this.fieldsetsPromisesByItemType.get(itemType.id); + if (!promise) { + promise = this.withThrottle(async () => { + const { data } = await this.client.fieldsets.rawList(itemType.id); + return data; + }); + this.fieldsetsPromisesByItemType.set(itemType.id, promise); + } + const fieldsets = await promise; + this.fieldsetsByItemType.set(itemType.id, fieldsets); + this.fieldsetsPromisesByItemType.delete(itemType.id); + } } // Check if we already have the fields cached @@ -140,25 +189,22 @@ export class ProjectSchema { return [cachedFields, this.fieldsetsByItemType.get(itemType.id) || []]; } - // Fetch and cache the fields - const { data: fields } = await this.client.fields.rawRelated(itemType.id); - - this.alreadyFetchedRelatedFields.set(itemType.id, true); - - const fieldsByItemTypeId = groupBy( - fields, - 'relationships.item_type.data.id', - ); - - this.fieldsByItemType.set( - itemType.id, - fieldsByItemTypeId[itemType.id] || [], - ); - - for (const [itemTypeId, fields] of Object.entries(fieldsByItemTypeId)) { - this.fieldsByItemType.set(itemTypeId, fields); + let fields = this.fieldsByItemType.get(itemType.id); + if (!fields || !this.alreadyFetchedRelatedFields.get(itemType.id)) { + let promise = this.fieldsPromisesByItemType.get(itemType.id); + if (!promise) { + promise = this.withThrottle(async () => { + const { data } = await this.client.fields.rawList(itemType.id); + return data; + }); + this.fieldsPromisesByItemType.set(itemType.id, promise); + } + fields = await promise; + this.fieldsByItemType.set(itemType.id, fields); + this.alreadyFetchedRelatedFields.set(itemType.id, true); + this.fieldsPromisesByItemType.delete(itemType.id); } - return this.getItemTypeFieldsAndFieldsets(itemType); + return [fields, this.fieldsetsByItemType.get(itemType.id) || []]; } } diff --git a/import-export-schema/src/utils/createCmaClient.ts b/import-export-schema/src/utils/createCmaClient.ts new file mode 100644 index 00000000..2ecd94c7 --- /dev/null +++ b/import-export-schema/src/utils/createCmaClient.ts @@ -0,0 +1,25 @@ +import { + buildClient, + type Client, + type ClientConfigOptions, +} from '@datocms/cma-client'; +import type { RenderConfigScreenCtx, RenderPageCtx } from 'datocms-plugin-sdk'; + +type CtxWithAuth = + | Pick + | Pick; + +/** Create a CMA client configured for the current plugin session. */ +export function createCmaClient( + ctx: CtxWithAuth, + overrides?: Partial, +): Client { + return buildClient({ + apiToken: ctx.currentUserAccessToken!, + environment: ctx.environment, + // Sensible defaults for plugin usage + autoRetry: true, + requestTimeout: 60000, + ...(overrides || {}), + }); +} diff --git a/import-export-schema/src/utils/datocms/appearance.ts b/import-export-schema/src/utils/datocms/appearance.ts new file mode 100644 index 00000000..04f0dd4b --- /dev/null +++ b/import-export-schema/src/utils/datocms/appearance.ts @@ -0,0 +1,81 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import { + defaultAppearanceForFieldType, + isHardcodedEditor, +} from '@/utils/datocms/fieldTypeInfo'; + +/** + * Return an appearance suitable for export. If the original field appearance is + * missing, or if it references a non-exported plugin editor, fallback to the + * default appearance for the field type. Also filters addons to those whose IDs + * are included in the allowlist. + */ +export async function ensureExportableAppearance( + field: SchemaTypes.Field, + allowedPluginIds: string[], +): Promise> { + const original = field.attributes.appearance; + const hasAppearance = !!original; + const editorId = original?.editor; + const editorIsHardcoded = editorId ? await isHardcodedEditor(editorId) : true; + + const appearance = + hasAppearance && (editorIsHardcoded || allowedPluginIds.includes(editorId!)) + ? { ...original } + : await defaultAppearanceForFieldType(field.attributes.field_type); + + // Filter addons by allowlist + appearance.addons = (original?.addons ?? []).filter((addon: { id: string }) => + allowedPluginIds.includes(addon.id), + ) as NonNullable; + + return appearance; +} + +/** + * Map a field appearance to the target project by translating any plugin-based + * editor/addon IDs using the provided mapping. If the editor is hardcoded, keep + * it as-is. Missing appearances will be replaced with a default for the field type. + */ +export async function mapAppearanceToProject( + field: SchemaTypes.Field, + pluginIdMappings: Map, +): Promise> { + const base = await defaultAppearanceForFieldType(field.attributes.field_type); + const original = field.attributes.appearance; + let next = { ...base } as NonNullable< + SchemaTypes.Field['attributes']['appearance'] + >; + + if (original) { + const editorId = original.editor; + const isHardcoded = await isHardcodedEditor(editorId); + if (isHardcoded) { + next = { + ...next, + editor: editorId, + parameters: original.parameters, + field_extension: original.field_extension, + }; + } else if (editorId && pluginIdMappings.has(editorId)) { + next = { + ...next, + editor: pluginIdMappings.get(editorId)!, + }; + } + + const sourceAddons = (original.addons ?? []) as Array< + { id: string } & Record + >; + next.addons = sourceAddons + .filter((addon) => pluginIdMappings.has(addon.id)) + .map((addon) => ({ + ...addon, + id: pluginIdMappings.get(addon.id)!, + parameters: + (addon as { parameters?: Record }).parameters ?? {}, + })); + } + + return next; +} diff --git a/import-export-schema/src/utils/datocms/fieldTypeInfo.ts b/import-export-schema/src/utils/datocms/fieldTypeInfo.ts index e1e3dc2f..e7d9eb9d 100644 --- a/import-export-schema/src/utils/datocms/fieldTypeInfo.ts +++ b/import-export-schema/src/utils/datocms/fieldTypeInfo.ts @@ -1,5 +1,7 @@ import type { FieldAttributes } from '@datocms/cma-client/dist/types/generated/SchemaTypes'; +/** Utilities for resolving editor metadata used in field appearance exports/imports. */ + type FieldTypeInfo = Record< string, { @@ -10,15 +12,61 @@ type FieldTypeInfo = Record< let cached: Promise | undefined; -async function fetchFieldTypeInfo() { - if (cached) { - return cached; - } +// Built-in fallback for default editors when the remote metadata endpoint is +// unavailable (eg: offline, CORS/network issues). Parameters are kept empty +// unless the editor requires a specific shape. +const FALLBACK_DEFAULT_EDITORS: Record< + string, + { id: string; parameters: Record } +> = { + boolean: { id: 'boolean', parameters: {} }, + color: { id: 'color_picker', parameters: {} }, + date: { id: 'date_picker', parameters: {} }, + date_time: { id: 'date_time_picker', parameters: {} }, + file: { id: 'file', parameters: {} }, + float: { id: 'float', parameters: {} }, + gallery: { id: 'gallery', parameters: {} }, + integer: { id: 'integer', parameters: {} }, + json: { id: 'json', parameters: {} }, + lat_lon: { id: 'map', parameters: {} }, + link: { id: 'link_select', parameters: {} }, + links: { id: 'links_select', parameters: {} }, + rich_text: { id: 'rich_text', parameters: {} }, + seo: { id: 'seo', parameters: {} }, + single_block: { id: 'framed_single_block', parameters: {} }, + slug: { id: 'slug', parameters: {} }, + string: { id: 'single_line', parameters: {} }, + structured_text: { id: 'structured_text', parameters: {} }, + text: { id: 'textarea', parameters: {} }, + video: { id: 'video', parameters: {} }, +}; - cached = fetch('https://internal.datocms.com/field-types').then((response) => - response.json(), +function fallbackFieldTypeInfo(): FieldTypeInfo { + const entries = Object.entries(FALLBACK_DEFAULT_EDITORS).map( + ([fieldType, editor]) => [ + fieldType, + { + default_editor: editor, + other_editor_ids: [] as string[], + }, + ], ); + return Object.fromEntries(entries) as FieldTypeInfo; +} +async function fetchFieldTypeInfo() { + if (cached) return cached; + cached = (async () => { + try { + const response = await fetch('https://internal.datocms.com/field-types'); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const data = (await response.json()) as FieldTypeInfo; + return data; + } catch { + // Fall back to a local static map to keep flows working safely + return fallbackFieldTypeInfo(); + } + })(); return cached; } @@ -32,17 +80,29 @@ async function allEditors() { } export async function isHardcodedEditor(editor: string) { - return (await allEditors()).includes(editor); + try { + return (await allEditors()).includes(editor); + } catch { + // Fallback to a conservative check against known built-ins + return Object.values(FALLBACK_DEFAULT_EDITORS) + .map((e) => e.id) + .includes(editor); + } } export async function defaultAppearanceForFieldType( fieldType: string, ): Promise { const info = (await fetchFieldTypeInfo())[fieldType]; + const defaultEditor = info?.default_editor || + FALLBACK_DEFAULT_EDITORS[fieldType] || { + id: 'single_line', + parameters: {}, + }; return { - editor: info.default_editor.id, - parameters: info.default_editor.parameters, + editor: defaultEditor.id, + parameters: defaultEditor.parameters, field_extension: undefined, addons: [], }; diff --git a/import-export-schema/src/utils/datocms/schema.ts b/import-export-schema/src/utils/datocms/schema.ts index e48c0f52..e96e36af 100644 --- a/import-export-schema/src/utils/datocms/schema.ts +++ b/import-export-schema/src/utils/datocms/schema.ts @@ -1,3 +1,9 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import type { FieldAttributes } from '@datocms/cma-client/dist/types/generated/SchemaTypes'; +import get from 'lodash-es/get'; +/** + * Shared lookups and helper utilities for interpreting DatoCMS field metadata. + */ import boolean from '@/icons/fieldgroup-boolean.svg?react'; import color from '@/icons/fieldgroup-color.svg?react'; import datetime from '@/icons/fieldgroup-datetime.svg?react'; @@ -9,10 +15,10 @@ import reference from '@/icons/fieldgroup-reference.svg?react'; import richText from '@/icons/fieldgroup-rich_text.svg?react'; import seo from '@/icons/fieldgroup-seo.svg?react'; import structuredText from '@/icons/fieldgroup-structured_text.svg?react'; -import type { SchemaTypes } from '@datocms/cma-client'; -import type { FieldAttributes } from '@datocms/cma-client/dist/types/generated/SchemaTypes'; -import { get } from 'lodash-es'; -import { isHardcodedEditor } from './fieldTypeInfo'; + +// Note: Avoid network requests when resolving plugin dependencies. +// Call sites should pass the set of installed plugin IDs to determine +// whether an editor/addon refers to a plugin. type SvgComponent = React.FunctionComponent< React.ComponentProps<'svg'> & { @@ -192,6 +198,7 @@ export const validatorsContainingBlocks: Array<{ }, ]; +// Collect all item type IDs referenced by validators for the given field. export function findLinkedItemTypeIds(field: SchemaTypes.Field) { const fieldLinkedItemTypeIds = new Set(); @@ -217,15 +224,28 @@ export function findLinkedItemTypeIds(field: SchemaTypes.Field) { return fieldLinkedItemTypeIds; } -export async function findLinkedPluginIds(field: SchemaTypes.Field) { +// Collect plugin IDs referenced via appearance editors/addons, filtering by installed list when available. +export function findLinkedPluginIds( + field: SchemaTypes.Field, + installedPluginIds?: Set, +) { const fieldLinkedPluginIds = new Set(); + // Some fields may have no appearance set (older exports or defaults) + const editorId = field.attributes.appearance?.editor; + const hasInstalledList = !!installedPluginIds && installedPluginIds.size > 0; - if (!(await isHardcodedEditor(field.attributes.appearance.editor))) { - fieldLinkedPluginIds.add(field.attributes.appearance.editor); + // If we have a list of installed plugins, only collect editors that match. + // If not, skip editor to avoid false-positives for built-in editors. + if (editorId && hasInstalledList && installedPluginIds!.has(editorId)) { + fieldLinkedPluginIds.add(editorId); } - for (const addon of field.attributes.appearance.addons) { - fieldLinkedPluginIds.add(addon.id); + for (const addon of field.attributes.appearance?.addons ?? []) { + // If we don't have the installed list yet, include addons optimistically; + // otherwise include only if installed. + if (!hasInstalledList || installedPluginIds!.has(addon.id)) { + fieldLinkedPluginIds.add(addon.id); + } } return fieldLinkedPluginIds; diff --git a/import-export-schema/src/utils/debug.ts b/import-export-schema/src/utils/debug.ts new file mode 100644 index 00000000..de15b4c3 --- /dev/null +++ b/import-export-schema/src/utils/debug.ts @@ -0,0 +1,27 @@ +const DEFAULT_FLAG = 'schemaDebug'; + +function readFlag(flag: string): boolean { + try { + if (typeof window === 'undefined') { + return false; + } + return window.localStorage?.getItem(flag) === '1'; + } catch { + return false; + } +} + +export function debugLog( + message: string, + payload?: unknown, + flag: string = DEFAULT_FLAG, +) { + if (!readFlag(flag)) { + return; + } + if (payload === undefined) { + console.log(`[${flag}] ${message}`); + } else { + console.log(`[${flag}] ${message}`, payload); + } +} diff --git a/import-export-schema/src/utils/fieldTypeGroups.ts b/import-export-schema/src/utils/fieldTypeGroups.ts deleted file mode 100644 index fb34989d..00000000 --- a/import-export-schema/src/utils/fieldTypeGroups.ts +++ /dev/null @@ -1,136 +0,0 @@ -import boolean from '@/icons/fieldgroup-boolean.svg?react'; -import color from '@/icons/fieldgroup-color.svg?react'; -import datetime from '@/icons/fieldgroup-datetime.svg?react'; -import json from '@/icons/fieldgroup-json.svg?react'; -import location from '@/icons/fieldgroup-location.svg?react'; -import media from '@/icons/fieldgroup-media.svg?react'; -import number from '@/icons/fieldgroup-number.svg?react'; -import reference from '@/icons/fieldgroup-reference.svg?react'; -import richText from '@/icons/fieldgroup-rich_text.svg?react'; -import seo from '@/icons/fieldgroup-seo.svg?react'; -import structuredText from '@/icons/fieldgroup-structured_text.svg?react'; -import type { FieldAttributes } from '@datocms/cma-client/dist/types/generated/SchemaTypes'; - -type SvgComponent = React.FunctionComponent< - React.ComponentProps<'svg'> & { - title?: string; - titleId?: string; - desc?: string; - descId?: string; - } ->; - -const groups: Array<{ name: string; types: FieldAttributes['field_type'][] }> = - [ - { - name: 'text', - types: ['string', 'text', 'structured_text'], - }, - { - name: 'rich_text', - types: ['single_block', 'rich_text'], - }, - { - name: 'media', - types: ['file', 'gallery', 'video'], - }, - { - name: 'datetime', - types: ['date', 'date_time'], - }, - { - name: 'number', - types: ['integer', 'float'], - }, - { - name: 'boolean', - types: ['boolean'], - }, - { - name: 'location', - types: ['lat_lon'], - }, - { - name: 'color', - types: ['color'], - }, - { - name: 'seo', - types: ['slug', 'seo'], - }, - { - name: 'reference', - types: ['link', 'links'], - }, - { - name: 'json', - types: ['json'], - }, - ]; - -export default groups; - -export const groupThemes: Record< - string, - { - IconComponent: SvgComponent; - fgColor: string; - bgColor: string; - } -> = { - boolean: { - IconComponent: boolean, - fgColor: '#c82b1d', - bgColor: '#fde5e3', - }, - color: { - IconComponent: color, - fgColor: '#b02857', - bgColor: '#fce2eb', - }, - datetime: { - IconComponent: datetime, - fgColor: '#d76f0e', - bgColor: '#fef0e2', - }, - json: { - IconComponent: json, - fgColor: '#80a617', - bgColor: '#f5fdde', - }, - location: { - IconComponent: location, - fgColor: '#1d9f2f', - bgColor: '#defce2', - }, - media: { - IconComponent: media, - fgColor: '#38ada3', - bgColor: '#e5fbf9', - }, - number: { - IconComponent: number, - fgColor: '#008499', - bgColor: '#d7faff', - }, - reference: { - IconComponent: reference, - fgColor: '#1b5899', - bgColor: '#ddecfc', - }, - rich_text: { - IconComponent: richText, - fgColor: '#38388d', - bgColor: '#e2e2fa', - }, - seo: { - IconComponent: seo, - fgColor: '#7e2e86', - bgColor: '#f8dffa', - }, - text: { - IconComponent: structuredText, - fgColor: '#998100', - bgColor: '#FFF8D6', - }, -}; diff --git a/import-export-schema/src/utils/graph/analysis.ts b/import-export-schema/src/utils/graph/analysis.ts new file mode 100644 index 00000000..1bf43ee9 --- /dev/null +++ b/import-export-schema/src/utils/graph/analysis.ts @@ -0,0 +1,147 @@ +import type { ItemTypeNode } from '@/components/ItemTypeNodeRenderer'; +import type { PluginNode } from '@/components/PluginNodeRenderer'; +import type { AppEdge, Graph } from './types'; + +/** Graph analytics helpers for metrics, traversals, and conflict tooling. */ + +type Adjacency = Map>; + +function ensure(map: Map>, key: string) { + let set = map.get(key); + if (!set) { + set = new Set(); + map.set(key, set); + } + return set; +} + +export function buildDirectedAdjacency(graph: Graph): Adjacency { + const adj: Adjacency = new Map(); + for (const node of graph.nodes) { + ensure(adj, node.id); + } + for (const edge of graph.edges) { + ensure(adj, edge.source).add(edge.target); + // make sure target exists even if isolated + ensure(adj, edge.target); + } + return adj; +} + +export function buildUndirectedAdjacency(graph: Graph): Adjacency { + const adj: Adjacency = new Map(); + for (const node of graph.nodes) { + ensure(adj, node.id); + } + for (const edge of graph.edges) { + ensure(adj, edge.source).add(edge.target); + ensure(adj, edge.target).add(edge.source); + } + return adj; +} + +export function getConnectedComponents(graph: Graph): string[][] { + const adj = buildUndirectedAdjacency(graph); + const seen = new Set(); + const components: string[][] = []; + + for (const id of adj.keys()) { + if (seen.has(id)) continue; + const comp: string[] = []; + const queue: string[] = [id]; + seen.add(id); + while (queue.length) { + const cur = queue.shift()!; + comp.push(cur); + for (const nb of adj.get(cur)!) { + if (!seen.has(nb)) { + seen.add(nb); + queue.push(nb); + } + } + } + components.push(comp); + } + + return components; +} + +// Tarjan's algorithm for SCCs +export function getStronglyConnectedComponents(graph: Graph): string[][] { + const adj = buildDirectedAdjacency(graph); + let index = 0; + const indices = new Map(); + const lowlink = new Map(); + const onStack = new Set(); + const stack: string[] = []; + const sccs: string[][] = []; + + function strongconnect(v: string) { + indices.set(v, index); + lowlink.set(v, index); + index++; + stack.push(v); + onStack.add(v); + + for (const w of adj.get(v)!) { + if (!indices.has(w)) { + strongconnect(w); + lowlink.set(v, Math.min(lowlink.get(v)!, lowlink.get(w)!)); + } else if (onStack.has(w)) { + lowlink.set(v, Math.min(lowlink.get(v)!, indices.get(w)!)); + } + } + + if (lowlink.get(v) === indices.get(v)) { + const comp: string[] = []; + let w: string | undefined; + do { + w = stack.pop(); + if (w === undefined) break; + onStack.delete(w); + comp.push(w); + } while (w !== v); + sccs.push(comp); + } + } + + for (const v of adj.keys()) { + if (!indices.has(v)) strongconnect(v); + } + + return sccs; +} + +export function countCycles(graph: Graph): number { + const sccs = getStronglyConnectedComponents(graph); + return sccs.filter((comp) => comp.length > 1).length; +} + +export function splitNodesByType(graph: Graph): { + itemTypeNodes: ItemTypeNode[]; + pluginNodes: PluginNode[]; +} { + const itemTypeNodes = graph.nodes.filter( + (n) => n.type === 'itemType', + ) as ItemTypeNode[]; + const pluginNodes = graph.nodes.filter( + (n) => n.type === 'plugin', + ) as PluginNode[]; + return { itemTypeNodes, pluginNodes }; +} + +export function findInboundEdges( + graph: Graph, + targetId: string, + sourceWhitelist?: Set, +): AppEdge[] { + return graph.edges.filter((e) => { + if (e.target !== targetId) return false; + if (!sourceWhitelist) return true; + return sourceWhitelist.has(e.source); + }); +} + +export function findOutboundEdges(graph: Graph, sourceId: string): AppEdge[] { + return graph.edges.filter((e) => e.source === sourceId); +} diff --git a/import-export-schema/src/utils/graph/buildGraph.ts b/import-export-schema/src/utils/graph/buildGraph.ts new file mode 100644 index 00000000..5ac3459c --- /dev/null +++ b/import-export-schema/src/utils/graph/buildGraph.ts @@ -0,0 +1,204 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import { buildHierarchyNodes } from '@/utils/graph/buildHierarchyNodes'; +import { buildEdgesForItemType } from '@/utils/graph/edges'; +import { buildItemTypeNode, buildPluginNode } from '@/utils/graph/nodes'; +import { rebuildGraphWithPositionsFromHierarchy } from '@/utils/graph/rebuildGraphWithPositionsFromHierarchy'; +import { deterministicGraphSort } from '@/utils/graph/sort'; +import type { Graph, SchemaProgressUpdate } from '@/utils/graph/types'; +import type { ISchemaSource } from '@/utils/schema/ISchemaSource'; + +/** Build a dependency graph from any schema source, reporting progress as we traverse. */ +type BuildGraphOptions = { + source: ISchemaSource; + initialItemTypes: SchemaTypes.ItemType[]; + selectedItemTypeIds?: string[]; // export use-case to include edges + itemTypeIdsToSkip?: string[]; // import use-case to avoid edges + onProgress?: (update: SchemaProgressUpdate) => void; +}; + +export async function buildGraph({ + source, + initialItemTypes, + selectedItemTypeIds = [], + itemTypeIdsToSkip = [], + onProgress, +}: BuildGraphOptions): Promise { + const graph: Graph = { nodes: [], edges: [] }; + const hierarchyEdgeSet = new Set(); + const hierarchyEdges: Array<{ source: string; target: string }> = []; + + function recordHierarchyEdge(sourceId: string, targetId: string) { + const key = `${sourceId}->${targetId}`; + if (hierarchyEdgeSet.has(key)) { + return; + } + hierarchyEdgeSet.add(key); + hierarchyEdges.push({ source: sourceId, target: targetId }); + } + + const knownPluginIds = await source.getKnownPluginIds(); + + const rootItemTypeIds = new Set(initialItemTypes.map((it) => it.id)); + const visitedItemTypeIds = new Set(Array.from(rootItemTypeIds)); + const itemTypesById = new Map( + initialItemTypes.map((it) => [it.id, it]), + ); + const fieldsByItemTypeId = new Map(); + const fieldsetsByItemTypeId = new Map(); + const discoveredPluginIdsInOrder: string[] = []; + const pluginsById = new Map(); + + const toExplore: SchemaTypes.ItemType[] = [...initialItemTypes]; + onProgress?.({ done: 0, total: 0, label: 'Scanning schema…', phase: 'scan' }); + + while (toExplore.length > 0) { + const current = toExplore.shift(); + if (!current) break; + const [fields, fieldsets] = + await source.getItemTypeFieldsAndFieldsets(current); + fieldsByItemTypeId.set(current.id, fields); + fieldsetsByItemTypeId.set(current.id, fieldsets); + + onProgress?.({ + done: 0, + total: 0, + label: `Scanning: ${current.attributes.name}`, + phase: 'scan', + }); + + // Discover neighbors via edges helper + const [, linkedItemTypeIds, linkedPluginIds] = buildEdgesForItemType( + current, + fields, + new Set(initialItemTypes.map((it) => it.id)), + knownPluginIds, + ); + + for (const linkedItemTypeId of linkedItemTypeIds) { + if (!visitedItemTypeIds.has(linkedItemTypeId)) { + const linked = await source.getItemTypeById(linkedItemTypeId); + visitedItemTypeIds.add(linkedItemTypeId); + itemTypesById.set(linkedItemTypeId, linked); + toExplore.push(linked); + } + } + for (const linkedPluginId of linkedPluginIds) { + if (!pluginsById.has(linkedPluginId)) { + const plugin = await source.getPluginById(linkedPluginId); + pluginsById.set(linkedPluginId, plugin); + discoveredPluginIdsInOrder.push(linkedPluginId); + } + } + } + + // Total is count of nodes to render + const total = visitedItemTypeIds.size + pluginsById.size; + let done = 0; + onProgress?.({ done, total, label: 'Preparing export…', phase: 'build' }); + + for (const itemTypeId of visitedItemTypeIds) { + const itemType = itemTypesById.get(itemTypeId); + if (!itemType) { + continue; + } + const fields = fieldsByItemTypeId.get(itemTypeId) || []; + const fieldsets = fieldsetsByItemTypeId.get(itemTypeId) || []; + + onProgress?.({ + done, + total, + label: `Model/Block: ${itemType.attributes.name}`, + phase: 'build', + }); + + graph.nodes.push(buildItemTypeNode(itemType, fields, fieldsets)); + + if (!itemTypeIdsToSkip.includes(itemType.id)) { + const [edges, linkedItemTypeIds, linkedPluginIds] = buildEdgesForItemType( + itemType, + fields, + rootItemTypeIds, + knownPluginIds, + ); + + for (const linkedItemTypeId of linkedItemTypeIds) { + recordHierarchyEdge( + `itemType--${itemType.id}`, + `itemType--${linkedItemTypeId}`, + ); + } + + for (const linkedPluginId of linkedPluginIds) { + recordHierarchyEdge( + `itemType--${itemType.id}`, + `plugin--${linkedPluginId}`, + ); + } + + // Include edges when: + // - No selection was provided (eg. Import graph) → include all edges + // - The source item type is selected + // - Any target item type of this edge set is selected + const includeEdges = + selectedItemTypeIds.length === 0 || + selectedItemTypeIds.includes(itemType.id) || + Array.from(linkedItemTypeIds).some((id) => + selectedItemTypeIds.includes(id), + ) || + edges.length > 0; + + if (includeEdges) { + graph.edges.push(...edges); + } + + // Queue discovered neighbors + for (const linkedItemTypeId of linkedItemTypeIds) { + if (!visitedItemTypeIds.has(linkedItemTypeId)) { + const linked = await source.getItemTypeById(linkedItemTypeId); + visitedItemTypeIds.add(linkedItemTypeId); + itemTypesById.set(linkedItemTypeId, linked); + toExplore.push(linked); + } + } + + for (const linkedPluginId of linkedPluginIds) { + if (!pluginsById.has(linkedPluginId)) { + const plugin = await source.getPluginById(linkedPluginId); + pluginsById.set(linkedPluginId, plugin); + discoveredPluginIdsInOrder.push(linkedPluginId); + } + } + } + + done += 1; + onProgress?.({ + done, + total, + label: `Fields/Fieldsets for ${itemType.attributes.name}`, + phase: 'build', + }); + } + + for (const pluginId of discoveredPluginIdsInOrder) { + const plugin = pluginsById.get(pluginId); + if (!plugin) continue; + graph.nodes.push(buildPluginNode(plugin)); + done += 1; + onProgress?.({ + done, + total, + label: `Plugin: ${plugin.attributes.name}`, + phase: 'build', + }); + } + + const sortedGraph = deterministicGraphSort(graph); + if (sortedGraph.nodes.length === 0) return sortedGraph; + + const hierarchy = buildHierarchyNodes( + sortedGraph, + selectedItemTypeIds, + hierarchyEdges, + ); + return rebuildGraphWithPositionsFromHierarchy(hierarchy, sortedGraph.edges); +} diff --git a/import-export-schema/src/utils/graph/buildHierarchyNodes.ts b/import-export-schema/src/utils/graph/buildHierarchyNodes.ts index a83c5edd..ec70f5cd 100644 --- a/import-export-schema/src/utils/graph/buildHierarchyNodes.ts +++ b/import-export-schema/src/utils/graph/buildHierarchyNodes.ts @@ -1,33 +1,91 @@ import { stratify } from 'd3-hierarchy'; import type { AppNode, Graph } from './types'; +/** + * Build a D3 hierarchy from the graph, optionally preferring certain inbound edges. + */ export function buildHierarchyNodes( graph: Graph, priorityGivenToEdgesComingFromItemTypeIds?: string[], + fallbackEdges: Array<{ source: string; target: string }> = [], ) { + const nodeIds = new Set(graph.nodes.map((n) => n.id)); + const targetsFromGraph = new Set(graph.edges.map((e) => e.target)); + + const fallbackParentsByTarget = new Map>(); + for (const { source, target } of fallbackEdges) { + if (!nodeIds.has(source) || !nodeIds.has(target)) { + continue; + } + const existing = fallbackParentsByTarget.get(target); + if (existing) { + existing.add(source); + } else { + fallbackParentsByTarget.set(target, new Set([source])); + } + } + + const fallbackTargets = new Set(fallbackParentsByTarget.keys()); + const targets = new Set([...targetsFromGraph, ...fallbackTargets]); + const rootIds = Array.from(nodeIds).filter((id) => !targets.has(id)); + + const hasMultipleRoots = rootIds.length > 1; + const nodesForHierarchy: AppNode[] = hasMultipleRoots + ? ([ + // Synthetic root only used to satisfy single-root requirement + { + id: 'synthetic-root', + type: 'plugin', + position: { x: 0, y: 0 }, + data: {}, + } as unknown as AppNode, + ...graph.nodes, + ] as AppNode[]) + : graph.nodes; + + const priorityNodeIds = new Set( + (priorityGivenToEdgesComingFromItemTypeIds ?? []).flatMap((id) => [ + id, + `itemType--${id}`, + `plugin--${id}`, + ]), + ); + return stratify() .id((d) => d.id) .parentId((d) => { - const edgesPointingToNode = graph.edges.filter((e) => { - return e.target === d.id; - }); + if (hasMultipleRoots && rootIds.includes(d.id)) { + return 'synthetic-root'; + } + + const edgesPointingToNode = graph.edges.filter((e) => e.target === d.id); + + const fallbackSources = fallbackParentsByTarget.get(d.id); + const fallbackCandidates = fallbackSources + ? Array.from(fallbackSources).map((source) => ({ source })) + : []; + + const candidates = + edgesPointingToNode.length > 0 + ? edgesPointingToNode + : fallbackCandidates; - if (!priorityGivenToEdgesComingFromItemTypeIds) { - return edgesPointingToNode[0]?.source; + if (candidates.length === 0) { + return candidates[0]?.source; } - if (edgesPointingToNode.length <= 0) { - return edgesPointingToNode[0]?.source; + if (priorityNodeIds.size === 0) { + return candidates[0]?.source; } - const proprityEdges = edgesPointingToNode.filter((e) => { - return priorityGivenToEdgesComingFromItemTypeIds.includes(e.source); - }); + const priorityEdges = candidates.filter((e) => + priorityNodeIds.has(e.source), + ); - const regularEdges = edgesPointingToNode.filter((e) => { - return !priorityGivenToEdgesComingFromItemTypeIds.includes(e.source); - }); + const regularEdges = candidates.filter( + (e) => !priorityNodeIds.has(e.source), + ); - return [...proprityEdges, ...regularEdges][0]?.source; - })(graph.nodes); + return [...priorityEdges, ...regularEdges][0]?.source; + })(nodesForHierarchy); } diff --git a/import-export-schema/src/utils/graph/dependencies.ts b/import-export-schema/src/utils/graph/dependencies.ts new file mode 100644 index 00000000..8e1c1dd4 --- /dev/null +++ b/import-export-schema/src/utils/graph/dependencies.ts @@ -0,0 +1,80 @@ +import { + findLinkedItemTypeIds, + findLinkedPluginIds, +} from '@/utils/datocms/schema'; +import type { Graph } from '@/utils/graph/types'; + +export type DependencyExpansionResult = { + itemTypeIds: Set; + pluginIds: Set; + addedItemTypeIds: string[]; + addedPluginIds: string[]; +}; + +type ExpandOptions = { + graph?: Graph; + seedItemTypeIds: Iterable; + seedPluginIds: Iterable; + installedPluginIds?: Set; +}; + +/** + * Expand the current selection with all linked item types and plugins. + */ +export function expandSelectionWithDependencies({ + graph, + seedItemTypeIds, + seedPluginIds, + installedPluginIds, +}: ExpandOptions): DependencyExpansionResult { + const initialItemIds = Array.from(new Set(seedItemTypeIds)); + const initialPluginIds = Array.from(new Set(seedPluginIds)); + const nextItemTypeIds = new Set(initialItemIds); + const nextPluginIds = new Set(initialPluginIds); + + if (!graph) { + return { + itemTypeIds: nextItemTypeIds, + pluginIds: nextPluginIds, + addedItemTypeIds: [], + addedPluginIds: [], + }; + } + + const queue = [...initialItemIds]; + while (queue.length > 0) { + const currentId = queue.pop(); + if (!currentId) continue; + const node = graph.nodes.find( + (candidate) => candidate.id === `itemType--${currentId}`, + ); + if (!node || node.type !== 'itemType') continue; + + for (const field of node.data.fields) { + for (const linkedId of findLinkedItemTypeIds(field)) { + if (!nextItemTypeIds.has(linkedId)) { + nextItemTypeIds.add(linkedId); + queue.push(linkedId); + } + } + + for (const pluginId of findLinkedPluginIds(field, installedPluginIds)) { + nextPluginIds.add(pluginId); + } + } + } + + const addedItemTypeIds = Array.from(nextItemTypeIds).filter( + (id) => !initialItemIds.includes(id), + ); + const addedPluginIds = Array.from(nextPluginIds).filter( + (id) => !initialPluginIds.includes(id), + ); + + return { + itemTypeIds: nextItemTypeIds, + pluginIds: nextPluginIds, + addedItemTypeIds, + addedPluginIds, + }; +} diff --git a/import-export-schema/src/utils/graph/edges.ts b/import-export-schema/src/utils/graph/edges.ts new file mode 100644 index 00000000..8a562eb4 --- /dev/null +++ b/import-export-schema/src/utils/graph/edges.ts @@ -0,0 +1,69 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import { MarkerType } from '@xyflow/react'; +import find from 'lodash-es/find'; +import { + findLinkedItemTypeIds, + findLinkedPluginIds, +} from '@/utils/datocms/schema'; +import type { AppEdge } from '@/utils/graph/types'; + +/** Build edges for a single item type, aggregating field references with arrows. */ +export function buildEdgesForItemType( + itemType: SchemaTypes.ItemType, + fields: SchemaTypes.Field[], + rootItemTypeIds: Set, + installedPluginIds: Set, +) { + const edges: AppEdge[] = []; + const linkedItemTypeIds = new Set(); + const linkedPluginIds = new Set(); + + for (const field of fields) { + for (const linkedItemTypeId of findLinkedItemTypeIds(field)) { + if (rootItemTypeIds.has(linkedItemTypeId)) continue; + + const id = `toItemType--${itemType.id}->${linkedItemTypeId}`; + linkedItemTypeIds.add(linkedItemTypeId); + const existing = find(edges, { id }); + if (existing) { + const data = existing.data ?? { fields: [] }; + data.fields.push(field); + existing.data = data; + } else { + edges.push({ + id, + source: `itemType--${itemType.id}`, + target: `itemType--${linkedItemTypeId}`, + type: 'field', + data: { fields: [field] }, + markerEnd: { type: MarkerType.ArrowClosed }, + }); + } + } + + for (const linkedPluginId of findLinkedPluginIds( + field, + installedPluginIds, + )) { + const id = `toPlugin--${itemType.id}->${linkedPluginId}`; + const existing = find(edges, { id }); + linkedPluginIds.add(linkedPluginId); + if (existing) { + const data = existing.data ?? { fields: [] }; + data.fields.push(field); + existing.data = data; + } else { + edges.push({ + id, + source: `itemType--${itemType.id}`, + target: `plugin--${linkedPluginId}`, + type: 'field', + data: { fields: [field] }, + markerEnd: { type: MarkerType.ArrowClosed }, + }); + } + } + } + + return [edges, linkedItemTypeIds, linkedPluginIds] as const; +} diff --git a/import-export-schema/src/utils/graph/nodes.ts b/import-export-schema/src/utils/graph/nodes.ts new file mode 100644 index 00000000..b0bc0a70 --- /dev/null +++ b/import-export-schema/src/utils/graph/nodes.ts @@ -0,0 +1,26 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import type { ItemTypeNode } from '@/components/ItemTypeNodeRenderer'; +import type { PluginNode } from '@/components/PluginNodeRenderer'; + +/** Lightweight constructors for React Flow nodes representing schema entities. */ +export function buildPluginNode(plugin: SchemaTypes.Plugin): PluginNode { + return { + id: `plugin--${plugin.id}`, + position: { x: 0, y: 0 }, + type: 'plugin', + data: { plugin }, + }; +} + +export function buildItemTypeNode( + itemType: SchemaTypes.ItemType, + fields: SchemaTypes.Field[], + fieldsets: SchemaTypes.Fieldset[], +): ItemTypeNode { + return { + id: `itemType--${itemType.id}`, + position: { x: 0, y: 0 }, + type: 'itemType', + data: { itemType, fields, fieldsets }, + }; +} diff --git a/import-export-schema/src/utils/graph/rebuildGraphWithPositionsFromHierarchy.ts b/import-export-schema/src/utils/graph/rebuildGraphWithPositionsFromHierarchy.ts index 0d1a3941..dbacb854 100644 --- a/import-export-schema/src/utils/graph/rebuildGraphWithPositionsFromHierarchy.ts +++ b/import-export-schema/src/utils/graph/rebuildGraphWithPositionsFromHierarchy.ts @@ -1,6 +1,9 @@ import { type HierarchyNode, tree } from 'd3-hierarchy'; import type { AppNode, Graph } from './types'; +/** + * Convert a D3 hierarchy into positioned React Flow nodes while reusing edge data. + */ export function rebuildGraphWithPositionsFromHierarchy( hierarchy: HierarchyNode, edges: Graph['edges'], @@ -12,21 +15,19 @@ export function rebuildGraphWithPositionsFromHierarchy( const root = layout(hierarchy); return { - nodes: root.descendants().map((hierarchyNode) => { - return { - ...hierarchyNode.data, - // This bit is super important! We *mutated* the object in the `forEach` - // above so the reference is the same. React needs to see a new reference - // to trigger a re-render of the node. - data: { ...hierarchyNode.data.data }, - // targetPosition : 'left', - // sourcePosition : 'right', - position: { - x: hierarchyNode.x!, - y: hierarchyNode.y!, - }, - } as AppNode; - }), + nodes: root + .descendants() + .filter((n) => n.data.id !== 'synthetic-root') + .map((hierarchyNode) => { + return { + ...hierarchyNode.data, + data: { ...hierarchyNode.data.data }, + position: { + x: hierarchyNode.x!, + y: hierarchyNode.y!, + }, + } as AppNode; + }), edges, }; } diff --git a/import-export-schema/src/utils/graph/sort.ts b/import-export-schema/src/utils/graph/sort.ts new file mode 100644 index 00000000..00573e01 --- /dev/null +++ b/import-export-schema/src/utils/graph/sort.ts @@ -0,0 +1,18 @@ +import sortBy from 'lodash-es/sortBy'; +import type { AppNode, Graph } from '@/utils/graph/types'; + +/** Stable ordering so layout + list views don't flicker across renders. */ +export function deterministicGraphSort(graph: Graph) { + return { + nodes: sortBy(graph.nodes, [ + 'type', + (n: AppNode) => + 'itemType' in n.data ? n.data.itemType.attributes.api_key : undefined, + (n: AppNode) => + 'itemType' in n.data + ? n.data.itemType.attributes.name + : n.data.plugin.attributes.name, + ]), + edges: graph.edges, + }; +} diff --git a/import-export-schema/src/utils/graph/types.ts b/import-export-schema/src/utils/graph/types.ts index a1972395..e0a895f7 100644 --- a/import-export-schema/src/utils/graph/types.ts +++ b/import-export-schema/src/utils/graph/types.ts @@ -1,11 +1,12 @@ +import type { EdgeTypes } from '@xyflow/react'; import { type FieldEdge, FieldEdgeRenderer, } from '@/components/FieldEdgeRenderer'; import type { ItemTypeNode } from '@/components/ItemTypeNodeRenderer'; import type { PluginNode } from '@/components/PluginNodeRenderer'; -import type { EdgeTypes } from '@xyflow/react'; +/** Shared graph typings + edge registration used across export/import canvases. */ export type AppNode = ItemTypeNode | PluginNode; export type AppEdge = FieldEdge; @@ -18,3 +19,10 @@ export type Graph = { nodes: Array; edges: Array; }; + +export type SchemaProgressUpdate = { + done: number; + total: number; + label: string; + phase?: 'scan' | 'build'; +}; diff --git a/import-export-schema/src/utils/schema/ExportSchemaSource.ts b/import-export-schema/src/utils/schema/ExportSchemaSource.ts new file mode 100644 index 00000000..2bef84ad --- /dev/null +++ b/import-export-schema/src/utils/schema/ExportSchemaSource.ts @@ -0,0 +1,33 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import type { ExportSchema } from '@/entrypoints/ExportPage/ExportSchema'; +import type { ISchemaSource } from './ISchemaSource'; + +/** Adapts an export document into the schema source interface for graph building. */ +export class ExportSchemaSource implements ISchemaSource { + private schema: ExportSchema; + + constructor(schema: ExportSchema) { + this.schema = schema; + } + + async getItemTypeById(id: string): Promise { + return this.schema.getItemTypeById(id); + } + + async getPluginById(id: string): Promise { + return this.schema.getPluginById(id); + } + + async getItemTypeFieldsAndFieldsets( + itemType: SchemaTypes.ItemType, + ): Promise<[SchemaTypes.Field[], SchemaTypes.Fieldset[]]> { + return [ + this.schema.getItemTypeFields(itemType), + this.schema.getItemTypeFieldsets(itemType), + ]; + } + + getKnownPluginIds(): Set { + return new Set(Array.from(this.schema.pluginsById.keys())); + } +} diff --git a/import-export-schema/src/utils/schema/ISchemaSource.ts b/import-export-schema/src/utils/schema/ISchemaSource.ts new file mode 100644 index 00000000..7d4d425f --- /dev/null +++ b/import-export-schema/src/utils/schema/ISchemaSource.ts @@ -0,0 +1,11 @@ +import type { SchemaTypes } from '@datocms/cma-client'; + +/** Contract implemented by both live project schemas and serialized export docs. */ +export interface ISchemaSource { + getItemTypeById(id: string): Promise; + getPluginById(id: string): Promise; + getItemTypeFieldsAndFieldsets( + itemType: SchemaTypes.ItemType, + ): Promise<[SchemaTypes.Field[], SchemaTypes.Fieldset[]]>; + getKnownPluginIds(): Promise> | Set; +} diff --git a/import-export-schema/src/utils/schema/ProjectSchemaSource.ts b/import-export-schema/src/utils/schema/ProjectSchemaSource.ts new file mode 100644 index 00000000..6751742c --- /dev/null +++ b/import-export-schema/src/utils/schema/ProjectSchemaSource.ts @@ -0,0 +1,40 @@ +import type { SchemaTypes } from '@datocms/cma-client'; +import type { ProjectSchema } from '@/utils/ProjectSchema'; +import type { ISchemaSource } from './ISchemaSource'; + +/** Adapts the live CMA-backed project schema to the generic graph interface. */ +export class ProjectSchemaSource implements ISchemaSource { + private schema: ProjectSchema; + private cachedPluginIds?: Set; + + constructor( + schema: ProjectSchema, + options: { installedPluginIds?: Set } = {}, + ) { + this.schema = schema; + this.cachedPluginIds = options.installedPluginIds; + } + + async getItemTypeById(id: string): Promise { + return this.schema.getItemTypeById(id); + } + + async getPluginById(id: string): Promise { + return this.schema.getPluginById(id); + } + + async getItemTypeFieldsAndFieldsets( + itemType: SchemaTypes.ItemType, + ): Promise<[SchemaTypes.Field[], SchemaTypes.Fieldset[]]> { + return this.schema.getItemTypeFieldsAndFieldsets(itemType); + } + + async getKnownPluginIds(): Promise> { + if (this.cachedPluginIds) { + return this.cachedPluginIds; + } + const plugins = await this.schema.getAllPlugins(); + this.cachedPluginIds = new Set(plugins.map((p) => p.id)); + return this.cachedPluginIds; + } +} diff --git a/import-export-schema/tsconfig.app.json b/import-export-schema/tsconfig.app.json index 653d8e41..8a63a4a5 100644 --- a/import-export-schema/tsconfig.app.json +++ b/import-export-schema/tsconfig.app.json @@ -20,7 +20,7 @@ "noFallthroughCasesInSwitch": true, "paths": { - "@/*": ["./src/*"], + "@/*": ["./src/*"] } }, "include": ["src"] diff --git a/import-export-schema/tsconfig.app.tsbuildinfo b/import-export-schema/tsconfig.app.tsbuildinfo index 32433671..d3476ed9 100644 --- a/import-export-schema/tsconfig.app.tsbuildinfo +++ b/import-export-schema/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/field.tsx","./src/components/fieldedgerenderer.tsx","./src/components/itemtypenoderenderer.tsx","./src/components/pluginnoderenderer.tsx","./src/components/bezier.ts","./src/entrypoints/config/index.tsx","./src/entrypoints/exportpage/entitiestoexportcontext.ts","./src/entrypoints/exportpage/exportitemtypenoderenderer.tsx","./src/entrypoints/exportpage/exportpluginnoderenderer.tsx","./src/entrypoints/exportpage/exportschema.ts","./src/entrypoints/exportpage/inner.tsx","./src/entrypoints/exportpage/buildexportdoc.ts","./src/entrypoints/exportpage/buildgraphfromschema.ts","./src/entrypoints/exportpage/index.tsx","./src/entrypoints/exportpage/useanimatednodes.tsx","./src/entrypoints/importpage/filedropzone.tsx","./src/entrypoints/importpage/importitemtypenoderenderer.tsx","./src/entrypoints/importpage/importpluginnoderenderer.tsx","./src/entrypoints/importpage/inner.tsx","./src/entrypoints/importpage/resolutionsform.tsx","./src/entrypoints/importpage/selectedentitycontext.tsx","./src/entrypoints/importpage/buildgraphfromexportdoc.ts","./src/entrypoints/importpage/buildimportdoc.ts","./src/entrypoints/importpage/importschema.ts","./src/entrypoints/importpage/index.tsx","./src/entrypoints/importpage/conflictsmanager/collapsible.tsx","./src/entrypoints/importpage/conflictsmanager/conflictscontext.ts","./src/entrypoints/importpage/conflictsmanager/itemtypeconflict.tsx","./src/entrypoints/importpage/conflictsmanager/pluginconflict.tsx","./src/entrypoints/importpage/conflictsmanager/buildconflicts.ts","./src/entrypoints/importpage/conflictsmanager/index.tsx","./src/utils/projectschema.ts","./src/utils/downloadjson.ts","./src/utils/emojiagnosticsorter.ts","./src/utils/fieldtypegroups.ts","./src/utils/icons.tsx","./src/utils/isdefined.ts","./src/utils/render.tsx","./src/utils/types.ts","./src/utils/datocms/fieldtypeinfo.ts","./src/utils/datocms/schema.ts","./src/utils/graph/buildhierarchynodes.ts","./src/utils/graph/rebuildgraphwithpositionsfromhierarchy.ts","./src/utils/graph/types.ts"],"version":"5.7.3"} \ No newline at end of file +{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/components/blankslate.tsx","./src/components/exportlandingpanel.tsx","./src/components/exportselectionpanel.tsx","./src/components/field.tsx","./src/components/fieldedgerenderer.tsx","./src/components/graphcanvas.tsx","./src/components/itemtypenoderenderer.tsx","./src/components/pluginnoderenderer.tsx","./src/components/progressoverlay.tsx","./src/components/progressstallnotice.tsx","./src/components/taskoverlaystack.tsx","./src/components/taskprogressoverlay.tsx","./src/components/bezier.ts","./src/components/schemaoverview/collapsible.tsx","./src/components/schemaoverview/selectedentitycontext.tsx","./src/entrypoints/config/index.tsx","./src/entrypoints/exporthome/index.tsx","./src/entrypoints/exportpage/dependencyactionspanel.tsx","./src/entrypoints/exportpage/entitiestoexportcontext.ts","./src/entrypoints/exportpage/exportitemtypenoderenderer.tsx","./src/entrypoints/exportpage/exportpluginnoderenderer.tsx","./src/entrypoints/exportpage/exportschema.ts","./src/entrypoints/exportpage/exportschemaoverview.tsx","./src/entrypoints/exportpage/inner.tsx","./src/entrypoints/exportpage/buildexportdoc.ts","./src/entrypoints/exportpage/buildgraphfromschema.ts","./src/entrypoints/exportpage/index.tsx","./src/entrypoints/exportpage/useexportgraph.ts","./src/entrypoints/importpage/filedropzone.tsx","./src/entrypoints/importpage/graphentitiescontext.tsx","./src/entrypoints/importpage/importitemtypenoderenderer.tsx","./src/entrypoints/importpage/importpluginnoderenderer.tsx","./src/entrypoints/importpage/importworkflow.tsx","./src/entrypoints/importpage/inner.tsx","./src/entrypoints/importpage/resolutionsform.tsx","./src/entrypoints/importpage/buildgraphfromexportdoc.ts","./src/entrypoints/importpage/buildimportdoc.ts","./src/entrypoints/importpage/importschema.ts","./src/entrypoints/importpage/index.tsx","./src/entrypoints/importpage/userecipeloader.ts","./src/entrypoints/importpage/conflictsmanager/conflictscontext.ts","./src/entrypoints/importpage/conflictsmanager/itemtypeconflict.tsx","./src/entrypoints/importpage/conflictsmanager/pluginconflict.tsx","./src/entrypoints/importpage/conflictsmanager/buildconflicts.ts","./src/entrypoints/importpage/conflictsmanager/index.tsx","./src/shared/constants/graph.ts","./src/shared/hooks/usecmaclient.ts","./src/shared/hooks/useconflictsbuilder.ts","./src/shared/hooks/useexportallhandler.ts","./src/shared/hooks/useexportselection.ts","./src/shared/hooks/useprojectschema.ts","./src/shared/hooks/useschemaexporttask.ts","./src/shared/tasks/uselongtask.ts","./src/types/lodash-es.d.ts","./src/utils/projectschema.ts","./src/utils/createcmaclient.ts","./src/utils/debug.ts","./src/utils/downloadjson.ts","./src/utils/emojiagnosticsorter.ts","./src/utils/icons.tsx","./src/utils/isdefined.ts","./src/utils/render.tsx","./src/utils/types.ts","./src/utils/datocms/appearance.ts","./src/utils/datocms/fieldtypeinfo.ts","./src/utils/datocms/schema.ts","./src/utils/graph/analysis.ts","./src/utils/graph/buildgraph.ts","./src/utils/graph/buildhierarchynodes.ts","./src/utils/graph/dependencies.ts","./src/utils/graph/edges.ts","./src/utils/graph/nodes.ts","./src/utils/graph/rebuildgraphwithpositionsfromhierarchy.ts","./src/utils/graph/sort.ts","./src/utils/graph/types.ts","./src/utils/schema/exportschemasource.ts","./src/utils/schema/ischemasource.ts","./src/utils/schema/projectschemasource.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/import-export-schema/tsconfig.node.json b/import-export-schema/tsconfig.node.json index a68ef43a..24ed5371 100644 --- a/import-export-schema/tsconfig.node.json +++ b/import-export-schema/tsconfig.node.json @@ -19,7 +19,7 @@ "noFallthroughCasesInSwitch": true, "paths": { - "@/*": ["./src/*"], + "@/*": ["./src/*"] } }, "include": ["vite.config.ts"] diff --git a/import-export-schema/tsconfig.node.tsbuildinfo b/import-export-schema/tsconfig.node.tsbuildinfo index 0440098c..62c7bf92 100644 --- a/import-export-schema/tsconfig.node.tsbuildinfo +++ b/import-export-schema/tsconfig.node.tsbuildinfo @@ -1 +1 @@ -{"root":["./vite.config.ts"],"version":"5.7.3"} \ No newline at end of file +{"root":["./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/import-export-schema/vite.config.ts b/import-export-schema/vite.config.ts index 83b8b8f1..af61eb15 100644 --- a/import-export-schema/vite.config.ts +++ b/import-export-schema/vite.config.ts @@ -1,18 +1,48 @@ +import { fileURLToPath, URL } from 'node:url'; import react from '@vitejs/plugin-react'; -import { URL, fileURLToPath } from 'url'; import { defineConfig } from 'vite'; import svgr from 'vite-plugin-svgr'; // https://vitejs.dev/config/ -export default defineConfig({ - base: './', - plugins: [react(), svgr()], - resolve: { - alias: [ - { - find: '@', - replacement: fileURLToPath(new URL('./src', import.meta.url)), - }, +export default defineConfig(({ command }) => { + const isBuild = command === 'build'; + + return { + base: './', + plugins: [ + react(), + // SVGR for SVG imports + svgr({ svgrOptions: {} }), ], - }, + resolve: { + alias: [ + { + find: '@', + replacement: fileURLToPath(new URL('./src', import.meta.url)), + }, + ], + }, + build: { + sourcemap: false, + cssCodeSplit: true, + chunkSizeWarningLimit: 1024, + rollupOptions: { + output: { + manualChunks: { + 'vendor-react': ['react', 'react-dom'], + 'vendor-datocms': ['datocms-plugin-sdk', 'datocms-react-ui'], + 'vendor-xyflow': ['@xyflow/react', 'd3-hierarchy', 'd3-timer'], + 'vendor-icons': [ + '@fortawesome/react-fontawesome', + '@fortawesome/fontawesome-svg-core', + '@fortawesome/free-solid-svg-icons', + ], + 'vendor-lodash': ['lodash-es'], + }, + }, + }, + }, + // Drop consoles/debuggers in production bundles + esbuild: isBuild ? { drop: ['console', 'debugger'] } : undefined, + }; });