From 1d158c9bce019cbeafa9aae068aacd7296d02105 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Wed, 19 Nov 2025 14:57:36 +0000 Subject: [PATCH 01/81] Refactor code structure for improved readability and maintainability --- editor-app/components/SetIndividual.tsx | 380 +- editor-app/lib/Model.ts | 53 +- editor-app/package-lock.json | 6342 +++++++++++++++++++++++ editor-app/package.json | 1 + editor-app/yarn.lock | 1296 ++--- 5 files changed, 7168 insertions(+), 904 deletions(-) create mode 100644 editor-app/package-lock.json diff --git a/editor-app/components/SetIndividual.tsx b/editor-app/components/SetIndividual.tsx index 98d1e08..ffb9f04 100644 --- a/editor-app/components/SetIndividual.tsx +++ b/editor-app/components/SetIndividual.tsx @@ -49,7 +49,6 @@ const SetIndividual = (props: Props) => { endsWithParticipant: false, }; - const newType = useRef(null); const [errors, setErrors] = useState([]); const [inputs, setInputs] = useState( selectedIndividual ? selectedIndividual : defaultIndividual @@ -60,6 +59,13 @@ const SetIndividual = (props: Props) => { const [individualHasParticipants, setIndividualHasParticipants] = useState(false); + // New state for custom type selector + const [typeOpen, setTypeOpen] = useState(false); + const [typeSearch, setTypeSearch] = useState(""); + const [editingTypeId, setEditingTypeId] = useState(null); + const [editingTypeValue, setEditingTypeValue] = useState(""); + const typeDropdownRef = useRef(null); + useEffect(() => { if (selectedIndividual) { setIndividualHasParticipants( @@ -80,12 +86,32 @@ const SetIndividual = (props: Props) => { } }, [selectedIndividual, dataset]); + // click outside to close type dropdown + useEffect(() => { + function handleClickOutside(ev: MouseEvent) { + if ( + typeDropdownRef.current && + !typeDropdownRef.current.contains(ev.target as Node) + ) { + setTypeOpen(false); + setEditingTypeId(null); + setEditingTypeValue(""); + } + } + if (typeOpen) document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [typeOpen]); + const handleClose = () => { setShow(false); setInputs(defaultIndividual); setSelectedIndividual(undefined); setErrors([]); setDirty(false); + setTypeOpen(false); + setTypeSearch(""); + setEditingTypeId(null); + setEditingTypeValue(""); }; const handleShow = () => { if (selectedIndividual) { @@ -97,8 +123,7 @@ const SetIndividual = (props: Props) => { }; const handleAdd = (event: any) => { event.preventDefault(); - if (!dirty) - return handleClose(); + if (!dirty) return handleClose(); const isValid = validateInputs(); if (isValid) { setIndividual(inputs); @@ -119,13 +144,7 @@ const SetIndividual = (props: Props) => { updateInputs(e.target.name, e.target.value); }; - const handleTypeChange = (e: any) => { - dataset.individualTypes.forEach((type) => { - if (e.target.value == type.id) { - updateInputs(e.target.name, type); - } - }); - }; + // remove old handleTypeChange and add new type handlers below const handleBeginsWithParticipant = (e: any) => { const checked = e.target.checked; @@ -170,15 +189,109 @@ const SetIndividual = (props: Props) => { } }; - const addType = (e: any) => { - if (newType.current && newType.current.value) { - updateDataset((d) => - d.addIndividualType(uuidv4(), newType.current.value) - ); - newType.current.value = null; + // ----- New helper functions for custom type selector ----- + const filteredTypes = dataset.individualTypes.filter((t) => + t.name.toLowerCase().includes(typeSearch.toLowerCase()) + ); + + const showCreateTypeOption = + typeSearch.trim().length > 0 && + !dataset.individualTypes.some( + (t) => t.name.toLowerCase() === typeSearch.trim().toLowerCase() + ); + + const handleSelectType = (typeId: string) => { + const t = dataset.individualTypes.find((x) => x.id === typeId); + if (t) { + updateInputs("type", t); } + setTypeOpen(false); + setTypeSearch(""); + setEditingTypeId(null); + setEditingTypeValue(""); }; + const handleCreateTypeFromSearch = () => { + const name = typeSearch.trim(); + if (!name) return; + const newId = uuidv4(); + + // add to dataset (keeps existing project pattern) + updateDataset((d) => { + d.addIndividualType(newId, name); + return d; + }); + + // Immediately select the newly created type for this form. + // Create a minimal Kind-like object so selection is immediate even + // if the dataset state hasn't re-rendered yet. + const createdType = { id: newId, name, isCoreHqdm: false }; + updateInputs("type", createdType); + + setTypeOpen(false); + setTypeSearch(""); + }; + + const startEditType = (typeId: string, currentName: string, e: any) => { + e.stopPropagation(); + // prevent editing core HQDM defaults + const found = dataset.individualTypes.find((x) => x.id === typeId); + if (found && found.isCoreHqdm) return; + setEditingTypeId(typeId); + setEditingTypeValue(currentName); + }; + + const saveEditType = () => { + if (!editingTypeId) return; + const newName = editingTypeValue.trim(); + if (!newName) return; + + // Update model: rename the Kind and update all Individuals that reference it + updateDataset((d) => { + // find the canonical kind in the model and rename it + const kind = d.individualTypes.find((x) => x.id === editingTypeId); + if (kind) kind.name = newName; + + // update any Individuals that still reference the old Kind object + // by replacing their .type with the canonical Kind from the model + d.individuals.forEach((ind /*, key */) => { + if (ind.type && ind.type.id === editingTypeId) { + const canonical = d.individualTypes.find( + (x) => x.id === editingTypeId + ); + if (canonical) ind.type = canonical; + } + }); + + // if defaultIndividualType matches, update that reference too + if ( + d.defaultIndividualType && + d.defaultIndividualType.id === editingTypeId + ) { + const canonical = d.individualTypes.find((x) => x.id === editingTypeId); + if (canonical) d.defaultIndividualType = canonical; + } + + return d; + }); + + // Update the form selection immediately to show edited name + updateInputs("type", { + id: editingTypeId, + name: newName, + isCoreHqdm: false, + }); + + setEditingTypeId(null); + setEditingTypeValue(""); + }; + + const cancelEditType = () => { + setEditingTypeId(null); + setEditingTypeValue(""); + }; + // ----- end helpers ----- + return ( <> + + {typeOpen && ( +
+
+ setTypeSearch(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter" && showCreateTypeOption) { + e.preventDefault(); + handleCreateTypeFromSearch(); + } + }} + autoFocus + /> +
+ +
+ {filteredTypes.map((t) => ( +
handleSelectType(t.id)} + > + {editingTypeId === t.id ? ( +
+ + setEditingTypeValue(e.target.value) + } + onKeyDown={(e) => { + if (e.key === "Enter") saveEditType(); + if (e.key === "Escape") cancelEditType(); + }} + autoFocus + /> +
+ + +
+
+ ) : ( + <> +
{t.name}
+
+ {inputs?.type?.id === t.id && ( + + )} + {/* hide/disable edit for core HQDM types */} + {!t.isCoreHqdm && ( + + )} +
+ + )} +
+ ))} + + {showCreateTypeOption && ( +
+ Create "{typeSearch}" +
+ )} + + {filteredTypes.length === 0 && !showCreateTypeOption && ( +
+ No results found +
+ )} +
+
)} - {dataset.individualTypes.map((type) => ( - - ))} - + - - Add option - - - + {/* ----------------- end replaced Type field ----------------- */} + Description { - - - - - - - - - - - - - {errors.length > 0 && ( - - {errors.map((error, i) => ( -

- {error} -

- ))} -
- )} - -
-
+
+
+ +
+
+ + +
+
+
+ {errors.length > 0 && ( + + {errors.map((error, i) => ( +

+ {error} +

+ ))} +
+ )} +
diff --git a/editor-app/lib/Model.ts b/editor-app/lib/Model.ts index ddd45df..3602a54 100644 --- a/editor-app/lib/Model.ts +++ b/editor-app/lib/Model.ts @@ -7,11 +7,15 @@ import { EPOCH_END } from "./ActivityLib"; * A class used to list the types needed for drop-downs in the UI. */ export class Kind { - constructor( - readonly id: Id, - readonly name: string, - readonly isCoreHqdm: boolean - ) {} + readonly id: Id; + name: string; + readonly isCoreHqdm: boolean; + + constructor(id: Id, name: string, isCoreHqdm: boolean) { + this.id = id; + this.name = name; + this.isCoreHqdm = isCoreHqdm; + } } /** @@ -25,9 +29,9 @@ export class Model { readonly activityTypes: Array; readonly individualTypes: Array; - readonly defaultRole: Kind; - readonly defaultActivityType: Kind; - readonly defaultIndividualType: Kind; + defaultRole: Kind; + defaultActivityType: Kind; + defaultIndividualType: Kind; // Overall information about the model name: Maybe; @@ -47,13 +51,9 @@ export class Model { /* These default roles created here need to match the equivalents * created in ActivityLib:toModel. */ - this.roles = [ - new Kind(HQDM_NS + "participant", "Participant", true), - ]; + this.roles = [new Kind(HQDM_NS + "participant", "Participant", true)]; this.defaultRole = this.roles[0]; - this.activityTypes = [ - new Kind(HQDM_NS + "activity", "Task", true), - ]; + this.activityTypes = [new Kind(HQDM_NS + "activity", "Task", true)]; this.defaultActivityType = this.activityTypes[0]; this.individualTypes = [ new Kind(HQDM_NS + "person", "Person", true), @@ -83,10 +83,10 @@ export class Model { newModel.roles.push(r); }); this.activityTypes - .filter((a) => !a.isCoreHqdm) - .forEach((a) => { - newModel.activityTypes.push(a); - }); + .filter((a) => !a.isCoreHqdm) + .forEach((a) => { + newModel.activityTypes.push(a); + }); this.individualTypes .filter((i) => !i.isCoreHqdm) .forEach((i) => { @@ -260,12 +260,10 @@ export class Model { console.log("Normalising activity bounds: %o", this.activities); const parts = new Map, Id[]>(); - this.activities.forEach(a => { + this.activities.forEach((a) => { const list = parts.get(a.partOf); - if (list) - list.push(a.id); - else - parts.set(a.partOf, [a.id]); + if (list) list.push(a.id); + else parts.set(a.partOf, [a.id]); }); const to_process: Id[] = []; @@ -276,8 +274,7 @@ export class Model { if (list) { /* This skips the top-level activities. We could instead rescale * to some standard boundaries (e.g. 0-1000). */ - if (next) - to_process.push(next); + if (next) to_process.push(next); to_check.push(...list); } } @@ -285,9 +282,9 @@ export class Model { for (const id of to_process) { const act = this.activities.get(id)!; - const kids = parts.get(id)!.map(id => this.activities.get(id)!); - const earliest = Math.min(...kids.map(a => a.beginning)); - const latest = Math.max(...kids.map(a => a.ending)); + const kids = parts.get(id)!.map((id) => this.activities.get(id)!); + const earliest = Math.min(...kids.map((a) => a.beginning)); + const latest = Math.max(...kids.map((a) => a.ending)); const scale = (act.ending - act.beginning) / (latest - earliest); const offset = act.beginning - earliest; diff --git a/editor-app/package-lock.json b/editor-app/package-lock.json new file mode 100644 index 0000000..2ff6e62 --- /dev/null +++ b/editor-app/package-lock.json @@ -0,0 +1,6342 @@ +{ + "name": "activity-diagram-app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "activity-diagram-app", + "version": "0.1.0", + "hasInstallScript": true, + "dependencies": { + "@apollo-protocol/hqdm-lib": "^0.0.2", + "@dnd-kit/core": "6.3.1", + "@dnd-kit/sortable": "10.0.0", + "@dnd-kit/utilities": "3.2.2", + "@types/d3": "^7.4.3", + "@types/node": "24.0.14", + "@types/react": "19.1.8", + "@types/react-dom": "19.1.6", + "bootstrap": "^5.3.3", + "d3": "^7.9.0", + "eslint": "^9.31.0", + "eslint-config-next": "15.4.1", + "lodash": "^4.17.21", + "lucide-react": "^0.554.0", + "next": "15.4.1", + "react": "19.1.0", + "react-bootstrap": "^2.10.2", + "react-dom": "19.1.0", + "react-modal-image": "^2.6.0", + "react-select": "^5.8.0", + "typescript": "5.8.3", + "uuidv4": "^6.2.13" + } + }, + "node_modules/@apollo-protocol/hqdm-lib": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@apollo-protocol/hqdm-lib/-/hqdm-lib-0.0.2.tgz", + "integrity": "sha512-5Jh4FmSIe1Zmpm04Hh30EPNSz+bcEdI5tC7+z6SJg/gOAxwYKsfjqQuvKSHrohf6EEHampYhirYp6tMGlVrc2w==", + "dependencies": { + "@rdfjs/serializer-jsonld": "^2.0.0", + "n3": "^1.16.3", + "string_decoder": "^1.3.0", + "uuid": "^9.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", + "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz", + "integrity": "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", + "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.3.tgz", + "integrity": "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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==", + "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/@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==", + "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==" + }, + "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==" + }, + "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==", + "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==", + "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==" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "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==", + "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==" + }, + "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==" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "dependencies": { + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", + "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", + "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", + "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz", + "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz", + "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", + "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz", + "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz", + "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz", + "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz", + "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz", + "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz", + "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz", + "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz", + "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz", + "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz", + "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz", + "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz", + "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.4" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz", + "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz", + "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz", + "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.1.tgz", + "integrity": "sha512-DXQwFGAE2VH+f2TJsKepRXpODPU+scf5fDbKOME8MMyeyswe4XwgRdiiIYmBfkXU+2ssliLYznajTrOQdnLR5A==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.4.1.tgz", + "integrity": "sha512-lQnHUxN7mMksK7IxgKDIXNMWFOBmksVrjamMEURXiYfo7zgsc30lnU8u4y/MJktSh+nB80ktTQeQbWdQO6c8Ow==", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.1.tgz", + "integrity": "sha512-L+81yMsiHq82VRXS2RVq6OgDwjvA4kDksGU8hfiDHEXP+ncKIUhUsadAVB+MRIp2FErs/5hpXR0u2eluWPAhig==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.1.tgz", + "integrity": "sha512-jfz1RXu6SzL14lFl05/MNkcN35lTLMJWPbqt7Xaj35+ZWAX342aePIJrN6xBdGeKl6jPXJm0Yqo3Xvh3Gpo3Uw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.1.tgz", + "integrity": "sha512-k0tOFn3dsnkaGfs6iQz8Ms6f1CyQe4GacXF979sL8PNQxjYS1swx9VsOyUQYaPoGV8nAZ7OX8cYaeiXGq9ahPQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.1.tgz", + "integrity": "sha512-4ogGQ/3qDzbbK3IwV88ltihHFbQVq6Qr+uEapzXHXBH1KsVBZOB50sn6BWHPcFjwSoMX2Tj9eH/fZvQnSIgc3g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.1.tgz", + "integrity": "sha512-Jj0Rfw3wIgp+eahMz/tOGwlcYYEFjlBPKU7NqoOkTX0LY45i5W0WcDpgiDWSLrN8KFQq/LW7fZq46gxGCiOYlQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.1.tgz", + "integrity": "sha512-9WlEZfnw1vFqkWsTMzZDgNL7AUI1aiBHi0S2m8jvycPyCq/fbZjtE/nDkhJRYbSjXbtRHYLDBlmP95kpjEmJbw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.1.tgz", + "integrity": "sha512-WodRbZ9g6CQLRZsG3gtrA9w7Qfa9BwDzhFVdlI6sV0OCPq9JrOrJSp9/ioLsezbV8w9RCJ8v55uzJuJ5RgWLZg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.1.tgz", + "integrity": "sha512-y+wTBxelk2xiNofmDOVU7O5WxTHcvOoL3srOM0kxTzKDjQ57kPU0tpnPJ/BWrRnsOwXEv0+3QSbGR7hY4n9LkQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rdfjs/serializer-jsonld": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rdfjs/serializer-jsonld/-/serializer-jsonld-2.0.1.tgz", + "integrity": "sha512-O8WzdY7THsse/nMsrMLd2e51ADHO2SIUrkiZ9Va/8W3lXeeeiwDRPMppWy/i9yL4q6EM8iMW1riV7E0mK3fsBQ==", + "dependencies": { + "@rdfjs/sink": "^2.0.1", + "readable-stream": "^4.5.2" + } + }, + "node_modules/@rdfjs/sink": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rdfjs/sink/-/sink-2.0.1.tgz", + "integrity": "sha512-smzIFGF6EH1sLAJR9F3p2wMNrN44JjPeYAoITTJLqtuNC319K7IXaJ+qNLBGTtapZ/jvpx2Tks0TjcH9KrAvEA==" + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.9.tgz", + "integrity": "sha512-2P5thfjfPy/np18e5wD4WPt8ydNXhij1jwA8oehxZTFqlgVMGXzcWKxTb4RtJrLFsqPO7RUQTiY8QJk0M4Vy2g==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", + "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" + }, + "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==" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" + }, + "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==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dependencies": { + "@types/geojson": "*" + } + }, + "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==" + }, + "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==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==" + }, + "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==" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" + }, + "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==" + }, + "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==", + "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==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "node_modules/@types/node": { + "version": "24.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", + "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", + "dependencies": { + "undici-types": "~7.8.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==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==" + }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "peerDependencies": { + "@types/react": "^19.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==", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", + "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/type-utils": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.37.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", + "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "dependencies": { + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", + "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", + "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", + "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", + "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", + "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", + "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "dependencies": { + "@typescript-eslint/project-service": "8.37.0", + "@typescript-eslint/tsconfig-utils": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", + "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", + "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bootstrap": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz", + "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "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" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "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==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "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==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "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==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "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==", + "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" + } + }, + "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==", + "dependencies": { + "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/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "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==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.4.1.tgz", + "integrity": "sha512-XIIN+lq8XuSwXUrcv+0uHMDFGJFPxLAw04/a4muFZYygSvStvVa15nY7kh4Il6yOVJyxdMUyVdQ9ApGedaeupw==", + "dependencies": { + "@next/eslint-plugin-next": "15.4.1", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 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==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.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==" + }, + "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==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "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==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.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==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "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==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lucide-react": { + "version": "0.554.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz", + "integrity": "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.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==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==" + }, + "node_modules/n3": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.26.0.tgz", + "integrity": "sha512-SQknS0ua90rN+3RHuk8BeIqeYyqIH/+ecViZxX08jR4j6MugqWRjtONl3uANG/crWXnOM2WIqBJtjIhVYFha+w==", + "dependencies": { + "buffer": "^6.0.3", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">=12.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.0.tgz", + "integrity": "sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/next": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/next/-/next-15.4.1.tgz", + "integrity": "sha512-eNKB1q8C7o9zXF8+jgJs2CzSLIU3T6bQtX6DcTnCq1sIR1CJ0GlSyRs1BubQi3/JgCnr9Vr+rS5mOMI38FFyQw==", + "dependencies": { + "@next/env": "15.4.1", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.4.1", + "@next/swc-darwin-x64": "15.4.1", + "@next/swc-linux-arm64-gnu": "15.4.1", + "@next/swc-linux-arm64-musl": "15.4.1", + "@next/swc-linux-x64-gnu": "15.4.1", + "@next/swc-linux-x64-musl": "15.4.1", + "@next/swc-win32-arm64-msvc": "15.4.1", + "@next/swc-win32-x64-msvc": "15.4.1", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "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==" + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-bootstrap": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", + "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal-image": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-modal-image/-/react-modal-image-2.6.0.tgz", + "integrity": "sha512-NNc1xPKzFAn0VsNMdJ8NXt6c54aL/z0fcoYmw9qn4SBUONdGl+8LOQ0sTfo0wtdzcjLiby/ncloHcAL+UI+wIA==" + }, + "node_modules/react-select": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz", + "integrity": "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.2.0" + }, + "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" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", + "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.3", + "@img/sharp-darwin-x64": "0.34.3", + "@img/sharp-libvips-darwin-arm64": "1.2.0", + "@img/sharp-libvips-darwin-x64": "1.2.0", + "@img/sharp-libvips-linux-arm": "1.2.0", + "@img/sharp-libvips-linux-arm64": "1.2.0", + "@img/sharp-libvips-linux-ppc64": "1.2.0", + "@img/sharp-libvips-linux-s390x": "1.2.0", + "@img/sharp-libvips-linux-x64": "1.2.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", + "@img/sharp-libvips-linuxmusl-x64": "1.2.0", + "@img/sharp-linux-arm": "0.34.3", + "@img/sharp-linux-arm64": "0.34.3", + "@img/sharp-linux-ppc64": "0.34.3", + "@img/sharp-linux-s390x": "0.34.3", + "@img/sharp-linux-x64": "0.34.3", + "@img/sharp-linuxmusl-arm64": "0.34.3", + "@img/sharp-linuxmusl-x64": "0.34.3", + "@img/sharp-wasm32": "0.34.3", + "@img/sharp-win32-arm64": "0.34.3", + "@img/sharp-win32-ia32": "0.34.3", + "@img/sharp-win32-x64": "0.34.3" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "optional": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "hasInstallScript": true, + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uuidv4": { + "version": "6.2.13", + "resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz", + "integrity": "sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dependencies": { + "@types/uuid": "8.3.4", + "uuid": "8.3.2" + } + }, + "node_modules/uuidv4/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/editor-app/package.json b/editor-app/package.json index 2ed4821..91535f6 100644 --- a/editor-app/package.json +++ b/editor-app/package.json @@ -23,6 +23,7 @@ "eslint": "^9.31.0", "eslint-config-next": "15.4.1", "lodash": "^4.17.21", + "lucide-react": "^0.554.0", "next": "15.4.1", "react": "19.1.0", "react-bootstrap": "^2.10.2", diff --git a/editor-app/yarn.lock b/editor-app/yarn.lock index e9b67cb..8270745 100644 --- a/editor-app/yarn.lock +++ b/editor-app/yarn.lock @@ -4,7 +4,7 @@ "@apollo-protocol/hqdm-lib@^0.0.2": version "0.0.2" - resolved "https://registry.yarnpkg.com/@apollo-protocol/hqdm-lib/-/hqdm-lib-0.0.2.tgz#8da40c2027a88ce11ebf9994abbd4935832d3352" + resolved "https://registry.npmjs.org/@apollo-protocol/hqdm-lib/-/hqdm-lib-0.0.2.tgz" integrity sha512-5Jh4FmSIe1Zmpm04Hh30EPNSz+bcEdI5tC7+z6SJg/gOAxwYKsfjqQuvKSHrohf6EEHampYhirYp6tMGlVrc2w== dependencies: "@rdfjs/serializer-jsonld" "^2.0.0" @@ -14,7 +14,7 @@ "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== dependencies: "@babel/helper-validator-identifier" "^7.27.1" @@ -23,7 +23,7 @@ "@babel/generator@^7.28.0": version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz" integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== dependencies: "@babel/parser" "^7.28.0" @@ -34,12 +34,12 @@ "@babel/helper-globals@^7.28.0": version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== "@babel/helper-module-imports@^7.16.7": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz" integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== dependencies: "@babel/traverse" "^7.27.1" @@ -47,29 +47,29 @@ "@babel/helper-string-parser@^7.27.1": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== "@babel/helper-validator-identifier@^7.27.1": version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz" integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== "@babel/parser@^7.27.2", "@babel/parser@^7.28.0": version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz" integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== dependencies: "@babel/types" "^7.28.0" "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": version "7.27.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz" integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== "@babel/template@^7.27.2": version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz" integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== dependencies: "@babel/code-frame" "^7.27.1" @@ -78,7 +78,7 @@ "@babel/traverse@^7.27.1": version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz" integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== dependencies: "@babel/code-frame" "^7.27.1" @@ -91,7 +91,7 @@ "@babel/types@^7.27.1", "@babel/types@^7.28.0": version "7.28.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.1.tgz#2aaf3c10b31ba03a77ac84f52b3912a0edef4cf9" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz" integrity sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ== dependencies: "@babel/helper-string-parser" "^7.27.1" @@ -99,14 +99,14 @@ "@dnd-kit/accessibility@^3.1.1": version "3.1.1" - resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af" + resolved "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz" integrity sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw== dependencies: tslib "^2.0.0" -"@dnd-kit/core@6.3.1": +"@dnd-kit/core@^6.3.0", "@dnd-kit/core@6.3.1": version "6.3.1" - resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.3.1.tgz#4c36406a62c7baac499726f899935f93f0e6d003" + resolved "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz" integrity sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ== dependencies: "@dnd-kit/accessibility" "^3.1.1" @@ -115,44 +115,22 @@ "@dnd-kit/sortable@10.0.0": version "10.0.0" - resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-10.0.0.tgz#1f9382b90d835cd5c65d92824fa9dafb78c4c3e8" + resolved "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz" integrity sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg== dependencies: "@dnd-kit/utilities" "^3.2.2" tslib "^2.0.0" -"@dnd-kit/utilities@3.2.2", "@dnd-kit/utilities@^3.2.2": +"@dnd-kit/utilities@^3.2.2", "@dnd-kit/utilities@3.2.2": version "3.2.2" - resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.2.tgz#5a32b6af356dc5f74d61b37d6f7129a4040ced7b" + resolved "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz" integrity sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== dependencies: tslib "^2.0.0" -"@emnapi/core@^1.4.3": - version "1.4.4" - resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.4.4.tgz#76620673f3033626c6d79b1420d69f06a6bb153c" - integrity sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g== - dependencies: - "@emnapi/wasi-threads" "1.0.3" - tslib "^2.4.0" - -"@emnapi/runtime@^1.4.3", "@emnapi/runtime@^1.4.4": - version "1.4.4" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.4.4.tgz#19a8f00719c51124e2d0fbf4aaad3fa7b0c92524" - integrity sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg== - dependencies: - tslib "^2.4.0" - -"@emnapi/wasi-threads@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.0.3.tgz#83fa228bde0e71668aad6db1af4937473d1d3ab1" - integrity sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw== - dependencies: - tslib "^2.4.0" - "@emotion/babel-plugin@^11.13.5": version "11.13.5" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0" + resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz" integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== dependencies: "@babel/helper-module-imports" "^7.16.7" @@ -169,7 +147,7 @@ "@emotion/cache@^11.14.0", "@emotion/cache@^11.4.0": version "11.14.0" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.14.0.tgz#ee44b26986eeb93c8be82bb92f1f7a9b21b2ed76" + resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz" integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== dependencies: "@emotion/memoize" "^0.9.0" @@ -180,17 +158,17 @@ "@emotion/hash@^0.9.2": version "0.9.2" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz" integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== "@emotion/memoize@^0.9.0": version "0.9.0" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== "@emotion/react@^11.8.1": version "11.14.0" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.14.0.tgz#cfaae35ebc67dd9ef4ea2e9acc6cd29e157dd05d" + resolved "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz" integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== dependencies: "@babel/runtime" "^7.18.3" @@ -204,7 +182,7 @@ "@emotion/serialize@^1.3.3": version "1.3.3" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.3.tgz#d291531005f17d704d0463a032fe679f376509e8" + resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz" integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== dependencies: "@emotion/hash" "^0.9.2" @@ -215,44 +193,44 @@ "@emotion/sheet@^1.4.0": version "1.4.0" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz" integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== "@emotion/unitless@^0.10.0": version "0.10.0" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" + resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz" integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== "@emotion/use-insertion-effect-with-fallbacks@^1.2.0": version "1.2.0" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf" + 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== "@emotion/utils@^1.4.2": version "1.4.2" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.2.tgz#6df6c45881fcb1c412d6688a311a98b7f59c1b52" + resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz" integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== "@emotion/weak-memoize@^0.4.0": version "0.4.0" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0": version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz" integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== dependencies: eslint-visitor-keys "^3.4.3" "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== "@eslint/config-array@^0.21.0": version "0.21.0" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" + resolved "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz" integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== dependencies: "@eslint/object-schema" "^2.1.6" @@ -261,19 +239,19 @@ "@eslint/config-helpers@^0.3.0": version "0.3.0" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.0.tgz#3e09a90dfb87e0005c7694791e58e97077271286" + resolved "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz" integrity sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw== "@eslint/core@^0.15.0", "@eslint/core@^0.15.1": version "0.15.1" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.1.tgz#d530d44209cbfe2f82ef86d6ba08760196dd3b60" + resolved "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz" integrity sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA== dependencies: "@types/json-schema" "^7.0.15" "@eslint/eslintrc@^3.3.1": version "3.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz" integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== dependencies: ajv "^6.12.4" @@ -288,17 +266,17 @@ "@eslint/js@9.31.0": version "9.31.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.31.0.tgz#adb1f39953d8c475c4384b67b67541b0d7206ed8" + resolved "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz" integrity sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw== "@eslint/object-schema@^2.1.6": version "2.1.6" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + resolved "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz" integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== "@eslint/plugin-kit@^0.3.1": version "0.3.3" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz#32926b59bd407d58d817941e48b2a7049359b1fd" + resolved "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz" integrity sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag== dependencies: "@eslint/core" "^0.15.1" @@ -306,14 +284,14 @@ "@floating-ui/core@^1.7.2": version "1.7.2" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.2.tgz#3d1c35263950b314b6d5a72c8bfb9e3c1551aefd" + resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz" integrity sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw== dependencies: "@floating-ui/utils" "^0.2.10" "@floating-ui/dom@^1.0.1": version "1.7.2" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.2.tgz#3540b051cf5ce0d4f4db5fb2507a76e8ea5b4a45" + resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz" integrity sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA== dependencies: "@floating-ui/core" "^1.7.2" @@ -321,17 +299,17 @@ "@floating-ui/utils@^0.2.10": version "0.2.10" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c" + resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz" integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== "@humanfs/core@^0.19.1": version "0.19.1" - resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz" integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== "@humanfs/node@^0.16.6": version "0.16.6" - resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + resolved "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz" integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== dependencies: "@humanfs/core" "^0.19.1" @@ -339,152 +317,27 @@ "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/retry@^0.3.0": version "0.3.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz" integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== "@humanwhocodes/retry@^0.4.2": version "0.4.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz" integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== -"@img/sharp-darwin-arm64@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz#4850c8ace3c1dc13607fa07d43377b1f9aa774da" - integrity sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg== - optionalDependencies: - "@img/sharp-libvips-darwin-arm64" "1.2.0" - -"@img/sharp-darwin-x64@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz#edf93fb01479604f14ad6a64a716e2ef2bb23100" - integrity sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA== - optionalDependencies: - "@img/sharp-libvips-darwin-x64" "1.2.0" - -"@img/sharp-libvips-darwin-arm64@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz#e20e9041031acde1de19da121dc5162c7d2cf251" - integrity sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ== - -"@img/sharp-libvips-darwin-x64@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz#918ca81c5446f31114834cb908425a7532393185" - integrity sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg== - -"@img/sharp-libvips-linux-arm64@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz#1a5beafc857b43f378c3030427aa981ee3edbc54" - integrity sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA== - -"@img/sharp-libvips-linux-arm@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz#bff51182d5238ca35c5fe9e9f594a18ad6a5480d" - integrity sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw== - -"@img/sharp-libvips-linux-ppc64@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz#10c53ccf6f2d47d71fb3fa282697072c8fe9e40e" - integrity sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ== - -"@img/sharp-libvips-linux-s390x@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz#392fd7557ddc5c901f1bed7ab3c567c08833ef3b" - integrity sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw== - -"@img/sharp-libvips-linux-x64@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz#9315cf90a2fdcdc0e29ea7663cbd8b0f15254400" - integrity sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg== - -"@img/sharp-libvips-linuxmusl-arm64@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz#705e03e67d477f6f842f37eb7f66285b1150dc06" - integrity sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q== - -"@img/sharp-libvips-linuxmusl-x64@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz#ec905071cc538df64848d5900e0d386d77c55f13" - integrity sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q== - -"@img/sharp-linux-arm64@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz#476f8f13ce192555391ae9d4bc658637a6acf3e5" - integrity sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA== - optionalDependencies: - "@img/sharp-libvips-linux-arm64" "1.2.0" - -"@img/sharp-linux-arm@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz#9898cd68ea3e3806b94fe25736d5d7ecb5eac121" - integrity sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A== - optionalDependencies: - "@img/sharp-libvips-linux-arm" "1.2.0" - -"@img/sharp-linux-ppc64@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz#6a7cd4c608011333a0ddde6d96e03ac042dd9079" - integrity sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA== - optionalDependencies: - "@img/sharp-libvips-linux-ppc64" "1.2.0" - -"@img/sharp-linux-s390x@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz#48e27ab969efe97d270e39297654c0e0c9b42919" - integrity sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ== - optionalDependencies: - "@img/sharp-libvips-linux-s390x" "1.2.0" - -"@img/sharp-linux-x64@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz#5aa77ad4aa447ddf6d642e2a2c5599eb1292dfaa" - integrity sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ== - optionalDependencies: - "@img/sharp-libvips-linux-x64" "1.2.0" - -"@img/sharp-linuxmusl-arm64@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz#62053a9d77c7d4632c677619325b741254689dd7" - integrity sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64" "1.2.0" - -"@img/sharp-linuxmusl-x64@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz#5107c7709c7e0a44fe5abef59829f1de86fa0a3a" - integrity sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64" "1.2.0" - -"@img/sharp-wasm32@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz#c1dcabb834ec2f71308a810b399bb6e6e3b79619" - integrity sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg== - dependencies: - "@emnapi/runtime" "^1.4.4" - -"@img/sharp-win32-arm64@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz#3e8654e368bb349d45799a0d7aeb29db2298628e" - integrity sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ== - -"@img/sharp-win32-ia32@0.34.3": - version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz#9d4c105e8d5074a351a81a0b6d056e0af913bf76" - integrity sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw== - "@img/sharp-win32-x64@0.34.3": version "0.34.3" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz#d20c89bd41b1dd3d76d8575714aaaa3c43204b6a" + resolved "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz" integrity sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g== "@jridgewell/gen-mapping@^0.3.12": version "0.3.12" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz" integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" @@ -492,99 +345,55 @@ "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.4" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz#7358043433b2e5da569aa02cbc4c121da3af27d7" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz" integrity sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": version "0.3.29" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz" integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@napi-rs/wasm-runtime@^0.2.11": - version "0.2.12" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" - integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== - dependencies: - "@emnapi/core" "^1.4.3" - "@emnapi/runtime" "^1.4.3" - "@tybys/wasm-util" "^0.10.0" - "@next/env@15.4.1": version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/env/-/env-15.4.1.tgz#17d2067266636cfa65c0803738e6208f729070ee" + resolved "https://registry.npmjs.org/@next/env/-/env-15.4.1.tgz" integrity sha512-DXQwFGAE2VH+f2TJsKepRXpODPU+scf5fDbKOME8MMyeyswe4XwgRdiiIYmBfkXU+2ssliLYznajTrOQdnLR5A== "@next/eslint-plugin-next@15.4.1": version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.4.1.tgz#1073fd7cc2d9e154249dc2bb40ce886a8606f73d" + resolved "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.4.1.tgz" integrity sha512-lQnHUxN7mMksK7IxgKDIXNMWFOBmksVrjamMEURXiYfo7zgsc30lnU8u4y/MJktSh+nB80ktTQeQbWdQO6c8Ow== dependencies: fast-glob "3.3.1" -"@next/swc-darwin-arm64@15.4.1": - version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.1.tgz#490444a0905fcd63b15ec9da6f32683d3b81914a" - integrity sha512-L+81yMsiHq82VRXS2RVq6OgDwjvA4kDksGU8hfiDHEXP+ncKIUhUsadAVB+MRIp2FErs/5hpXR0u2eluWPAhig== - -"@next/swc-darwin-x64@15.4.1": - version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.1.tgz#8eac522eca30f20dedc93c8dccfb7efd91226d16" - integrity sha512-jfz1RXu6SzL14lFl05/MNkcN35lTLMJWPbqt7Xaj35+ZWAX342aePIJrN6xBdGeKl6jPXJm0Yqo3Xvh3Gpo3Uw== - -"@next/swc-linux-arm64-gnu@15.4.1": - version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.1.tgz#7e62ddf0e09d4d5198562242e0def3a43b9ee333" - integrity sha512-k0tOFn3dsnkaGfs6iQz8Ms6f1CyQe4GacXF979sL8PNQxjYS1swx9VsOyUQYaPoGV8nAZ7OX8cYaeiXGq9ahPQ== - -"@next/swc-linux-arm64-musl@15.4.1": - version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.1.tgz#6ea7cf91e898e3bae079e4ccc56fc8ef69130489" - integrity sha512-4ogGQ/3qDzbbK3IwV88ltihHFbQVq6Qr+uEapzXHXBH1KsVBZOB50sn6BWHPcFjwSoMX2Tj9eH/fZvQnSIgc3g== - -"@next/swc-linux-x64-gnu@15.4.1": - version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.1.tgz#a879a19ccbb5a12f218a543adf0d9312f5ce70f9" - integrity sha512-Jj0Rfw3wIgp+eahMz/tOGwlcYYEFjlBPKU7NqoOkTX0LY45i5W0WcDpgiDWSLrN8KFQq/LW7fZq46gxGCiOYlQ== - -"@next/swc-linux-x64-musl@15.4.1": - version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.1.tgz#025a2798d0beb242d878afedfc4968f0f1a02cf1" - integrity sha512-9WlEZfnw1vFqkWsTMzZDgNL7AUI1aiBHi0S2m8jvycPyCq/fbZjtE/nDkhJRYbSjXbtRHYLDBlmP95kpjEmJbw== - -"@next/swc-win32-arm64-msvc@15.4.1": - version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.1.tgz#27928627e2c2d04075846c1e5a47d23760ce444e" - integrity sha512-WodRbZ9g6CQLRZsG3gtrA9w7Qfa9BwDzhFVdlI6sV0OCPq9JrOrJSp9/ioLsezbV8w9RCJ8v55uzJuJ5RgWLZg== - "@next/swc-win32-x64-msvc@15.4.1": version "15.4.1" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.1.tgz#7eb5ac6ffd8c945863f14a5d32ded6859d4ebe17" + resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.1.tgz" integrity sha512-y+wTBxelk2xiNofmDOVU7O5WxTHcvOoL3srOM0kxTzKDjQ57kPU0tpnPJ/BWrRnsOwXEv0+3QSbGR7hY4n9LkQ== "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -592,17 +401,17 @@ "@nolyfill/is-core-module@1.0.39": version "1.0.39" - resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e" + resolved "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz" integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== "@popperjs/core@^2.11.8": version "2.11.8" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== "@rdfjs/serializer-jsonld@^2.0.0": version "2.0.1" - resolved "https://registry.yarnpkg.com/@rdfjs/serializer-jsonld/-/serializer-jsonld-2.0.1.tgz#7be408b93b0bd430c8be4a23cffb4dc28ea76232" + resolved "https://registry.npmjs.org/@rdfjs/serializer-jsonld/-/serializer-jsonld-2.0.1.tgz" integrity sha512-O8WzdY7THsse/nMsrMLd2e51ADHO2SIUrkiZ9Va/8W3lXeeeiwDRPMppWy/i9yL4q6EM8iMW1riV7E0mK3fsBQ== dependencies: "@rdfjs/sink" "^2.0.1" @@ -610,33 +419,33 @@ "@rdfjs/sink@^2.0.1": version "2.0.1" - resolved "https://registry.yarnpkg.com/@rdfjs/sink/-/sink-2.0.1.tgz#3e7cbe9b7a7f3731d51a1af1cf9468b1a716b1ee" + resolved "https://registry.npmjs.org/@rdfjs/sink/-/sink-2.0.1.tgz" integrity sha512-smzIFGF6EH1sLAJR9F3p2wMNrN44JjPeYAoITTJLqtuNC319K7IXaJ+qNLBGTtapZ/jvpx2Tks0TjcH9KrAvEA== "@react-aria/ssr@^3.5.0": version "3.9.9" - resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.9.tgz#a35c6840962e72357560d4dcb4a6300f94272354" + resolved "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.9.tgz" integrity sha512-2P5thfjfPy/np18e5wD4WPt8ydNXhij1jwA8oehxZTFqlgVMGXzcWKxTb4RtJrLFsqPO7RUQTiY8QJk0M4Vy2g== dependencies: "@swc/helpers" "^0.5.0" "@restart/hooks@^0.4.9": version "0.4.16" - resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb" + resolved "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz" integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== dependencies: dequal "^2.0.3" "@restart/hooks@^0.5.0": version "0.5.1" - resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.5.1.tgz#6776b3859e33aea72b23b81fc47021edf17fd247" + resolved "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz" integrity sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q== dependencies: dequal "^2.0.3" "@restart/ui@^1.9.4": version "1.9.4" - resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.9.4.tgz#9d61f56f2647f5ab8a33d87b278b9ce183511a26" + resolved "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz" integrity sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA== dependencies: "@babel/runtime" "^7.26.0" @@ -651,67 +460,53 @@ "@rtsao/scc@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" + resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== "@rushstack/eslint-patch@^1.10.3": version "1.12.0" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz#326a7b46f6d4cfa54ae25bb888551697873069b4" + resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz" integrity sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw== -"@swc/helpers@0.5.15": +"@swc/helpers@^0.5.0", "@swc/helpers@0.5.15": version "0.5.15" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz" integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== dependencies: tslib "^2.8.0" -"@swc/helpers@^0.5.0": - version "0.5.17" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" - integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== - dependencies: - tslib "^2.8.0" - -"@tybys/wasm-util@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.0.tgz#2fd3cd754b94b378734ce17058d0507c45c88369" - integrity sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ== - dependencies: - tslib "^2.4.0" - "@types/d3-array@*": version "3.2.1" - resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz" integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== "@types/d3-axis@*": version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795" + resolved "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz" integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== dependencies: "@types/d3-selection" "*" "@types/d3-brush@*": version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c" + resolved "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz" integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== dependencies: "@types/d3-selection" "*" "@types/d3-chord@*": version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d" + resolved "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz" integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== "@types/d3-color@*": version "3.1.3" - resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz" integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== "@types/d3-contour@*": version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231" + resolved "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz" integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== dependencies: "@types/d3-array" "*" @@ -719,136 +514,136 @@ "@types/d3-delaunay@*": version "6.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1" + resolved "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz" integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== "@types/d3-dispatch@*": version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz#096efdf55eb97480e3f5621ff9a8da552f0961e7" + resolved "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz" integrity sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ== "@types/d3-drag@*": version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02" + resolved "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz" integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== dependencies: "@types/d3-selection" "*" "@types/d3-dsv@*": version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17" + resolved "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz" integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== "@types/d3-ease@*": version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz" integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== "@types/d3-fetch@*": version "3.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980" + resolved "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz" integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== dependencies: "@types/d3-dsv" "*" "@types/d3-force@*": version "3.0.10" - resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.10.tgz#6dc8fc6e1f35704f3b057090beeeb7ac674bff1a" + resolved "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz" integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw== "@types/d3-format@*": version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90" + resolved "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz" integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== "@types/d3-geo@*": version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440" + resolved "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz" integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== dependencies: "@types/geojson" "*" "@types/d3-hierarchy@*": version "3.1.7" - resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz#6023fb3b2d463229f2d680f9ac4b47466f71f17b" + resolved "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz" integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg== "@types/d3-interpolate@*": version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz" integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== dependencies: "@types/d3-color" "*" "@types/d3-path@*": version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a" + resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz" integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== "@types/d3-polygon@*": version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c" + resolved "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz" integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== "@types/d3-quadtree@*": version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f" + resolved "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz" integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== "@types/d3-random@*": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb" + resolved "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz" integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== "@types/d3-scale-chromatic@*": version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39" + resolved "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz" integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ== "@types/d3-scale@*": version "4.0.9" - resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb" + resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz" integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== dependencies: "@types/d3-time" "*" "@types/d3-selection@*": version "3.0.11" - resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.11.tgz#bd7a45fc0a8c3167a631675e61bc2ca2b058d4a3" + resolved "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz" integrity sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w== "@types/d3-shape@*": version "3.1.7" - resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.7.tgz#2b7b423dc2dfe69c8c93596e673e37443348c555" + resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz" integrity sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg== dependencies: "@types/d3-path" "*" "@types/d3-time-format@*": version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2" + resolved "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz" integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== "@types/d3-time@*": version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f" + resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz" integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== "@types/d3-timer@*": version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz" integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== "@types/d3-transition@*": version "3.0.9" - resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.9.tgz#1136bc57e9ddb3c390dccc9b5ff3b7d2b8d94706" + resolved "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz" integrity sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg== dependencies: "@types/d3-selection" "*" "@types/d3-zoom@*": version "3.0.8" - resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b" + resolved "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz" integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== dependencies: "@types/d3-interpolate" "*" @@ -856,7 +651,7 @@ "@types/d3@^7.4.3": version "7.4.3" - resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2" + resolved "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz" integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== dependencies: "@types/d3-array" "*" @@ -892,71 +687,71 @@ "@types/estree@^1.0.6": version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== "@types/geojson@*": version "7946.0.16" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" + resolved "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz" integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== "@types/json-schema@^7.0.15": version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/json5@^0.0.29": version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/node@24.0.14": version "24.0.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.14.tgz#6e3d4fb6d858c48c69707394e1a0e08ce1ecc1bc" + resolved "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz" integrity sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw== dependencies: undici-types "~7.8.0" "@types/parse-json@^4.0.0": version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz" integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== "@types/prop-types@^15.7.12": version "15.7.15" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" + resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz" integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== "@types/react-dom@19.1.6": version "19.1.6" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.6.tgz#4af629da0e9f9c0f506fc4d1caa610399c595d64" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz" integrity sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== "@types/react-transition-group@^4.4.0", "@types/react-transition-group@^4.4.6": version "4.4.12" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" + resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz" integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== -"@types/react@19.1.8", "@types/react@>=16.9.11": +"@types/react@*", "@types/react@^19.0.0", "@types/react@>=16.14.8", "@types/react@>=16.9.11", "@types/react@19.1.8": version "19.1.8" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.8.tgz#ff8395f2afb764597265ced15f8dddb0720ae1c3" + resolved "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz" integrity sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g== dependencies: csstype "^3.0.2" "@types/uuid@8.3.4": version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/warning@^3.0.3": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798" + resolved "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz" integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q== "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz#332392883f936137cd6252c8eb236d298e514e70" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz" integrity sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA== dependencies: "@eslint-community/regexpp" "^4.10.0" @@ -969,9 +764,9 @@ natural-compare "^1.4.0" ts-api-utils "^2.1.0" -"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": +"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser@^8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.37.0.tgz#b87f6b61e25ad5cc5bbf8baf809b8da889c89804" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz" integrity sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA== dependencies: "@typescript-eslint/scope-manager" "8.37.0" @@ -982,7 +777,7 @@ "@typescript-eslint/project-service@8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.37.0.tgz#0594352e32a4ac9258591b88af77b5653800cdfe" + resolved "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz" integrity sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA== dependencies: "@typescript-eslint/tsconfig-utils" "^8.37.0" @@ -991,20 +786,20 @@ "@typescript-eslint/scope-manager@8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz#a31a3c80ca2ef4ed58de13742debb692e7d4c0a4" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz" integrity sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA== dependencies: "@typescript-eslint/types" "8.37.0" "@typescript-eslint/visitor-keys" "8.37.0" -"@typescript-eslint/tsconfig-utils@8.37.0", "@typescript-eslint/tsconfig-utils@^8.37.0": +"@typescript-eslint/tsconfig-utils@^8.37.0", "@typescript-eslint/tsconfig-utils@8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz#47a2760d265c6125f8e7864bc5c8537cad2bd053" + resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz" integrity sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg== "@typescript-eslint/type-utils@8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz#2a682e4c6ff5886712dad57e9787b5e417124507" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz" integrity sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow== dependencies: "@typescript-eslint/types" "8.37.0" @@ -1013,14 +808,14 @@ debug "^4.3.4" ts-api-utils "^2.1.0" -"@typescript-eslint/types@8.37.0", "@typescript-eslint/types@^8.37.0": +"@typescript-eslint/types@^8.37.0", "@typescript-eslint/types@8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.37.0.tgz#09517aa9625eb3c68941dde3ac8835740587b6ff" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz" integrity sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ== "@typescript-eslint/typescript-estree@8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz#a07e4574d8e6e4355a558f61323730c987f5fcbc" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz" integrity sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg== dependencies: "@typescript-eslint/project-service" "8.37.0" @@ -1036,7 +831,7 @@ "@typescript-eslint/utils@8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.37.0.tgz#189ea59b2709f5d898614611f091a776751ee335" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz" integrity sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A== dependencies: "@eslint-community/eslint-utils" "^4.7.0" @@ -1046,129 +841,37 @@ "@typescript-eslint/visitor-keys@8.37.0": version "8.37.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz#cdb6a6bd3e8d6dd69bd70c1bdda36e2d18737455" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz" integrity sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w== dependencies: "@typescript-eslint/types" "8.37.0" eslint-visitor-keys "^4.2.1" -"@unrs/resolver-binding-android-arm-eabi@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" - integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== - -"@unrs/resolver-binding-android-arm64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" - integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== - -"@unrs/resolver-binding-darwin-arm64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" - integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== - -"@unrs/resolver-binding-darwin-x64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" - integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== - -"@unrs/resolver-binding-freebsd-x64@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" - integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== - -"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" - integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== - -"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" - integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== - -"@unrs/resolver-binding-linux-arm64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" - integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== - -"@unrs/resolver-binding-linux-arm64-musl@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" - integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== - -"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" - integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== - -"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" - integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== - -"@unrs/resolver-binding-linux-riscv64-musl@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" - integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== - -"@unrs/resolver-binding-linux-s390x-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" - integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== - -"@unrs/resolver-binding-linux-x64-gnu@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" - integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== - -"@unrs/resolver-binding-linux-x64-musl@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" - integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== - -"@unrs/resolver-binding-wasm32-wasi@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" - integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== - dependencies: - "@napi-rs/wasm-runtime" "^0.2.11" - -"@unrs/resolver-binding-win32-arm64-msvc@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" - integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== - -"@unrs/resolver-binding-win32-ia32-msvc@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" - integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== - "@unrs/resolver-binding-win32-x64-msvc@1.11.1": version "1.11.1" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" + resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz" integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== abort-controller@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== dependencies: event-target-shim "^5.0.0" acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.15.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.15.0: version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== ajv@^6.12.4: version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -1178,24 +881,24 @@ ajv@^6.12.4: ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== aria-query@^5.3.2: version "5.3.2" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz" integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz" integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== dependencies: call-bound "^1.0.3" @@ -1203,7 +906,7 @@ array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: array-includes@^3.1.6, array-includes@^3.1.8, array-includes@^3.1.9: version "3.1.9" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz" integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== dependencies: call-bind "^1.0.8" @@ -1217,7 +920,7 @@ array-includes@^3.1.6, array-includes@^3.1.8, array-includes@^3.1.9: array.prototype.findlast@^1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + resolved "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz" integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== dependencies: call-bind "^1.0.7" @@ -1229,7 +932,7 @@ array.prototype.findlast@^1.2.5: array.prototype.findlastindex@^1.2.6: version "1.2.6" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564" + resolved "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz" integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== dependencies: call-bind "^1.0.8" @@ -1242,7 +945,7 @@ array.prototype.findlastindex@^1.2.6: array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.3: version "1.3.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz" integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== dependencies: call-bind "^1.0.8" @@ -1252,7 +955,7 @@ array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.3: array.prototype.flatmap@^1.3.2, array.prototype.flatmap@^1.3.3: version "1.3.3" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" + resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz" integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== dependencies: call-bind "^1.0.8" @@ -1262,7 +965,7 @@ array.prototype.flatmap@^1.3.2, array.prototype.flatmap@^1.3.3: array.prototype.tosorted@^1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + resolved "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz" integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== dependencies: call-bind "^1.0.7" @@ -1273,7 +976,7 @@ array.prototype.tosorted@^1.1.4: arraybuffer.prototype.slice@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + resolved "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz" integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== dependencies: array-buffer-byte-length "^1.0.1" @@ -1286,34 +989,34 @@ arraybuffer.prototype.slice@^1.0.4: ast-types-flow@^0.0.8: version "0.0.8" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz" integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== async-function@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz" integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== available-typed-arrays@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz" integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== dependencies: possible-typed-array-names "^1.0.0" axe-core@^4.10.0: version "4.10.3" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.3.tgz#04145965ac7894faddbac30861e5d8f11bfd14fc" + resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz" integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== axobject-query@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" + resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz" integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== babel-plugin-macros@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + resolved "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz" integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== dependencies: "@babel/runtime" "^7.12.5" @@ -1322,22 +1025,22 @@ babel-plugin-macros@^3.1.0: balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== bootstrap@^5.3.3: version "5.3.7" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.7.tgz#8640065036124d961d885d80b5945745e1154d90" + resolved "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz" integrity sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw== brace-expansion@^1.1.7: version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: balanced-match "^1.0.0" @@ -1345,21 +1048,21 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz" integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== dependencies: balanced-match "^1.0.0" braces@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" buffer@^6.0.3: version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" @@ -1367,7 +1070,7 @@ buffer@^6.0.3: call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: es-errors "^1.3.0" @@ -1375,7 +1078,7 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply- call-bind@^1.0.7, call-bind@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz" integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== dependencies: call-bind-apply-helpers "^1.0.0" @@ -1385,7 +1088,7 @@ call-bind@^1.0.7, call-bind@^1.0.8: call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== dependencies: call-bind-apply-helpers "^1.0.2" @@ -1393,17 +1096,17 @@ call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== caniuse-lite@^1.0.30001579: version "1.0.30001727" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz" integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== chalk@^4.0.0: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -1411,29 +1114,29 @@ chalk@^4.0.0: classnames@^2.3.2: version "2.5.1" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== client-only@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" + resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.9.0: version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== dependencies: color-name "^1.0.0" @@ -1441,7 +1144,7 @@ color-string@^1.9.0: color@^4.2.3: version "4.2.3" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + resolved "https://registry.npmjs.org/color/-/color-4.2.3.tgz" integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== dependencies: color-convert "^2.0.1" @@ -1449,22 +1152,22 @@ color@^4.2.3: commander@7: version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== convert-source-map@^1.5.0: version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== cosmiconfig@^7.0.0: version "7.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz" integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== dependencies: "@types/parse-json" "^4.0.0" @@ -1475,7 +1178,7 @@ cosmiconfig@^7.0.0: cross-spawn@^7.0.6: version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" @@ -1484,24 +1187,24 @@ cross-spawn@^7.0.6: csstype@^3.0.2: version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: +d3-array@^3.2.0, "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: version "3.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== dependencies: internmap "1 - 2" d3-axis@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" + resolved "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz" integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== d3-brush@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + resolved "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz" integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== dependencies: d3-dispatch "1 - 3" @@ -1512,38 +1215,38 @@ d3-brush@3: d3-chord@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + resolved "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz" integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== dependencies: d3-path "1 - 3" "d3-color@1 - 3", d3-color@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== d3-contour@4: version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" + resolved "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz" integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== dependencies: d3-array "^3.2.0" d3-delaunay@6: version "6.0.4" - resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + resolved "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz" integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== dependencies: delaunator "5" "d3-dispatch@1 - 3", d3-dispatch@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz" integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== "d3-drag@2 - 3", d3-drag@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz" integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== dependencies: d3-dispatch "1 - 3" @@ -1551,7 +1254,7 @@ d3-delaunay@6: "d3-dsv@1 - 3", d3-dsv@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + resolved "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz" integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== dependencies: commander "7" @@ -1560,19 +1263,19 @@ d3-delaunay@6: "d3-ease@1 - 3", d3-ease@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== d3-fetch@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + resolved "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz" integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== dependencies: d3-dsv "1 - 3" d3-force@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + resolved "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz" integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== dependencies: d3-dispatch "1 - 3" @@ -1581,51 +1284,51 @@ d3-force@3: "d3-format@1 - 3", d3-format@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== d3-geo@3: version "3.1.1" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" + resolved "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz" integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== dependencies: d3-array "2.5.0 - 3" d3-hierarchy@3: version "3.1.2" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + resolved "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz" integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== "d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== dependencies: d3-color "1 - 3" -"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: +d3-path@^3.1.0, "d3-path@1 - 3", d3-path@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== d3-polygon@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + resolved "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz" integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== "d3-quadtree@1 - 3", d3-quadtree@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + resolved "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz" integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== d3-random@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + resolved "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz" integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== d3-scale-chromatic@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + resolved "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz" integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== dependencies: d3-color "1 - 3" @@ -1633,7 +1336,7 @@ d3-scale-chromatic@3: d3-scale@4: version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== dependencies: d3-array "2.10.0 - 3" @@ -1644,38 +1347,38 @@ d3-scale@4: "d3-selection@2 - 3", d3-selection@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz" integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== d3-shape@3: version "3.2.0" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== dependencies: d3-path "^3.1.0" "d3-time-format@2 - 4", d3-time-format@4: version "4.1.0" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== dependencies: d3-time "1 - 3" "d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== dependencies: d3-array "2 - 3" "d3-timer@1 - 3", d3-timer@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== "d3-transition@2 - 3", d3-transition@3: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz" integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== dependencies: d3-color "1 - 3" @@ -1686,7 +1389,7 @@ d3-shape@3: d3-zoom@3: version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz" integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== dependencies: d3-dispatch "1 - 3" @@ -1697,7 +1400,7 @@ d3-zoom@3: d3@^7.9.0: version "7.9.0" - resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + resolved "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz" integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== dependencies: d3-array "3" @@ -1733,12 +1436,12 @@ d3@^7.9.0: damerau-levenshtein@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== data-view-buffer@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + resolved "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz" integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== dependencies: call-bound "^1.0.3" @@ -1747,7 +1450,7 @@ data-view-buffer@^1.0.2: data-view-byte-length@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + resolved "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz" integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== dependencies: call-bound "^1.0.3" @@ -1756,7 +1459,7 @@ data-view-byte-length@^1.0.2: data-view-byte-offset@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + resolved "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz" integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== dependencies: call-bound "^1.0.2" @@ -1765,26 +1468,26 @@ data-view-byte-offset@^1.0.1: debug@^3.2.7: version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0: version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz" integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== dependencies: ms "^2.1.3" deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== dependencies: es-define-property "^1.0.0" @@ -1793,7 +1496,7 @@ define-data-property@^1.0.1, define-data-property@^1.1.4: define-properties@^1.1.3, define-properties@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: define-data-property "^1.0.1" @@ -1802,31 +1505,31 @@ define-properties@^1.1.3, define-properties@^1.2.1: delaunator@5: version "5.0.1" - resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" + resolved "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz" integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== dependencies: robust-predicates "^3.0.2" dequal@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== detect-libc@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz" integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== doctrine@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== dependencies: esutils "^2.0.2" dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== dependencies: "@babel/runtime" "^7.8.7" @@ -1834,7 +1537,7 @@ dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: call-bind-apply-helpers "^1.0.1" @@ -1843,19 +1546,19 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1: emoji-regex@^9.2.2: version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0: version "1.24.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz" integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== dependencies: array-buffer-byte-length "^1.0.2" @@ -1915,17 +1618,17 @@ es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23 es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-iterator-helpers@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75" + resolved "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz" integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== dependencies: call-bind "^1.0.8" @@ -1947,14 +1650,14 @@ es-iterator-helpers@^1.2.1: es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== dependencies: es-errors "^1.3.0" es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== dependencies: es-errors "^1.3.0" @@ -1964,14 +1667,14 @@ es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: es-shim-unscopables@^1.0.2, es-shim-unscopables@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" + resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz" integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== dependencies: hasown "^2.0.2" es-to-primitive@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz" integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== dependencies: is-callable "^1.2.7" @@ -1980,12 +1683,12 @@ es-to-primitive@^1.3.0: escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-next@15.4.1: version "15.4.1" - resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.4.1.tgz#bd9457fab7c7084d034a643f4e1770d45dcca97d" + resolved "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.4.1.tgz" integrity sha512-XIIN+lq8XuSwXUrcv+0uHMDFGJFPxLAw04/a4muFZYygSvStvVa15nY7kh4Il6yOVJyxdMUyVdQ9ApGedaeupw== dependencies: "@next/eslint-plugin-next" "15.4.1" @@ -2001,7 +1704,7 @@ eslint-config-next@15.4.1: eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9: version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz" integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== dependencies: debug "^3.2.7" @@ -2010,7 +1713,7 @@ eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9: eslint-import-resolver-typescript@^3.5.2: version "3.10.1" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz#23dac32efa86a88e2b8232eb244ac499ad636db2" + resolved "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz" integrity sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ== dependencies: "@nolyfill/is-core-module" "1.0.39" @@ -2023,14 +1726,14 @@ eslint-import-resolver-typescript@^3.5.2: eslint-module-utils@^2.12.1: version "2.12.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz#f76d3220bfb83c057651359295ab5854eaad75ff" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz" integrity sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw== dependencies: debug "^3.2.7" -eslint-plugin-import@^2.31.0: +eslint-plugin-import@*, eslint-plugin-import@^2.31.0: version "2.32.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz#602b55faa6e4caeaa5e970c198b5c00a37708980" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz" integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA== dependencies: "@rtsao/scc" "^1.1.0" @@ -2055,7 +1758,7 @@ eslint-plugin-import@^2.31.0: eslint-plugin-jsx-a11y@^6.10.0: version "6.10.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz#d2812bb23bf1ab4665f1718ea442e8372e638483" + resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz" integrity sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q== dependencies: aria-query "^5.3.2" @@ -2076,12 +1779,12 @@ eslint-plugin-jsx-a11y@^6.10.0: eslint-plugin-react-hooks@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" + resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz" integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== eslint-plugin-react@^7.37.0: version "7.37.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" + resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz" integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== dependencies: array-includes "^3.1.8" @@ -2105,7 +1808,7 @@ eslint-plugin-react@^7.37.0: eslint-scope@^8.4.0: version "8.4.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz" integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" @@ -2113,17 +1816,17 @@ eslint-scope@^8.4.0: eslint-visitor-keys@^3.4.3: version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint-visitor-keys@^4.2.1: version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz" integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== -eslint@^9.31.0: +eslint@*, "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.23.0 || ^8.0.0 || ^9.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.31.0: version "9.31.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.31.0.tgz#9a488e6da75bbe05785cd62e43c5ea99356d21ba" + resolved "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz" integrity sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" @@ -2164,7 +1867,7 @@ eslint@^9.31.0: espree@^10.0.1, espree@^10.4.0: version "10.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + resolved "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz" integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== dependencies: acorn "^8.15.0" @@ -2173,109 +1876,109 @@ espree@^10.0.1, espree@^10.4.0: esquery@^1.5.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz" integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== event-target-shim@^5.0.0: version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== events@^3.3.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== +fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.4" + micromatch "^4.0.8" -fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== +fast-glob@3.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.8" + micromatch "^4.0.4" fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz" integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== dependencies: reusify "^1.0.4" fdir@^6.4.4: version "6.4.6" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.6.tgz#2b268c0232697063111bbf3f64810a2a741ba281" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz" integrity sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== file-entry-cache@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz" integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: flat-cache "^4.0.0" fill-range@^7.1.1: version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" find-root@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -2283,7 +1986,7 @@ find-up@^5.0.0: flat-cache@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz" integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: flatted "^3.2.9" @@ -2291,24 +1994,24 @@ flat-cache@^4.0.0: flatted@^3.2.9: version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz" integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== dependencies: is-callable "^1.2.7" function-bind@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: version "1.1.8" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz" integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== dependencies: call-bind "^1.0.8" @@ -2320,12 +2023,12 @@ function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: functions-have-names@^1.2.3: version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: call-bind-apply-helpers "^1.0.2" @@ -2341,7 +2044,7 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@ get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: dunder-proto "^1.0.1" @@ -2349,7 +2052,7 @@ get-proto@^1.0.0, get-proto@^1.0.1: get-symbol-description@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz" integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== dependencies: call-bound "^1.0.3" @@ -2358,33 +2061,33 @@ get-symbol-description@^1.1.0: get-tsconfig@^4.10.0: version "4.10.1" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.1.tgz#d34c1c01f47d65a606c37aa7a177bc3e56ab4b2e" + resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz" integrity sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ== dependencies: resolve-pkg-maps "^1.0.0" glob-parent@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob-parent@^6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" globals@^14.0.0: version "14.0.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== globalthis@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz" integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== dependencies: define-properties "^1.2.1" @@ -2392,89 +2095,89 @@ globalthis@^1.0.4: gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== graphemer@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== has-bigints@^1.0.2: version "1.1.0" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz" integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: es-define-property "^1.0.0" has-proto@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz" integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== dependencies: dunder-proto "^1.0.0" has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-tostringtag@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" hasown@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" hoist-non-react-statics@^3.3.1: version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: react-is "^16.7.0" iconv-lite@0.6: version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" ieee754@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^5.2.0: version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== ignore@^7.0.0: version "7.0.5" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + resolved "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz" integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== import-fresh@^3.2.1: version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" @@ -2482,12 +2185,12 @@ import-fresh@^3.2.1: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== internal-slot@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz" integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== dependencies: es-errors "^1.3.0" @@ -2496,19 +2199,19 @@ internal-slot@^1.1.0: "internmap@1 - 2": version "2.0.3" - resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== invariant@^2.2.4: version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== dependencies: loose-envify "^1.0.0" is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz" integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== dependencies: call-bind "^1.0.8" @@ -2517,17 +2220,17 @@ is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-arrayish@^0.3.1: version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== is-async-function@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + resolved "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz" integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== dependencies: async-function "^1.0.0" @@ -2538,14 +2241,14 @@ is-async-function@^2.0.0: is-bigint@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz" integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== dependencies: has-bigints "^1.0.2" is-boolean-object@^1.2.1: version "1.2.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz" integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== dependencies: call-bound "^1.0.3" @@ -2553,26 +2256,26 @@ is-boolean-object@^1.2.1: is-bun-module@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/is-bun-module/-/is-bun-module-2.0.0.tgz#4d7859a87c0fcac950c95e666730e745eae8bddd" + resolved "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz" integrity sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ== dependencies: semver "^7.7.1" is-callable@^1.2.7: version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-core-module@^2.13.0, is-core-module@^2.16.0, is-core-module@^2.16.1: version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: hasown "^2.0.2" is-data-view@^1.0.1, is-data-view@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + resolved "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz" integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== dependencies: call-bound "^1.0.2" @@ -2581,7 +2284,7 @@ is-data-view@^1.0.1, is-data-view@^1.0.2: is-date-object@^1.0.5, is-date-object@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz" integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== dependencies: call-bound "^1.0.2" @@ -2589,19 +2292,19 @@ is-date-object@^1.0.5, is-date-object@^1.1.0: is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-finalizationregistry@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + resolved "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz" integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== dependencies: call-bound "^1.0.3" is-generator-function@^1.0.10: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz" integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== dependencies: call-bound "^1.0.3" @@ -2611,24 +2314,24 @@ is-generator-function@^1.0.10: is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-map@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== is-negative-zero@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz" integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== is-number-object@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz" integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== dependencies: call-bound "^1.0.3" @@ -2636,12 +2339,12 @@ is-number-object@^1.1.1: is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-regex@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz" integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== dependencies: call-bound "^1.0.2" @@ -2651,19 +2354,19 @@ is-regex@^1.2.1: is-set@^2.0.3: version "2.0.3" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz" integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== is-shared-array-buffer@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz" integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== dependencies: call-bound "^1.0.3" is-string@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz" integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== dependencies: call-bound "^1.0.3" @@ -2671,7 +2374,7 @@ is-string@^1.1.1: is-symbol@^1.0.4, is-symbol@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz" integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== dependencies: call-bound "^1.0.2" @@ -2680,26 +2383,26 @@ is-symbol@^1.0.4, is-symbol@^1.1.1: is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz" integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== dependencies: which-typed-array "^1.1.16" is-weakmap@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz" integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== is-weakref@^1.0.2, is-weakref@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz" integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== dependencies: call-bound "^1.0.3" is-weakset@^2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz" integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== dependencies: call-bound "^1.0.3" @@ -2707,17 +2410,17 @@ is-weakset@^2.0.3: isarray@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== iterator.prototype@^1.1.4: version "1.1.5" - resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" + resolved "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz" integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== dependencies: define-data-property "^1.1.4" @@ -2729,51 +2432,51 @@ iterator.prototype@^1.1.4: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" jsesc@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== json-buffer@3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + 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== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json5@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz" integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: version "3.3.5" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz" integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== dependencies: array-includes "^3.1.6" @@ -2783,26 +2486,26 @@ json5@^1.0.2: keyv@^4.5.4: version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== dependencies: json-buffer "3.0.1" language-subtag-registry@^0.3.20: version "0.3.23" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" + resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz" integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== language-tags@^1.0.9: version "1.0.9" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + resolved "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz" integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== dependencies: language-subtag-registry "^0.3.20" levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -2810,51 +2513,56 @@ levn@^0.4.1: lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash@^4.17.21: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" +lucide-react@^0.554.0: + version "0.554.0" + resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz" + integrity sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA== + math-intrinsics@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== memoize-one@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz" integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== merge2@^1.3.0: version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4, micromatch@^4.0.8: version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: braces "^3.0.3" @@ -2862,31 +2570,31 @@ micromatch@^4.0.4, micromatch@^4.0.8: minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^9.0.4: version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== ms@^2.1.1, ms@^2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== n3@^1.16.3: version "1.26.0" - resolved "https://registry.yarnpkg.com/n3/-/n3-1.26.0.tgz#3d69de04bee680b9ebec9dbc1033dc1e6934d351" + resolved "https://registry.npmjs.org/n3/-/n3-1.26.0.tgz" integrity sha512-SQknS0ua90rN+3RHuk8BeIqeYyqIH/+ecViZxX08jR4j6MugqWRjtONl3uANG/crWXnOM2WIqBJtjIhVYFha+w== dependencies: buffer "^6.0.3" @@ -2894,22 +2602,22 @@ n3@^1.16.3: nanoid@^3.3.6: version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== napi-postinstall@^0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.0.tgz#888e51d1fb500e86dcf6ace1baccdbb377e654ce" + resolved "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.0.tgz" integrity sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== next@15.4.1: version "15.4.1" - resolved "https://registry.yarnpkg.com/next/-/next-15.4.1.tgz#05c96856177b5077d98de1714a3e414b7b0959f3" + resolved "https://registry.npmjs.org/next/-/next-15.4.1.tgz" integrity sha512-eNKB1q8C7o9zXF8+jgJs2CzSLIU3T6bQtX6DcTnCq1sIR1CJ0GlSyRs1BubQi3/JgCnr9Vr+rS5mOMI38FFyQw== dependencies: "@next/env" "15.4.1" @@ -2930,22 +2638,22 @@ next@15.4.1: object-assign@^4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object.assign@^4.1.4, object.assign@^4.1.7: version "4.1.7" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz" integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== dependencies: call-bind "^1.0.8" @@ -2957,7 +2665,7 @@ object.assign@^4.1.4, object.assign@^4.1.7: object.entries@^1.1.9: version "1.1.9" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz" integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== dependencies: call-bind "^1.0.8" @@ -2967,7 +2675,7 @@ object.entries@^1.1.9: object.fromentries@^2.0.8: version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== dependencies: call-bind "^1.0.7" @@ -2977,7 +2685,7 @@ object.fromentries@^2.0.8: object.groupby@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + resolved "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz" integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== dependencies: call-bind "^1.0.7" @@ -2986,7 +2694,7 @@ object.groupby@^1.0.3: object.values@^1.1.6, object.values@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz" integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== dependencies: call-bind "^1.0.8" @@ -2996,7 +2704,7 @@ object.values@^1.1.6, object.values@^1.2.1: optionator@^0.9.3: version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: deep-is "^0.1.3" @@ -3008,7 +2716,7 @@ optionator@^0.9.3: own-keys@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + resolved "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz" integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== dependencies: get-intrinsic "^1.2.6" @@ -3017,28 +2725,28 @@ own-keys@^1.0.1: p-limit@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" parse-json@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -3048,47 +2756,47 @@ parse-json@^5.0.0: path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== picocolors@^1.0.0, picocolors@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2: +"picomatch@^3 || ^4", picomatch@^4.0.2: version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== possible-typed-array-names@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz" integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== postcss@8.4.31: version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== dependencies: nanoid "^3.3.6" @@ -3097,17 +2805,17 @@ postcss@8.4.31: prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== process@^0.11.10: version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== prop-types-extra@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" + resolved "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz" integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== dependencies: react-is "^16.3.2" @@ -3115,7 +2823,7 @@ prop-types-extra@^1.1.0: prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" @@ -3124,17 +2832,17 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: punycode@^2.1.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== react-bootstrap@^2.10.2: version "2.10.10" - resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-2.10.10.tgz#be0b0d951a69987152d75c0e6986c80425efdf21" + resolved "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz" integrity sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ== dependencies: "@babel/runtime" "^7.24.7" @@ -3151,31 +2859,31 @@ react-bootstrap@^2.10.2: uncontrollable "^7.2.1" warning "^4.0.3" -react-dom@19.1.0: +"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react-dom@>=16.14.0, react-dom@>=16.6.0, react-dom@>=16.8.0, react-dom@19.1.0: version "19.1.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.0.tgz#133558deca37fa1d682708df8904b25186793623" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz" integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== dependencies: scheduler "^0.26.0" react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0: version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== react-lifecycles-compat@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== react-modal-image@^2.6.0: version "2.6.0" - resolved "https://registry.yarnpkg.com/react-modal-image/-/react-modal-image-2.6.0.tgz#24b5963b5a50c029a445d125efb49870738700d5" + resolved "https://registry.npmjs.org/react-modal-image/-/react-modal-image-2.6.0.tgz" integrity sha512-NNc1xPKzFAn0VsNMdJ8NXt6c54aL/z0fcoYmw9qn4SBUONdGl+8LOQ0sTfo0wtdzcjLiby/ncloHcAL+UI+wIA== react-select@^5.8.0: version "5.10.2" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.10.2.tgz#8dffc69dfd7d74684d9613e6eb27204e3b99e127" + resolved "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz" integrity sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ== dependencies: "@babel/runtime" "^7.12.0" @@ -3190,7 +2898,7 @@ react-select@^5.8.0: react-transition-group@^4.3.0, react-transition-group@^4.4.5: version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== dependencies: "@babel/runtime" "^7.5.5" @@ -3198,14 +2906,14 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -react@19.1.0: +"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^19.1.0, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", react@>=0.14.0, react@>=15.0.0, react@>=16.14.0, react@>=16.6.0, react@>=16.8.0, react@19.1.0: version "19.1.0" - resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75" + resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz" integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== readable-stream@^4.0.0, readable-stream@^4.5.2: version "4.7.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.7.0.tgz#cedbd8a1146c13dfff8dab14068028d58c15ac91" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz" integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg== dependencies: abort-controller "^3.0.0" @@ -3216,7 +2924,7 @@ readable-stream@^4.0.0, readable-stream@^4.5.2: reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: version "1.0.10" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz" integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== dependencies: call-bind "^1.0.8" @@ -3230,7 +2938,7 @@ reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: version "1.5.4" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz" integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== dependencies: call-bind "^1.0.8" @@ -3242,17 +2950,17 @@ regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-pkg-maps@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== resolve@^1.19.0, resolve@^1.22.4: version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: is-core-module "^2.16.0" @@ -3261,7 +2969,7 @@ resolve@^1.19.0, resolve@^1.22.4: resolve@^2.0.0-next.5: version "2.0.0-next.5" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz" integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== dependencies: is-core-module "^2.13.0" @@ -3270,29 +2978,29 @@ resolve@^2.0.0-next.5: reusify@^1.0.4: version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== robust-predicates@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" + resolved "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz" integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" rw@1: version "1.3.3" - resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz" integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== safe-array-concat@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz" integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== dependencies: call-bind "^1.0.8" @@ -3303,12 +3011,12 @@ safe-array-concat@^1.1.3: safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-push-apply@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + resolved "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz" integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== dependencies: es-errors "^1.3.0" @@ -3316,7 +3024,7 @@ safe-push-apply@^1.0.0: safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz" integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== dependencies: call-bound "^1.0.2" @@ -3325,27 +3033,27 @@ safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== scheduler@^0.26.0: version "0.26.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz" integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== semver@^6.3.1: version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.6.0, semver@^7.7.1, semver@^7.7.2: version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== set-function-length@^1.2.2: version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: define-data-property "^1.1.4" @@ -3357,7 +3065,7 @@ set-function-length@^1.2.2: set-function-name@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + resolved "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz" integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== dependencies: define-data-property "^1.1.4" @@ -3367,7 +3075,7 @@ set-function-name@^2.0.2: set-proto@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + resolved "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz" integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== dependencies: dunder-proto "^1.0.1" @@ -3376,7 +3084,7 @@ set-proto@^1.0.0: sharp@^0.34.3: version "0.34.3" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.3.tgz#10a03bcd15fb72f16355461af0b9245ccb8a5da3" + resolved "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz" integrity sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg== dependencies: color "^4.2.3" @@ -3408,19 +3116,19 @@ sharp@^0.34.3: shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== side-channel-list@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz" integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: es-errors "^1.3.0" @@ -3428,7 +3136,7 @@ side-channel-list@^1.0.0: side-channel-map@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz" integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== dependencies: call-bound "^1.0.2" @@ -3438,7 +3146,7 @@ side-channel-map@^1.0.1: side-channel-weakmap@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== dependencies: call-bound "^1.0.2" @@ -3449,7 +3157,7 @@ side-channel-weakmap@^1.0.2: side-channel@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== dependencies: es-errors "^1.3.0" @@ -3460,37 +3168,44 @@ side-channel@^1.1.0: simple-swizzle@^0.2.2: version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== dependencies: is-arrayish "^0.3.1" source-map-js@^1.0.2: version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== source-map@^0.5.7: version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== stable-hash@^0.0.5: version "0.0.5" - resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.5.tgz#94e8837aaeac5b4d0f631d2972adef2924b40269" + resolved "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz" integrity sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA== stop-iteration-iterator@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz" integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== dependencies: es-errors "^1.3.0" internal-slot "^1.1.0" +string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string.prototype.includes@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz#eceef21283640761a81dbe16d6c7171a4edf7d92" + resolved "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz" integrity sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg== dependencies: call-bind "^1.0.7" @@ -3499,7 +3214,7 @@ string.prototype.includes@^2.0.1: string.prototype.matchall@^4.0.12: version "4.0.12" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" + resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz" integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== dependencies: call-bind "^1.0.8" @@ -3518,7 +3233,7 @@ string.prototype.matchall@^4.0.12: string.prototype.repeat@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + resolved "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz" integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== dependencies: define-properties "^1.1.3" @@ -3526,7 +3241,7 @@ string.prototype.repeat@^1.0.0: string.prototype.trim@^1.2.10: version "1.2.10" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + resolved "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz" integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== dependencies: call-bind "^1.0.8" @@ -3539,7 +3254,7 @@ string.prototype.trim@^1.2.10: string.prototype.trimend@^1.0.9: version "1.0.9" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz" integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== dependencies: call-bind "^1.0.8" @@ -3549,57 +3264,50 @@ string.prototype.trimend@^1.0.9: string.prototype.trimstart@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz" integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== dependencies: call-bind "^1.0.7" define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - strip-bom@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== styled-jsx@5.1.6: version "5.1.6" - resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.6.tgz#83b90c077e6c6a80f7f5e8781d0f311b2fe41499" + resolved "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz" integrity sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA== dependencies: client-only "0.0.1" stylis@4.2.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + resolved "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== tinyglobby@^0.2.13: version "0.2.14" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz" integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== dependencies: fdir "^6.4.4" @@ -3607,19 +3315,19 @@ tinyglobby@^0.2.13: to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" ts-api-utils@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" + resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz" integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== tsconfig-paths@^3.15.0: version "3.15.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz" integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== dependencies: "@types/json5" "^0.0.29" @@ -3629,19 +3337,19 @@ tsconfig-paths@^3.15.0: tslib@^2.0.0, tslib@^2.4.0, tslib@^2.8.0: version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" typed-array-buffer@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz" integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== dependencies: call-bound "^1.0.3" @@ -3650,7 +3358,7 @@ typed-array-buffer@^1.0.3: typed-array-byte-length@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + resolved "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz" integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== dependencies: call-bind "^1.0.8" @@ -3661,7 +3369,7 @@ typed-array-byte-length@^1.0.3: typed-array-byte-offset@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + resolved "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz" integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== dependencies: available-typed-arrays "^1.0.7" @@ -3674,7 +3382,7 @@ typed-array-byte-offset@^1.0.4: typed-array-length@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz" integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== dependencies: call-bind "^1.0.7" @@ -3684,14 +3392,14 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" -typescript@5.8.3: +typescript@>=3.3.1, typescript@>=4.8.4, "typescript@>=4.8.4 <5.9.0", typescript@5.8.3: version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz" integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== unbox-primitive@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz" integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== dependencies: call-bound "^1.0.3" @@ -3701,7 +3409,7 @@ unbox-primitive@^1.1.0: uncontrollable@^7.2.1: version "7.2.1" - resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" + resolved "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz" integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== dependencies: "@babel/runtime" "^7.6.3" @@ -3711,17 +3419,17 @@ uncontrollable@^7.2.1: uncontrollable@^8.0.4: version "8.0.4" - resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-8.0.4.tgz#a0a8307f638795162fafd0550f4a1efa0f8c5eb6" + resolved "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz" integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== undici-types@~7.8.0: version "7.8.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz" integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw== unrs-resolver@^1.6.2: version "1.11.1" - resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" + resolved "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz" integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== dependencies: napi-postinstall "^0.3.0" @@ -3748,29 +3456,29 @@ unrs-resolver@^1.6.2: uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" use-isomorphic-layout-effect@^1.2.0: version "1.2.1" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz#2f11a525628f56424521c748feabc2ffcc962fce" + resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz" integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA== -uuid@8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - uuid@^9.0.0: version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + uuidv4@^6.2.13: version "6.2.13" - resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.13.tgz#8f95ec5ef22d1f92c8e5d4c70b735d1c89572cb7" + resolved "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz" integrity sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ== dependencies: "@types/uuid" "8.3.4" @@ -3778,14 +3486,14 @@ uuidv4@^6.2.13: warning@^4.0.0, warning@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== dependencies: loose-envify "^1.0.0" which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz" integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== dependencies: is-bigint "^1.1.0" @@ -3796,7 +3504,7 @@ which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: which-builtin-type@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + resolved "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz" integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== dependencies: call-bound "^1.0.2" @@ -3815,7 +3523,7 @@ which-builtin-type@^1.2.1: which-collection@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz" integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== dependencies: is-map "^2.0.3" @@ -3825,7 +3533,7 @@ which-collection@^1.0.2: which-typed-array@^1.1.16, which-typed-array@^1.1.19: version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz" integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== dependencies: available-typed-arrays "^1.0.7" @@ -3838,22 +3546,22 @@ which-typed-array@^1.1.16, which-typed-array@^1.1.19: which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" word-wrap@^1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== yaml@^1.10.0: version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From aad58e7d727c236b54f7bbe069ff68e904662144 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Wed, 19 Nov 2025 15:28:11 +0000 Subject: [PATCH 02/81] Add custom activity and role type selectors with search and create functionality --- editor-app/components/SetActivity.tsx | 394 ++++++++++++++++----- editor-app/components/SetParticipation.tsx | 312 +++++++++++++--- 2 files changed, 568 insertions(+), 138 deletions(-) diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index 36e87c7..82bfa93 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -1,4 +1,10 @@ -import React, { Dispatch, SetStateAction, useRef, useState } from "react"; +import React, { + Dispatch, + SetStateAction, + useRef, + useState, + useEffect, +} from "react"; import Button from "react-bootstrap/Button"; import Modal from "react-bootstrap/Modal"; import Form from "react-bootstrap/Form"; @@ -47,11 +53,17 @@ const SetActivity = (props: Props) => { partOf: activityContext, }; - const newType = useRef(null); const [inputs, setInputs] = useState(defaultActivity); const [errors, setErrors] = useState([]); const [dirty, setDirty] = useState(false); + // Custom activity-type selector state (search / create / inline edit) + const [typeOpen, setTypeOpen] = useState(false); + const [typeSearch, setTypeSearch] = useState(""); + const [editingTypeId, setEditingTypeId] = useState(null); + const [editingTypeValue, setEditingTypeValue] = useState(""); + const typeDropdownRef = useRef(null); + function updateIndividuals(d: Model) { d.individuals.forEach((individual) => { const earliestBeginning = d.earliestParticipantBeginning(individual.id); @@ -66,6 +78,22 @@ const SetActivity = (props: Props) => { }); } + // click outside to close type dropdown + useEffect(() => { + function handleClickOutside(ev: MouseEvent) { + if ( + typeDropdownRef.current && + !typeDropdownRef.current.contains(ev.target as Node) + ) { + setTypeOpen(false); + setEditingTypeId(null); + setEditingTypeValue(""); + } + } + if (typeOpen) document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [typeOpen]); + const handleClose = () => { setShow(false); setInputs(defaultActivity); @@ -83,8 +111,7 @@ const SetActivity = (props: Props) => { }; const handleAdd = (event: any) => { event.preventDefault(); - if (!dirty) - return handleClose(); + if (!dirty) return handleClose(); const isValid = validateInputs(); if (isValid) { updateDataset((d) => { @@ -123,11 +150,7 @@ const SetActivity = (props: Props) => { }; const handleTypeChange = (e: any) => { - dataset.activityTypes.forEach((type) => { - if (e.target.value == type.id) { - updateInputs(e.target.name, type) - } - }); + // deprecated: replaced by custom selector }; const handleChangeNumeric = (e: any) => { @@ -198,13 +221,90 @@ const SetActivity = (props: Props) => { } }; - const addType = (e: any) => { - if (newType.current && newType.current.value) { - updateDataset((d) => d.addActivityType(uuidv4(), newType.current.value)); - newType.current.value = null; - } + // ----- New helper functions for custom activity-type selector ----- + const filteredTypes = dataset.activityTypes.filter((t) => + t.name.toLowerCase().includes(typeSearch.toLowerCase()) + ); + + const showCreateTypeOption = + typeSearch.trim().length > 0 && + !dataset.activityTypes.some( + (t) => t.name.toLowerCase() === typeSearch.trim().toLowerCase() + ); + + const handleSelectType = (typeId: string) => { + const t = dataset.activityTypes.find((x) => x.id === typeId); + if (t) updateInputs("type", t); + setTypeOpen(false); + setTypeSearch(""); + setEditingTypeId(null); + setEditingTypeValue(""); + }; + + const handleCreateTypeFromSearch = () => { + const name = typeSearch.trim(); + if (!name) return; + const newId = uuidv4(); + + updateDataset((d) => { + d.addActivityType(newId, name); + return d; + }); + + // Immediately select the newly created type for this form + updateInputs("type", { id: newId, name, isCoreHqdm: false }); + setTypeOpen(false); + setTypeSearch(""); }; + const startEditType = (typeId: string, currentName: string, e: any) => { + e.stopPropagation(); + const found = dataset.activityTypes.find((x) => x.id === typeId); + if (found && found.isCoreHqdm) return; + setEditingTypeId(typeId); + setEditingTypeValue(currentName); + }; + + const saveEditType = () => { + if (!editingTypeId) return; + const newName = editingTypeValue.trim(); + if (!newName) return; + + updateDataset((d) => { + const kind = d.activityTypes.find((x) => x.id === editingTypeId); + if (kind) kind.name = newName; + + // update activities that reference this type to use canonical Kind + d.activities.forEach((a) => { + if (a.type && a.type.id === editingTypeId) { + const canonical = d.activityTypes.find((x) => x.id === editingTypeId); + if (canonical) a.type = canonical; + } + }); + + if (d.defaultActivityType && d.defaultActivityType.id === editingTypeId) { + const canonical = d.activityTypes.find((x) => x.id === editingTypeId); + if (canonical) d.defaultActivityType = canonical; + } + + return d; + }); + + updateInputs("type", { + id: editingTypeId, + name: newName, + isCoreHqdm: false, + }); + setEditingTypeId(null); + setEditingTypeValue(""); + }; + + const cancelEditType = () => { + setEditingTypeId(null); + setEditingTypeValue(""); + }; + // ----- end helpers ----- + return ( <> + + {typeOpen && ( +
+
+ setTypeSearch(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter" && showCreateTypeOption) { + e.preventDefault(); + handleCreateTypeFromSearch(); + } + }} + autoFocus + /> +
+ +
+ {filteredTypes.map((t) => ( +
handleSelectType(t.id)} + > + {editingTypeId === t.id ? ( +
+ + setEditingTypeValue(e.target.value) + } + onKeyDown={(e) => { + if (e.key === "Enter") saveEditType(); + if (e.key === "Escape") cancelEditType(); + }} + autoFocus + /> +
+ + +
+
+ ) : ( + <> +
{t.name}
+
+ {inputs?.type?.id === t.id && ( + + )} + {!t.isCoreHqdm && ( + + )} +
+ + )} +
+ ))} + + {showCreateTypeOption && ( +
+ Create "{typeSearch}" +
+ )} + + {filteredTypes.length === 0 && !showCreateTypeOption && ( +
+ No results found +
+ )} +
+
)} - {dataset.activityTypes.map((type) => ( - - ))} - +
- - Add option - - - Description { - - - - - - - - - - - - - - - {errors.length > 0 && ( - - {errors.map((error, i) => ( -

- {error} -

- ))} -
- )} - -
-
+
+
+ + + +
+
+ + +
+
+
+ {errors.length > 0 && ( + + {errors.map((error, i) => ( +

+ {error} +

+ ))} +
+ )} +
diff --git a/editor-app/components/SetParticipation.tsx b/editor-app/components/SetParticipation.tsx index 7d1d018..1279695 100644 --- a/editor-app/components/SetParticipation.tsx +++ b/editor-app/components/SetParticipation.tsx @@ -1,4 +1,10 @@ -import React, { Dispatch, SetStateAction, useRef, useState } from "react"; +import React, { + Dispatch, + SetStateAction, + useRef, + useState, + useEffect, +} from "react"; import Button from "react-bootstrap/Button"; import Modal from "react-bootstrap/Modal"; import Form from "react-bootstrap/Form"; @@ -32,9 +38,30 @@ const SetParticipation = (props: Props) => { updateDataset, } = props; - const newRole = useRef(null); + const [dirty, setDirty] = useState(false); - const [dirty, setDirty] = useState(false); + // Custom role selector state (search / create / inline edit) + const [roleOpen, setRoleOpen] = useState(false); + const [roleSearch, setRoleSearch] = useState(""); + const [editingRoleId, setEditingRoleId] = useState(null); + const [editingRoleValue, setEditingRoleValue] = useState(""); + const roleDropdownRef = useRef(null); + + // click outside to close role dropdown + useEffect(() => { + function handleClickOutside(ev: MouseEvent) { + if ( + roleDropdownRef.current && + !roleDropdownRef.current.contains(ev.target as Node) + ) { + setRoleOpen(false); + setEditingRoleId(null); + setEditingRoleValue(""); + } + } + if (roleOpen) document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [roleOpen]); const handleClose = () => { setShow(false); @@ -63,24 +90,120 @@ const SetParticipation = (props: Props) => { }; const handleTypeChange = (e: any) => { + // kept for backward compatibility if used; prefer custom selector dataset.roles.forEach((role) => { if (e.target.value == role.id) { setSelectedParticipation({ ...selectedParticipation, [e.target.name]: role, }); + setDirty(true); } }); - setDirty(true); }; - const addRole = (e: any) => { - if (newRole.current && newRole.current.value) { - updateDataset((d) => d.addRoleType(uuidv4(), newRole.current.value)); - newRole.current.value = null; + // ----- role selector helpers ----- + const filteredRoles = dataset.roles.filter((r) => + r.name.toLowerCase().includes(roleSearch.toLowerCase()) + ); + + const showCreateRoleOption = + roleSearch.trim().length > 0 && + !dataset.roles.some( + (r) => r.name.toLowerCase() === roleSearch.trim().toLowerCase() + ); + + const handleSelectRole = (roleId: string) => { + const r = dataset.roles.find((x) => x.id === roleId); + if (r && selectedParticipation) { + setSelectedParticipation({ ...selectedParticipation, role: r }); + setDirty(true); } + setRoleOpen(false); + setRoleSearch(""); + setEditingRoleId(null); + setEditingRoleValue(""); }; + const handleCreateRoleFromSearch = () => { + const name = roleSearch.trim(); + if (!name) return; + const newId = uuidv4(); + + updateDataset((d) => { + d.addRoleType(newId, name); + return d; + }); + + // immediately select the created role for this participation + const createdRole = { id: newId, name, isCoreHqdm: false }; + if (selectedParticipation) { + setSelectedParticipation({ ...selectedParticipation, role: createdRole }); + setDirty(true); + } + + setRoleOpen(false); + setRoleSearch(""); + }; + + const startEditRole = (roleId: string, currentName: string, e: any) => { + e.stopPropagation(); + const found = dataset.roles.find((x) => x.id === roleId); + if (found && found.isCoreHqdm) return; // prevent editing core defaults + setEditingRoleId(roleId); + setEditingRoleValue(currentName); + }; + + const saveEditRole = () => { + if (!editingRoleId) return; + const newName = editingRoleValue.trim(); + if (!newName) return; + + updateDataset((d) => { + const kind = d.roles.find((x) => x.id === editingRoleId); + if (kind) kind.name = newName; + + // Update all participations referencing this role across activities + d.activities.forEach((a) => { + a.participations.forEach((p) => { + if (p.role && p.role.id === editingRoleId) { + const canonical = d.roles.find((x) => x.id === editingRoleId); + if (canonical) p.role = canonical; + } + }); + }); + + // update defaultRole reference if needed + if (d.defaultRole && d.defaultRole.id === editingRoleId) { + const canonical = d.roles.find((x) => x.id === editingRoleId); + if (canonical) d.defaultRole = canonical; + } + + return d; + }); + + // Update current selectedParticipation role if it was the edited one + if ( + selectedParticipation && + selectedParticipation.role?.id === editingRoleId + ) { + setSelectedParticipation({ + ...selectedParticipation, + role: { id: editingRoleId, name: newName, isCoreHqdm: false }, + }); + setDirty(true); + } + + setEditingRoleId(null); + setEditingRoleValue(""); + }; + + const cancelEditRole = () => { + setEditingRoleId(null); + setEditingRoleValue(""); + }; + // ----- end role helpers ----- + return ( <> @@ -91,46 +214,151 @@ const SetParticipation = (props: Props) => {
Role - - {selectedParticipation?.role == undefined && ( - + + + {roleOpen && ( +
+
+ setRoleSearch(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter" && showCreateRoleOption) { + e.preventDefault(); + handleCreateRoleFromSearch(); + } + }} + autoFocus + /> +
+ +
+ {filteredRoles.map((r) => ( +
handleSelectRole(r.id)} + > + {editingRoleId === r.id ? ( +
+ + setEditingRoleValue(e.target.value) + } + onKeyDown={(e) => { + if (e.key === "Enter") saveEditRole(); + if (e.key === "Escape") cancelEditRole(); + }} + autoFocus + /> +
+ + +
+
+ ) : ( + <> +
{r.name}
+
+ {selectedParticipation?.role?.id === r.id && ( + + )} + {!r.isCoreHqdm && ( + + )} +
+ + )} +
+ ))} + + {showCreateRoleOption && ( +
+ Create "{roleSearch}" +
+ )} + + {filteredRoles.length === 0 && !showCreateRoleOption && ( +
+ No results found +
+ )} +
+
)} - {dataset.roles.map((role) => ( - - ))} -
+
- - Add option - - -
- - +
+ + +
From 6d5b8aca1a20cf289c886af5dc279447596e32d6 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 20 Nov 2025 11:41:57 +0000 Subject: [PATCH 03/81] Add hide non-participating individuals feature to ActivityDiagram --- editor-app/components/ActivityDiagram.tsx | 16 ++++-- editor-app/components/ActivityDiagramWrap.tsx | 45 +++++++++++++-- editor-app/components/HideIndividuals.tsx | 57 +++++++++++++++++++ editor-app/diagram/DrawActivityDiagram.ts | 24 ++++++-- 4 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 editor-app/components/HideIndividuals.tsx diff --git a/editor-app/components/ActivityDiagram.tsx b/editor-app/components/ActivityDiagram.tsx index facfc10..e8ead69 100644 --- a/editor-app/components/ActivityDiagram.tsx +++ b/editor-app/components/ActivityDiagram.tsx @@ -19,6 +19,7 @@ interface Props { rightClickActivity: (a: Activity) => void; rightClickParticipation: (a: Activity, p: Participation) => void; svgRef: MutableRefObject; + hideNonParticipating?: boolean; } const ActivityDiagram = (props: Props) => { @@ -34,6 +35,7 @@ const ActivityDiagram = (props: Props) => { rightClickActivity, rightClickParticipation, svgRef, + hideNonParticipating = false, } = props; const [plot, setPlot] = useState({ @@ -53,7 +55,8 @@ const ActivityDiagram = (props: Props) => { clickParticipation, rightClickIndividual, rightClickActivity, - rightClickParticipation + rightClickParticipation, + hideNonParticipating ) ); }, [ @@ -67,6 +70,7 @@ const ActivityDiagram = (props: Props) => { rightClickIndividual, rightClickActivity, rightClickParticipation, + hideNonParticipating, ]); const buildCrumbs = () => { @@ -78,12 +82,14 @@ const ActivityDiagram = (props: Props) => { const text = act ? act.name : {dataset.name ?? "Top"}; context.push( setActivityContext(link) }} key={id ?? "."} - >{text}); - if (id == undefined) - break; + > + {text} + + ); + if (id == undefined) break; id = act!.partOf; } return context.reverse(); diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index df3514a..3b5ec47 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -15,6 +15,9 @@ import { Model } from "@/lib/Model"; import { Activity, Id, Individual, Maybe, Participation } from "@/lib/Schema"; import ExportJson from "./ExportJson"; import ExportSvg from "./ExportSvg"; +import { Button } from "react-bootstrap"; +import HideIndividuals from "./HideIndividuals"; +import React from "react"; const beforeUnloadHandler = (ev: BeforeUnloadEvent) => { ev.returnValue = ""; @@ -25,6 +28,8 @@ const beforeUnloadHandler = (ev: BeforeUnloadEvent) => { /* XXX Most of this component needs refactoring into a Controller class, * leaving the react component as just the View. */ export default function ActivityDiagramWrap() { + // compactMode hides individuals that participate in zero activities + const [compactMode, setCompactMode] = useState(false); const model = new Model(); const [dataset, setDataset] = useState(model); const [dirty, setDirty] = useState(false); @@ -47,10 +52,8 @@ export default function ActivityDiagramWrap() { const [showSortIndividuals, setShowSortIndividuals] = useState(false); useEffect(() => { - if (dirty) - window.addEventListener("beforeunload", beforeUnloadHandler); - else - window.removeEventListener("beforeunload", beforeUnloadHandler); + if (dirty) window.addEventListener("beforeunload", beforeUnloadHandler); + else window.removeEventListener("beforeunload", beforeUnloadHandler); }, [dirty]); const updateDataset = (updater: Dispatch) => { @@ -135,6 +138,7 @@ export default function ActivityDiagramWrap() { rightClickActivity={rightClickActivity} rightClickParticipation={rightClickParticipation} svgRef={svgRef} + hideNonParticipating={compactMode} /> @@ -176,12 +180,18 @@ export default function ActivityDiagramWrap() { showSortIndividuals={showSortIndividuals} setShowSortIndividuals={setShowSortIndividuals} /> + - 0} undo={undo} - clearDiagram={clearDiagram}/> + clearDiagram={clearDiagram} + /> ); } + +// Pass compactMode down to ActivityDiagram (already rendered above) +// Update ActivityDiagram invocation near top of file: + +/* + Replace the existing ActivityDiagram invocation with: + + + (The earlier invocation should be replaced so ActivityDiagram receives the prop.) +*/ diff --git a/editor-app/components/HideIndividuals.tsx b/editor-app/components/HideIndividuals.tsx new file mode 100644 index 0000000..2001729 --- /dev/null +++ b/editor-app/components/HideIndividuals.tsx @@ -0,0 +1,57 @@ +import React, { Dispatch, SetStateAction } from "react"; +import Button from "react-bootstrap/Button"; +import OverlayTrigger from "react-bootstrap/OverlayTrigger"; +import Tooltip from "react-bootstrap/Tooltip"; +import { Model } from "@/lib/Model"; + +interface Props { + compactMode: boolean; + setCompactMode: Dispatch>; + dataset: Model; +} + +const HideIndividuals = ({ compactMode, setCompactMode, dataset }: Props) => { + // Find if there are individuals with no activity + const hasInactiveIndividuals = (() => { + const participating = new Set(); + dataset.activities.forEach((a) => + a.participations.forEach((p: any) => participating.add(p.individualId)) + ); + for (const i of Array.from(dataset.individuals.values())) { + if (!participating.has(i.id)) return true; + } + return false; + })(); + + if (!hasInactiveIndividuals) return null; + + const tooltip = ( + + This will hide individuals with no activity. + + ); + + const unhideTooltip = ( + + This will show all individuals with no activity. + + ); + + return ( +
+ + + +
+ ); +}; + +export default HideIndividuals; diff --git a/editor-app/diagram/DrawActivityDiagram.ts b/editor-app/diagram/DrawActivityDiagram.ts index b962b7d..ab3f736 100644 --- a/editor-app/diagram/DrawActivityDiagram.ts +++ b/editor-app/diagram/DrawActivityDiagram.ts @@ -43,24 +43,36 @@ export function drawActivityDiagram( clickParticipation: (a: Activity, p: Participation) => void, rightClickIndividual: (i: Individual) => void, rightClickActivity: (a: Activity) => void, - rightClickParticipation: (a: Activity, p: Participation) => void + rightClickParticipation: (a: Activity, p: Participation) => void, + hideNonParticipating: boolean = false ) { //Prepare Model data into arrays - const individualsArray: Individual[] = []; + let individualsArray: Individual[] = []; const activitiesArray: Activity[] = []; const { individuals, activities } = dataset; + individuals.forEach((i: Individual) => individualsArray.push(i)); activities.forEach((a: Activity) => { - if (a.partOf === activityContext) - activitiesArray.push(a); + if (a.partOf === activityContext) activitiesArray.push(a); }); + if (hideNonParticipating) { + const participating = new Set(); + activitiesArray.forEach((a) => + a.participations.forEach((p: Participation) => + participating.add(p.individualId) + ) + ); + individualsArray = individualsArray.filter((i) => participating.has(i.id)); + } + //Draw Diagram parts const svgElement = clearDiagram(svgRef); - const height = calculateViewportHeight(configData, dataset.individuals); + const individualsMap = new Map(individualsArray.map((i) => [i.id, i])); + const height = calculateViewportHeight(configData, individualsMap); const tooltip = createTooltip(); - const drawCtx: DrawContext = { + const drawCtx: DrawContext = { config: configData, svgElement, tooltip, From c8afc967be9b9d61047bd96f1b7ff778daf72bc0 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 20 Nov 2025 12:22:56 +0000 Subject: [PATCH 04/81] Add DiagramLegend component and update ActivityDiagramWrap to filter activities in view --- editor-app/components/ActivityDiagramWrap.tsx | 52 +++++--- editor-app/components/DiagramLegend.tsx | 48 +++++++ editor-app/components/Footer.tsx | 118 +++++++++++------- editor-app/diagram/DrawActivities.ts | 72 ++--------- 4 files changed, 168 insertions(+), 122 deletions(-) create mode 100644 editor-app/components/DiagramLegend.tsx diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index 3b5ec47..d9421ea 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -18,6 +18,8 @@ import ExportSvg from "./ExportSvg"; import { Button } from "react-bootstrap"; import HideIndividuals from "./HideIndividuals"; import React from "react"; +import Card from "react-bootstrap/Card"; +import DiagramLegend from "./DiagramLegend"; const beforeUnloadHandler = (ev: BeforeUnloadEvent) => { ev.returnValue = ""; @@ -123,23 +125,45 @@ export default function ActivityDiagramWrap() { const activitiesArray: Activity[] = []; dataset.activities.forEach((a: Activity) => activitiesArray.push(a)); + // Filter activities for the current context + let activitiesInView: Activity[] = []; + if (activityContext) { + // Only include activities that are part of the current context + activitiesInView = activitiesArray.filter( + (a) => a.partOf === activityContext + ); + } else { + // Top-level activities (no parent) + activitiesInView = activitiesArray.filter((a) => !a.partOf); + } + return ( <> - + + + + + + + + ( + + + Legend + {activities.map((activity, idx) => ( +
+ + {activity.name} +
+ ))} +
+ + Not Participating +
+
+
+); + +export default DiagramLegend; diff --git a/editor-app/components/Footer.tsx b/editor-app/components/Footer.tsx index 5c5003b..f4b8115 100644 --- a/editor-app/components/Footer.tsx +++ b/editor-app/components/Footer.tsx @@ -1,55 +1,87 @@ import Link from "next/link"; +import pkg from "../package.json"; function Footer() { const style = {}; + const year = new Date().getFullYear(); + return ( <>
-
-

-

+
+

-
-
-
-
More
-
    -
  • Get in touch
  • -
  • Give feedback
  • -
- -
-
-

2023 Apollo Protocol Activity Diagram Editor

Created by AMRC in collaboration with CIS

-

-
-
- -
-
- - ... - - -
-
- ... -
- -
+
+
+
+
More
+
    +
  • + + Get in touch + +
  • +
  • + + Give feedback + +
  • +
+
+
+
+
{year} Apollo Protocol Activity Diagram Editor
+
Created by AMRC in collaboration with CIS
+
Version: v{pkg.version}
+
+
+
+
+
+ + + ... + + +
+
+ + ... + +
+
-
-
- Funded by ... -
-
- -
-
-
- -
-
+
+
+ Funded by{" "} + + ... + +
+
+ + + + + ); } diff --git a/editor-app/diagram/DrawActivities.ts b/editor-app/diagram/DrawActivities.ts index 0729ec5..4f6a273 100644 --- a/editor-app/diagram/DrawActivities.ts +++ b/editor-app/diagram/DrawActivities.ts @@ -88,67 +88,8 @@ function labelActivities( timeInterval: number, startOfTime: number ) { - const { config, svgElement, activities } = ctx; - - if (config.labels.activity.enabled === false) { - return; - } - - let labels: Label[] = []; - - svgElement - .selectAll(".activityLabel") - .data(activities.values()) - .join("text") - .attr("class", "activityLabel") - .attr("id", (d: Activity) => `al${d.id}`) - .attr("x", (d: Activity) => { - const box = getBoxOfExistingActivity(svgElement, d); - return box.x + box.width / 2; - }) - .attr("y", (d: Activity) => { - const box = getBoxOfExistingActivity(svgElement, d); - const individualBoxHeight = - config.layout.individual.height + config.layout.individual.gap; - let position = box.y; - if (d.participations?.size === 1) { - position = box.y - config.labels.activity.topMargin / 1.5; - return position; - } - if ( - ((box.height + config.layout.individual.gap * 0.4) / - individualBoxHeight) % - 2 == - 0 - ) { - position = box.y + box.height / 2; - } else { - position = - box.y + - box.height / 2 - - config.layout.individual.height / 2 - - config.layout.individual.gap / 2; - } - position += config.labels.activity.topMargin; - return position; - }) - .attr("text-anchor", "middle") - .attr("font-family", "Roboto, Arial, sans-serif") - .attr("font-size", config.labels.activity.fontSize) - .attr("fill", config.labels.activity.color) - .text((d: Activity) => { - let label = d["name"]; - if (label.length > config.labels.activity.maxChars) { - label = label.substring(0, config.labels.activity.maxChars); - label += "..."; - } - return label; - }) - - .each((d: Activity, i: number, nodes: SVGGraphicsElement[]) => { - removeLabelIfItOverlaps(labels, nodes[i]); - labels.push(nodes[i].getBBox()); - }); + // Labels are now provided via the external legend; do not draw activity text on the SVG. + return; } export function hoverActivities(ctx: DrawContext) { @@ -192,8 +133,7 @@ function activityTooltip(ctx: DrawContext, activity: Activity) { if (activity.beginning !== undefined) tip += "
Beginning: " + activity.beginning; if (activity.ending) tip += "
Ending: " + activity.ending; - if (ctx.dataset.hasParts(activity.id)) - tip += "
Has sub-tasks"; + if (ctx.dataset.hasParts(activity.id)) tip += "
Has sub-tasks"; return tip; } @@ -210,10 +150,12 @@ export function clickActivities( rightClickActivity(a); }; - svgElement.select("#a" + a.id) + svgElement + .select("#a" + a.id) .on("click", lclick) .on("contextmenu", rclick); - svgElement.select("#al" + a.id) + svgElement + .select("#al" + a.id) .on("click", lclick) .on("contextmenu", rclick); }); From 2e28ab595847113005a0a394dd507fedc7a40f89 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 20 Nov 2025 12:40:58 +0000 Subject: [PATCH 05/81] Update HideIndividuals component to accept activitiesInView prop and adjust logic for inactive individuals --- editor-app/components/ActivityDiagramWrap.tsx | 1 + editor-app/components/HideIndividuals.tsx | 26 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index d9421ea..a67416b 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -208,6 +208,7 @@ export default function ActivityDiagramWrap() { compactMode={compactMode} setCompactMode={setCompactMode} dataset={dataset} + activitiesInView={activitiesInView} /> diff --git a/editor-app/components/HideIndividuals.tsx b/editor-app/components/HideIndividuals.tsx index 2001729..6d454aa 100644 --- a/editor-app/components/HideIndividuals.tsx +++ b/editor-app/components/HideIndividuals.tsx @@ -3,18 +3,25 @@ import Button from "react-bootstrap/Button"; import OverlayTrigger from "react-bootstrap/OverlayTrigger"; import Tooltip from "react-bootstrap/Tooltip"; import { Model } from "@/lib/Model"; +import { Activity } from "@/lib/Schema"; interface Props { compactMode: boolean; setCompactMode: Dispatch>; dataset: Model; + activitiesInView: Activity[]; } -const HideIndividuals = ({ compactMode, setCompactMode, dataset }: Props) => { - // Find if there are individuals with no activity +const HideIndividuals = ({ + compactMode, + setCompactMode, + dataset, + activitiesInView, +}: Props) => { + // Find if there are individuals with no activity in the current view const hasInactiveIndividuals = (() => { const participating = new Set(); - dataset.activities.forEach((a) => + activitiesInView.forEach((a) => a.participations.forEach((p: any) => participating.add(p.individualId)) ); for (const i of Array.from(dataset.individuals.values())) { @@ -31,23 +38,14 @@ const HideIndividuals = ({ compactMode, setCompactMode, dataset }: Props) => { ); - const unhideTooltip = ( - - This will show all individuals with no activity. - - ); - return (
- +
From cee6f1f34435a283db50cda0c2e3bc4ea9319b9f Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 20 Nov 2025 15:30:12 +0000 Subject: [PATCH 06/81] Enhance ActivityDiagramWrap and DiagramLegend to support parts count display --- editor-app/components/ActivityDiagramWrap.tsx | 10 ++++ editor-app/components/DiagramLegend.tsx | 58 +++++++++++++------ editor-app/diagram/DrawActivities.ts | 20 ++++++- editor-app/lib/Model.ts | 30 +++++++--- 4 files changed, 91 insertions(+), 27 deletions(-) diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index a67416b..f862b03 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -137,6 +137,15 @@ export default function ActivityDiagramWrap() { activitiesInView = activitiesArray.filter((a) => !a.partOf); } + const partsCountMap: Record = {}; + activitiesInView.forEach((a) => { + partsCountMap[a.id] = + typeof dataset.getPartsCount === "function" + ? dataset.getPartsCount(a.id) + : 0; + }); + + // render return ( <> @@ -145,6 +154,7 @@ export default function ActivityDiagramWrap() { diff --git a/editor-app/components/DiagramLegend.tsx b/editor-app/components/DiagramLegend.tsx index 8a8f0f5..aa7e2f2 100644 --- a/editor-app/components/DiagramLegend.tsx +++ b/editor-app/components/DiagramLegend.tsx @@ -5,41 +5,61 @@ import { Activity } from "@/lib/Schema"; interface Props { activities: Activity[]; activityColors: string[]; + partsCount?: Record; } -const DiagramLegend = ({ activities, activityColors }: Props) => ( +const DiagramLegend = ({ activities, activityColors, partsCount }: Props) => ( Legend - {activities.map((activity, idx) => ( -
+ {activities.map((activity, idx) => { + const count = partsCount ? partsCount[activity.id] ?? 0 : 0; + return ( +
+
+ + {count > 0 ? ( + + {activity.name}{" "} + + ({count} subtask{count !== 1 ? "s" : ""}) + + + ) : ( + {activity.name} + )} +
+
+ ); + })} +
+
- {activity.name} + Not Participating
- ))} -
- - Not Participating
diff --git a/editor-app/diagram/DrawActivities.ts b/editor-app/diagram/DrawActivities.ts index 4f6a273..d7d75b0 100644 --- a/editor-app/diagram/DrawActivities.ts +++ b/editor-app/diagram/DrawActivities.ts @@ -12,7 +12,7 @@ import { activity } from "@apollo-protocol/hqdm-lib"; let mouseOverElement: any | null = null; export function drawActivities(ctx: DrawContext) { - const { config, svgElement, activities, individuals } = ctx; + const { config, svgElement, activities, individuals, dataset } = ctx; let startOfTime = Math.min(...activities.map((a) => a.beginning)); let endOfTime = Math.max(...activities.map((a) => a.ending)); @@ -77,6 +77,24 @@ export function drawActivities(ctx: DrawContext) { }) .attr("opacity", config.presentation.activity.opacity); + // small helper functions to reuse computations + const xOf = (a: Activity) => x + timeInterval * (a.beginning - startOfTime); + const yOf = (a: Activity) => + calculateTopPositionOfNewActivity(svgElement, a) - + config.layout.individual.gap * 0.3; + const widthOf = (a: Activity) => (a.ending - a.beginning) * timeInterval; + const heightOf = (a: Activity) => { + const h = calculateLengthOfNewActivity(svgElement, a); + return h + ? h - + calculateTopPositionOfNewActivity(svgElement, a) + + config.layout.individual.gap * 0.6 + + config.layout.individual.height + : 0; + }; + + // Subtask badge rendering removed — badge no longer drawn on activities. + labelActivities(ctx, x, timeInterval, startOfTime); return svgElement; diff --git a/editor-app/lib/Model.ts b/editor-app/lib/Model.ts index 3602a54..db39e3f 100644 --- a/editor-app/lib/Model.ts +++ b/editor-app/lib/Model.ts @@ -213,13 +213,29 @@ export class Model { * Given an activity ID, finds if the activity has any parts. */ hasParts(activityId: string) { - const it = this.activities.values(); - for (let res = it.next(); !res.done; res = it.next()) { - if (res.value.partOf == activityId) { - return true; - } - } - return false; + return this.getPartsCount(activityId) > 0; + } + + /** + * Return the list of child activities (parts) for a given activity id. + */ + getParts(activityId: string): Activity[] { + const parts: Activity[] = []; + this.activities.forEach((a) => { + if (a.partOf === activityId) parts.push(a); + }); + return parts; + } + + /** + * Return the number of child activities (parts) for a given activity id. + */ + getPartsCount(activityId: string): number { + let count = 0; + this.activities.forEach((a) => { + if (a.partOf === activityId) count++; + }); + return count; } /** From 8a72334c4fed98a4453927b0c674d83da2d4e59c Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 20 Nov 2025 16:19:23 +0000 Subject: [PATCH 07/81] Add parent activity management to SetActivity component and Model class --- editor-app/components/SetActivity.tsx | 108 +++++++++++++++++++++++++- editor-app/lib/Model.ts | 49 ++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index 82bfa93..cc77044 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -8,6 +8,7 @@ import React, { import Button from "react-bootstrap/Button"; import Modal from "react-bootstrap/Modal"; import Form from "react-bootstrap/Form"; +import ListGroup from "react-bootstrap/ListGroup"; import Alert from "react-bootstrap/Alert"; import Container from "react-bootstrap/Container"; import Row from "react-bootstrap/Row"; @@ -63,6 +64,21 @@ const SetActivity = (props: Props) => { const [editingTypeId, setEditingTypeId] = useState(null); const [editingTypeValue, setEditingTypeValue] = useState(""); const typeDropdownRef = useRef(null); + const [showParentModal, setShowParentModal] = useState(false); + const [selectedParentId, setSelectedParentId] = useState(null); + + // Safe local ancestor check (walks partOf chain). Avoids depending on Model.isAncestor. + const isAncestorLocal = ( + ancestorId: string, + descendantId: string + ): boolean => { + let cur = dataset.activities.get(descendantId); + while (cur && cur.partOf) { + if (cur.partOf === ancestorId) return true; + cur = dataset.activities.get(cur.partOf as string); + } + return false; + }; function updateIndividuals(d: Model) { d.individuals.forEach((individual) => { @@ -305,6 +321,32 @@ const SetActivity = (props: Props) => { }; // ----- end helpers ----- + const handlePromote = () => { + if (!inputs || !inputs.partOf) return; + const parent = dataset.getParent(inputs.partOf as Id); + const newParentId = parent ? parent.id : null; // parent.partOf => grandparent + updateDataset((d) => { + d.setActivityParent(inputs.id, newParentId); + return d; + }); + updateInputs("partOf", newParentId ?? undefined); + }; + + const openChangeParent = () => { + // prepare list in modal by setting default selection to current parent + setSelectedParentId(inputs.partOf ? (inputs.partOf as string) : null); + setShowParentModal(true); + }; + + const handleApplyParent = () => { + updateDataset((d) => { + d.setActivityParent(inputs.id, selectedParentId ?? null); + return d; + }); + updateInputs("partOf", selectedParentId ?? undefined); + setShowParentModal(false); + }; + return ( <>
-
+
+
+
+ + +
+
{errors.length > 0 && ( @@ -576,6 +638,50 @@ const SetActivity = (props: Props) => {
+ + {/* Parent chooser modal */} + setShowParentModal(false)}> + + Choose parent activity (or None) + + + + setSelectedParentId(null)} + > + None (make top-level) + + {Array.from(dataset.activities.values()) + .filter((a) => { + // exclude self and descendants + if (!inputs || !inputs.id) return false; + if (a.id === inputs.id) return false; + if (isAncestorLocal(inputs.id, a.id)) return false; // avoid cycles + return true; + }) + .map((a) => ( + setSelectedParentId(a.id)} + > + {a.name} {a.partOf ? `(partOf ${a.partOf})` : ""} + + ))} + + + + + + + ); }; diff --git a/editor-app/lib/Model.ts b/editor-app/lib/Model.ts index db39e3f..5a9943c 100644 --- a/editor-app/lib/Model.ts +++ b/editor-app/lib/Model.ts @@ -313,4 +313,53 @@ export class Model { console.log("Normalised activity bounds: %o", this.activities); } + + // Return the activity object for a given id (or undefined) + getActivity(activityId: Id): Activity | undefined { + return this.activities.get(activityId); + } + + // Set the parent (partOf) for an activity. parentId may be null for top-level. + setActivityParent(activityId: Id, parentId: Id | null) { + const act = this.activities.get(activityId); + if (!act) return; + act.partOf = parentId ?? undefined; + // if you need any consistency updates (e.g., adjust times/participants), do them here + this.addActivity(act); // replace stored activity + } + + // Get parent activity (or undefined) + getParent(activityId: Id): Activity | undefined { + const a = this.activities.get(activityId); + if (!a || !a.partOf) return undefined; + return this.activities.get(a.partOf); + } + + // Return ancestors chain (closest parent first) + getAncestors(activityId: Id): Activity[] { + const ancestors: Activity[] = []; + let cur = this.getParent(activityId); + while (cur) { + ancestors.push(cur); + if (!cur.partOf) break; + cur = this.getParent(cur.id); + } + return ancestors; + } + + // True if candidateAncestorId is an ancestor of descendantId + isAncestor(candidateAncestorId: Id, descendantId: Id): boolean { + let cur = this.getParent(descendantId); + while (cur) { + if (cur.id === candidateAncestorId) return true; + if (!cur.partOf) break; + cur = this.getParent(cur.id); + } + return false; + } + + // True if descendantId is a descendant of ancestorId + isDescendant(ancestorId: Id, descendantId: Id): boolean { + return this.isAncestor(ancestorId, descendantId); + } } From c05e9a5361222f09c2b76a4419a180a8e62c0e3b Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 20 Nov 2025 16:27:00 +0000 Subject: [PATCH 08/81] Enhance parent activity display in SetActivity component --- editor-app/components/SetActivity.tsx | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index cc77044..2f88237 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -661,16 +661,22 @@ const SetActivity = (props: Props) => { if (isAncestorLocal(inputs.id, a.id)) return false; // avoid cycles return true; }) - .map((a) => ( - setSelectedParentId(a.id)} - > - {a.name} {a.partOf ? `(partOf ${a.partOf})` : ""} - - ))} + .map((a) => { + const parentName = + a.partOf && dataset.activities.get(a.partOf as string) + ? dataset.activities.get(a.partOf as string)!.name + : a.partOf ?? ""; + return ( + setSelectedParentId(a.id)} + > + {a.name} {parentName ? `(Part of ${parentName})` : ""} + + ); + })} From a9fca1431932b730feafa5d7f8280608c3c277ac Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 20 Nov 2025 16:44:34 +0000 Subject: [PATCH 09/81] Enhance activity parent management to support ancestor expansion in SetActivity and Model classes --- editor-app/components/SetActivity.tsx | 30 ++++++++++++++++++++++++--- editor-app/lib/Model.ts | 27 ++++++++++++++++++------ 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index 2f88237..0753bcb 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -321,12 +321,36 @@ const SetActivity = (props: Props) => { }; // ----- end helpers ----- + // ...existing code... const handlePromote = () => { if (!inputs || !inputs.partOf) return; - const parent = dataset.getParent(inputs.partOf as Id); - const newParentId = parent ? parent.id : null; // parent.partOf => grandparent + // find current parent and then its parent (grandparent) - that's the new parent + const currentParent = dataset.getParent(inputs.partOf as Id); + const grandParent = currentParent + ? dataset.getParent(currentParent.id) + : undefined; + const newParentId = grandParent ? grandParent.id : null; + + // confirm if child outside of new parent's timeframe + if (newParentId) { + const parentAct = dataset.activities.get(newParentId); + if ( + parentAct && + (inputs.beginning < parentAct.beginning || + inputs.ending > parentAct.ending) + ) { + if ( + !window.confirm( + `Promoting will require expanding "${parentAct.name}" timeframe to include this activity. Proceed?` + ) + ) { + return; + } + } + } + updateDataset((d) => { - d.setActivityParent(inputs.id, newParentId); + d.setActivityParent(inputs.id, newParentId, true); return d; }); updateInputs("partOf", newParentId ?? undefined); diff --git a/editor-app/lib/Model.ts b/editor-app/lib/Model.ts index 5a9943c..8a1336c 100644 --- a/editor-app/lib/Model.ts +++ b/editor-app/lib/Model.ts @@ -320,12 +320,27 @@ export class Model { } // Set the parent (partOf) for an activity. parentId may be null for top-level. - setActivityParent(activityId: Id, parentId: Id | null) { - const act = this.activities.get(activityId); - if (!act) return; - act.partOf = parentId ?? undefined; - // if you need any consistency updates (e.g., adjust times/participants), do them here - this.addActivity(act); // replace stored activity + setActivityParent( + activityId: Id, + parentId: Id | null, + expandAncestors = false + ) { + const a = this.activities.get(activityId); + if (!a) return; + a.partOf = parentId ?? undefined; + this.activities.set(a.id, a); + + if (expandAncestors && parentId) { + let cur = this.activities.get(parentId); + while (cur) { + // ensure parent contains child + if (a.beginning < cur.beginning) cur.beginning = a.beginning; + if (a.ending > cur.ending) cur.ending = a.ending; + this.activities.set(cur.id, cur); + if (!cur.partOf) break; + cur = this.activities.get(cur.partOf); + } + } } // Get parent activity (or undefined) From 7a2bf38c536b99fe5d1c4e30eb59a2ea78be2def Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Fri, 21 Nov 2025 11:32:13 +0000 Subject: [PATCH 10/81] Enhance DiagramLegend and ActivityDiagramWrap to support activity selection and display; update HideIndividuals tooltip and button text for clarity; refactor DrawActivities and DrawParticipations for improved rendering; add ArrowUp SVG component. --- editor-app/components/ActivityDiagramWrap.tsx | 4 + editor-app/components/DiagramLegend.tsx | 144 +++++++++++------- editor-app/components/HideIndividuals.tsx | 8 +- editor-app/components/svg/ArrowUp.tsx | 17 +++ editor-app/diagram/DrawActivities.ts | 18 +-- editor-app/diagram/DrawParticipations.ts | 109 +++++++++---- 6 files changed, 203 insertions(+), 97 deletions(-) create mode 100644 editor-app/components/svg/ArrowUp.tsx diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index f862b03..d9c6bac 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -155,6 +155,10 @@ export default function ActivityDiagramWrap() { activities={activitiesInView} activityColors={config.presentation.activity.fill} partsCount={partsCountMap} + onOpenActivity={(a) => { + setSelectedActivity(a); + setShowActivity(true); + }} /> diff --git a/editor-app/components/DiagramLegend.tsx b/editor-app/components/DiagramLegend.tsx index aa7e2f2..0055983 100644 --- a/editor-app/components/DiagramLegend.tsx +++ b/editor-app/components/DiagramLegend.tsx @@ -1,68 +1,106 @@ -import React from "react"; +import React, { useState } from "react"; import Card from "react-bootstrap/Card"; +import Button from "react-bootstrap/Button"; +import OverlayTrigger from "react-bootstrap/OverlayTrigger"; +import Tooltip from "react-bootstrap/Tooltip"; import { Activity } from "@/lib/Schema"; +import { ArrowUp } from "../components/svg/ArrowUp"; interface Props { activities: Activity[]; activityColors: string[]; partsCount?: Record; + onOpenActivity?: (a: Activity) => void; } -const DiagramLegend = ({ activities, activityColors, partsCount }: Props) => ( - - - Legend - {activities.map((activity, idx) => { - const count = partsCount ? partsCount[activity.id] ?? 0 : 0; - return ( -
-
- - {count > 0 ? ( - - {activity.name}{" "} - - ({count} subtask{count !== 1 ? "s" : ""}) +const DiagramLegend = ({ + activities, + activityColors, + partsCount, + onOpenActivity, +}: Props) => { + const [hovered, setHovered] = useState(null); + return ( + + + Activity Legend + {activities.map((activity, idx) => { + const count = partsCount ? partsCount[activity.id] ?? 0 : 0; + return ( +
+
+ + {count > 0 ? ( + + {activity.name}{" "} + + ({count} subtask{count !== 1 ? "s" : ""}) + - - ) : ( - {activity.name} - )} + ) : ( + {activity.name} + )} +
+ + {/* open/edit button on the right */} +
+ {onOpenActivity ? ( + + Open activity editor + + } + > + + + ) : null} +
+ ); + })} +
+
+ + No Activity
- ); - })} -
-
- - Not Participating
-
- - -); + + + ); +}; export default DiagramLegend; diff --git a/editor-app/components/HideIndividuals.tsx b/editor-app/components/HideIndividuals.tsx index 6d454aa..1f196ea 100644 --- a/editor-app/components/HideIndividuals.tsx +++ b/editor-app/components/HideIndividuals.tsx @@ -32,7 +32,11 @@ const HideIndividuals = ({ if (!hasInactiveIndividuals) return null; - const tooltip = ( + const tooltip = compactMode ? ( + + This will show individuals with no activity. + + ) : ( This will hide individuals with no activity. @@ -45,7 +49,7 @@ const HideIndividuals = ({ variant={compactMode ? "secondary" : "primary"} onClick={() => setCompactMode(!compactMode)} > - {compactMode ? "Unhide Individuals" : "Hide Individuals"} + {compactMode ? "Show Individuals" : "Hide Individuals"}
diff --git a/editor-app/components/svg/ArrowUp.tsx b/editor-app/components/svg/ArrowUp.tsx new file mode 100644 index 0000000..15f2b45 --- /dev/null +++ b/editor-app/components/svg/ArrowUp.tsx @@ -0,0 +1,17 @@ +export const ArrowUp = () => { + return ( + + + + ); +}; diff --git a/editor-app/diagram/DrawActivities.ts b/editor-app/diagram/DrawActivities.ts index d7d75b0..64d5fa6 100644 --- a/editor-app/diagram/DrawActivities.ts +++ b/editor-app/diagram/DrawActivities.ts @@ -63,19 +63,11 @@ export function drawActivities(ctx: DrawContext) { config.layout.individual.height : 0; }) - .attr("stroke", (a: Activity, i: number) => { - return config.presentation.activity.stroke[ - i % config.presentation.activity.stroke.length - ]; - }) - .attr("stroke-dasharray", config.presentation.activity.strokeDasharray) - .attr("stroke-width", config.presentation.activity.strokeWidth) - .attr("fill", (a: Activity, i: number) => { - return config.presentation.activity.fill[ - i % config.presentation.activity.fill.length - ]; - }) - .attr("opacity", config.presentation.activity.opacity); + // Make the top-level activity rect invisible so per-participation blocks show clearly. + // (keeps DOM elements for hit-testing / layout if other code relies on them) + .attr("stroke", "none") + .attr("fill", "none") + .attr("opacity", 1); // small helper functions to reuse computations const xOf = (a: Activity) => x + timeInterval * (a.beginning - startOfTime); diff --git a/editor-app/diagram/DrawParticipations.ts b/editor-app/diagram/DrawParticipations.ts index 233702d..0e34a3f 100644 --- a/editor-app/diagram/DrawParticipations.ts +++ b/editor-app/diagram/DrawParticipations.ts @@ -1,5 +1,5 @@ import { MouseEvent } from "react"; -import { Activity } from "@/lib/Schema"; +import { Activity, Participation } from "@/lib/Schema"; import { ConfigData } from "./config"; import { DrawContext } from "./DrawHelpers"; @@ -8,51 +8,102 @@ let mouseOverElement: any | null = null; export function drawParticipations(ctx: DrawContext) { const { config, svgElement, activities } = ctx; - const parts: any[] = []; - activities.forEach((a) => { - a.participations?.forEach((p) => { - parts.push({ - box: getPositionOfParticipation(svgElement, a.id, p.individualId), - activityId: a.id, - individualId: p.individualId, - participation: p, - }); - }); + if (!activities || activities.length === 0) return svgElement; + + const startOfTime = Math.min(...activities.map((a) => a.beginning)); + const endOfTime = Math.max(...activities.map((a) => a.ending)); + const duration = Math.max(1, endOfTime - startOfTime); + let totalLeftMargin = + config.viewPort.x * config.viewPort.zoom - + config.layout.individual.xMargin * 2; + totalLeftMargin -= config.layout.individual.temporalMargin; + + try { + const { keepIndividualLabels } = require("./DrawHelpers"); + if ( + config.labels.individual.enabled && + keepIndividualLabels(ctx.individuals) + ) { + totalLeftMargin -= config.layout.individual.textLength; + } + } catch { + // ignore + } + + const timeInterval = totalLeftMargin / duration; + const xBase = + config.layout.individual.xMargin + + config.layout.individual.temporalMargin + + (config.labels.individual.enabled + ? config.layout.individual.textLength + : 0); + + const parts: { + activity: Activity; + participation: Participation; + activityIndex: number; + }[] = []; + activities.forEach((a, idx) => { + (a.participations || []).forEach((p: Participation) => + parts.push({ activity: a, participation: p, activityIndex: idx }) + ); }); svgElement - .selectAll(".participation") - .data(parts.values()) + .selectAll(".participation-rect") + .data(parts, (d: any) => `${d.activity.id}:${d.participation.individualId}`) .join("rect") - .attr("class", "participation") - .attr("id", (p: any) => "p" + p.activityId + p.individualId) - .attr("x", (d: any) => d.box.x) - .attr("y", (d: any) => d.box.y) - .attr("width", (d: any) => d.box.width) - .attr("height", (d: any) => d.box.height) - .attr("stroke", config.presentation.participation.stroke) - .attr("stroke-dasharray", config.presentation.participation.strokeDasharray) - .attr("stroke-width", config.presentation.participation.strokeWidth) - .attr("fill", config.presentation.participation.fill) - .attr("opacity", config.presentation.participation.opacity); + .attr("class", "participation-rect") + .attr( + "id", + (d: any) => `p_${d.activity.id}_${d.participation.individualId}` + ) + .attr( + "x", + (d: any) => xBase + timeInterval * (d.activity.beginning - startOfTime) + ) + .attr("width", (d: any) => + Math.max(1, (d.activity.ending - d.activity.beginning) * timeInterval) + ) + .attr("y", (d: any) => { + const node = svgElement + .select("#i" + d.participation.individualId) + .node(); + if (!node) return 0; + const box = node.getBBox(); + return box.y; + }) + .attr("height", () => config.layout.individual.height) + .attr( + "fill", + (d: any) => + config.presentation.activity.fill[ + d.activityIndex % config.presentation.activity.fill.length + ] + ) + .attr("stroke", "none") + .attr("opacity", 1); + // Add hover behavior using the same pattern as original hoverParticipations(ctx); + + return svgElement; } function hoverParticipations(ctx: DrawContext) { const { config, svgElement, tooltip } = ctx; svgElement - .selectAll(".participation") + .selectAll(".participation-rect") .on("mouseover", function (event: MouseEvent) { mouseOverElement = event.target as HTMLElement; - mouseOverElement.style.opacity = - config.presentation.participation.opacityHover; + mouseOverElement.style.opacity = String( + config.presentation.activity.opacityHover ?? 0.9 + ); tooltip.style("display", "block"); }) .on("mouseout", function (event: MouseEvent) { if (mouseOverElement) { - mouseOverElement.style.opacity = - config.presentation.participation.opacity; + mouseOverElement.style.opacity = "1"; mouseOverElement = null; } tooltip.style("display", "none"); From 20ee411eda160b4b0fdb95c0c8e73f2e0aa61845 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Fri, 21 Nov 2025 12:07:57 +0000 Subject: [PATCH 11/81] Refactor SetActivity component buttons for improved visibility and functionality; update DrawParticipations click handlers for better event management; enhance config structure with additional properties for axis and activity labels. --- editor-app/components/SetActivity.tsx | 34 +++--- editor-app/diagram/DrawParticipations.ts | 32 +++--- editor-app/diagram/config.ts | 129 ++++++++++++----------- 3 files changed, 102 insertions(+), 93 deletions(-) diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index 0753bcb..cc885a3 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -613,39 +613,39 @@ const SetActivity = (props: Props) => {
diff --git a/editor-app/diagram/DrawParticipations.ts b/editor-app/diagram/DrawParticipations.ts index 0e34a3f..e7ea042 100644 --- a/editor-app/diagram/DrawParticipations.ts +++ b/editor-app/diagram/DrawParticipations.ts @@ -128,23 +128,23 @@ export function clickParticipations( clickParticipation: any, rightClickParticipation: any ) { - const { svgElement, activities } = ctx; - - activities.forEach((a) => { - a.participations.forEach((p) => { - svgElement - .select("#p" + a.id + p.individualId) - .on("click", function (event: MouseEvent) { - clickParticipation(a, p); - }); - svgElement - .select("#p" + a.id + p.individualId) - .on("contextmenu", function (event: MouseEvent) { - event.preventDefault(); - rightClickParticipation(a, p); - }); + const { svgElement } = ctx; + + // Attach handlers directly to the participation rects created in drawParticipations. + svgElement + .selectAll(".participation-rect") + .on("click", function (event: any, d: any) { + // d has shape { activity, participation, activityIndex } + if (d && d.activity && d.participation) { + clickParticipation(d.activity, d.participation); + } + }) + .on("contextmenu", function (event: any, d: any) { + event.preventDefault(); + if (d && d.activity && d.participation) { + rightClickParticipation(d.activity, d.participation); + } }); - }); } function participationTooltip(part: any) { diff --git a/editor-app/diagram/config.ts b/editor-app/diagram/config.ts index f90310e..61324f6 100644 --- a/editor-app/diagram/config.ts +++ b/editor-app/diagram/config.ts @@ -66,77 +66,86 @@ export interface ConfigData { } export const config: ConfigData = { - "viewPort": { - "zoom": 1, - "x": 1000 + viewPort: { + zoom: 1, + x: 1000, }, - "layout": { - "individual": { - "topMargin": 25, - "bottomMargin": 30, - "height": 20, - "gap": 10, - "xMargin": 40, - "temporalMargin": 10, - "textLength": 100 - } + layout: { + individual: { + topMargin: 25, + bottomMargin: 30, + height: 20, + gap: 10, + xMargin: 40, + temporalMargin: 10, + textLength: 100, + }, }, - "presentation": { - "individual": { - "strokeWidth": "1px", - "stroke": "#7F7F7F", - "fill": "#B1B1B0", - "fillHover": "#8d8d8b" + presentation: { + individual: { + strokeWidth: "1px", + stroke: "#7F7F7F", + fill: "#B1B1B0", + fillHover: "#8d8d8b", }, - "activity": { - "strokeWidth": "1px", - "stroke": [ - "#29123b" - ], - "strokeDasharray": "5,3", - "fill": [ + activity: { + strokeWidth: "1px", + stroke: ["#29123b"], + strokeDasharray: "5,3", + fill: [ "#440099", "#e7004c", "#ff9664", "#00bbcc", "#a1ded2", - "#981f92" + "#981f92", + "#f15bb5", + "#fee440", + "#00bb4f", + "#ff6f59", + "#d11149", + "#00aedb", + "#f37735", + "#ffc425", + "#4caf50", + "#000000", + "#ffffff", ], - "opacity": "0.5", - "opacityHover": "0.7" + opacity: "0.5", + opacityHover: "0.7", }, - "participation": { - "strokeWidth": "1px", - "stroke": "#29123b", - "strokeDasharray": "5,3", - "fill": "#F2F2F2", - "opacity": "0.5", - "opacityHover": "0.9" + participation: { + strokeWidth: "1px", + stroke: "#29123b", + strokeDasharray: "5,3", + fill: "#F2F2F2", + opacity: "0.5", + opacityHover: "0.9", + }, + axis: { + colour: "#7F7F7F", + width: 15, + margin: 20, + textOffsetX: 5, + textOffsetY: 4, + endMargin: 30, }, - "axis": { - "colour": "#7F7F7F", - "width": 15, - "margin": 20, - "textOffsetX": 5, - "textOffsetY": 4, - "endMargin": 30 - } }, - "labels": { - "individual": { - "enabled": true, - "leftMargin": 5, - "topMargin": 5, - "color": "black", - "fontSize": "0.8em", - "maxChars": 24 + labels: { + individual: { + enabled: true, + leftMargin: 5, + topMargin: 5, + color: "black", + fontSize: "0.8em", + maxChars: 24, + }, + activity: { + enabled: true, + topMargin: 5, + color: "#441d62", + fontSize: "0.7em", + maxChars: 24, }, - "activity": { - "enabled": true, - "topMargin": 5, - "color": "#441d62", - "fontSize": "0.7em", - "maxChars": 24 - } - } + }, }; From dab673c5fb0cfbcaa8099f2d68f5abf3a83a2c7e Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Fri, 21 Nov 2025 12:31:40 +0000 Subject: [PATCH 12/81] Enhance DrawParticipations rendering by adjusting rectangle height, border radius, and stroke properties; implement color darkening function for improved visual clarity. --- editor-app/diagram/DrawParticipations.ts | 37 ++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/editor-app/diagram/DrawParticipations.ts b/editor-app/diagram/DrawParticipations.ts index e7ea042..ad912cf 100644 --- a/editor-app/diagram/DrawParticipations.ts +++ b/editor-app/diagram/DrawParticipations.ts @@ -49,6 +49,11 @@ export function drawParticipations(ctx: DrawContext) { ); }); + const rectHeight = Math.min(36, config.layout.individual.height); // glass height, bounded by row + const rx = 4; // border-radius + const strokeWidth = 1; + const fillOpacity = 0.85; + svgElement .selectAll(".participation-rect") .data(parts, (d: any) => `${d.activity.id}:${d.participation.individualId}`) @@ -71,9 +76,12 @@ export function drawParticipations(ctx: DrawContext) { .node(); if (!node) return 0; const box = node.getBBox(); - return box.y; + // vertically center the glass rect inside the individual row + return box.y + Math.max(0, (box.height - rectHeight) / 2); }) - .attr("height", () => config.layout.individual.height) + .attr("height", () => rectHeight) + .attr("rx", rx) + .attr("ry", rx) .attr( "fill", (d: any) => @@ -81,7 +89,16 @@ export function drawParticipations(ctx: DrawContext) { d.activityIndex % config.presentation.activity.fill.length ] ) - .attr("stroke", "none") + .attr("fill-opacity", fillOpacity) + .attr("stroke", (d: any) => + darkenHex( + config.presentation.activity.fill[ + d.activityIndex % config.presentation.activity.fill.length + ], + 0.28 + ) + ) + .attr("stroke-width", strokeWidth) .attr("opacity", 1); // Add hover behavior using the same pattern as original @@ -182,3 +199,17 @@ function getPositionOfParticipation( height: height, }; } + +/** darken a #rrggbb colour by pct (0..1) */ +function darkenHex(hex: string, pct: number) { + if (!hex) return "#000"; + const h = hex.replace("#", ""); + const r = parseInt(h.substring(0, 2), 16); + const g = parseInt(h.substring(2, 4), 16); + const b = parseInt(h.substring(4, 6), 16); + const dr = Math.max(0, Math.min(255, Math.floor(r * (1 - pct)))); + const dg = Math.max(0, Math.min(255, Math.floor(g * (1 - pct)))); + const db = Math.max(0, Math.min(255, Math.floor(b * (1 - pct)))); + const toHex = (v: number) => v.toString(16).padStart(2, "0"); + return `#${toHex(dr)}${toHex(dg)}${toHex(db)}`; +} From 870a03f4bb42e2aa1d3f7891c389fd16b0106e02 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Fri, 21 Nov 2025 12:41:21 +0000 Subject: [PATCH 13/81] Remove unused color entries from activity fill configuration for cleaner code. --- editor-app/diagram/config.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/editor-app/diagram/config.ts b/editor-app/diagram/config.ts index 61324f6..a9d10f9 100644 --- a/editor-app/diagram/config.ts +++ b/editor-app/diagram/config.ts @@ -102,12 +102,7 @@ export const config: ConfigData = { "#f15bb5", "#fee440", "#00bb4f", - "#ff6f59", - "#d11149", "#00aedb", - "#f37735", - "#ffc425", - "#4caf50", "#000000", "#ffffff", ], From 27923d48d321a932c0670df94928521dfdcd7398 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Mon, 24 Nov 2025 14:36:38 +0000 Subject: [PATCH 14/81] Enhance SetIndividual and Model components to support entity types and installations; add entity type selection and parent system dropdown; update Model to manage installations centrally. --- editor-app/components/SetIndividual.tsx | 96 ++++++++++++++++++++++++- editor-app/lib/Model.ts | 61 ++++++++++++++-- editor-app/lib/Schema.ts | 33 ++++++++- 3 files changed, 180 insertions(+), 10 deletions(-) diff --git a/editor-app/components/SetIndividual.tsx b/editor-app/components/SetIndividual.tsx index ffb9f04..e0cfdd5 100644 --- a/editor-app/components/SetIndividual.tsx +++ b/editor-app/components/SetIndividual.tsx @@ -2,6 +2,7 @@ import React, { Dispatch, SetStateAction, useEffect, + useMemo, useRef, useState, } from "react"; @@ -11,8 +12,8 @@ import Row from "react-bootstrap/Row"; import Col from "react-bootstrap/Col"; import Form from "react-bootstrap/Form"; import Container from "react-bootstrap/Container"; -import { Individual } from "@/lib/Schema"; -import { Model } from "@/lib/Model"; +import { Model } from "../lib/Model"; +import { EntityType, Individual } from "../lib/Schema"; import { v4 as uuidv4 } from "uuid"; import { Alert, InputGroup } from "react-bootstrap"; @@ -102,6 +103,22 @@ const SetIndividual = (props: Props) => { return () => document.removeEventListener("mousedown", handleClickOutside); }, [typeOpen]); + // Ensure defaults so new items behave + useEffect(() => { + if (!inputs.entityType) updateInputs("entityType", EntityType.Individual); + if (inputs.beginning === undefined) updateInputs("beginning", -1); + if (inputs.ending === undefined) updateInputs("ending", Model.END_OF_TIME); + }, [show]); // when opening the dialog + + // Systems list for parent dropdown + const availableSystems = useMemo( + () => + Array.from(dataset.individuals.values()).filter( + (i) => (i.entityType ?? EntityType.Individual) === EntityType.System + ), + [dataset] + ); + const handleClose = () => { setShow(false); setInputs(defaultIndividual); @@ -494,6 +511,81 @@ const SetIndividual = (props: Props) => { onChange={handleEndsWithParticipant} /> + + {/* Entity type selection */} + + Entity type + + updateInputs("entityType", e.target.value as EntityType) + } + > + + + + + + + {/* Parent system – only for components */} + {(inputs.entityType ?? EntityType.Individual) === + EntityType.SystemComponent && ( + + Parent system + + updateInputs("parentSystemId", e.target.value || undefined) + } + > + + {availableSystems.map((s) => ( + + ))} + + + )} + + {/* Temporal bounds */} + + Beginning (time) + + updateInputs( + "beginning", + e.target.value === "" ? -1 : parseInt(e.target.value, 10) + ) + } + placeholder="Leave empty to start at earliest" + /> + + + + Ending (time) + + updateInputs( + "ending", + e.target.value === "" + ? Model.END_OF_TIME + : parseInt(e.target.value, 10) + ) + } + placeholder="Leave empty to extend to end" + /> + diff --git a/editor-app/lib/Model.ts b/editor-app/lib/Model.ts index 8a1336c..bd57d25 100644 --- a/editor-app/lib/Model.ts +++ b/editor-app/lib/Model.ts @@ -1,6 +1,12 @@ /* eslint-disable max-classes-per-file */ import { HQDM_NS } from "@apollo-protocol/hqdm-lib"; -import type { Activity, Id, Individual, Maybe } from "./Schema.js"; +import type { + Activity, + Id, + Individual, + Installation, + Maybe, +} from "./Schema.js"; import { EPOCH_END } from "./ActivityLib"; /** @@ -22,8 +28,8 @@ export class Kind { * A class that is the UI Model. */ export class Model { - readonly activities: Map; - readonly individuals: Map; + readonly activities: Map; + readonly individuals: Map; readonly roles: Array; readonly activityTypes: Array; @@ -38,12 +44,15 @@ export class Model { description: Maybe; filename: string; + // NEW: central store of installations + installations: Map; + constructor(name?: string, description?: string) { this.name = name; this.description = description; this.filename = "activity_diagram.ttl"; - this.activities = new Map(); - this.individuals = new Map(); + this.activities = new Map(); + this.individuals = new Map(); /* XXX There is an inconsistency here. Most objects in the UI model * have their id set to a plain UUID, i.e. not to an IRI. These core @@ -61,6 +70,8 @@ export class Model { new Kind(HQDM_NS + "ordinary_physical_object", "Resource", true), ]; this.defaultIndividualType = this.individualTypes[2]; + + this.installations = new Map(); } static END_OF_TIME = EPOCH_END; @@ -377,4 +388,44 @@ export class Model { isDescendant(ancestorId: Id, descendantId: Id): boolean { return this.isAncestor(ancestorId, descendantId); } + + // Persist/update an individual as usual + setIndividual(individual: Individual) { + // ...existing code to put into this.individuals... + this.individuals.set(individual.id, individual); + + // Also mirror any inline installations into the central map + if (individual.installations) { + individual.installations.forEach((inst) => { + this.installations.set(inst.id, inst); + }); + } + } + + // NEW: convenience methods for managing installations + addInstallation(inst: Installation) { + this.installations.set(inst.id, inst); + + const comp = this.individuals.get(inst.componentId); + if (comp) { + const list = [...(comp.installations ?? [])].filter( + (i) => i.id !== inst.id + ); + list.push(inst); + comp.installations = list; + this.individuals.set(comp.id, comp); + } + } + + removeInstallation(id: Id) { + const inst = this.installations.get(id); + if (inst) { + const comp = this.individuals.get(inst.componentId); + if (comp && comp.installations) { + comp.installations = comp.installations.filter((i) => i.id !== id); + this.individuals.set(comp.id, comp); + } + } + this.installations.delete(id); + } } diff --git a/editor-app/lib/Schema.ts b/editor-app/lib/Schema.ts index dd071a4..99cad32 100644 --- a/editor-app/lib/Schema.ts +++ b/editor-app/lib/Schema.ts @@ -2,8 +2,8 @@ * This file contains the schema for the data model. */ -import type { Maybe } from '@apollo-protocol/hqdm-lib'; -import type { Kind } from './Model'; +import type { Maybe } from "@apollo-protocol/hqdm-lib"; +import type { Kind } from "./Model"; export { Maybe }; export type Id = string; @@ -19,7 +19,6 @@ export interface STExtent { beginning: number; ending: number; } - /** * An activity is a thing that happens over time. @@ -35,6 +34,9 @@ export interface Activity extends STExtent { export interface Individual extends STExtent { beginsWithParticipant: boolean; //not persisted to HQDM. Indicates that the beginning time should be synchronised to participants. endsWithParticipant: boolean; //not persisted to HQDM. Indicates that the ending time should be synchronised to participants. + entityType?: EntityType; // defaults to individual when absent + parentSystemId?: Id; // only used when entityType === SystemComponent + installations?: Installation[]; // optional list of installation periods } /** @@ -44,3 +46,28 @@ export interface Participation { individualId: Id; role: Maybe; } + +// Add entity typing for Individuals +export enum EntityType { + Individual = "individual", + System = "system", + SystemComponent = "systemComponent", +} + +// Temporal extent is already defined as STExtent in your schema. +// We add an Installation that reuses beginning/ending. +export interface Installation extends STExtent { + id: Id; + componentId: Id; // the component being installed + systemId: Id; // the system it is installed into +} + +// Extend Individual with system fields (kept optional for back-compat) +export interface Individual extends STExtent { + beginsWithParticipant: boolean; + endsWithParticipant: boolean; + + entityType?: EntityType; + parentSystemId?: Id; + installations?: Installation[]; +} From 585295ecd70a95712075a13efddf128e5b6289ec Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Mon, 24 Nov 2025 15:59:33 +0000 Subject: [PATCH 15/81] Enhance ActivityDiagram and related components to support sorted individuals; refactor individual sorting logic and update props for improved rendering and organization. --- editor-app/components/ActivityDiagram.tsx | 10 +- editor-app/components/ActivityDiagramWrap.tsx | 69 ++++++++++- editor-app/diagram/DrawActivityDiagram.ts | 16 ++- editor-app/diagram/DrawHelpers.ts | 3 +- editor-app/diagram/DrawIndividuals.ts | 112 ++++++++++-------- 5 files changed, 146 insertions(+), 64 deletions(-) diff --git a/editor-app/components/ActivityDiagram.tsx b/editor-app/components/ActivityDiagram.tsx index e8ead69..f1edcac 100644 --- a/editor-app/components/ActivityDiagram.tsx +++ b/editor-app/components/ActivityDiagram.tsx @@ -19,7 +19,8 @@ interface Props { rightClickActivity: (a: Activity) => void; rightClickParticipation: (a: Activity, p: Participation) => void; svgRef: MutableRefObject; - hideNonParticipating?: boolean; + hideNonParticipating: boolean; + sortedIndividuals?: Individual[]; } const ActivityDiagram = (props: Props) => { @@ -35,7 +36,8 @@ const ActivityDiagram = (props: Props) => { rightClickActivity, rightClickParticipation, svgRef, - hideNonParticipating = false, + hideNonParticipating, + sortedIndividuals, } = props; const [plot, setPlot] = useState({ @@ -56,7 +58,8 @@ const ActivityDiagram = (props: Props) => { rightClickIndividual, rightClickActivity, rightClickParticipation, - hideNonParticipating + hideNonParticipating, + sortedIndividuals ) ); }, [ @@ -71,6 +74,7 @@ const ActivityDiagram = (props: Props) => { rightClickActivity, rightClickParticipation, hideNonParticipating, + sortedIndividuals, ]); const buildCrumbs = () => { diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index d9c6bac..d8ec47c 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -12,7 +12,14 @@ import SortIndividuals from "./SortIndividuals"; import SetParticipation from "./SetParticipation"; import Undo from "./Undo"; import { Model } from "@/lib/Model"; -import { Activity, Id, Individual, Maybe, Participation } from "@/lib/Schema"; +import { + Activity, + Id, + Individual, + Maybe, + Participation, + EntityType, +} from "@/lib/Schema"; import ExportJson from "./ExportJson"; import ExportSvg from "./ExportSvg"; import { Button } from "react-bootstrap"; @@ -119,11 +126,60 @@ export default function ActivityDiagramWrap() { ); }; - const individualsArray: Individual[] = []; - dataset.individuals.forEach((i: Individual) => individualsArray.push(i)); + // Sort individuals: Systems first with their components grouped underneath + const individualsArray = Array.from(dataset.individuals.values()); - const activitiesArray: Activity[] = []; - dataset.activities.forEach((a: Activity) => activitiesArray.push(a)); + const sortedIndividuals = [...individualsArray].sort((a, b) => { + // Helper: Get group key - systems use own ID, components use parent ID + const getGroupKey = (ind: Individual) => { + if ((ind.entityType ?? EntityType.Individual) === EntityType.System) { + return ind.id; + } + if ( + (ind.entityType ?? EntityType.Individual) === + EntityType.SystemComponent && + ind.parentSystemId + ) { + return ind.parentSystemId; + } + return `_individual_${ind.id}`; // Regular individuals get unique keys + }; + + const groupA = getGroupKey(a); + const groupB = getGroupKey(b); + + // Different groups? Systems/components come first, then individuals + if (groupA !== groupB) { + const aIsIndividual = + (a.entityType ?? EntityType.Individual) === EntityType.Individual; + const bIsIndividual = + (b.entityType ?? EntityType.Individual) === EntityType.Individual; + + if (aIsIndividual && !bIsIndividual) return 1; // a after b + if (!aIsIndividual && bIsIndividual) return -1; // a before b + + return groupA.localeCompare(groupB); + } + + // Same group: System first, then components alphabetically + const aIsSystem = + (a.entityType ?? EntityType.Individual) === EntityType.System; + const bIsSystem = + (b.entityType ?? EntityType.Individual) === EntityType.System; + const aIsComponent = + (a.entityType ?? EntityType.Individual) === EntityType.SystemComponent; + const bIsComponent = + (b.entityType ?? EntityType.Individual) === EntityType.SystemComponent; + + if (aIsSystem && bIsComponent) return -1; + if (aIsComponent && bIsSystem) return 1; + + // Both same type: sort by name + return a.name.localeCompare(b.name); + }); + + // Build an array of activities from the dataset so it can be filtered below + const activitiesArray = Array.from(dataset.activities.values()); // Filter activities for the current context let activitiesInView: Activity[] = []; @@ -164,7 +220,7 @@ export default function ActivityDiagramWrap() { diff --git a/editor-app/diagram/DrawActivityDiagram.ts b/editor-app/diagram/DrawActivityDiagram.ts index ab3f736..b36c3a3 100644 --- a/editor-app/diagram/DrawActivityDiagram.ts +++ b/editor-app/diagram/DrawActivityDiagram.ts @@ -20,6 +20,7 @@ import { import { clickParticipations, drawParticipations } from "./DrawParticipations"; import * as d3 from "d3"; import { Model } from "@/lib/Model"; +import { EntityType } from "@/lib/Schema"; export interface Plot { width: number; @@ -44,14 +45,19 @@ export function drawActivityDiagram( rightClickIndividual: (i: Individual) => void, rightClickActivity: (a: Activity) => void, rightClickParticipation: (a: Activity, p: Participation) => void, - hideNonParticipating: boolean = false + hideNonParticipating: boolean = false, + sortedIndividuals?: Individual[] ) { - //Prepare Model data into arrays - let individualsArray: Individual[] = []; + // Prepare Model data into arrays + // Use sorted individuals if provided, otherwise use dataset order + let individualsArray: Individual[] = sortedIndividuals || []; + if (!sortedIndividuals) { + dataset.individuals.forEach((i: Individual) => individualsArray.push(i)); + } + const activitiesArray: Activity[] = []; const { individuals, activities } = dataset; - individuals.forEach((i: Individual) => individualsArray.push(i)); activities.forEach((a: Activity) => { if (a.partOf === activityContext) activitiesArray.push(a); }); @@ -78,7 +84,7 @@ export function drawActivityDiagram( tooltip, dataset, activities: activitiesArray, - individuals: individualsArray, + individuals: individualsArray, // Pass sorted individuals }; drawIndividuals(drawCtx); diff --git a/editor-app/diagram/DrawHelpers.ts b/editor-app/diagram/DrawHelpers.ts index 2e6432c..d838ccc 100644 --- a/editor-app/diagram/DrawHelpers.ts +++ b/editor-app/diagram/DrawHelpers.ts @@ -9,10 +9,9 @@ export interface DrawContext { config: ConfigData; svgElement: any; tooltip: any; - dataset: Model; activities: Activity[]; - individuals: Individual[]; + individuals: Individual[]; // Add sorted individuals list } export interface Label { diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index 06a2310..6502868 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -1,5 +1,5 @@ import { MouseEvent } from "react"; -import { Activity, Individual } from "@/lib/Schema"; +import { Activity, Individual, EntityType } from "@/lib/Schema"; import { Model } from "@/lib/Model"; import { DrawContext, @@ -21,7 +21,9 @@ interface Layout { } export function drawIndividuals(ctx: DrawContext) { - const { config, svgElement, individuals, activities } = ctx; + const { config, svgElement, activities } = ctx; + // Use sorted individuals from context + const individuals = ctx.individuals; let startOfTime = Math.min(...activities.map((a) => a.beginning)); let endOfTime = Math.max(...activities.map((a) => a.ending)); @@ -46,7 +48,8 @@ export function drawIndividuals(ctx: DrawContext) { } const chevOff = config.layout.individual.height / 3; - const fullWidth = chevOff + + const fullWidth = + chevOff + config.viewPort.x * config.viewPort.zoom + config.layout.individual.temporalMargin - config.layout.individual.xMargin * 2; @@ -54,35 +57,43 @@ export function drawIndividuals(ctx: DrawContext) { const layout = new Map(); /* yuck */ - let next_y = config.layout.individual.topMargin + config.layout.individual.gap; + let next_y = + config.layout.individual.topMargin + config.layout.individual.gap; for (const i of individuals) { const start = i.beginning >= startOfTime; const stop = i.ending <= endOfTime; - const x = start - ? lhs_x + timeInterval * (i.beginning - startOfTime) - : config.layout.individual.xMargin - chevOff; + // Indent system components by 30px + const isComponent = + (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent; + const indent = isComponent ? 30 : 0; + + const x = + (start + ? lhs_x + timeInterval * (i.beginning - startOfTime) + : config.layout.individual.xMargin - chevOff) + indent; const y = next_y; next_y = y + config.layout.individual.height + config.layout.individual.gap; - const w = - (!start && !stop) ? fullWidth - : (start && !stop) ? ( - (endOfTime - i.beginning) * timeInterval + - config.layout.individual.temporalMargin - ) - : (!start && stop) ? ( - fullWidth - - (endOfTime - i.ending) * timeInterval - - config.layout.individual.temporalMargin - ) - : (i.ending - i.beginning) * timeInterval; + const w = + !start && !stop + ? fullWidth - indent + : start && !stop + ? (endOfTime - i.beginning) * timeInterval + + config.layout.individual.temporalMargin - + indent + : !start && stop + ? fullWidth - + indent - + (endOfTime - i.ending) * timeInterval - + config.layout.individual.temporalMargin + : (i.ending - i.beginning) * timeInterval - indent; const h = config.layout.individual.height; layout.set(i.id, { x, y, w, h, start, stop }); - }; + } svgElement .selectAll(".individual") @@ -92,11 +103,13 @@ export function drawIndividuals(ctx: DrawContext) { .attr("id", (d: Individual) => "i" + d["id"]) .attr("d", (i: Individual) => { const { x, y, w, h, start, stop } = layout.get(i.id)!; - return `M ${x} ${y} l ${w} 0` - + (stop ? `l 0 ${h}` : `l ${chevOff} ${h/2} ${-chevOff} ${h/2}`) - + `l ${-w} 0` - + (start ? "" : `l ${chevOff} ${-h/2} ${-chevOff} ${-h/2}`) - + "Z"; + return ( + `M ${x} ${y} l ${w} 0` + + (stop ? `l 0 ${h}` : `l ${chevOff} ${h / 2} ${-chevOff} ${h / 2}`) + + `l ${-w} 0` + + (start ? "" : `l ${chevOff} ${-h / 2} ${-chevOff} ${-h / 2}`) + + "Z" + ); }) .attr("stroke", config.presentation.individual.stroke) .attr("stroke-width", config.presentation.individual.strokeWidth) @@ -158,10 +171,12 @@ export function clickIndividuals( rightClickIndividual(i); }; - svgElement.select("#i" + i.id) + svgElement + .select("#i" + i.id) .on("click", lclick) .on("contextmenu", rclick); - svgElement.select("#il" + i.id) + svgElement + .select("#il" + i.id) .on("click", lclick) .on("contextmenu", rclick); }); @@ -183,30 +198,31 @@ export function labelIndividuals(ctx: DrawContext) { config.labels.individual.topMargin; svgElement - .selectAll(".individualLabel") - .data(individuals.values()) + .selectAll(".individual-label") + .data(individuals) .join("text") - .attr("class", "individualLabel") - .attr("id", (i: Individual) => `il${i.id}`) - .attr( - "x", - config.layout.individual.xMargin + config.labels.individual.leftMargin - ) - .attr("y", () => { - const oldY = y; - y = y + config.layout.individual.height + config.layout.individual.gap; - return oldY; + .attr("class", "individual-label") + .attr("id", (i: Individual) => "individual-label-" + i.id) + .attr("x", (i: Individual) => { + const isComponent = + (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent; + const indent = isComponent ? 30 : 0; + return config.layout.individual.xMargin + indent + 5; + }) + .attr("y", (i: Individual, index: number) => { + return ( + y + + index * (config.layout.individual.height + config.layout.individual.gap) + ); }) - .attr("text-anchor", "start") - .attr("font-family", "Roboto, Arial, sans-serif") .attr("font-size", config.labels.individual.fontSize) - .text((d: Individual) => { - let label = d["name"]; - if (label.length > config.labels.individual.maxChars) { - label = label.substring(0, config.labels.individual.maxChars); - label += "..."; - } - return label; + .attr("fill", config.labels.individual.color) + .text((i: Individual) => { + // Add arrow prefix for system components + const isComponent = + (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent; + const prefix = isComponent ? "↳ " : ""; + return prefix + i.name; }) .each((d: Individual, i: number, nodes: SVGGraphicsElement[]) => { removeLabelIfItOverlaps(labels, nodes[i]); From dbecd1e171fe4c3fa40cfd90b5dbc2ede8e0b79e Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 25 Nov 2025 10:23:59 +0000 Subject: [PATCH 16/81] Enhance ActivityDiagram and related components to support installation periods; refactor individual sorting logic and update props for improved rendering and organization. --- editor-app/components/ActivityDiagramWrap.tsx | 156 +++++++---- editor-app/components/SetIndividual.tsx | 259 ++++++++++++++++-- editor-app/diagram/DrawActivityDiagram.ts | 14 + editor-app/diagram/DrawIndividuals.ts | 121 +++++++- editor-app/diagram/DrawInstallations.ts | 127 +++++++++ editor-app/lib/Schema.ts | 44 ++- 6 files changed, 611 insertions(+), 110 deletions(-) create mode 100644 editor-app/diagram/DrawInstallations.ts diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index d8ec47c..fbb1c35 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useRef, Dispatch } from "react"; +import { useEffect, useState, useRef, Dispatch, useMemo } from "react"; import { config } from "@/diagram/config"; import SetIndividual from "@/components/SetIndividual"; import SetActivity from "@/components/SetActivity"; @@ -87,6 +87,9 @@ export default function ActivityDiagramWrap() { const svgRef = useRef(null); + // Build an array of individuals from the dataset + const individualsArray = Array.from(dataset.individuals.values()); + const deleteIndividual = (id: string) => { updateDataset((d: Model) => d.removeIndividual(id)); }; @@ -126,57 +129,116 @@ export default function ActivityDiagramWrap() { ); }; - // Sort individuals: Systems first with their components grouped underneath - const individualsArray = Array.from(dataset.individuals.values()); + // Sort individuals to show nested hierarchy + const sortedIndividuals = useMemo(() => { + const result: Individual[] = []; + const visited = new Set(); - const sortedIndividuals = [...individualsArray].sort((a, b) => { - // Helper: Get group key - systems use own ID, components use parent ID - const getGroupKey = (ind: Individual) => { - if ((ind.entityType ?? EntityType.Individual) === EntityType.System) { - return ind.id; - } - if ( - (ind.entityType ?? EntityType.Individual) === - EntityType.SystemComponent && - ind.parentSystemId - ) { + // Helper: Get the "parent" of an individual + // - For SystemComponents: parentSystemId + // - For InstalledComponents: the targetId of their first installation (if any) + const getParentId = (ind: Individual): string | undefined => { + const entityType = ind.entityType ?? EntityType.Individual; + + if (entityType === EntityType.SystemComponent) { return ind.parentSystemId; } - return `_individual_${ind.id}`; // Regular individuals get unique keys + + if (entityType === EntityType.InstalledComponent) { + // InstalledComponent's parent is the SystemComponent it's installed into + if (ind.installations && ind.installations.length > 0) { + return ind.installations[0].targetId; + } + } + + return undefined; }; - const groupA = getGroupKey(a); - const groupB = getGroupKey(b); - - // Different groups? Systems/components come first, then individuals - if (groupA !== groupB) { - const aIsIndividual = - (a.entityType ?? EntityType.Individual) === EntityType.Individual; - const bIsIndividual = - (b.entityType ?? EntityType.Individual) === EntityType.Individual; - - if (aIsIndividual && !bIsIndividual) return 1; // a after b - if (!aIsIndividual && bIsIndividual) return -1; // a before b - - return groupA.localeCompare(groupB); - } - - // Same group: System first, then components alphabetically - const aIsSystem = - (a.entityType ?? EntityType.Individual) === EntityType.System; - const bIsSystem = - (b.entityType ?? EntityType.Individual) === EntityType.System; - const aIsComponent = - (a.entityType ?? EntityType.Individual) === EntityType.SystemComponent; - const bIsComponent = - (b.entityType ?? EntityType.Individual) === EntityType.SystemComponent; - - if (aIsSystem && bIsComponent) return -1; - if (aIsComponent && bIsSystem) return 1; - - // Both same type: sort by name - return a.name.localeCompare(b.name); - }); + // Recursive function to add an individual and its descendants + const addWithDescendants = (ind: Individual) => { + if (visited.has(ind.id)) return; + visited.add(ind.id); + result.push(ind); + + // Find children (SystemComponents or InstalledComponents whose parent/target is this individual) + const children = individualsArray.filter((child) => { + const childEntityType = child.entityType ?? EntityType.Individual; + + // SystemComponents with this as parent + if ( + childEntityType === EntityType.SystemComponent && + child.parentSystemId === ind.id + ) { + return true; + } + + // InstalledComponents installed into this (if this is a SystemComponent) + if ( + childEntityType === EntityType.InstalledComponent && + child.installations + ) { + return child.installations.some((inst) => inst.targetId === ind.id); + } + + return false; + }); + + // Sort children by name and add them + children + .sort((a, b) => a.name.localeCompare(b.name)) + .forEach((child) => addWithDescendants(child)); + }; + + // Start with top-level items (Systems, regular Individuals, or things with no parent) + const topLevel = individualsArray.filter((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + + // Systems are always top-level + if (entityType === EntityType.System) return true; + + // Regular individuals are top-level + if (entityType === EntityType.Individual) return true; + + // SystemComponents without a parent are top-level (shouldn't happen, but handle it) + if (entityType === EntityType.SystemComponent && !ind.parentSystemId) + return true; + + // InstalledComponents without installations are top-level + if (entityType === EntityType.InstalledComponent) { + if (!ind.installations || ind.installations.length === 0) return true; + // Also top-level if their target doesn't exist + const targetExists = ind.installations.some((inst) => + individualsArray.some((i) => i.id === inst.targetId) + ); + return !targetExists; + } + + return false; + }); + + // Add all top-level items with their descendants + topLevel + .sort((a, b) => { + // Systems first, then individuals + const aIsSystem = + (a.entityType ?? EntityType.Individual) === EntityType.System; + const bIsSystem = + (b.entityType ?? EntityType.Individual) === EntityType.System; + if (aIsSystem && !bIsSystem) return -1; + if (!aIsSystem && bIsSystem) return 1; + return a.name.localeCompare(b.name); + }) + .forEach((ind) => addWithDescendants(ind)); + + // Add any remaining individuals that weren't visited (orphans) + individualsArray.forEach((ind) => { + if (!visited.has(ind.id)) { + result.push(ind); + } + }); + + return result; + }, [individualsArray]); // Build an array of activities from the dataset so it can be filtered below const activitiesArray = Array.from(dataset.activities.values()); diff --git a/editor-app/components/SetIndividual.tsx b/editor-app/components/SetIndividual.tsx index e0cfdd5..ba3feef 100644 --- a/editor-app/components/SetIndividual.tsx +++ b/editor-app/components/SetIndividual.tsx @@ -13,9 +13,10 @@ import Col from "react-bootstrap/Col"; import Form from "react-bootstrap/Form"; import Container from "react-bootstrap/Container"; import { Model } from "../lib/Model"; -import { EntityType, Individual } from "../lib/Schema"; +import { EntityType, Individual, Installation } from "../lib/Schema"; import { v4 as uuidv4 } from "uuid"; import { Alert, InputGroup } from "react-bootstrap"; +import Card from "react-bootstrap/Card"; interface Props { deleteIndividual: (id: string) => void; @@ -108,13 +109,27 @@ const SetIndividual = (props: Props) => { if (!inputs.entityType) updateInputs("entityType", EntityType.Individual); if (inputs.beginning === undefined) updateInputs("beginning", -1); if (inputs.ending === undefined) updateInputs("ending", Model.END_OF_TIME); + if (!inputs.installations) updateInputs("installations", []); }, [show]); // when opening the dialog - // Systems list for parent dropdown - const availableSystems = useMemo( + // Systems AND installed components can contain component slots + const availableParents = useMemo( () => Array.from(dataset.individuals.values()).filter( - (i) => (i.entityType ?? EntityType.Individual) === EntityType.System + (i) => + (i.entityType ?? EntityType.Individual) === EntityType.System || + (i.entityType ?? EntityType.Individual) === + EntityType.InstalledComponent + ), + [dataset] + ); + + // Only SystemComponents can be installation targets (they're the slots!) + const availableInstallationTargets = useMemo( + () => + Array.from(dataset.individuals.values()).filter( + (i) => + (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent ), [dataset] ); @@ -138,15 +153,34 @@ const SetIndividual = (props: Props) => { setInputs(defaultIndividual); } }; - const handleAdd = (event: any) => { - event.preventDefault(); - if (!dirty) return handleClose(); - const isValid = validateInputs(); - if (isValid) { - setIndividual(inputs); - handleClose(); - } + // ...existing code... + + const handleAdd = () => { + // Generate ID if missing (new individual) + const id = inputs.id || "i" + Date.now(); + + // Create complete individual object + const newInd: Individual = { + id: id, + name: inputs.name || "Unnamed", + description: inputs.description || "", + type: inputs.type, + beginning: inputs.beginning ?? -1, + ending: inputs.ending ?? 100, // Default to 100 if Model.END_OF_TIME is not available + beginsWithParticipant: inputs.beginsWithParticipant ?? false, + endsWithParticipant: inputs.endsWithParticipant ?? false, + + // New fields + entityType: inputs.entityType ?? EntityType.Individual, + parentSystemId: inputs.parentSystemId, + installations: inputs.installations ?? [], + }; + + setIndividual(newInd); + handleClose(); }; + + // ...existing code... const handleDelete = (event: any) => { deleteIndividual(inputs.id); handleClose(); @@ -309,6 +343,42 @@ const SetIndividual = (props: Props) => { }; // ----- end helpers ----- + // Load selected individual data when dialog opens + useEffect(() => { + if (show && selectedIndividual) { + // Populate form with existing individual data + setInputs({ + id: selectedIndividual.id, + name: selectedIndividual.name || "", + description: selectedIndividual.description || "", + type: selectedIndividual.type, + beginning: selectedIndividual.beginning, + ending: selectedIndividual.ending, + beginsWithParticipant: + selectedIndividual.beginsWithParticipant ?? false, + endsWithParticipant: selectedIndividual.endsWithParticipant ?? false, + entityType: selectedIndividual.entityType ?? EntityType.Individual, + parentSystemId: selectedIndividual.parentSystemId, + installations: selectedIndividual.installations ?? [], + }); + } else if (show && !selectedIndividual) { + // Reset form for new individual + setInputs({ + id: "", + name: "", + description: "", + type: undefined, + beginning: -1, + ending: Model.END_OF_TIME, + beginsWithParticipant: false, + endsWithParticipant: false, + entityType: EntityType.Individual, + parentSystemId: undefined, + installations: [], + }); + } + }, [show, selectedIndividual]); + return ( <> +
+ + + + Install Into Slot (SystemComponent) + + { + const updated = (inputs.installations || []).map( + (i) => + i.id === inst.id + ? { ...i, targetId: e.target.value } + : i + ); + updateInputs("installations", updated); + }} + > + + {availableInstallationTargets.map((slot) => ( + + ))} + + + The slot/position this object will occupy + + + + + + + Attached from + { + const updated = ( + inputs.installations || [] + ).map((i) => + i.id === inst.id + ? { + ...i, + beginning: parseInt(e.target.value, 10), + } + : i + ); + updateInputs("installations", updated); + }} + /> + + + + + Attached until + { + const updated = ( + inputs.installations || [] + ).map((i) => + i.id === inst.id + ? { + ...i, + ending: parseInt(e.target.value, 10), + } + : i + ); + updateInputs("installations", updated); + }} + /> + + + + + ))} + + +
+
+ )} + + {/* Temporal bounds - same for all types */} Beginning (time) individualsArray.push(i)); + } else { + console.log("Using sortedIndividuals:", sortedIndividuals.length); } const activitiesArray: Activity[] = []; @@ -62,6 +66,8 @@ export function drawActivityDiagram( if (a.partOf === activityContext) activitiesArray.push(a); }); + console.log("individualsArray before filter:", individualsArray.length); + if (hideNonParticipating) { const participating = new Set(); activitiesArray.forEach((a) => @@ -72,6 +78,8 @@ export function drawActivityDiagram( individualsArray = individualsArray.filter((i) => participating.has(i.id)); } + console.log("individualsArray after filter:", individualsArray.length); + //Draw Diagram parts const svgElement = clearDiagram(svgRef); const individualsMap = new Map(individualsArray.map((i) => [i.id, i])); @@ -87,6 +95,11 @@ export function drawActivityDiagram( individuals: individualsArray, // Pass sorted individuals }; + console.log( + "DrawContext created with individuals:", + drawCtx.individuals.length + ); + drawIndividuals(drawCtx); hoverIndividuals(drawCtx); labelIndividuals(drawCtx); @@ -95,6 +108,7 @@ export function drawActivityDiagram( hoverActivities(drawCtx); clickActivities(drawCtx, clickActivity, rightClickActivity); drawParticipations(drawCtx); + drawInstallations(drawCtx); clickParticipations(drawCtx, clickParticipation, rightClickParticipation); drawAxisArrows(drawCtx, height); let plot: Plot = { diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index 6502868..c2f08d9 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -21,10 +21,20 @@ interface Layout { } export function drawIndividuals(ctx: DrawContext) { - const { config, svgElement, activities } = ctx; - // Use sorted individuals from context + const { config, svgElement, activities, dataset } = ctx; const individuals = ctx.individuals; + console.log( + "DrawIndividuals received individuals:", + individuals.length, + individuals.map((i) => i.name) + ); + + if (!individuals || individuals.length === 0) { + console.warn("No individuals to draw!"); + return svgElement; + } + let startOfTime = Math.min(...activities.map((a) => a.beginning)); let endOfTime = Math.max(...activities.map((a) => a.ending)); let duration = endOfTime - startOfTime; @@ -56,17 +66,59 @@ export function drawIndividuals(ctx: DrawContext) { const layout = new Map(); - /* yuck */ + // Helper: Calculate indentation level based on parent chain + const getIndentLevel = (ind: Individual): number => { + let level = 0; + let currentId: string | undefined = undefined; + const visited = new Set(); + + const entityType = ind.entityType ?? EntityType.Individual; + + if (entityType === EntityType.SystemComponent) { + currentId = ind.parentSystemId; + } else if (entityType === EntityType.InstalledComponent) { + // InstalledComponent's parent is the SystemComponent it's installed into + if (ind.installations && ind.installations.length > 0) { + currentId = ind.installations[0].targetId; + } + } + + // Walk up the parent chain + while (currentId && !visited.has(currentId)) { + visited.add(currentId); + level++; + + const parent = dataset.individuals.get(currentId); + if (!parent) break; + + const parentType = parent.entityType ?? EntityType.Individual; + + if (parentType === EntityType.SystemComponent) { + currentId = parent.parentSystemId; + } else if (parentType === EntityType.InstalledComponent) { + if (parent.installations && parent.installations.length > 0) { + currentId = parent.installations[0].targetId; + } else { + break; + } + } else { + break; // Systems and Individuals don't have parents + } + } + + return level; + }; + + /* Layout calculation */ let next_y = config.layout.individual.topMargin + config.layout.individual.gap; for (const i of individuals) { const start = i.beginning >= startOfTime; const stop = i.ending <= endOfTime; - // Indent system components by 30px - const isComponent = - (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent; - const indent = isComponent ? 30 : 0; + // Calculate indent based on hierarchy depth (30px per level) + const indentLevel = getIndentLevel(i); + const indent = indentLevel * 30; const x = (start @@ -183,12 +235,54 @@ export function clickIndividuals( } export function labelIndividuals(ctx: DrawContext) { - const { config, svgElement, individuals } = ctx; + const { config, svgElement, dataset } = ctx; + const individuals = ctx.individuals; if (config.labels.individual.enabled === false) { return; } + // Same helper as above + const getIndentLevel = (ind: Individual): number => { + let level = 0; + let currentId: string | undefined = undefined; + const visited = new Set(); + + const entityType = ind.entityType ?? EntityType.Individual; + + if (entityType === EntityType.SystemComponent) { + currentId = ind.parentSystemId; + } else if (entityType === EntityType.InstalledComponent) { + if (ind.installations && ind.installations.length > 0) { + currentId = ind.installations[0].targetId; + } + } + + while (currentId && !visited.has(currentId)) { + visited.add(currentId); + level++; + + const parent = dataset.individuals.get(currentId); + if (!parent) break; + + const parentType = parent.entityType ?? EntityType.Individual; + + if (parentType === EntityType.SystemComponent) { + currentId = parent.parentSystemId; + } else if (parentType === EntityType.InstalledComponent) { + if (parent.installations && parent.installations.length > 0) { + currentId = parent.installations[0].targetId; + } else { + break; + } + } else { + break; + } + } + + return level; + }; + let labels: Label[] = []; let y = @@ -204,9 +298,8 @@ export function labelIndividuals(ctx: DrawContext) { .attr("class", "individual-label") .attr("id", (i: Individual) => "individual-label-" + i.id) .attr("x", (i: Individual) => { - const isComponent = - (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent; - const indent = isComponent ? 30 : 0; + const indentLevel = getIndentLevel(i); + const indent = indentLevel * 30; return config.layout.individual.xMargin + indent + 5; }) .attr("y", (i: Individual, index: number) => { @@ -218,10 +311,8 @@ export function labelIndividuals(ctx: DrawContext) { .attr("font-size", config.labels.individual.fontSize) .attr("fill", config.labels.individual.color) .text((i: Individual) => { - // Add arrow prefix for system components - const isComponent = - (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent; - const prefix = isComponent ? "↳ " : ""; + const indentLevel = getIndentLevel(i); + const prefix = indentLevel > 0 ? "↳ " : ""; return prefix + i.name; }) .each((d: Individual, i: number, nodes: SVGGraphicsElement[]) => { diff --git a/editor-app/diagram/DrawInstallations.ts b/editor-app/diagram/DrawInstallations.ts new file mode 100644 index 0000000..766d615 --- /dev/null +++ b/editor-app/diagram/DrawInstallations.ts @@ -0,0 +1,127 @@ +import { DrawContext } from "./DrawHelpers"; +import { EntityType, Installation, Individual } from "@/lib/Schema"; + +export function drawInstallations(ctx: DrawContext) { + const { svgElement, individuals, activities, config } = ctx; + + // Only process installed components + const installedComponents = individuals.filter( + (i) => + (i.entityType ?? EntityType.Individual) === EntityType.InstalledComponent + ); + + if (installedComponents.length === 0) return; + + // Calculate time scale using BOTH activities AND individuals + let startOfTime = Infinity; + let endOfTime = -Infinity; + + // Consider activities + activities.forEach((a) => { + if (a.beginning < startOfTime) startOfTime = a.beginning; + if (a.ending > endOfTime) endOfTime = a.ending; + }); + + // Also consider individuals (in case there are no activities) + individuals.forEach((i) => { + if (i.beginning >= 0 && i.beginning < startOfTime) + startOfTime = i.beginning; + if (i.ending < 1000000 && i.ending > endOfTime) endOfTime = i.ending; + }); + + // Default fallback + if (!isFinite(startOfTime)) startOfTime = 0; + if (!isFinite(endOfTime)) endOfTime = 100; + + const duration = endOfTime - startOfTime; + let totalLeftMargin = + config.viewPort.x * config.viewPort.zoom - + config.layout.individual.xMargin * 2; + totalLeftMargin -= config.layout.individual.temporalMargin; + + const timeInterval = duration > 0 ? totalLeftMargin / duration : 1; + const lhs_x = + config.layout.individual.xMargin + + config.layout.individual.temporalMargin + + (config.labels.individual.enabled + ? config.layout.individual.textLength + : 0); + + // Collect all installations + const installations: Array<{ inst: Installation; component: Individual }> = + []; + installedComponents.forEach((comp) => { + if (comp.installations && comp.installations.length > 0) { + comp.installations.forEach((inst) => { + installations.push({ inst, component: comp }); + }); + } + }); + + if (installations.length === 0 || timeInterval <= 0) return; + + // Create or get the defs element for patterns + let defs = svgElement.select("defs"); + if (defs.empty()) { + defs = svgElement.append("defs"); + } + + // Create diagonal hatch pattern if it doesn't exist + if (defs.select("#diagonal-hatch").empty()) { + const pattern = defs + .append("pattern") + .attr("id", "diagonal-hatch") + .attr("patternUnits", "userSpaceOnUse") + .attr("width", 8) + .attr("height", 8) + .attr("patternTransform", "rotate(45)"); + + // Background (optional - remove for transparent background) + pattern + .append("rect") + .attr("width", 8) + .attr("height", 8) + .attr("fill", "white") + .attr("fill-opacity", 0.3); + + // Diagonal lines + pattern + .append("line") + .attr("x1", 0) + .attr("y1", 0) + .attr("x2", 0) + .attr("y2", 8) + .attr("stroke", "#374151") // Gray-700 + .attr("stroke-width", 1.5); + } + + // Draw hatched area for each installation period + svgElement + .selectAll(".installation-period") + .data(installations) + .join("rect") + .attr("class", "installation-period") + .attr("x", (d: any) => { + return lhs_x + timeInterval * (d.inst.beginning - startOfTime); + }) + .attr("y", (d: any) => { + // Find the component's row + const node = svgElement.select("#i" + d.component.id).node(); + if (!node) return 0; + return node.getBBox().y; + }) + .attr("width", (d: any) => { + return (d.inst.ending - d.inst.beginning) * timeInterval; + }) + .attr("height", (d: any) => { + const node = svgElement.select("#i" + d.component.id).node(); + if (!node) return 0; + return node.getBBox().height; + }) + .attr("fill", "url(#diagonal-hatch)") // Use the hatch pattern + .attr("stroke", "#374151") // Optional border + .attr("stroke-width", 1) + .attr("rx", 2) + .attr("ry", 2) + .raise(); +} diff --git a/editor-app/lib/Schema.ts b/editor-app/lib/Schema.ts index 99cad32..4bb1064 100644 --- a/editor-app/lib/Schema.ts +++ b/editor-app/lib/Schema.ts @@ -28,6 +28,23 @@ export interface Activity extends STExtent { partOf: Maybe; } +// Add entity typing for Individuals +export enum EntityType { + Individual = "individual", + System = "system", + SystemComponent = "systemComponent", // A slot/position within a system or installed object + InstalledComponent = "installedComponent", // A physical object that occupies a slot +} + +// Installation - just a temporal relationship record +export interface Installation { + id: Id; + componentId: Id; // The physical object being installed + targetId: Id; // The SystemComponent (slot) it's installed into + beginning: number; + ending: number; +} + /** * An individual is a person, place, or thing that participates in an activity. */ @@ -35,7 +52,7 @@ export interface Individual extends STExtent { beginsWithParticipant: boolean; //not persisted to HQDM. Indicates that the beginning time should be synchronised to participants. endsWithParticipant: boolean; //not persisted to HQDM. Indicates that the ending time should be synchronised to participants. entityType?: EntityType; // defaults to individual when absent - parentSystemId?: Id; // only used when entityType === SystemComponent + parentSystemId?: Id; // For SystemComponents and InstalledComponents - can be System OR InstalledComponent installations?: Installation[]; // optional list of installation periods } @@ -46,28 +63,3 @@ export interface Participation { individualId: Id; role: Maybe; } - -// Add entity typing for Individuals -export enum EntityType { - Individual = "individual", - System = "system", - SystemComponent = "systemComponent", -} - -// Temporal extent is already defined as STExtent in your schema. -// We add an Installation that reuses beginning/ending. -export interface Installation extends STExtent { - id: Id; - componentId: Id; // the component being installed - systemId: Id; // the system it is installed into -} - -// Extend Individual with system fields (kept optional for back-compat) -export interface Individual extends STExtent { - beginsWithParticipant: boolean; - endsWithParticipant: boolean; - - entityType?: EntityType; - parentSystemId?: Id; - installations?: Installation[]; -} From a00e05e1df5b74e04873e8b96bfcdf72b8165d9e Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 25 Nov 2025 12:34:41 +0000 Subject: [PATCH 17/81] Refactor SetIndividual component and enhance installation management - Cleaned up imports and removed unused components in SetIndividual.tsx. - Improved state management for individual inputs and errors. - Enhanced type handling for individual types and added validation. - Updated the installation handling logic to improve clarity and functionality. - Added new EditInstalledComponent for managing installation periods with a modal interface. - Implemented helper functions for installation references in DrawIndividuals and DrawInstallations. - Improved the drawing logic for installations to handle references correctly. - Updated the Schema to clarify the structure of Individual and Installation interfaces. --- editor-app/components/ActivityDiagramWrap.tsx | 147 ++++---- .../components/EditInstalledComponent.tsx | 250 ++++++++++++++ editor-app/components/SetIndividual.tsx | 325 +++--------------- editor-app/diagram/DrawIndividuals.ts | 122 +++++-- editor-app/diagram/DrawInstallations.ts | 180 +++++++--- editor-app/lib/Schema.ts | 14 +- 6 files changed, 631 insertions(+), 407 deletions(-) create mode 100644 editor-app/components/EditInstalledComponent.tsx diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index fbb1c35..0c644f2 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -27,6 +27,7 @@ import HideIndividuals from "./HideIndividuals"; import React from "react"; import Card from "react-bootstrap/Card"; import DiagramLegend from "./DiagramLegend"; +import EditInstalledComponent from "./EditInstalledComponent"; const beforeUnloadHandler = (ev: BeforeUnloadEvent) => { ev.returnValue = ""; @@ -60,6 +61,13 @@ export default function ActivityDiagramWrap() { const [showConfigModal, setShowConfigModal] = useState(false); const [showSortIndividuals, setShowSortIndividuals] = useState(false); + // Add new state for the InstalledComponent editor + const [showInstalledComponentEditor, setShowInstalledComponentEditor] = + useState(false); + const [selectedInstalledComponent, setSelectedInstalledComponent] = useState< + Individual | undefined + >(undefined); + useEffect(() => { if (dirty) window.addEventListener("beforeunload", beforeUnloadHandler); else window.removeEventListener("beforeunload", beforeUnloadHandler); @@ -104,8 +112,17 @@ export default function ActivityDiagramWrap() { }; const clickIndividual = (i: Individual) => { - setSelectedIndividual(i); - setShowIndividual(true); + // If it's an InstalledComponent, open the special editor + if ( + (i.entityType ?? EntityType.Individual) === EntityType.InstalledComponent + ) { + setSelectedInstalledComponent(i); + setShowInstalledComponentEditor(true); + } else { + // For other types, open the regular editor + setSelectedIndividual(i); + setShowIndividual(true); + } }; const clickActivity = (a: Activity) => { setSelectedActivity(a); @@ -130,66 +147,68 @@ export default function ActivityDiagramWrap() { }; // Sort individuals to show nested hierarchy + // InstalledComponents appear BOTH at top-level AND under their installation targets const sortedIndividuals = useMemo(() => { const result: Individual[] = []; const visited = new Set(); - // Helper: Get the "parent" of an individual - // - For SystemComponents: parentSystemId - // - For InstalledComponents: the targetId of their first installation (if any) - const getParentId = (ind: Individual): string | undefined => { - const entityType = ind.entityType ?? EntityType.Individual; - - if (entityType === EntityType.SystemComponent) { - return ind.parentSystemId; - } - - if (entityType === EntityType.InstalledComponent) { - // InstalledComponent's parent is the SystemComponent it's installed into - if (ind.installations && ind.installations.length > 0) { - return ind.installations[0].targetId; - } - } - - return undefined; - }; - // Recursive function to add an individual and its descendants const addWithDescendants = (ind: Individual) => { if (visited.has(ind.id)) return; visited.add(ind.id); result.push(ind); - // Find children (SystemComponents or InstalledComponents whose parent/target is this individual) - const children = individualsArray.filter((child) => { - const childEntityType = child.entityType ?? EntityType.Individual; + // Find children of this individual + const children: Individual[] = []; - // SystemComponents with this as parent + // 1. SystemComponents whose parent is this individual + individualsArray.forEach((child) => { + const childEntityType = child.entityType ?? EntityType.Individual; if ( childEntityType === EntityType.SystemComponent && child.parentSystemId === ind.id ) { - return true; + children.push(child); } - - // InstalledComponents installed into this (if this is a SystemComponent) - if ( - childEntityType === EntityType.InstalledComponent && - child.installations - ) { - return child.installations.some((inst) => inst.targetId === ind.id); - } - - return false; }); // Sort children by name and add them children .sort((a, b) => a.name.localeCompare(b.name)) .forEach((child) => addWithDescendants(child)); + + // 2. After adding SystemComponent children, check if this IS a SystemComponent + // If so, find InstalledComponents that are installed into this slot + const indEntityType = ind.entityType ?? EntityType.Individual; + if (indEntityType === EntityType.SystemComponent) { + // Find all InstalledComponents that have an installation targeting this slot + const installedHere = individualsArray.filter((ic) => { + const icType = ic.entityType ?? EntityType.Individual; + if (icType !== EntityType.InstalledComponent) return false; + if (!ic.installations || ic.installations.length === 0) return false; + return ic.installations.some((inst) => inst.targetId === ind.id); + }); + + // Add these as "installation references" + installedHere + .sort((a, b) => a.name.localeCompare(b.name)) + .forEach((ic) => { + // Create a reference entry with composite ID + // The ID format is: originalId__installed_in__slotId + const installRef: Individual = { + ...ic, + id: `${ic.id}__installed_in__${ind.id}`, + // Keep beginning/ending as the full timeline for the row shape + // The actual installation period will be drawn by DrawInstallations + beginning: -1, + ending: Model.END_OF_TIME, + }; + result.push(installRef); + }); + } }; - // Start with top-level items (Systems, regular Individuals, or things with no parent) + // Start with top-level items const topLevel = individualsArray.filter((ind) => { const entityType = ind.entityType ?? EntityType.Individual; @@ -199,44 +218,37 @@ export default function ActivityDiagramWrap() { // Regular individuals are top-level if (entityType === EntityType.Individual) return true; - // SystemComponents without a parent are top-level (shouldn't happen, but handle it) - if (entityType === EntityType.SystemComponent && !ind.parentSystemId) - return true; + // InstalledComponents are top-level (physical objects exist independently) + if (entityType === EntityType.InstalledComponent) return true; - // InstalledComponents without installations are top-level - if (entityType === EntityType.InstalledComponent) { - if (!ind.installations || ind.installations.length === 0) return true; - // Also top-level if their target doesn't exist - const targetExists = ind.installations.some((inst) => - individualsArray.some((i) => i.id === inst.targetId) + // SystemComponents without a valid parent are top-level (orphans) + if (entityType === EntityType.SystemComponent) { + if (!ind.parentSystemId) return true; + const parentExists = individualsArray.some( + (i) => i.id === ind.parentSystemId ); - return !targetExists; + return !parentExists; } return false; }); - // Add all top-level items with their descendants + // Sort top-level: Systems first, then InstalledComponents, then Individuals topLevel .sort((a, b) => { - // Systems first, then individuals - const aIsSystem = - (a.entityType ?? EntityType.Individual) === EntityType.System; - const bIsSystem = - (b.entityType ?? EntityType.Individual) === EntityType.System; - if (aIsSystem && !bIsSystem) return -1; - if (!aIsSystem && bIsSystem) return 1; + const aType = a.entityType ?? EntityType.Individual; + const bType = b.entityType ?? EntityType.Individual; + + // Systems first + if (aType === EntityType.System && bType !== EntityType.System) + return -1; + if (aType !== EntityType.System && bType === EntityType.System) + return 1; + return a.name.localeCompare(b.name); }) .forEach((ind) => addWithDescendants(ind)); - // Add any remaining individuals that weren't visited (orphans) - individualsArray.forEach((ind) => { - if (!visited.has(ind.id)) { - result.push(ind); - } - }); - return result; }, [individualsArray]); @@ -371,6 +383,15 @@ export default function ActivityDiagramWrap() {
+ + {/* Add the new InstalledComponent editor modal */} + ); } diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx new file mode 100644 index 0000000..f090554 --- /dev/null +++ b/editor-app/components/EditInstalledComponent.tsx @@ -0,0 +1,250 @@ +import React, { + Dispatch, + SetStateAction, + useEffect, + useMemo, + useState, +} from "react"; +import Button from "react-bootstrap/Button"; +import Modal from "react-bootstrap/Modal"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; +import Form from "react-bootstrap/Form"; +import Card from "react-bootstrap/Card"; +import { Model } from "../lib/Model"; +import { EntityType, Individual, Installation } from "../lib/Schema"; + +interface Props { + show: boolean; + setShow: Dispatch>; + individual: Individual | undefined; + setIndividual: (individual: Individual) => void; + dataset: Model; +} + +const EditInstalledComponent = (props: Props) => { + const { show, setShow, individual, setIndividual, dataset } = props; + + const [installations, setInstallations] = useState([]); + + // Load installations when modal opens + useEffect(() => { + if (show && individual) { + setInstallations( + individual.installations ? [...individual.installations] : [] + ); + } + }, [show, individual]); + + // Get all SystemComponents as potential installation targets + const availableSlots = useMemo(() => { + return Array.from(dataset.individuals.values()).filter( + (i) => + (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent + ); + }, [dataset]); + + // Group slots by their parent system for better UX + const slotsBySystem = useMemo(() => { + const groups: Map< + string, + { system: Individual | null; slots: Individual[] } + > = new Map(); + + availableSlots.forEach((slot) => { + const parentId = slot.parentSystemId || "unassigned"; + const parent = slot.parentSystemId + ? dataset.individuals.get(slot.parentSystemId) + : null; + + if (!groups.has(parentId)) { + groups.set(parentId, { system: parent || null, slots: [] }); + } + groups.get(parentId)!.slots.push(slot); + }); + + return groups; + }, [availableSlots, dataset]); + + const handleClose = () => { + setShow(false); + setInstallations([]); + }; + + const handleSave = () => { + if (!individual) return; + + const updated: Individual = { + ...individual, + installations: installations, + }; + + setIndividual(updated); + handleClose(); + }; + + const addInstallation = () => { + const newInst: Installation = { + id: `inst_${Date.now()}`, + componentId: individual?.id || "", + targetId: "", + beginning: 0, + ending: 10, + }; + setInstallations([...installations, newInst]); + }; + + const updateInstallation = ( + id: string, + field: keyof Installation, + value: any + ) => { + setInstallations( + installations.map((inst) => + inst.id === id ? { ...inst, [field]: value } : inst + ) + ); + }; + + const removeInstallation = (id: string) => { + setInstallations(installations.filter((inst) => inst.id !== id)); + }; + + // Get the system name for a given slot + const getSlotLabel = (slot: Individual): string => { + if (slot.parentSystemId) { + const parent = dataset.individuals.get(slot.parentSystemId); + if (parent) { + return `${parent.name} → ${slot.name}`; + } + } + return slot.name; + }; + + if (!individual) return null; + + return ( + + + Installation Periods: {individual.name} + + +

+ Define when and where this physical component is installed. A + component can be installed into different slots at different times. +

+ + {installations.length === 0 ? ( +
+

No installation periods defined.

+

This component is currently not installed anywhere.

+
+ ) : ( + installations.map((inst, idx) => ( + + + Installation {idx + 1} + + + + + Install Into Slot + + updateInstallation(inst.id, "targetId", e.target.value) + } + > + + {Array.from(slotsBySystem.entries()).map( + ([parentId, group]) => ( + + {group.slots.map((slot) => ( + + ))} + + ) + )} + + {inst.targetId && ( + + Installing into:{" "} + {getSlotLabel( + availableSlots.find((s) => s.id === inst.targetId)! + )} + + )} + + + + + + Installed From (time) + + updateInstallation( + inst.id, + "beginning", + parseInt(e.target.value, 10) || 0 + ) + } + /> + + + + + Removed At (time) + + updateInstallation( + inst.id, + "ending", + parseInt(e.target.value, 10) || 0 + ) + } + /> + + + + + + )) + )} + + +
+ + + + + +
+ ); +}; + +export default EditInstalledComponent; diff --git a/editor-app/components/SetIndividual.tsx b/editor-app/components/SetIndividual.tsx index ba3feef..dfbea8c 100644 --- a/editor-app/components/SetIndividual.tsx +++ b/editor-app/components/SetIndividual.tsx @@ -11,12 +11,10 @@ import Modal from "react-bootstrap/Modal"; import Row from "react-bootstrap/Row"; import Col from "react-bootstrap/Col"; import Form from "react-bootstrap/Form"; -import Container from "react-bootstrap/Container"; import { Model } from "../lib/Model"; -import { EntityType, Individual, Installation } from "../lib/Schema"; +import { EntityType, Individual } from "../lib/Schema"; import { v4 as uuidv4 } from "uuid"; -import { Alert, InputGroup } from "react-bootstrap"; -import Card from "react-bootstrap/Card"; +import { Alert } from "react-bootstrap"; interface Props { deleteIndividual: (id: string) => void; @@ -40,6 +38,7 @@ const SetIndividual = (props: Props) => { dataset, updateDataset, } = props; + let defaultIndividual: Individual = { id: "", name: "", @@ -49,10 +48,13 @@ const SetIndividual = (props: Props) => { ending: Model.END_OF_TIME, beginsWithParticipant: false, endsWithParticipant: false, + entityType: EntityType.Individual, + parentSystemId: undefined, + installations: [], }; - const [errors, setErrors] = useState([]); - const [inputs, setInputs] = useState( + const [errors, setErrors] = useState([]); + const [inputs, setInputs] = useState( selectedIndividual ? selectedIndividual : defaultIndividual ); const [dirty, setDirty] = useState(false); @@ -61,7 +63,7 @@ const SetIndividual = (props: Props) => { const [individualHasParticipants, setIndividualHasParticipants] = useState(false); - // New state for custom type selector + // State for custom type selector const [typeOpen, setTypeOpen] = useState(false); const [typeSearch, setTypeSearch] = useState(""); const [editingTypeId, setEditingTypeId] = useState(null); @@ -88,7 +90,7 @@ const SetIndividual = (props: Props) => { } }, [selectedIndividual, dataset]); - // click outside to close type dropdown + // Click outside to close type dropdown useEffect(() => { function handleClickOutside(ev: MouseEvent) { if ( @@ -145,32 +147,30 @@ const SetIndividual = (props: Props) => { setEditingTypeId(null); setEditingTypeValue(""); }; + const handleShow = () => { if (selectedIndividual) { setInputs(selectedIndividual); } else { - defaultIndividual.id = uuidv4(); - setInputs(defaultIndividual); + const newDefault = { ...defaultIndividual, id: uuidv4() }; + setInputs(newDefault); } }; - // ...existing code... const handleAdd = () => { - // Generate ID if missing (new individual) - const id = inputs.id || "i" + Date.now(); + if (!validateInputs()) return; + + const id = inputs.id || uuidv4(); - // Create complete individual object const newInd: Individual = { id: id, name: inputs.name || "Unnamed", description: inputs.description || "", type: inputs.type, - beginning: inputs.beginning ?? -1, - ending: inputs.ending ?? 100, // Default to 100 if Model.END_OF_TIME is not available + beginning: -1, + ending: Model.END_OF_TIME, beginsWithParticipant: inputs.beginsWithParticipant ?? false, endsWithParticipant: inputs.endsWithParticipant ?? false, - - // New fields entityType: inputs.entityType ?? EntityType.Individual, parentSystemId: inputs.parentSystemId, installations: inputs.installations ?? [], @@ -180,14 +180,13 @@ const SetIndividual = (props: Props) => { handleClose(); }; - // ...existing code... - const handleDelete = (event: any) => { + const handleDelete = () => { deleteIndividual(inputs.id); handleClose(); }; const updateInputs = (key: string, value: any) => { - setInputs({ ...inputs, [key]: value }); + setInputs((prev) => ({ ...prev, [key]: value })); setDirty(true); }; @@ -195,8 +194,6 @@ const SetIndividual = (props: Props) => { updateInputs(e.target.name, e.target.value); }; - // remove old handleTypeChange and add new type handlers below - const handleBeginsWithParticipant = (e: any) => { const checked = e.target.checked; const earliestBeginning = selectedIndividual @@ -210,8 +207,6 @@ const SetIndividual = (props: Props) => { } }; - /* XXX Why does this use Number.MAX_VALUE rather than - * Model.END_OF_TIME? */ const handleEndsWithParticipant = (e: any) => { const checked = e.target.checked; const lastEnding = selectedIndividual @@ -222,25 +217,22 @@ const SetIndividual = (props: Props) => { }; const validateInputs = () => { - let runningErrors = []; - //Name + let runningErrors: string[] = []; if (!inputs.name) { runningErrors.push("Name field is required"); } - //Type if (!inputs.type) { runningErrors.push("Type field is required"); } - if (runningErrors.length == 0) { + if (runningErrors.length === 0) { return true; } else { - // @ts-ignore setErrors(runningErrors); return false; } }; - // ----- New helper functions for custom type selector ----- + // Helper functions for custom type selector const filteredTypes = dataset.individualTypes.filter((t) => t.name.toLowerCase().includes(typeSearch.toLowerCase()) ); @@ -267,15 +259,11 @@ const SetIndividual = (props: Props) => { if (!name) return; const newId = uuidv4(); - // add to dataset (keeps existing project pattern) updateDataset((d) => { d.addIndividualType(newId, name); return d; }); - // Immediately select the newly created type for this form. - // Create a minimal Kind-like object so selection is immediate even - // if the dataset state hasn't re-rendered yet. const createdType = { id: newId, name, isCoreHqdm: false }; updateInputs("type", createdType); @@ -285,7 +273,6 @@ const SetIndividual = (props: Props) => { const startEditType = (typeId: string, currentName: string, e: any) => { e.stopPropagation(); - // prevent editing core HQDM defaults const found = dataset.individualTypes.find((x) => x.id === typeId); if (found && found.isCoreHqdm) return; setEditingTypeId(typeId); @@ -297,15 +284,11 @@ const SetIndividual = (props: Props) => { const newName = editingTypeValue.trim(); if (!newName) return; - // Update model: rename the Kind and update all Individuals that reference it updateDataset((d) => { - // find the canonical kind in the model and rename it const kind = d.individualTypes.find((x) => x.id === editingTypeId); if (kind) kind.name = newName; - // update any Individuals that still reference the old Kind object - // by replacing their .type with the canonical Kind from the model - d.individuals.forEach((ind /*, key */) => { + d.individuals.forEach((ind) => { if (ind.type && ind.type.id === editingTypeId) { const canonical = d.individualTypes.find( (x) => x.id === editingTypeId @@ -314,7 +297,6 @@ const SetIndividual = (props: Props) => { } }); - // if defaultIndividualType matches, update that reference too if ( d.defaultIndividualType && d.defaultIndividualType.id === editingTypeId @@ -326,7 +308,6 @@ const SetIndividual = (props: Props) => { return d; }); - // Update the form selection immediately to show edited name updateInputs("type", { id: editingTypeId, name: newName, @@ -341,43 +322,6 @@ const SetIndividual = (props: Props) => { setEditingTypeId(null); setEditingTypeValue(""); }; - // ----- end helpers ----- - - // Load selected individual data when dialog opens - useEffect(() => { - if (show && selectedIndividual) { - // Populate form with existing individual data - setInputs({ - id: selectedIndividual.id, - name: selectedIndividual.name || "", - description: selectedIndividual.description || "", - type: selectedIndividual.type, - beginning: selectedIndividual.beginning, - ending: selectedIndividual.ending, - beginsWithParticipant: - selectedIndividual.beginsWithParticipant ?? false, - endsWithParticipant: selectedIndividual.endsWithParticipant ?? false, - entityType: selectedIndividual.entityType ?? EntityType.Individual, - parentSystemId: selectedIndividual.parentSystemId, - installations: selectedIndividual.installations ?? [], - }); - } else if (show && !selectedIndividual) { - // Reset form for new individual - setInputs({ - id: "", - name: "", - description: "", - type: undefined, - beginning: -1, - ending: Model.END_OF_TIME, - beginsWithParticipant: false, - endsWithParticipant: false, - entityType: EntityType.Individual, - parentSystemId: undefined, - installations: [], - }); - } - }, [show, selectedIndividual]); return ( <> @@ -392,7 +336,13 @@ const SetIndividual = (props: Props) => { -
+ { + e.preventDefault(); + handleAdd(); + }} + > + {/* Name */} Name { /> - {/* ----------------- REPLACED Type field: custom select/create ----------------- */} + {/* Type dropdown with create/edit */} Type -
{ {typeOpen && (
@@ -501,7 +450,6 @@ const SetIndividual = (props: Props) => { {inputs?.type?.id === t.id && ( )} - {/* hide/disable edit for core HQDM types */} {!t.isCoreHqdm && (
- {/* ----------------- end replaced Type field ----------------- */} + {/* Description */} Description { className="form-control" /> - + + {/* Begins/Ends with participant */} + { onChange={handleBeginsWithParticipant} /> - + { /> - {/* Entity type selection */} + {/* Entity type selection - NO ICONS */} Entity type { - {/* Parent – for SystemComponents (slots need a parent container) */} + {/* Parent – for SystemComponents only */} {(inputs.entityType ?? EntityType.Individual) === EntityType.SystemComponent && ( @@ -618,191 +562,30 @@ const SetIndividual = (props: Props) => { const type = (p.entityType ?? EntityType.Individual) === EntityType.System - ? "System" - : "Installed Object"; + ? "[System]" + : "[Installed]"; return ( ); })} - - This defines WHERE this component slot exists - )} - {/* Installation periods – for InstalledComponents (physical objects) */} + {/* Note for InstalledComponents */} {(inputs.entityType ?? EntityType.Individual) === - EntityType.InstalledComponent && ( - - Installation Periods - - - Define when this physical object occupies a component slot - - - {(inputs.installations || []).map((inst, idx) => ( -
-
- Installation {idx + 1} - -
- - - - Install Into Slot (SystemComponent) - - { - const updated = (inputs.installations || []).map( - (i) => - i.id === inst.id - ? { ...i, targetId: e.target.value } - : i - ); - updateInputs("installations", updated); - }} - > - - {availableInstallationTargets.map((slot) => ( - - ))} - - - The slot/position this object will occupy - - - - - - - Attached from - { - const updated = ( - inputs.installations || [] - ).map((i) => - i.id === inst.id - ? { - ...i, - beginning: parseInt(e.target.value, 10), - } - : i - ); - updateInputs("installations", updated); - }} - /> - - - - - Attached until - { - const updated = ( - inputs.installations || [] - ).map((i) => - i.id === inst.id - ? { - ...i, - ending: parseInt(e.target.value, 10), - } - : i - ); - updateInputs("installations", updated); - }} - /> - - - -
- ))} - - -
-
- )} - - {/* Temporal bounds - same for all types */} - - Beginning (time) - - updateInputs( - "beginning", - e.target.value === "" ? -1 : parseInt(e.target.value, 10) - ) - } - placeholder="Leave empty to start at earliest" - /> - - - - Ending (time) - - updateInputs( - "ending", - e.target.value === "" - ? Model.END_OF_TIME - : parseInt(e.target.value, 10) - ) - } - placeholder="Leave empty to extend to end" - /> - + EntityType.InstalledComponent && + selectedIndividual && ( +
+ To manage installation periods, save this and then click on + the component in the diagram to open the Installation Editor. +
+ )} +
@@ -820,8 +603,8 @@ const SetIndividual = (props: Props) => { -
diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index c2f08d9..96fa7a7 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -20,6 +20,27 @@ interface Layout { stop: boolean; } +// Helper to check if this is an "installation reference" (virtual row) +function isInstallationRef(ind: Individual): boolean { + return ind.id.includes("__installed_in__"); +} + +// Get the original ID from an installation reference +function getOriginalId(ind: Individual): string { + if (isInstallationRef(ind)) { + return ind.id.split("__installed_in__")[0]; + } + return ind.id; +} + +// Get the slot ID from an installation reference +function getSlotId(ind: Individual): string | undefined { + if (isInstallationRef(ind)) { + return ind.id.split("__installed_in__")[1]; + } + return undefined; +} + export function drawIndividuals(ctx: DrawContext) { const { config, svgElement, activities, dataset } = ctx; const individuals = ctx.individuals; @@ -74,13 +95,34 @@ export function drawIndividuals(ctx: DrawContext) { const entityType = ind.entityType ?? EntityType.Individual; + // Installation references get indented based on their target slot + if (isInstallationRef(ind)) { + const slotId = getSlotId(ind); + if (slotId) { + // Start from the slot and count up + currentId = slotId; + while (currentId && !visited.has(currentId)) { + visited.add(currentId); + level++; + const parent = dataset.individuals.get(currentId); + if (!parent) break; + const parentType = parent.entityType ?? EntityType.Individual; + if (parentType === EntityType.SystemComponent) { + currentId = parent.parentSystemId; + } else { + break; + } + } + } + return level; + } + + // SystemComponents get indented based on parentSystemId if (entityType === EntityType.SystemComponent) { currentId = ind.parentSystemId; - } else if (entityType === EntityType.InstalledComponent) { - // InstalledComponent's parent is the SystemComponent it's installed into - if (ind.installations && ind.installations.length > 0) { - currentId = ind.installations[0].targetId; - } + } else { + // Individual, System, InstalledComponent (at top level) - no indentation + return 0; } // Walk up the parent chain @@ -95,14 +137,8 @@ export function drawIndividuals(ctx: DrawContext) { if (parentType === EntityType.SystemComponent) { currentId = parent.parentSystemId; - } else if (parentType === EntityType.InstalledComponent) { - if (parent.installations && parent.installations.length > 0) { - currentId = parent.installations[0].targetId; - } else { - break; - } } else { - break; // Systems and Individuals don't have parents + break; // Systems don't have parents } } @@ -203,6 +239,13 @@ export function hoverIndividuals(ctx: DrawContext) { function individualTooltip(individual: Individual) { let tip = "Individual"; + + // Check if this is an installation reference + if (isInstallationRef(individual)) { + tip = "Installed Component"; + tip += "
(Installation instance)"; + } + if (individual.name) tip += "
Name: " + individual.name; if (individual.type) tip += "
Type: " + individual.type.name; if (individual.description) @@ -215,20 +258,27 @@ export function clickIndividuals( clickIndividual: any, rightClickIndividual: any ) { - const { config, svgElement, individuals } = ctx; + const { config, svgElement, individuals, dataset } = ctx; individuals.forEach((i) => { - const lclick = (e: MouseEvent) => clickIndividual(i); + // For installation references, we need to get the original individual + const actualIndividual = isInstallationRef(i) + ? dataset.individuals.get(getOriginalId(i)) + : i; + + if (!actualIndividual) return; + + const lclick = (e: MouseEvent) => clickIndividual(actualIndividual); const rclick = (e: MouseEvent) => { e.preventDefault(); - rightClickIndividual(i); + rightClickIndividual(actualIndividual); }; svgElement - .select("#i" + i.id) + .select("#i" + i.id.replace(/__/g, "\\$&")) // Escape special chars in selector .on("click", lclick) .on("contextmenu", rclick); svgElement - .select("#il" + i.id) + .select("#il" + i.id.replace(/__/g, "\\$&")) .on("click", lclick) .on("contextmenu", rclick); }); @@ -242,7 +292,7 @@ export function labelIndividuals(ctx: DrawContext) { return; } - // Same helper as above + // Helper: Get indent level const getIndentLevel = (ind: Individual): number => { let level = 0; let currentId: string | undefined = undefined; @@ -250,12 +300,32 @@ export function labelIndividuals(ctx: DrawContext) { const entityType = ind.entityType ?? EntityType.Individual; + // Installation references get indented based on their target slot + if (isInstallationRef(ind)) { + const slotId = getSlotId(ind); + if (slotId) { + currentId = slotId; + while (currentId && !visited.has(currentId)) { + visited.add(currentId); + level++; + const parent = dataset.individuals.get(currentId); + if (!parent) break; + const parentType = parent.entityType ?? EntityType.Individual; + if (parentType === EntityType.SystemComponent) { + currentId = parent.parentSystemId; + } else { + break; + } + } + } + return level; + } + + // SystemComponents get indented if (entityType === EntityType.SystemComponent) { currentId = ind.parentSystemId; - } else if (entityType === EntityType.InstalledComponent) { - if (ind.installations && ind.installations.length > 0) { - currentId = ind.installations[0].targetId; - } + } else { + return 0; } while (currentId && !visited.has(currentId)) { @@ -269,12 +339,6 @@ export function labelIndividuals(ctx: DrawContext) { if (parentType === EntityType.SystemComponent) { currentId = parent.parentSystemId; - } else if (parentType === EntityType.InstalledComponent) { - if (parent.installations && parent.installations.length > 0) { - currentId = parent.installations[0].targetId; - } else { - break; - } } else { break; } @@ -313,7 +377,7 @@ export function labelIndividuals(ctx: DrawContext) { .text((i: Individual) => { const indentLevel = getIndentLevel(i); const prefix = indentLevel > 0 ? "↳ " : ""; - return prefix + i.name; + return `${prefix}${i.name}`; }) .each((d: Individual, i: number, nodes: SVGGraphicsElement[]) => { removeLabelIfItOverlaps(labels, nodes[i]); diff --git a/editor-app/diagram/DrawInstallations.ts b/editor-app/diagram/DrawInstallations.ts index 766d615..c51a4ec 100644 --- a/editor-app/diagram/DrawInstallations.ts +++ b/editor-app/diagram/DrawInstallations.ts @@ -1,16 +1,29 @@ import { DrawContext } from "./DrawHelpers"; import { EntityType, Installation, Individual } from "@/lib/Schema"; -export function drawInstallations(ctx: DrawContext) { - const { svgElement, individuals, activities, config } = ctx; +// Helper to check if this is an "installation reference" (virtual row) +function isInstallationRef(ind: Individual): boolean { + return ind.id.includes("__installed_in__"); +} + +// Get the original ID from an installation reference +function getOriginalId(ind: Individual): string { + if (isInstallationRef(ind)) { + return ind.id.split("__installed_in__")[0]; + } + return ind.id; +} - // Only process installed components - const installedComponents = individuals.filter( - (i) => - (i.entityType ?? EntityType.Individual) === EntityType.InstalledComponent - ); +// Get the slot ID from an installation reference +function getSlotId(ind: Individual): string | undefined { + if (isInstallationRef(ind)) { + return ind.id.split("__installed_in__")[1]; + } + return undefined; +} - if (installedComponents.length === 0) return; +export function drawInstallations(ctx: DrawContext) { + const { svgElement, individuals, activities, config, dataset } = ctx; // Calculate time scale using BOTH activities AND individuals let startOfTime = Infinity; @@ -29,17 +42,41 @@ export function drawInstallations(ctx: DrawContext) { if (i.ending < 1000000 && i.ending > endOfTime) endOfTime = i.ending; }); + // Also consider installation periods + individuals.forEach((ind) => { + if (isInstallationRef(ind)) { + const originalId = getOriginalId(ind); + const slotId = getSlotId(ind); + const originalComponent = dataset.individuals.get(originalId); + + if (originalComponent && originalComponent.installations) { + originalComponent.installations + .filter((inst) => inst.targetId === slotId) + .forEach((inst) => { + if (inst.beginning < startOfTime) startOfTime = inst.beginning; + if (inst.ending > endOfTime) endOfTime = inst.ending; + }); + } + } + }); + // Default fallback if (!isFinite(startOfTime)) startOfTime = 0; if (!isFinite(endOfTime)) endOfTime = 100; const duration = endOfTime - startOfTime; + if (duration <= 0) return; + let totalLeftMargin = config.viewPort.x * config.viewPort.zoom - config.layout.individual.xMargin * 2; totalLeftMargin -= config.layout.individual.temporalMargin; - const timeInterval = duration > 0 ? totalLeftMargin / duration : 1; + if (config.labels.individual.enabled) { + totalLeftMargin -= config.layout.individual.textLength; + } + + const timeInterval = totalLeftMargin / duration; const lhs_x = config.layout.individual.xMargin + config.layout.individual.temporalMargin + @@ -47,18 +84,38 @@ export function drawInstallations(ctx: DrawContext) { ? config.layout.individual.textLength : 0); - // Collect all installations - const installations: Array<{ inst: Installation; component: Individual }> = - []; - installedComponents.forEach((comp) => { - if (comp.installations && comp.installations.length > 0) { - comp.installations.forEach((inst) => { - installations.push({ inst, component: comp }); + // Collect installations ONLY for installation reference rows + const installationData: Array<{ + inst: Installation; + refId: string; // The installation reference row ID + originalComponent: Individual; + }> = []; + + individuals.forEach((ind) => { + if (!isInstallationRef(ind)) return; + + const originalId = getOriginalId(ind); + const slotId = getSlotId(ind); + if (!slotId) return; + + const originalComponent = dataset.individuals.get(originalId); + if (!originalComponent) return; + + const installations = originalComponent.installations || []; + const relevantInstallations = installations.filter( + (inst) => inst.targetId === slotId + ); + + relevantInstallations.forEach((inst) => { + installationData.push({ + inst, + refId: ind.id, + originalComponent, }); - } + }); }); - if (installations.length === 0 || timeInterval <= 0) return; + if (installationData.length === 0) return; // Create or get the defs element for patterns let defs = svgElement.select("defs"); @@ -76,7 +133,7 @@ export function drawInstallations(ctx: DrawContext) { .attr("height", 8) .attr("patternTransform", "rotate(45)"); - // Background (optional - remove for transparent background) + // Background (optional - for slight visibility) pattern .append("rect") .attr("width", 8) @@ -98,30 +155,75 @@ export function drawInstallations(ctx: DrawContext) { // Draw hatched area for each installation period svgElement .selectAll(".installation-period") - .data(installations) + .data(installationData) .join("rect") .attr("class", "installation-period") - .attr("x", (d: any) => { - return lhs_x + timeInterval * (d.inst.beginning - startOfTime); - }) - .attr("y", (d: any) => { - // Find the component's row - const node = svgElement.select("#i" + d.component.id).node(); - if (!node) return 0; - return node.getBBox().y; - }) - .attr("width", (d: any) => { - return (d.inst.ending - d.inst.beginning) * timeInterval; - }) - .attr("height", (d: any) => { - const node = svgElement.select("#i" + d.component.id).node(); - if (!node) return 0; - return node.getBBox().height; - }) - .attr("fill", "url(#diagonal-hatch)") // Use the hatch pattern - .attr("stroke", "#374151") // Optional border + .attr( + "x", + (d: { + inst: Installation; + refId: string; + originalComponent: Individual; + }) => { + // Clamp the beginning to the visible time range + const clampedStart = Math.max(d.inst.beginning, startOfTime); + return lhs_x + timeInterval * (clampedStart - startOfTime); + } + ) + .attr( + "y", + (d: { + inst: Installation; + refId: string; + originalComponent: Individual; + }) => { + // Find the installation reference row by its composite ID + // Need to escape special characters in the selector + const escapedId = d.refId.replace(/__/g, "\\$&"); + const node = svgElement + .select("#i" + escapedId) + .node() as SVGGraphicsElement | null; + if (!node) { + console.log(`Could not find row for refId: ${d.refId}`); + return 0; + } + return node.getBBox().y; + } + ) + .attr( + "width", + (d: { + inst: Installation; + refId: string; + originalComponent: Individual; + }) => { + // Clamp to visible time range + const clampedStart = Math.max(d.inst.beginning, startOfTime); + const clampedEnd = Math.min(d.inst.ending, endOfTime); + const width = (clampedEnd - clampedStart) * timeInterval; + return Math.max(0, width); + } + ) + .attr( + "height", + (d: { + inst: Installation; + refId: string; + originalComponent: Individual; + }) => { + const escapedId = d.refId.replace(/__/g, "\\$&"); + const node = svgElement + .select("#i" + escapedId) + .node() as SVGGraphicsElement | null; + if (!node) return 0; + return node.getBBox().height; + } + ) + .attr("fill", "url(#diagonal-hatch)") + .attr("stroke", "#374151") .attr("stroke-width", 1) .attr("rx", 2) .attr("ry", 2) + .attr("pointer-events", "none") .raise(); } diff --git a/editor-app/lib/Schema.ts b/editor-app/lib/Schema.ts index 4bb1064..e125a7d 100644 --- a/editor-app/lib/Schema.ts +++ b/editor-app/lib/Schema.ts @@ -49,13 +49,17 @@ export interface Installation { * An individual is a person, place, or thing that participates in an activity. */ export interface Individual extends STExtent { - beginsWithParticipant: boolean; //not persisted to HQDM. Indicates that the beginning time should be synchronised to participants. - endsWithParticipant: boolean; //not persisted to HQDM. Indicates that the ending time should be synchronised to participants. - entityType?: EntityType; // defaults to individual when absent - parentSystemId?: Id; // For SystemComponents and InstalledComponents - can be System OR InstalledComponent - installations?: Installation[]; // optional list of installation periods + beginsWithParticipant: boolean; + endsWithParticipant: boolean; + entityType?: EntityType; + parentSystemId?: Id; + installations?: Installation[]; } +// Note: beginning/ending are inherited from STExtent +// For Individual, System, SystemComponent: use -1 and END_OF_TIME to span full diagram +// For InstalledComponent: use actual lifecycle times + /** * A participation is a relationship between an individual and an activity that it particiaptes in. */ From b4d0a110cc10b50c269deb523a4b8d50497cd67c Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 25 Nov 2025 15:00:13 +0000 Subject: [PATCH 18/81] Enhance SetActivity and DrawIndividuals components to utilize installation periods for effective rendering; refactor individual selection and validation logic for improved accuracy. --- editor-app/components/SetActivity.tsx | 250 +++++++++++++++++++++++--- editor-app/diagram/DrawIndividuals.ts | 34 +++- 2 files changed, 251 insertions(+), 33 deletions(-) diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index cc885a3..0a582fb 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -4,19 +4,23 @@ import React, { useRef, useState, useEffect, + useMemo, } from "react"; import Button from "react-bootstrap/Button"; import Modal from "react-bootstrap/Modal"; import Form from "react-bootstrap/Form"; import ListGroup from "react-bootstrap/ListGroup"; import Alert from "react-bootstrap/Alert"; -import Container from "react-bootstrap/Container"; -import Row from "react-bootstrap/Row"; -import Col from "react-bootstrap/Col"; import { v4 as uuidv4 } from "uuid"; -import Select from "react-select"; -import { Individual, Id, Activity, Maybe, Participation } from "@/lib/Schema"; -import { InputGroup } from "react-bootstrap"; +import Select, { MultiValue } from "react-select"; +import { + Individual, + Id, + Activity, + Maybe, + Participation, + EntityType, +} from "@/lib/Schema"; import { Model } from "@/lib/Model"; interface Props { @@ -31,6 +35,22 @@ interface Props { setActivityContext: Dispatch>; } +// Helper to check if this is an "installation reference" (virtual row) +function isInstallationRef(ind: Individual): boolean { + return ind.id.includes("__installed_in__"); +} + +// Get the slot ID from an installation reference +function getSlotId(ind: Individual): string | undefined { + if (isInstallationRef(ind)) { + return ind.id.split("__installed_in__")[1]; + } + return undefined; +} + +// Define the option type for the Select component +type IndividualOption = Individual & { displayLabel: string }; + const SetActivity = (props: Props) => { const { show, @@ -43,6 +63,7 @@ const SetActivity = (props: Props) => { activityContext, setActivityContext, } = props; + let defaultActivity: Activity = { id: "", name: "", @@ -55,7 +76,7 @@ const SetActivity = (props: Props) => { }; const [inputs, setInputs] = useState(defaultActivity); - const [errors, setErrors] = useState([]); + const [errors, setErrors] = useState([]); const [dirty, setDirty] = useState(false); // Custom activity-type selector state (search / create / inline edit) @@ -67,6 +88,148 @@ const SetActivity = (props: Props) => { const [showParentModal, setShowParentModal] = useState(false); const [selectedParentId, setSelectedParentId] = useState(null); + // Build the individuals list with proper labels for the Select component + const individualsWithLabels = useMemo(() => { + const individualsArray = Array.from(dataset.individuals.values()); + + // Helper to get the hierarchy label for an individual + const getHierarchyLabel = (ind: Individual): string => { + const entityType = ind.entityType ?? EntityType.Individual; + + // For installation references, build full path: System - Slot - Component (installed component) + if (isInstallationRef(ind)) { + const slotId = getSlotId(ind); + if (slotId) { + const slot = dataset.individuals.get(slotId); + if (slot) { + // Find the parent system of the slot + if (slot.parentSystemId) { + const system = dataset.individuals.get(slot.parentSystemId); + if (system) { + return `${system.name} - ${slot.name} - ${ind.name} (installed component)`; + } + } + return `${slot.name} - ${ind.name} (installed component)`; + } + } + return `${ind.name} (installed component)`; + } + + // For SystemComponents, show: System - Component (system component slot) + if (entityType === EntityType.SystemComponent) { + if (ind.parentSystemId) { + const parent = dataset.individuals.get(ind.parentSystemId); + if (parent) { + return `${parent.name} - ${ind.name} (system component slot)`; + } + } + return `${ind.name} (system component slot)`; + } + + // For Systems + if (entityType === EntityType.System) { + return `${ind.name} (system)`; + } + + // For InstalledComponents at top level (shouldn't be selectable, but just in case) + if (entityType === EntityType.InstalledComponent) { + return `${ind.name} (installed component - not installed)`; + } + + // For regular individuals + return `${ind.name} (individual)`; + }; + + const result: IndividualOption[] = []; + const visited = new Set(); + + const addWithDescendants = (ind: Individual) => { + if (visited.has(ind.id)) return; + visited.add(ind.id); + + const entityType = ind.entityType ?? EntityType.Individual; + + // Skip top-level InstalledComponents - only their installation refs can participate + if (entityType === EntityType.InstalledComponent) { + // Don't add - will be added as installation refs under slots + } else { + result.push({ + ...ind, + displayLabel: getHierarchyLabel(ind), + }); + } + + // Find SystemComponent children + const children: Individual[] = []; + individualsArray.forEach((child) => { + const childEntityType = child.entityType ?? EntityType.Individual; + if ( + childEntityType === EntityType.SystemComponent && + child.parentSystemId === ind.id + ) { + children.push(child); + } + }); + + children + .sort((a, b) => a.name.localeCompare(b.name)) + .forEach((child) => addWithDescendants(child)); + + // Add installation references for SystemComponents + const indEntityType = ind.entityType ?? EntityType.Individual; + if (indEntityType === EntityType.SystemComponent) { + const installedHere = individualsArray.filter((ic) => { + const icType = ic.entityType ?? EntityType.Individual; + if (icType !== EntityType.InstalledComponent) return false; + if (!ic.installations || ic.installations.length === 0) return false; + return ic.installations.some((inst) => inst.targetId === ind.id); + }); + + installedHere + .sort((a, b) => a.name.localeCompare(b.name)) + .forEach((ic) => { + // Find the specific installation for this slot + const installation = ic.installations?.find( + (inst) => inst.targetId === ind.id + ); + + const installRef: IndividualOption = { + ...ic, + id: `${ic.id}__installed_in__${ind.id}`, + // Use the actual installation period, not fixed values + beginning: installation?.beginning ?? -1, + ending: installation?.ending ?? Model.END_OF_TIME, + displayLabel: "", // Will be set below + }; + installRef.displayLabel = getHierarchyLabel(installRef); + result.push(installRef); + }); + } + }; + + // Start with top-level items + const topLevel = individualsArray.filter((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + if (entityType === EntityType.System) return true; + if (entityType === EntityType.Individual) return true; + if (entityType === EntityType.InstalledComponent) return false; + if (entityType === EntityType.SystemComponent) { + if (!ind.parentSystemId) return true; + const parentExists = individualsArray.some( + (i) => i.id === ind.parentSystemId + ); + return !parentExists; + } + return false; + }); + + topLevel + .sort((a, b) => a.name.localeCompare(b.name)) + .forEach((ind) => addWithDescendants(ind)); + + return result; + }, [dataset]); + // Safe local ancestor check (walks partOf chain). Avoids depending on Model.isAncestor. const isAncestorLocal = ( ancestorId: string, @@ -165,17 +328,13 @@ const SetActivity = (props: Props) => { updateInputs(e.target.name, e.target.value); }; - const handleTypeChange = (e: any) => { - // deprecated: replaced by custom selector - }; - const handleChangeNumeric = (e: any) => { updateInputs(e.target.name, e.target.valueAsNumber); }; - const handleChangeMultiselect = (e: any) => { + const handleChangeMultiselect = (newValue: MultiValue) => { const participations = new Map(); - e.forEach((i: Individual) => { + newValue.forEach((i) => { const old = inputs.participations.get(i.id)?.role; let participation: Participation = { individualId: i.id, @@ -186,7 +345,7 @@ const SetActivity = (props: Props) => { updateInputs("participations", participations); }; - const getSelectedIndividuals = () => { + const getSelectedIndividuals = (): IndividualOption[] => { if ( selectedActivity === undefined || selectedActivity.participations === undefined @@ -195,16 +354,19 @@ const SetActivity = (props: Props) => { } const individualIds = Array.from( selectedActivity.participations, - ([key, value]) => key + ([key]) => key + ); + // Find matching individuals from our labeled list + const participatingIndividuals = individualsWithLabels.filter( + (participant) => { + return individualIds.includes(participant.id); + } ); - const participatingIndividuals = individuals.filter((participant) => { - return individualIds.includes(participant.id); - }); return participatingIndividuals; }; const validateInputs = () => { - let runningErrors = []; + let runningErrors: string[] = []; //Name if (!inputs.name) { runningErrors.push("Name field is required"); @@ -228,10 +390,47 @@ const SetActivity = (props: Props) => { runningErrors.push("Select at least one participant"); } + // Validate activity timing against installed component installation periods + if (inputs.participations) { + inputs.participations.forEach((participation, participantId) => { + if (isInstallationRef({ id: participantId } as Individual)) { + const originalId = participantId.split("__installed_in__")[0]; + const slotId = participantId.split("__installed_in__")[1]; + + const installedComponent = dataset.individuals.get(originalId); + if (installedComponent?.installations) { + const installation = installedComponent.installations.find( + (inst) => inst.targetId === slotId + ); + + if (installation) { + const installStart = installation.beginning ?? 0; + const installEnd = installation.ending ?? Model.END_OF_TIME; + + if ( + inputs.beginning < installStart || + inputs.ending > installEnd + ) { + const slot = dataset.individuals.get(slotId); + const slotName = slot?.name ?? slotId; + runningErrors.push( + `Activity timing (${inputs.beginning}-${inputs.ending}) is outside the installation period ` + + `of "${ + installedComponent.name + }" in "${slotName}" (${installStart}-${ + installEnd === Model.END_OF_TIME ? "∞" : installEnd + })` + ); + } + } + } + } + }); + } + if (runningErrors.length == 0) { return true; } else { - // @ts-ignore setErrors(runningErrors); return false; } @@ -321,7 +520,6 @@ const SetActivity = (props: Props) => { }; // ----- end helpers ----- - // ...existing code... const handlePromote = () => { if (!inputs || !inputs.partOf) return; // find current parent and then its parent (grandparent) - that's the new parent @@ -573,13 +771,11 @@ const SetActivity = (props: Props) => {
Participants - x.targetId === slotId ); - if (inst) { + + for (const inst of installationsForSlot) { const instBeginning = Math.max(0, inst.beginning ?? 0); if (instBeginning < startOfTime) startOfTime = instBeginning; if ( @@ -225,19 +248,32 @@ export function drawIndividuals(ctx: DrawContext) { } } - // For installed components (installation references), use installation period + // For installed components (installation references), use THIS installation's period if (isInstallationRef(i)) { - const originalId = getOriginalId(i); - const slotId = getSlotId(i); - if (originalId && slotId) { - const original = dataset.individuals.get(originalId); - if (original && original.installations) { - const inst = original.installations.find( - (x) => x.targetId === slotId - ); - if (inst) { - effectiveBeginning = Math.max(0, inst.beginning ?? 0); - effectiveEnding = inst.ending ?? Model.END_OF_TIME; + // The virtual row already has the correct beginning/ending set from ActivityDiagramWrap + // Just use them directly + effectiveBeginning = i.beginning >= 0 ? i.beginning : 0; + effectiveEnding = + i.ending < Model.END_OF_TIME ? i.ending : Model.END_OF_TIME; + + // Fallback: try to get from original if the virtual row doesn't have valid values + if (effectiveBeginning < 0 || effectiveEnding >= Model.END_OF_TIME) { + const originalId = getOriginalId(i); + const slotId = getSlotId(i); + const installationId = getInstallationId(i); + + if (originalId && slotId) { + const original = dataset.individuals.get(originalId); + if (original && original.installations) { + // Find THIS specific installation by ID if available + let installation = installationId + ? original.installations.find((x) => x.id === installationId) + : original.installations.find((x) => x.targetId === slotId); + + if (installation) { + effectiveBeginning = Math.max(0, installation.beginning ?? 0); + effectiveEnding = installation.ending ?? Model.END_OF_TIME; + } } } } @@ -399,16 +435,21 @@ export function clickIndividuals( ) { const { config, svgElement, individuals, dataset } = ctx; individuals.forEach((i) => { - const actualIndividual = isInstallationRef(i) - ? dataset.individuals.get(getOriginalId(i)) - : i; - - if (!actualIndividual) return; + // For installation references, pass the virtual individual (with composite ID) + // so the click handler can extract the slot ID + // For regular individuals, pass as-is + const individualToPass = i; - const lclick = (e: MouseEvent) => clickIndividual(actualIndividual); + const lclick = (e: MouseEvent) => clickIndividual(individualToPass); const rclick = (e: MouseEvent) => { e.preventDefault(); - rightClickIndividual(actualIndividual); + // For right-click, we might want the actual individual for editing + const actualIndividual = isInstallationRef(i) + ? dataset.individuals.get(getOriginalId(i)) + : i; + if (actualIndividual) { + rightClickIndividual(actualIndividual); + } }; svgElement diff --git a/editor-app/diagram/DrawInstallations.ts b/editor-app/diagram/DrawInstallations.ts index de736a8..b6d9d13 100644 --- a/editor-app/diagram/DrawInstallations.ts +++ b/editor-app/diagram/DrawInstallations.ts @@ -18,45 +18,34 @@ function getOriginalId(ind: Individual): string { // Get the slot ID from an installation reference function getSlotId(ind: Individual): string | undefined { if (isInstallationRef(ind)) { - return ind.id.split("__installed_in__")[1]; + const parts = ind.id.split("__installed_in__")[1]; + if (parts) { + const subParts = parts.split("__"); + return subParts[0]; + } } return undefined; } -// Type for installation draw data -interface InstallationDrawData { - ind: Individual; - inst: Installation; - refId: string; - originalComponent: Individual; +// Get the installation ID from an installation reference +function getInstallationId(ind: Individual): string | undefined { + if (isInstallationRef(ind)) { + const parts = ind.id.split("__installed_in__")[1]; + if (parts) { + const subParts = parts.split("__"); + return subParts[1]; + } + } + return ind._installationId; } export function drawInstallations(ctx: DrawContext) { - const { svgElement, individuals, activities, config, dataset } = ctx; + const { svgElement, individuals, config, dataset } = ctx; if (!individuals || individuals.length === 0) return; - if (!activities || activities.length === 0) return; - - // Collect installations ONLY for installation reference rows - const installationData: InstallationDrawData[] = []; - - individuals.forEach((ind) => { - if (!isInstallationRef(ind)) return; - const originalId = getOriginalId(ind); - const slotId = getSlotId(ind); - if (!slotId) return; - const originalComponent = dataset.individuals.get(originalId); - if (!originalComponent) return; - - const relevant = (originalComponent.installations || []).filter( - (inst) => inst.targetId === slotId - ); - relevant.forEach((inst) => - installationData.push({ ind, inst, refId: ind.id, originalComponent }) - ); - }); - if (installationData.length === 0) return; + // Remove existing installation hatches + svgElement.selectAll(".installation-period").remove(); // Create or get the defs element for patterns let defs = svgElement.select("defs"); @@ -74,7 +63,6 @@ export function drawInstallations(ctx: DrawContext) { .attr("height", 8) .attr("patternTransform", "rotate(45)"); - // Background pattern .append("rect") .attr("width", 8) @@ -82,7 +70,6 @@ export function drawInstallations(ctx: DrawContext) { .attr("fill", "white") .attr("fill-opacity", 0.3); - // Diagonal lines pattern .append("line") .attr("x1", 0) @@ -93,30 +80,27 @@ export function drawInstallations(ctx: DrawContext) { .attr("stroke-width", 1.5); } - // Draw hatched area using the same path as the individual (chevron shape) - // This clips the hatch to match exactly - svgElement - .selectAll(".installation-period") - .data( - installationData, - (d: InstallationDrawData) => `${d.refId}:${d.inst.id}` - ) - .join("path") - .attr("class", "installation-period") - .attr("d", (d: InstallationDrawData) => { - // Get the path data from the individual element - const escapedId = CSS.escape("i" + d.refId); - const node = svgElement - .select("#" + escapedId) - .node() as SVGPathElement | null; - if (node) { - // Copy the exact path from the individual shape - return node.getAttribute("d") || ""; - } - return ""; - }) - .attr("fill", "url(#diagonal-hatch)") - .attr("stroke", "none") - .attr("pointer-events", "none") - .raise(); + // For each installation reference row, draw a hatched overlay matching the chevron shape + individuals.forEach((ind) => { + if (!isInstallationRef(ind)) return; + + // Get the path data from the individual element + const escapedId = CSS.escape("i" + ind.id); + const node = svgElement + .select("#" + escapedId) + .node() as SVGPathElement | null; + if (!node) return; + + const pathData = node.getAttribute("d"); + if (!pathData) return; + + // Draw hatch overlay using the same path as the individual + svgElement + .append("path") + .attr("class", "installation-period") + .attr("d", pathData) + .attr("fill", "url(#diagonal-hatch)") + .attr("stroke", "none") + .attr("pointer-events", "none"); + }); } diff --git a/editor-app/lib/Model.ts b/editor-app/lib/Model.ts index a421560..2ea701b 100644 --- a/editor-app/lib/Model.ts +++ b/editor-app/lib/Model.ts @@ -8,6 +8,7 @@ import type { Maybe, Participation, } from "./Schema.js"; +import { EntityType } from "./Schema"; import { EPOCH_END } from "./ActivityLib"; /** @@ -604,4 +605,99 @@ export class Model { } this.installations.delete(id); } + + getDisplayIndividuals(): Individual[] { + const result: Individual[] = []; + const processedSystems = new Set(); + const addedVirtualIds = new Set(); // Track virtual rows we've already added + + // Helper to add an individual and its nested items + const addWithNested = (ind: Individual) => { + const entityType = ind.entityType ?? EntityType.Individual; + + // Don't add InstalledComponents at top level - they only appear as virtual rows under slots + if (entityType === EntityType.InstalledComponent) { + return; + } + + result.push(ind); + + // If this is a System, add its SystemComponents + if (entityType === EntityType.System) { + processedSystems.add(ind.id); + + // Find all SystemComponents that belong to this System + this.individuals.forEach((child) => { + if ( + (child.entityType ?? EntityType.Individual) === + EntityType.SystemComponent && + child.parentSystemId === ind.id + ) { + result.push(child); + + // For each SystemComponent (slot), find InstalledComponents + this.individuals.forEach((installedComp) => { + if ( + (installedComp.entityType ?? EntityType.Individual) === + EntityType.InstalledComponent + ) { + // Check if this InstalledComponent has installations in this slot + const installations = installedComp.installations || []; + const installationsInSlot = installations.filter( + (inst) => inst.targetId === child.id + ); + + // Create a SEPARATE virtual row for EACH installation period + installationsInSlot.forEach((inst) => { + const virtualId = `${installedComp.id}__installed_in__${child.id}__${inst.id}`; + + // Skip if already added + if (addedVirtualIds.has(virtualId)) return; + addedVirtualIds.add(virtualId); + + const virtualIndividual: Individual = { + ...installedComp, + id: virtualId, + name: installedComp.name, + beginning: inst.beginning ?? 0, + ending: inst.ending ?? Model.END_OF_TIME, + // Store the installation ID for reference + _installationId: inst.id, + }; + result.push(virtualIndividual); + }); + } + }); + } + }); + } + }; + + // First, add Systems and their nested items (including virtual installation rows) + this.individuals.forEach((ind) => { + if ((ind.entityType ?? EntityType.Individual) === EntityType.System) { + addWithNested(ind); + } + }); + + // Then add remaining individuals that are NOT: + // - Systems (already processed) + // - SystemComponents (added under their parent System) + // - InstalledComponents (only shown as virtual rows under slots) + this.individuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + if ( + entityType !== EntityType.System && + entityType !== EntityType.SystemComponent && + entityType !== EntityType.InstalledComponent + ) { + // Only add if not already in result + if (!result.find((r) => r.id === ind.id)) { + result.push(ind); + } + } + }); + + return result; + } } diff --git a/editor-app/lib/Schema.ts b/editor-app/lib/Schema.ts index e125a7d..0699aa3 100644 --- a/editor-app/lib/Schema.ts +++ b/editor-app/lib/Schema.ts @@ -8,6 +8,9 @@ import type { Kind } from "./Model"; export { Maybe }; export type Id = string; +// Type alias for individual types (references Kind from Model) +export type IndividualType = Kind; + /** * A spatio-temporal extent is a thing that exists in the world. */ @@ -48,12 +51,20 @@ export interface Installation { /** * An individual is a person, place, or thing that participates in an activity. */ -export interface Individual extends STExtent { - beginsWithParticipant: boolean; - endsWithParticipant: boolean; +export interface Individual { + id: string; + name: string; + type: IndividualType; + description?: string; + beginning: number; + ending: number; + beginsWithParticipant?: boolean; + endsWithParticipant?: boolean; entityType?: EntityType; - parentSystemId?: Id; + parentSystemId?: string; installations?: Installation[]; + // Internal: used for virtual installation rows to reference the specific installation + _installationId?: string; } // Note: beginning/ending are inherited from STExtent From c0de96e7739fcd8591aa223045cac60004f58089 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 27 Nov 2025 14:44:38 +0000 Subject: [PATCH 35/81] feat: Update getOriginalAndSlot and getVisibleInterval functions to support new installation ID format for improved matching --- editor-app/diagram/DrawParticipations.ts | 60 +++++++++++++++++------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/editor-app/diagram/DrawParticipations.ts b/editor-app/diagram/DrawParticipations.ts index ce87735..3103e0e 100644 --- a/editor-app/diagram/DrawParticipations.ts +++ b/editor-app/diagram/DrawParticipations.ts @@ -10,16 +10,26 @@ function isInstallationRefId(id: string): boolean { return id.includes("__installed_in__"); } +// Updated to handle new format: componentId__installed_in__slotId__installationId function getOriginalAndSlot( id: string -): { originalId: string; slotId: string } | null { +): { originalId: string; slotId: string; installationId?: string } | null { if (!isInstallationRefId(id)) return null; const parts = id.split("__installed_in__"); if (parts.length !== 2) return null; - return { originalId: parts[0], slotId: parts[1] }; + + const originalId = parts[0]; + const rest = parts[1]; + + // rest could be "slotId" (old format) or "slotId__installationId" (new format) + const restParts = rest.split("__"); + const slotId = restParts[0]; + const installationId = restParts.length > 1 ? restParts[1] : undefined; + + return { originalId, slotId, installationId }; } -// Add this helper function to get visible interval for a participation +// Updated to use installation ID when available for precise matching function getVisibleInterval( activityStart: number, activityEnd: number, @@ -38,15 +48,23 @@ function getVisibleInterval( return { start: activityStart, end: activityEnd }; } - const inst = original.installations.find( - (i: any) => i.targetId === ref.slotId - ); + // Find the specific installation - prefer by ID if available, otherwise by slot + let inst; + if (ref.installationId) { + inst = original.installations.find((i: any) => i.id === ref.installationId); + } + + // Fallback to slot-based lookup if no installation ID or not found + if (!inst) { + inst = original.installations.find((i: any) => i.targetId === ref.slotId); + } + if (!inst) { return { start: activityStart, end: activityEnd }; } const instStart = Math.max(0, inst.beginning ?? 0); - const instEnd = inst.ending; + const instEnd = inst.ending ?? Model.END_OF_TIME; // Compute overlap const visibleStart = Math.max(activityStart, instStart); @@ -67,15 +85,23 @@ export function drawParticipations(ctx: DrawContext) { if (individuals) { individuals.forEach((ind) => { if (ind.id.includes("__installed_in__")) { - const parts = ind.id.split("__installed_in__"); - if (parts.length === 2) { - const originalId = parts[0]; - const slotId = parts[1]; - const original = dataset.individuals.get(originalId); + const ref = getOriginalAndSlot(ind.id); + if (ref) { + const original = dataset.individuals.get(ref.originalId); if (original && original.installations) { - const inst = original.installations.find( - (x) => x.targetId === slotId - ); + // Find the specific installation + let inst; + if (ref.installationId) { + inst = original.installations.find( + (x: any) => x.id === ref.installationId + ); + } + if (!inst) { + inst = original.installations.find( + (x: any) => x.targetId === ref.slotId + ); + } + if (inst) { // Ensure beginning is at least 0 const instBeginning = Math.max(0, inst.beginning ?? 0); @@ -242,10 +268,10 @@ export function drawParticipations(ctx: DrawContext) { }) .attr("y", (d: any) => { const node = svgElement - .select("#i" + d.participation.individualId) + .select("#i" + CSS.escape(d.participation.individualId)) .node(); if (!node) return 0; - const box = node.getBBox(); + const box = (node as SVGGraphicsElement).getBBox(); const totalLanes = d.totalLanes || 1; const laneIndex = d.lane || 0; From 091be4bff75863e75510f10fa92378bdd4066f7d Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 27 Nov 2025 14:54:33 +0000 Subject: [PATCH 36/81] feat: Implement separator drawing logic between SystemComponent groups for improved visual clarity --- editor-app/diagram/DrawIndividuals.ts | 105 ++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index c72b4aa..b08447c 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -140,6 +140,56 @@ function getNestingLevel(ind: Individual, dataset: Model): number { return level; } +// Helper to determine if we should draw a separator after this individual +function shouldDrawSeparatorAfter( + ind: Individual, + nextInd: Individual | undefined, + dataset: Model +): boolean { + const entityType = ind.entityType ?? EntityType.Individual; + + // If there's no next individual, no separator needed + if (!nextInd) return false; + + const nextEntityType = nextInd.entityType ?? EntityType.Individual; + + // Case 1: Current is an installation reference + if (isInstallationRef(ind)) { + const currentSlotId = getSlotId(ind); + + // If next is also an installation reference + if (isInstallationRef(nextInd)) { + const nextSlotId = getSlotId(nextInd); + // Draw separator if they're in different slots + return currentSlotId !== nextSlotId; + } + + // If next is a SystemComponent (new slot) or something else entirely + return true; + } + + // Case 2: Current is a SystemComponent (slot) + if (entityType === EntityType.SystemComponent) { + // If next is an installation in THIS slot, no separator yet + if (isInstallationRef(nextInd)) { + const nextSlotId = getSlotId(nextInd); + if (nextSlotId === ind.id) { + return false; // Installation belongs to this slot, no separator + } + } + + // If next is another SystemComponent in the same system, draw separator + if (nextEntityType === EntityType.SystemComponent) { + return ind.parentSystemId === nextInd.parentSystemId; + } + + // If we're leaving the system entirely, draw separator + return true; + } + + return false; +} + export function drawIndividuals(ctx: DrawContext) { const { config, svgElement, activities, dataset } = ctx; const individuals = ctx.individuals; @@ -379,9 +429,64 @@ export function drawIndividuals(ctx: DrawContext) { .attr("stroke-width", config.presentation.individual.strokeWidth) .attr("fill", config.presentation.individual.fill); + // Draw separators between SystemComponent groups + drawGroupSeparators(ctx, individuals, layout, config); + return svgElement; } +// Draw separator lines between SystemComponent groups (slot + its installations) +function drawGroupSeparators( + ctx: DrawContext, + individuals: Individual[], + layout: Map, + config: ConfigData +) { + const { svgElement, dataset } = ctx; + + // Remove existing separators + svgElement.selectAll(".group-separator").remove(); + + const separatorPositions: { y: number; x1: number; x2: number }[] = []; + + // Calculate the full width for separators + const individualLabelsEnabled = + config.labels.individual.enabled && keepIndividualLabels(individuals); + const x1 = config.layout.individual.xMargin; + const x2 = + config.viewPort.x * config.viewPort.zoom - config.layout.individual.xMargin; + + for (let i = 0; i < individuals.length; i++) { + const ind = individuals[i]; + const nextInd = individuals[i + 1]; + + if (shouldDrawSeparatorAfter(ind, nextInd, dataset)) { + const layoutData = layout.get(ind.id); + if (layoutData) { + // Position the separator below this individual + const y = + layoutData.y + layoutData.h + config.layout.individual.gap / 2; + separatorPositions.push({ y, x1, x2 }); + } + } + } + + // Draw the separators + svgElement + .selectAll(".group-separator") + .data(separatorPositions) + .join("line") + .attr("class", "group-separator") + .attr("x1", (d: { y: number; x1: number; x2: number }) => d.x1) + .attr("y1", (d: { y: number; x1: number; x2: number }) => d.y) + .attr("x2", (d: { y: number; x1: number; x2: number }) => d.x2) + .attr("y2", (d: { y: number; x1: number; x2: number }) => d.y) + .attr("stroke", "#d1d5db") // Light gray color + .attr("stroke-width", 1) + .attr("stroke-dasharray", "4,4") // Dashed line + .attr("opacity", 0.8); +} + export function hoverIndividuals(ctx: DrawContext) { const { config, svgElement, tooltip } = ctx; svgElement From 07ca4df373c90360dfb0de0e44f4ef7a66bcaa68 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 27 Nov 2025 15:47:41 +0000 Subject: [PATCH 37/81] feat: Enhance slot time bounds handling across components for improved installation management --- .../components/EditInstalledComponent.tsx | 256 ++++++++++++++---- editor-app/components/SetActivity.tsx | 65 ++++- editor-app/components/SetIndividual.tsx | 136 ++++++++-- editor-app/diagram/DrawIndividuals.ts | 107 ++++++-- editor-app/diagram/DrawParticipations.ts | 61 ++++- editor-app/lib/Schema.ts | 11 +- 6 files changed, 509 insertions(+), 127 deletions(-) diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index 62a4039..d82935c 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -11,7 +11,6 @@ interface Props { setIndividual: (individual: Individual) => void; dataset: Model; updateDataset?: (updater: (d: Model) => void) => void; - // Optional: specific slot ID to filter installations (when clicking on an installed component row) targetSlotId?: string; } @@ -30,29 +29,54 @@ const EditInstalledComponent = (props: Props) => { [] ); const [allInstallations, setAllInstallations] = useState([]); - - // Track removed installations to clean up participations on save const [removedInstallations, setRemovedInstallations] = useState< Installation[] >([]); - - // Validation errors const [errors, setErrors] = useState([]); - - // Track raw input values for better UX (allows empty fields while typing) const [rawInputs, setRawInputs] = useState< Map >(new Map()); - - // Whether we're showing all installations or just filtered ones const [showAll, setShowAll] = useState(false); + // Helper function to get effective slot time bounds + const getSlotTimeBounds = ( + slotId: string + ): { beginning: number; ending: number; slotName: string } => { + const slot = dataset.individuals.get(slotId); + if (!slot) { + return { beginning: 0, ending: Model.END_OF_TIME, slotName: slotId }; + } + + let beginning = slot.beginning; + let ending = slot.ending; + + // If slot doesn't have explicit bounds, inherit from parent system + if (beginning < 0 && slot.parentSystemId) { + const parentSystem = dataset.individuals.get(slot.parentSystemId); + if (parentSystem) { + beginning = parentSystem.beginning >= 0 ? parentSystem.beginning : 0; + } else { + beginning = 0; + } + } else if (beginning < 0) { + beginning = 0; + } + + if (ending >= Model.END_OF_TIME && slot.parentSystemId) { + const parentSystem = dataset.individuals.get(slot.parentSystemId); + if (parentSystem && parentSystem.ending < Model.END_OF_TIME) { + ending = parentSystem.ending; + } + } + + return { beginning, ending, slotName: slot.name }; + }; + useEffect(() => { if (individual && individual.installations) { const allInst = [...individual.installations]; setAllInstallations(allInst); - // If a specific slot is targeted, filter to just installations for that slot if (targetSlotId) { const filtered = allInst.filter( (inst) => inst.targetId === targetSlotId @@ -64,7 +88,6 @@ const EditInstalledComponent = (props: Props) => { setShowAll(true); } - // Initialize raw inputs for all installations const inputs = new Map(); allInst.forEach((inst) => { inputs.set(inst.id, { @@ -98,10 +121,18 @@ const EditInstalledComponent = (props: Props) => { const beginningStr = raw?.beginning ?? String(inst.beginning); const endingStr = raw?.ending ?? String(inst.ending); - const slotName = getSlotName(inst.targetId) || `Row ${idx + 1}`; + const slotInfo = inst.targetId + ? getSlotTimeBounds(inst.targetId) + : { + beginning: 0, + ending: Model.END_OF_TIME, + slotName: `Row ${idx + 1}`, + }; + const slotName = slotInfo.slotName; if (!inst.targetId) { newErrors.push(`${slotName}: Please select a target slot.`); + return; } if (beginningStr.trim() === "") { @@ -125,6 +156,18 @@ const EditInstalledComponent = (props: Props) => { if (beginning >= ending) { newErrors.push(`${slotName}: "From" must be less than "Until".`); } + + // Validate against slot bounds + if (beginning < slotInfo.beginning) { + newErrors.push( + `${slotName}: "From" (${beginning}) cannot be before slot starts (${slotInfo.beginning}).` + ); + } + if (slotInfo.ending < Model.END_OF_TIME && ending > slotInfo.ending) { + newErrors.push( + `${slotName}: "Until" (${ending}) cannot be after slot ends (${slotInfo.ending}).` + ); + } } }); @@ -144,17 +187,17 @@ const EditInstalledComponent = (props: Props) => { bySlot.forEach((installations, slotId) => { if (installations.length < 2) return; - const slotName = getSlotName(slotId); + const slotInfo = getSlotTimeBounds(slotId); // Sort by beginning time - installations.sort((a, b) => a.beginning - b.ending); + installations.sort((a, b) => (a.beginning ?? 0) - (b.beginning ?? 0)); for (let i = 0; i < installations.length - 1; i++) { const current = installations[i]; const next = installations[i + 1]; - if (current.ending > next.beginning) { + if ((current.ending ?? 0) > (next.beginning ?? 0)) { newErrors.push( - `${slotName}: Periods overlap (${current.beginning}-${current.ending} and ${next.beginning}-${next.ending}).` + `${slotInfo.slotName}: Periods overlap (${current.beginning}-${current.ending} and ${next.beginning}-${next.ending}).` ); } } @@ -171,7 +214,6 @@ const EditInstalledComponent = (props: Props) => { return; } - // Parse raw inputs into final values const updatedInstallations = localInstallations.map((inst) => { const raw = rawInputs.get(inst.id); return { @@ -181,40 +223,29 @@ const EditInstalledComponent = (props: Props) => { }; }); - // Merge with existing installations let finalInstallations: Installation[]; if (targetSlotId && !showAll) { - // We're editing installations for a specific slot only - // Keep installations for OTHER slots, add/update installations for THIS slot const keptFromOtherSlots = allInstallations.filter( (i) => i.targetId !== targetSlotId ); - - // Remove any that were marked for removal const removedIds = new Set(removedInstallations.map((i) => i.id)); const filteredUpdated = updatedInstallations.filter( (i) => !removedIds.has(i.id) ); - finalInstallations = [...keptFromOtherSlots, ...filteredUpdated]; } else { - // We're showing all, so just use the updated list (minus removed) const removedIds = new Set(removedInstallations.map((i) => i.id)); finalInstallations = updatedInstallations.filter( (i) => !removedIds.has(i.id) ); } - console.log("Saving installations:", finalInstallations); - if (updateDataset) { updateDataset((d: Model) => { - // Clean up participations for removed installations removedInstallations.forEach((removedInst) => { - const participationKey = `${individual.id}__installed_in__${removedInst.targetId}`; + const participationKey = `${individual.id}__installed_in__${removedInst.targetId}__${removedInst.id}`; - const activitiesToDelete: string[] = []; d.activities.forEach((activity) => { const parts = activity.participations; if (!parts) return; @@ -224,11 +255,8 @@ const EditInstalledComponent = (props: Props) => { parts.delete(participationKey); d.activities.set(activity.id, activity); } - if (parts.size === 0) activitiesToDelete.push(activity.id); } }); - - activitiesToDelete.forEach((aid) => d.activities.delete(aid)); }); const updated: Individual = { @@ -249,21 +277,32 @@ const EditInstalledComponent = (props: Props) => { }; const addInstallation = () => { - // When adding a new installation, use the targetSlotId if we're in filtered mode + // Get slot bounds if we have a target slot + const slotBounds = targetSlotId + ? getSlotTimeBounds(targetSlotId) + : { beginning: 0, ending: 10 }; + + const defaultBeginning = slotBounds.beginning; + const defaultEnding = + slotBounds.ending < Model.END_OF_TIME + ? slotBounds.ending + : defaultBeginning + 10; + const newInst: Installation = { id: uuidv4(), componentId: individual?.id || "", - targetId: targetSlotId || "", // Pre-fill with current slot if filtering - beginning: 0, - ending: 10, + targetId: targetSlotId || "", + beginning: defaultBeginning, + ending: defaultEnding, }; - console.log("Adding new installation:", newInst); - setLocalInstallations((prev) => [...prev, newInst]); setRawInputs((prev) => { const next = new Map(prev); - next.set(newInst.id, { beginning: "0", ending: "10" }); + next.set(newInst.id, { + beginning: String(defaultBeginning), + ending: String(defaultEnding), + }); return next; }); }; @@ -339,12 +378,41 @@ const EditInstalledComponent = (props: Props) => { return slot.name; }; + // Helper to check if a value is outside slot bounds + const isOutsideSlotBounds = ( + instId: string, + field: "beginning" | "ending" + ): boolean => { + const inst = localInstallations.find((i) => i.id === instId); + if (!inst || !inst.targetId) return false; + + const raw = rawInputs.get(instId); + const value = parseInt( + field === "beginning" ? raw?.beginning ?? "" : raw?.ending ?? "", + 10 + ); + if (isNaN(value)) return false; + + const slotBounds = getSlotTimeBounds(inst.targetId); + + if (field === "beginning") { + return value < slotBounds.beginning; + } else { + return slotBounds.ending < Model.END_OF_TIME && value > slotBounds.ending; + } + }; + if (!individual) return null; const isFiltered = !!targetSlotId && !showAll; const totalInstallations = allInstallations.length; const slotName = targetSlotId ? getSlotName(targetSlotId) : ""; + // Get slot bounds for display + const targetSlotBounds = targetSlotId + ? getSlotTimeBounds(targetSlotId) + : null; + const modalTitle = isFiltered ? `Edit Installation: ${individual.name} in ${slotName}` : `Edit All Installations: ${individual.name}`; @@ -371,6 +439,20 @@ const EditInstalledComponent = (props: Props) => {
)} + {/* Show slot time bounds if filtering by slot */} + {isFiltered && targetSlotBounds && ( + + Slot availability: {targetSlotBounds.beginning} -{" "} + {targetSlotBounds.ending >= Model.END_OF_TIME + ? "∞" + : targetSlotBounds.ending} +
+ + Installation periods must be within these bounds. + +
+ )} +

{isFiltered ? `Manage installation periods for this component in "${slotName}". You can have multiple non-overlapping periods (e.g., installed from 0-5, removed, then reinstalled from 8-15).` @@ -423,6 +505,20 @@ const EditInstalledComponent = (props: Props) => { ending: String(inst.ending), }; + // Get slot bounds for this installation + const instSlotBounds = inst.targetId + ? getSlotTimeBounds(inst.targetId) + : null; + + const beginningOutOfBounds = isOutsideSlotBounds( + inst.id, + "beginning" + ); + const endingOutOfBounds = isOutsideSlotBounds( + inst.id, + "ending" + ); + return ( {idx + 1} @@ -431,13 +527,33 @@ const EditInstalledComponent = (props: Props) => { + onChange={(e) => { updateInstallation( inst.id, "targetId", e.target.value - ) - } + ); + // Reset times to slot defaults when changing slot + if (e.target.value) { + const newSlotBounds = getSlotTimeBounds( + e.target.value + ); + const newEnding = + newSlotBounds.ending < Model.END_OF_TIME + ? newSlotBounds.ending + : newSlotBounds.beginning + 10; + updateRawInput( + inst.id, + "beginning", + String(newSlotBounds.beginning) + ); + updateRawInput( + inst.id, + "ending", + String(newEnding) + ); + } + }} className={!inst.targetId ? "border-warning" : ""} > @@ -449,43 +565,87 @@ const EditInstalledComponent = (props: Props) => { ); if (parent) parentName = parent.name + " → "; } + const slotBounds = getSlotTimeBounds(slot.id); + const boundsStr = + slotBounds.ending < Model.END_OF_TIME + ? ` (${slotBounds.beginning}-${slotBounds.ending})` + : slotBounds.beginning > 0 + ? ` (${slotBounds.beginning}-∞)` + : ""; return ( ); })} + {inst.targetId && instSlotBounds && ( + + Available: {instSlotBounds.beginning}- + {instSlotBounds.ending >= Model.END_OF_TIME + ? "∞" + : instSlotBounds.ending} + + )} )} updateRawInput(inst.id, "beginning", e.target.value) } - placeholder="0" + placeholder={String(instSlotBounds?.beginning ?? 0)} className={ - raw.beginning === "" ? "border-warning" : "" + raw.beginning === "" || beginningOutOfBounds + ? "border-danger" + : "" } + isInvalid={beginningOutOfBounds} /> + {beginningOutOfBounds && instSlotBounds && ( + + Min: {instSlotBounds.beginning} + + )} updateRawInput(inst.id, "ending", e.target.value) } - placeholder="10" - className={raw.ending === "" ? "border-warning" : ""} + placeholder={String(instSlotBounds?.ending ?? 10)} + className={ + raw.ending === "" || endingOutOfBounds + ? "border-danger" + : "" + } + isInvalid={endingOutOfBounds} /> + {endingOutOfBounds && instSlotBounds && ( + + Max:{" "} + {instSlotBounds.ending >= Model.END_OF_TIME + ? "∞" + : instSlotBounds.ending} + + )}

- {/* Entity type selection - NO ICONS */} + {/* Entity type selection */} Entity type { onChange={(e) => updateInputs("entityType", e.target.value as EntityType) } + disabled={isEditing} // Can't change entity type when editing > @@ -510,6 +621,11 @@ const SetIndividual = (props: Props) => { Installed Component (physical object) + {isEditing && ( + + Entity type cannot be changed after creation. + + )} {/* Parent – for SystemComponents only */} @@ -522,8 +638,9 @@ const SetIndividual = (props: Props) => { onChange={(e) => updateInputs("parentSystemId", e.target.value || undefined) } + disabled={isEditing} // Can't change parent when editing > - + {availableParents.map((p) => { const type = (p.entityType ?? EntityType.Individual) === @@ -537,6 +654,11 @@ const SetIndividual = (props: Props) => { ); })} + {isEditing && ( + + Parent cannot be changed after creation. + + )}
)} @@ -579,10 +701,36 @@ const SetIndividual = (props: Props) => { )} - {/* Show beginning/ending for Systems */} - {inputs.entityType === EntityType.System && ( + {/* SystemComponent time bounds - only when ADDING */} + {inputs.entityType === EntityType.SystemComponent && !isEditing && ( <> - + + + Set the time period when this slot/position exists. + Installed components can only be installed within this + period. + + {" "} + These values cannot be changed after creation. + + + + + {inputs.parentSystemId && + getParentBounds(inputs.parentSystemId) && ( + + Parent bounds:{" "} + {formatTime( + getParentBounds(inputs.parentSystemId)!.beginning + )}{" "} + -{" "} + {formatTime( + getParentBounds(inputs.parentSystemId)!.ending + )} + + )} + + Beginning { setInputs({ ...inputs, beginning: val }); setDirty(true); }} - placeholder="Leave empty to span full timeline" + placeholder="Leave empty to inherit from parent" min="0" /> - + Ending { setInputs({ ...inputs, ending: val }); setDirty(true); }} - placeholder="Leave empty to span full timeline" + placeholder="Leave empty to inherit from parent" min="1" /> )} - {/* Show beginning/ending for SystemComponents */} - {inputs.entityType === EntityType.SystemComponent && ( - <> - - - Beginning (optional - inherits from parent System if not - set) - - = 0 ? inputs.beginning : ""} - onChange={(e) => { - const val = - e.target.value === "" ? -1 : Number(e.target.value); - setInputs({ ...inputs, beginning: val }); - setDirty(true); - }} - placeholder="Inherits from parent system" - min="0" - /> - - - - Ending (optional - inherits from parent System if not set) - - { - const val = - e.target.value === "" - ? Model.END_OF_TIME - : Number(e.target.value); - setInputs({ ...inputs, ending: val }); - setDirty(true); - }} - placeholder="Inherits from parent system" - min="1" - /> - - + {/* SystemComponent time bounds - read-only when EDITING */} + {inputs.entityType === EntityType.SystemComponent && isEditing && ( + + + Slot time period:{" "} + {formatTime(selectedIndividual!.beginning)} -{" "} + {formatTime(selectedIndividual!.ending)} +
+ + Time bounds cannot be changed after creation. + +
+
)} {/* Note for InstalledComponents */} diff --git a/editor-app/diagram/DrawParticipations.ts b/editor-app/diagram/DrawParticipations.ts index 720204e..b4f6029 100644 --- a/editor-app/diagram/DrawParticipations.ts +++ b/editor-app/diagram/DrawParticipations.ts @@ -1,5 +1,5 @@ import { MouseEvent } from "react"; -import { Activity, Participation } from "@/lib/Schema"; +import { Activity, Participation, EntityType } from "@/lib/Schema"; import { ConfigData } from "./config"; import { DrawContext } from "./DrawHelpers"; import { Model } from "@/lib/Model"; @@ -64,7 +64,7 @@ function getSlotEffectiveTimeBounds( return { beginning, ending }; } -// Update getVisibleInterval to also respect slot bounds: +// Update getVisibleInterval to also handle SystemComponents and other entities with fixed timelines function getVisibleInterval( activityStart: number, activityEnd: number, @@ -72,48 +72,108 @@ function getVisibleInterval( dataset: any ): { start: number; end: number } { const ref = getOriginalAndSlot(individualId); - if (!ref) { - // Normal individual - use full activity interval - return { start: activityStart, end: activityEnd }; + + if (ref) { + // This is an installed component - crop to installation period AND slot bounds + const original = dataset.individuals.get(ref.originalId); + if (!original || !original.installations) { + return { start: activityStart, end: activityEnd }; + } + + // Find the specific installation + let inst; + if (ref.installationId) { + inst = original.installations.find( + (i: any) => i.id === ref.installationId + ); + } + if (!inst) { + inst = original.installations.find((i: any) => i.targetId === ref.slotId); + } + if (!inst) { + return { start: activityStart, end: activityEnd }; + } + + // Get installation bounds + const instStart = Math.max(0, inst.beginning ?? 0); + const instEnd = inst.ending ?? Model.END_OF_TIME; + + // Get slot bounds + const slotBounds = getSlotEffectiveTimeBounds(ref.slotId, dataset); + + // Compute the most restrictive bounds (intersection of all three) + const visibleStart = Math.max( + activityStart, + instStart, + slotBounds.beginning + ); + const visibleEnd = Math.min( + activityEnd, + instEnd, + slotBounds.ending < Model.END_OF_TIME ? slotBounds.ending : activityEnd + ); + + return { start: visibleStart, end: visibleEnd }; } - // This is an installed component - crop to installation period AND slot bounds - const original = dataset.individuals.get(ref.originalId); - if (!original || !original.installations) { + // Not an installation reference - check if it's a regular individual + const individual = dataset.individuals.get(individualId); + if (!individual) { return { start: activityStart, end: activityEnd }; } - // Find the specific installation - let inst; - if (ref.installationId) { - inst = original.installations.find((i: any) => i.id === ref.installationId); + const entityType = individual.entityType ?? EntityType.Individual; + + // For SystemComponents, clip to their effective time bounds + if (entityType === EntityType.SystemComponent) { + const slotBounds = getSlotEffectiveTimeBounds(individualId, dataset); + + const visibleStart = Math.max(activityStart, slotBounds.beginning); + const visibleEnd = Math.min( + activityEnd, + slotBounds.ending < Model.END_OF_TIME ? slotBounds.ending : activityEnd + ); + + return { start: visibleStart, end: visibleEnd }; } - if (!inst) { - inst = original.installations.find((i: any) => i.targetId === ref.slotId); + + // For Systems, clip to their time bounds if set + if (entityType === EntityType.System) { + const sysBeginning = individual.beginning >= 0 ? individual.beginning : 0; + const sysEnding = + individual.ending < Model.END_OF_TIME ? individual.ending : activityEnd; + + const visibleStart = Math.max(activityStart, sysBeginning); + const visibleEnd = Math.min(activityEnd, sysEnding); + + return { start: visibleStart, end: visibleEnd }; } - if (!inst) { + + // For InstalledComponents (the parent, not a specific installation), + // we shouldn't draw participation on the parent row itself + // But if we do, use full activity interval + if (entityType === EntityType.InstalledComponent) { return { start: activityStart, end: activityEnd }; } - // Get installation bounds - const instStart = Math.max(0, inst.beginning ?? 0); - const instEnd = inst.ending ?? Model.END_OF_TIME; - - // Get slot bounds - const slotBounds = getSlotEffectiveTimeBounds(ref.slotId, dataset); - - // Compute the most restrictive bounds (intersection of all three) - // 1. Activity time - // 2. Installation time - // 3. Slot time - const visibleStart = Math.max(activityStart, instStart, slotBounds.beginning); - const visibleEnd = Math.min( - activityEnd, - instEnd, - slotBounds.ending < Model.END_OF_TIME ? slotBounds.ending : activityEnd - ); - - return { start: visibleStart, end: visibleEnd }; + // For regular Individuals, check if they have fixed time bounds + // Only clip if they have explicit bounds set (not using participant-based timing) + if (individual.beginning >= 0 || individual.ending < Model.END_OF_TIME) { + const indBeginning = individual.beginning >= 0 ? individual.beginning : 0; + const indEnding = + individual.ending < Model.END_OF_TIME ? individual.ending : activityEnd; + + // Only clip if NOT using participant-based timing + // (beginsWithParticipant/endsWithParticipant means the bounds track activities, so don't clip) + if (!individual.beginsWithParticipant && !individual.endsWithParticipant) { + const visibleStart = Math.max(activityStart, indBeginning); + const visibleEnd = Math.min(activityEnd, indEnding); + return { start: visibleStart, end: visibleEnd }; + } + } + + // Default: use full activity interval + return { start: activityStart, end: activityEnd }; } export function drawParticipations(ctx: DrawContext) { From 51e46397aaff064eab354c53266467b841e71c3b Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Thu, 27 Nov 2025 17:09:52 +0000 Subject: [PATCH 39/81] feat: Refine SystemComponent parent validation and compatibility checks for slot bounds --- editor-app/components/SetIndividual.tsx | 206 +++++++++++++++++------- 1 file changed, 144 insertions(+), 62 deletions(-) diff --git a/editor-app/components/SetIndividual.tsx b/editor-app/components/SetIndividual.tsx index 69de270..d8ebca2 100644 --- a/editor-app/components/SetIndividual.tsx +++ b/editor-app/components/SetIndividual.tsx @@ -126,16 +126,17 @@ const SetIndividual = (props: Props) => { if (!inputs.installations) updateInputs("installations", []); }, [show]); - // Systems AND installed components can contain component slots + // Only Systems can be parents for SystemComponents (not InstalledComponents) const availableParents = useMemo( () => Array.from(dataset.individuals.values()).filter( (i) => - (i.entityType ?? EntityType.Individual) === EntityType.System || - (i.entityType ?? EntityType.Individual) === - EntityType.InstalledComponent + // Must be a System only + (i.entityType ?? EntityType.Individual) === EntityType.System && + // Cannot be the same as the item being edited + i.id !== inputs.id ), - [dataset] + [dataset, inputs.id] ); // Helper to get parent's time bounds for display @@ -152,6 +153,63 @@ const SetIndividual = (props: Props) => { }; }; + // Helper to check if slot's time bounds fit within a parent's bounds + const slotFitsInParent = ( + slotBeginning: number, + slotEnding: number, + parentSystemId?: string + ): { fits: boolean; message?: string } => { + if (!parentSystemId) return { fits: true }; + + const parentBounds = getParentBounds(parentSystemId); + if (!parentBounds) return { fits: true }; + + const effectiveSlotBeginning = slotBeginning >= 0 ? slotBeginning : 0; + const effectiveSlotEnding = + slotEnding < Model.END_OF_TIME ? slotEnding : Model.END_OF_TIME; + + if ( + parentBounds.beginning > 0 && + effectiveSlotBeginning < parentBounds.beginning + ) { + return { + fits: false, + message: `Slot begins at ${effectiveSlotBeginning} but parent starts at ${parentBounds.beginning}`, + }; + } + + if ( + parentBounds.ending < Model.END_OF_TIME && + effectiveSlotEnding > parentBounds.ending + ) { + return { + fits: false, + message: `Slot ends at ${effectiveSlotEnding} but parent ends at ${parentBounds.ending}`, + }; + } + + return { fits: true }; + }; + + // Check which parents are compatible with current slot bounds + const getParentCompatibility = ( + parentId: string + ): { compatible: boolean; reason?: string } => { + if (!isEditing || inputs.entityType !== EntityType.SystemComponent) { + return { compatible: true }; + } + + const slotBeginning = selectedIndividual!.beginning; + const slotEnding = selectedIndividual!.ending; + + const fitCheck = slotFitsInParent(slotBeginning, slotEnding, parentId); + + return { + compatible: fitCheck.fits, + reason: fitCheck.message, + }; + }; + const handleClose = () => { setShow(false); setInputs(defaultIndividual); @@ -296,33 +354,49 @@ const SetIndividual = (props: Props) => { runningErrors.push("Type field is required"); } - // Validate SystemComponent time bounds when adding - if ( - !isEditing && - inputs.entityType === EntityType.SystemComponent && - inputs.parentSystemId - ) { - const parentBounds = getParentBounds(inputs.parentSystemId); - if (parentBounds) { - const slotBeginning = inputs.beginning >= 0 ? inputs.beginning : 0; - const slotEnding = - inputs.ending < Model.END_OF_TIME ? inputs.ending : Model.END_OF_TIME; - - if ( - parentBounds.beginning >= 0 && - slotBeginning < parentBounds.beginning - ) { - runningErrors.push( - `Beginning (${slotBeginning}) cannot be before parent's beginning (${parentBounds.beginning})` - ); + // Validate SystemComponent + if (inputs.entityType === EntityType.SystemComponent) { + // When ADDING: validate slot bounds fit within parent + if (!isEditing && inputs.parentSystemId) { + const parentBounds = getParentBounds(inputs.parentSystemId); + if (parentBounds) { + const slotBeginning = inputs.beginning >= 0 ? inputs.beginning : 0; + const slotEnding = + inputs.ending < Model.END_OF_TIME + ? inputs.ending + : Model.END_OF_TIME; + + if ( + parentBounds.beginning > 0 && + slotBeginning < parentBounds.beginning + ) { + runningErrors.push( + `Beginning (${slotBeginning}) cannot be before parent's beginning (${parentBounds.beginning})` + ); + } + if ( + parentBounds.ending < Model.END_OF_TIME && + slotEnding > parentBounds.ending + ) { + runningErrors.push( + `Ending (${slotEnding}) cannot be after parent's ending (${parentBounds.ending})` + ); + } } - if ( - parentBounds.ending < Model.END_OF_TIME && - slotEnding > parentBounds.ending - ) { - runningErrors.push( - `Ending (${slotEnding}) cannot be after parent's ending (${parentBounds.ending})` - ); + } + + // When EDITING: validate existing slot bounds fit within NEW parent + if (isEditing && inputs.parentSystemId) { + const slotBeginning = selectedIndividual!.beginning; + const slotEnding = selectedIndividual!.ending; + + const fitCheck = slotFitsInParent( + slotBeginning, + slotEnding, + inputs.parentSystemId + ); + if (!fitCheck.fits && fitCheck.message) { + runningErrors.push(fitCheck.message); } } } @@ -433,6 +507,15 @@ const SetIndividual = (props: Props) => { return String(value); }; + // Get current slot bounds for display when editing SystemComponent + const currentSlotBounds = + isEditing && inputs.entityType === EntityType.SystemComponent + ? { + beginning: selectedIndividual!.beginning, + ending: selectedIndividual!.ending, + } + : null; + return ( <> - {selectedIndividual ? "Edit Individual" : "Add Individual"} + {selectedIndividual ? "Edit Entity" : "Add Entity"} From bb09fd0c43cf21410f6597b785bacfbcc27ce83b Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Fri, 28 Nov 2025 15:50:32 +0000 Subject: [PATCH 41/81] Refactor installation handling in diagram and model - Updated `getOriginalAndSlot` to `getOriginalAndTarget` for clarity and consistency with new installation format. - Modified `getSlotEffectiveTimeBounds` to `getTargetEffectiveTimeBounds` to reflect changes in terminology and logic. - Enhanced `getVisibleInterval` to accommodate new target handling for SystemComponents and InstalledComponents. - Adjusted Model class to ensure installations are correctly mirrored and cleaned up when individuals are modified. - Implemented `EditSystemComponentInstallation` component for managing installation periods, including validation and error handling. - Improved overall structure and readability of the code, ensuring better maintainability and understanding of installation relationships. --- editor-app/components/ActivityDiagramWrap.tsx | 216 ++---- .../components/EditInstalledComponent.tsx | 65 +- .../EditSystemComponentInstallation.tsx | 694 ++++++++++++++++++ editor-app/components/SetActivity.tsx | 428 ++++++----- editor-app/components/SetIndividual.tsx | 293 +------- editor-app/components/SetParticipation.tsx | 20 +- editor-app/diagram/DrawIndividuals.ts | 425 +++++------ editor-app/diagram/DrawParticipations.ts | 93 +-- editor-app/lib/Model.ts | 280 ++++--- editor-app/lib/Schema.ts | 24 +- 10 files changed, 1453 insertions(+), 1085 deletions(-) create mode 100644 editor-app/components/EditSystemComponentInstallation.tsx diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index f0143bf..85b4abf 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -28,6 +28,7 @@ import React from "react"; import Card from "react-bootstrap/Card"; import DiagramLegend from "./DiagramLegend"; import EditInstalledComponent from "./EditInstalledComponent"; +import EditSystemComponentInstallation from "./EditSystemComponentInstallation"; import EntityTypeLegend from "./EntityTypeLegend"; const beforeUnloadHandler = (ev: BeforeUnloadEvent) => { @@ -62,17 +63,28 @@ export default function ActivityDiagramWrap() { const [showConfigModal, setShowConfigModal] = useState(false); const [showSortIndividuals, setShowSortIndividuals] = useState(false); - // Add new state for the InstalledComponent editor + // State for the InstalledComponent editor const [showInstalledComponentEditor, setShowInstalledComponentEditor] = useState(false); const [selectedInstalledComponent, setSelectedInstalledComponent] = useState< Individual | undefined >(undefined); - // Add state for target slot ID (when clicking on a specific installation row) + // State for target slot ID (when clicking on a specific installation row) const [targetSlotId, setTargetSlotId] = useState( undefined ); + // State for the SystemComponent editor + const [showSystemComponentEditor, setShowSystemComponentEditor] = + useState(false); + const [selectedSystemComponent, setSelectedSystemComponent] = useState< + Individual | undefined + >(undefined); + // State for target system ID (when clicking on a specific installation row) + const [targetSystemId, setTargetSystemId] = useState( + undefined + ); + useEffect(() => { if (dirty) window.addEventListener("beforeunload", beforeUnloadHandler); else window.removeEventListener("beforeunload", beforeUnloadHandler); @@ -118,24 +130,45 @@ export default function ActivityDiagramWrap() { const clickIndividual = (i: Individual) => { // Check if this is an installation reference (virtual row) - // Format: componentId__installed_in__slotId__installationId + // Format: componentId__installed_in__targetId__installationId if (i.id.includes("__installed_in__")) { const originalId = i.id.split("__installed_in__")[0]; const rest = i.id.split("__installed_in__")[1]; - const [slotId, installationId] = rest.split("__"); + const [targetId, installationId] = rest.split("__"); const originalComponent = dataset.individuals.get(originalId); if (originalComponent) { - setSelectedInstalledComponent(originalComponent); - setTargetSlotId(slotId); - // Optionally, you could also pass the specific installationId - setShowInstalledComponentEditor(true); - return; + const originalType = + originalComponent.entityType ?? EntityType.Individual; + + if (originalType === EntityType.SystemComponent) { + // SystemComponent installed in System + setSelectedSystemComponent(originalComponent); + setTargetSystemId(targetId); + setShowSystemComponentEditor(true); + return; + } else if (originalType === EntityType.InstalledComponent) { + // InstalledComponent installed in SystemComponent + setSelectedInstalledComponent(originalComponent); + setTargetSlotId(targetId); + setShowInstalledComponentEditor(true); + return; + } } } - // If it's an InstalledComponent (the parent), show all installations + // If it's a SystemComponent (the parent), show installation editor + if ( + (i.entityType ?? EntityType.Individual) === EntityType.SystemComponent + ) { + setSelectedSystemComponent(i); + setTargetSystemId(undefined); // Show all installations + setShowSystemComponentEditor(true); + return; + } + + // If it's an InstalledComponent (the parent), show installation editor if ( (i.entityType ?? EntityType.Individual) === EntityType.InstalledComponent ) { @@ -145,7 +178,7 @@ export default function ActivityDiagramWrap() { return; } - // For other types, open the regular editor + // For other types (System, Individual), open the regular editor setSelectedIndividual(i); setShowIndividual(true); }; @@ -171,150 +204,10 @@ export default function ActivityDiagramWrap() { ); }; - // Sort individuals to show nested hierarchy - // InstalledComponents appear BOTH at top-level AND under their installation targets + // Use the Model's getDisplayIndividuals method for sorting const sortedIndividuals = useMemo(() => { - const result: Individual[] = []; - const visited = new Set(); - const addedVirtualIds = new Set(); // Track virtual rows we've already added - - // Recursive function to add an individual and its descendants - const addWithDescendants = (ind: Individual) => { - if (visited.has(ind.id)) return; - visited.add(ind.id); - result.push(ind); - - // Find children of this individual - const children: Individual[] = []; - - // 1. SystemComponents whose parent is this individual - individualsArray.forEach((child) => { - const childEntityType = child.entityType ?? EntityType.Individual; - if ( - childEntityType === EntityType.SystemComponent && - child.parentSystemId === ind.id - ) { - children.push(child); - } - }); - - // Sort children by name and add them - children - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((child) => addWithDescendants(child)); - - // 2. After adding SystemComponent children, check if this IS a SystemComponent - // If so, find InstalledComponents that are installed into this slot - const indEntityType = ind.entityType ?? EntityType.Individual; - if (indEntityType === EntityType.SystemComponent) { - // Find all InstalledComponents that have an installation targeting this slot - const installedHere = individualsArray.filter((ic) => { - const icType = ic.entityType ?? EntityType.Individual; - if (icType !== EntityType.InstalledComponent) return false; - if (!ic.installations || ic.installations.length === 0) return false; - return ic.installations.some((inst) => inst.targetId === ind.id); - }); - - // Add these as "installation references" - ONE ROW PER INSTALLATION PERIOD - installedHere - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((ic) => { - // Get all installations for this slot - const installationsInThisSlot = (ic.installations || []).filter( - (inst) => inst.targetId === ind.id - ); - - // Create a SEPARATE virtual row for EACH installation period - installationsInThisSlot.forEach((inst) => { - // Format: componentId__installed_in__slotId__installationId - const virtualId = `${ic.id}__installed_in__${ind.id}__${inst.id}`; - - // Skip if already added - if (addedVirtualIds.has(virtualId)) return; - addedVirtualIds.add(virtualId); - - const installRef: Individual = { - ...ic, - id: virtualId, - name: `${ic.name} (${inst.beginning ?? 0}-${ - inst.ending ?? "∞" - })`, - // Use the installation's time period for this specific row - beginning: inst.beginning ?? 0, - ending: inst.ending ?? Model.END_OF_TIME, - // Store the installation ID for reference - _installationId: inst.id, - }; - result.push(installRef); - }); - }); - } - }; - - // Separate entities into groups - const systems: Individual[] = []; - const installedComponents: Individual[] = []; - const regularIndividuals: Individual[] = []; - const orphanedSystemComponents: Individual[] = []; - - individualsArray.forEach((ind) => { - const entityType = ind.entityType ?? EntityType.Individual; - - if (entityType === EntityType.System) { - systems.push(ind); - } else if (entityType === EntityType.InstalledComponent) { - installedComponents.push(ind); - } else if (entityType === EntityType.SystemComponent) { - // SystemComponents without a valid parent are orphans (show at top level) - if (!ind.parentSystemId) { - orphanedSystemComponents.push(ind); - } else { - const parentExists = individualsArray.some( - (i) => i.id === ind.parentSystemId - ); - if (!parentExists) { - orphanedSystemComponents.push(ind); - } - // Otherwise, they will be added as children of their parent - } - } else if (entityType === EntityType.Individual) { - regularIndividuals.push(ind); - } - }); - - // 1. Add Systems first (with their nested SystemComponents and InstalledComponents) - systems - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((sys) => addWithDescendants(sys)); - - // 2. Add orphaned SystemComponents (if any) - orphanedSystemComponents - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((sc) => addWithDescendants(sc)); - - // 3. Add InstalledComponents at top level (so they can be edited/clicked) - // These are the "parent" entries - their virtual installation rows appear under slots - installedComponents - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((ic) => { - if (!visited.has(ic.id)) { - visited.add(ic.id); - result.push(ic); - } - }); - - // 4. Add regular Individuals last (sorted alphabetically) - regularIndividuals - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((ind) => { - if (!visited.has(ind.id)) { - visited.add(ind.id); - result.push(ind); - } - }); - - return result; - }, [individualsArray]); + return dataset.getDisplayIndividuals(); + }, [dataset]); // Build an array of activities from the dataset so it can be filtered below const activitiesArray = Array.from(dataset.activities.values()); @@ -360,7 +253,7 @@ export default function ActivityDiagramWrap() { - {/* Add the new InstalledComponent editor modal */} + {/* InstalledComponent editor modal */} + + {/* SystemComponent editor modal */} + ); } diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index d82935c..ee341b7 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -39,6 +39,7 @@ const EditInstalledComponent = (props: Props) => { const [showAll, setShowAll] = useState(false); // Helper function to get effective slot time bounds + // Now uses the slot's installation into a System to determine bounds const getSlotTimeBounds = ( slotId: string ): { beginning: number; ending: number; slotName: string } => { @@ -50,25 +51,28 @@ const EditInstalledComponent = (props: Props) => { let beginning = slot.beginning; let ending = slot.ending; - // If slot doesn't have explicit bounds, inherit from parent system - if (beginning < 0 && slot.parentSystemId) { - const parentSystem = dataset.individuals.get(slot.parentSystemId); - if (parentSystem) { - beginning = parentSystem.beginning >= 0 ? parentSystem.beginning : 0; - } else { - beginning = 0; + // If slot doesn't have explicit bounds, inherit from its installation into a System + if (slot.installations && slot.installations.length > 0) { + // Use the union of all installation periods + const instBeginnings = slot.installations.map((inst) => + Math.max(0, inst.beginning ?? 0) + ); + const instEndings = slot.installations.map( + (inst) => inst.ending ?? Model.END_OF_TIME + ); + const earliestBeginning = Math.min(...instBeginnings); + const latestEnding = Math.max(...instEndings); + + if (beginning < 0) { + beginning = earliestBeginning; + } + if (ending >= Model.END_OF_TIME && latestEnding < Model.END_OF_TIME) { + ending = latestEnding; } } else if (beginning < 0) { beginning = 0; } - if (ending >= Model.END_OF_TIME && slot.parentSystemId) { - const parentSystem = dataset.individuals.get(slot.parentSystemId); - if (parentSystem && parentSystem.ending < Model.END_OF_TIME) { - ending = parentSystem.ending; - } - } - return { beginning, ending, slotName: slot.name }; }; @@ -366,15 +370,26 @@ const EditInstalledComponent = (props: Props) => { (ind.entityType ?? EntityType.Individual) === EntityType.SystemComponent ); - // Helper to get slot name with parent - const getSlotName = (slotId: string): string => { + // Helper to get slot name with system + const getSlotDisplayName = (slotId: string): string => { if (!slotId) return ""; const slot = availableSlots.find((s) => s.id === slotId); if (!slot) return slotId; - if (slot.parentSystemId) { - const parent = dataset.individuals.get(slot.parentSystemId); - if (parent) return `${parent.name} → ${slot.name}`; + + // Find which system(s) this slot is installed in + if (slot.installations && slot.installations.length > 0) { + const systemIds = Array.from( + new Set(slot.installations.map((inst) => inst.targetId)) + ); + const systemNames = systemIds + .map((sysId) => { + const system = dataset.individuals.get(sysId); + return system?.name ?? sysId; + }) + .join(", "); + return `${slot.name} (in ${systemNames})`; } + return slot.name; }; @@ -406,7 +421,7 @@ const EditInstalledComponent = (props: Props) => { const isFiltered = !!targetSlotId && !showAll; const totalInstallations = allInstallations.length; - const slotName = targetSlotId ? getSlotName(targetSlotId) : ""; + const slotName = targetSlotId ? getSlotDisplayName(targetSlotId) : ""; // Get slot bounds for display const targetSlotBounds = targetSlotId @@ -558,13 +573,6 @@ const EditInstalledComponent = (props: Props) => { > {availableSlots.map((slot) => { - let parentName = ""; - if (slot.parentSystemId) { - const parent = dataset.individuals.get( - slot.parentSystemId - ); - if (parent) parentName = parent.name + " → "; - } const slotBounds = getSlotTimeBounds(slot.id); const boundsStr = slotBounds.ending < Model.END_OF_TIME @@ -574,8 +582,7 @@ const EditInstalledComponent = (props: Props) => { : ""; return ( ); diff --git a/editor-app/components/EditSystemComponentInstallation.tsx b/editor-app/components/EditSystemComponentInstallation.tsx new file mode 100644 index 0000000..e321b81 --- /dev/null +++ b/editor-app/components/EditSystemComponentInstallation.tsx @@ -0,0 +1,694 @@ +import React, { useState, useEffect } from "react"; +import { Button, Modal, Form, Table, Alert } from "react-bootstrap"; +import { v4 as uuidv4 } from "uuid"; +import { Individual, Installation, EntityType } from "@/lib/Schema"; +import { Model } from "@/lib/Model"; + +interface Props { + show: boolean; + setShow: (show: boolean) => void; + individual: Individual | undefined; + setIndividual: (individual: Individual) => void; + dataset: Model; + updateDataset?: (updater: (d: Model) => void) => void; + targetSystemId?: string; +} + +const EditSystemComponentInstallation = (props: Props) => { + const { + show, + setShow, + individual, + setIndividual, + dataset, + updateDataset, + targetSystemId, + } = props; + + const [localInstallations, setLocalInstallations] = useState( + [] + ); + const [allInstallations, setAllInstallations] = useState([]); + const [removedInstallations, setRemovedInstallations] = useState< + Installation[] + >([]); + const [errors, setErrors] = useState([]); + const [rawInputs, setRawInputs] = useState< + Map + >(new Map()); + const [showAll, setShowAll] = useState(false); + + // Helper function to get effective system time bounds + const getSystemTimeBounds = ( + systemId: string + ): { beginning: number; ending: number; systemName: string } => { + const system = dataset.individuals.get(systemId); + if (!system) { + return { beginning: 0, ending: Model.END_OF_TIME, systemName: systemId }; + } + + let beginning = system.beginning >= 0 ? system.beginning : 0; + let ending = system.ending; + + return { beginning, ending, systemName: system.name }; + }; + + useEffect(() => { + if (individual && individual.installations) { + const allInst = [...individual.installations]; + setAllInstallations(allInst); + + if (targetSystemId) { + const filtered = allInst.filter( + (inst) => inst.targetId === targetSystemId + ); + setLocalInstallations(filtered); + setShowAll(false); + } else { + setLocalInstallations(allInst); + setShowAll(true); + } + + const inputs = new Map(); + allInst.forEach((inst) => { + inputs.set(inst.id, { + beginning: String(inst.beginning ?? 0), + ending: String(inst.ending ?? 10), + }); + }); + setRawInputs(inputs); + } else { + setLocalInstallations([]); + setAllInstallations([]); + setRawInputs(new Map()); + setShowAll(!targetSystemId); + } + setRemovedInstallations([]); + setErrors([]); + }, [individual, show, targetSystemId]); + + const handleClose = () => { + setShow(false); + setRemovedInstallations([]); + setErrors([]); + setShowAll(false); + }; + + const validateInstallations = (): boolean => { + const newErrors: string[] = []; + + localInstallations.forEach((inst, idx) => { + const raw = rawInputs.get(inst.id); + const beginningStr = raw?.beginning ?? String(inst.beginning); + const endingStr = raw?.ending ?? String(inst.ending); + + const systemInfo = inst.targetId + ? getSystemTimeBounds(inst.targetId) + : { + beginning: 0, + ending: Model.END_OF_TIME, + systemName: `Row ${idx + 1}`, + }; + const systemName = systemInfo.systemName; + + if (!inst.targetId) { + newErrors.push(`${systemName}: Please select a target system.`); + return; + } + + if (beginningStr.trim() === "") { + newErrors.push(`${systemName}: "From" time is required.`); + } + + if (endingStr.trim() === "") { + newErrors.push(`${systemName}: "Until" time is required.`); + } + + const beginning = parseInt(beginningStr, 10); + const ending = parseInt(endingStr, 10); + + if (!isNaN(beginning) && !isNaN(ending)) { + if (beginning < 0) { + newErrors.push(`${systemName}: "From" cannot be negative.`); + } + if (ending < 1) { + newErrors.push(`${systemName}: "Until" must be at least 1.`); + } + if (beginning >= ending) { + newErrors.push(`${systemName}: "From" must be less than "Until".`); + } + + // Validate against system bounds + if (beginning < systemInfo.beginning) { + newErrors.push( + `${systemName}: "From" (${beginning}) cannot be before system starts (${systemInfo.beginning}).` + ); + } + if ( + systemInfo.ending < Model.END_OF_TIME && + ending > systemInfo.ending + ) { + newErrors.push( + `${systemName}: "Until" (${ending}) cannot be after system ends (${systemInfo.ending}).` + ); + } + } + }); + + // Check for overlapping periods in the same system + const bySystem = new Map(); + localInstallations.forEach((inst) => { + if (!inst.targetId) return; + const raw = rawInputs.get(inst.id); + const beginning = parseInt(raw?.beginning ?? String(inst.beginning), 10); + const ending = parseInt(raw?.ending ?? String(inst.ending), 10); + if (isNaN(beginning) || isNaN(ending)) return; + + const list = bySystem.get(inst.targetId) || []; + list.push({ ...inst, beginning, ending }); + bySystem.set(inst.targetId, list); + }); + + bySystem.forEach((installations, systemId) => { + if (installations.length < 2) return; + const systemInfo = getSystemTimeBounds(systemId); + + // Sort by beginning time + installations.sort((a, b) => (a.beginning ?? 0) - (b.beginning ?? 0)); + + for (let i = 0; i < installations.length - 1; i++) { + const current = installations[i]; + const next = installations[i + 1]; + if ((current.ending ?? 0) > (next.beginning ?? 0)) { + newErrors.push( + `${systemInfo.systemName}: Periods overlap (${current.beginning}-${current.ending} and ${next.beginning}-${next.ending}).` + ); + } + } + }); + + setErrors(newErrors); + return newErrors.length === 0; + }; + + const handleSave = () => { + if (!individual) return; + + if (!validateInstallations()) { + return; + } + + const updatedInstallations = localInstallations.map((inst) => { + const raw = rawInputs.get(inst.id); + return { + ...inst, + beginning: parseInt(raw?.beginning ?? String(inst.beginning), 10) || 0, + ending: parseInt(raw?.ending ?? String(inst.ending), 10) || 10, + }; + }); + + let finalInstallations: Installation[]; + + if (targetSystemId && !showAll) { + const keptFromOtherSystems = allInstallations.filter( + (i) => i.targetId !== targetSystemId + ); + const removedIds = new Set(removedInstallations.map((i) => i.id)); + const filteredUpdated = updatedInstallations.filter( + (i) => !removedIds.has(i.id) + ); + finalInstallations = [...keptFromOtherSystems, ...filteredUpdated]; + } else { + const removedIds = new Set(removedInstallations.map((i) => i.id)); + finalInstallations = updatedInstallations.filter( + (i) => !removedIds.has(i.id) + ); + } + + if (updateDataset) { + updateDataset((d: Model) => { + // Clean up participations for removed installations + removedInstallations.forEach((removedInst) => { + const participationKey = `${individual.id}__installed_in__${removedInst.targetId}__${removedInst.id}`; + + d.activities.forEach((activity) => { + const parts = activity.participations; + if (!parts) return; + + if (parts instanceof Map) { + if (parts.has(participationKey)) { + parts.delete(participationKey); + d.activities.set(activity.id, activity); + } + } + }); + }); + + const updated: Individual = { + ...individual, + installations: finalInstallations, + }; + d.addIndividual(updated); + }); + } else { + const updated: Individual = { + ...individual, + installations: finalInstallations, + }; + setIndividual(updated); + } + + handleClose(); + }; + + const addInstallation = () => { + // Get system bounds if we have a target system + const systemBounds = targetSystemId + ? getSystemTimeBounds(targetSystemId) + : { beginning: 0, ending: 10 }; + + const defaultBeginning = systemBounds.beginning; + const defaultEnding = + systemBounds.ending < Model.END_OF_TIME + ? systemBounds.ending + : defaultBeginning + 10; + + const newInst: Installation = { + id: uuidv4(), + componentId: individual?.id || "", + targetId: targetSystemId || "", + beginning: defaultBeginning, + ending: defaultEnding, + }; + + setLocalInstallations((prev) => [...prev, newInst]); + setRawInputs((prev) => { + const next = new Map(prev); + next.set(newInst.id, { + beginning: String(defaultBeginning), + ending: String(defaultEnding), + }); + return next; + }); + }; + + const removeInstallation = (instId: string) => { + const removed = localInstallations.find((inst) => inst.id === instId); + if (removed) { + setRemovedInstallations((prev) => [...prev, removed]); + } + setLocalInstallations((prev) => prev.filter((inst) => inst.id !== instId)); + setRawInputs((prev) => { + const next = new Map(prev); + next.delete(instId); + return next; + }); + setErrors([]); + }; + + const updateInstallation = ( + instId: string, + field: keyof Installation, + value: any + ) => { + setLocalInstallations((prev) => + prev.map((inst) => + inst.id === instId ? { ...inst, [field]: value } : inst + ) + ); + if (errors.length > 0) { + setErrors([]); + } + }; + + const updateRawInput = ( + instId: string, + field: "beginning" | "ending", + value: string + ) => { + setRawInputs((prev) => { + const next = new Map(prev); + const current = next.get(instId) || { beginning: "0", ending: "10" }; + next.set(instId, { ...current, [field]: value }); + return next; + }); + + if (value !== "") { + const parsed = parseInt(value, 10); + if (!isNaN(parsed)) { + updateInstallation(instId, field, parsed); + } + } + + if (errors.length > 0) { + setErrors([]); + } + }; + + // Get available systems + const availableSystems = Array.from(dataset.individuals.values()).filter( + (ind) => (ind.entityType ?? EntityType.Individual) === EntityType.System + ); + + // Helper to get system name + const getSystemName = (systemId: string): string => { + if (!systemId) return ""; + const system = availableSystems.find((s) => s.id === systemId); + return system?.name ?? systemId; + }; + + // Helper to check if a value is outside system bounds + const isOutsideSystemBounds = ( + instId: string, + field: "beginning" | "ending" + ): boolean => { + const inst = localInstallations.find((i) => i.id === instId); + if (!inst || !inst.targetId) return false; + + const raw = rawInputs.get(instId); + const value = parseInt( + field === "beginning" ? raw?.beginning ?? "" : raw?.ending ?? "", + 10 + ); + if (isNaN(value)) return false; + + const systemBounds = getSystemTimeBounds(inst.targetId); + + if (field === "beginning") { + return value < systemBounds.beginning; + } else { + return ( + systemBounds.ending < Model.END_OF_TIME && value > systemBounds.ending + ); + } + }; + + if (!individual) return null; + + const isFiltered = !!targetSystemId && !showAll; + const totalInstallations = allInstallations.length; + const systemName = targetSystemId ? getSystemName(targetSystemId) : ""; + + // Get system bounds for display + const targetSystemBounds = targetSystemId + ? getSystemTimeBounds(targetSystemId) + : null; + + const modalTitle = isFiltered + ? `Edit Installation: ${individual.name} in ${systemName}` + : `Edit All Installations: ${individual.name}`; + + return ( + + + {modalTitle} + + + {isFiltered && totalInstallations > localInstallations.length && ( +
+ +
+ )} + + {/* Show system time bounds if filtering by system */} + {isFiltered && targetSystemBounds && ( + + System availability: {targetSystemBounds.beginning}{" "} + -{" "} + {targetSystemBounds.ending >= Model.END_OF_TIME + ? "∞" + : targetSystemBounds.ending} +
+ + Installation periods must be within these bounds. + +
+ )} + +

+ {isFiltered + ? `Manage installation periods for this system component in "${systemName}". You can have multiple non-overlapping periods.` + : "Manage all installation periods for this system component across different systems."} +

+ + {localInstallations.length === 0 ? ( +
+

+ No installations configured. + {isFiltered + ? ` Add a period when this component is installed in "${systemName}".` + : " Add an installation to place this component in a system."} +

+ +
+ ) : ( + <> + + + + + {!isFiltered && ( + + )} + + + + + + + {localInstallations.map((inst, idx) => { + const raw = rawInputs.get(inst.id) || { + beginning: String(inst.beginning), + ending: String(inst.ending), + }; + + // Get system bounds for this installation + const instSystemBounds = inst.targetId + ? getSystemTimeBounds(inst.targetId) + : null; + + const beginningOutOfBounds = isOutsideSystemBounds( + inst.id, + "beginning" + ); + const endingOutOfBounds = isOutsideSystemBounds( + inst.id, + "ending" + ); + + return ( + + + {!isFiltered && ( + + )} + + + + + ); + })} + +
+ # + + Target System * + + From * + + Until * + + Actions +
{idx + 1} + { + updateInstallation( + inst.id, + "targetId", + e.target.value + ); + // Reset times to system defaults when changing system + if (e.target.value) { + const newSystemBounds = getSystemTimeBounds( + e.target.value + ); + const newEnding = + newSystemBounds.ending < Model.END_OF_TIME + ? newSystemBounds.ending + : newSystemBounds.beginning + 10; + updateRawInput( + inst.id, + "beginning", + String(newSystemBounds.beginning) + ); + updateRawInput( + inst.id, + "ending", + String(newEnding) + ); + } + }} + className={!inst.targetId ? "border-warning" : ""} + > + + {availableSystems.map((system) => { + const systemBounds = getSystemTimeBounds( + system.id + ); + const boundsStr = + systemBounds.ending < Model.END_OF_TIME + ? ` (${systemBounds.beginning}-${systemBounds.ending})` + : systemBounds.beginning > 0 + ? ` (${systemBounds.beginning}-∞)` + : ""; + return ( + + ); + })} + + {inst.targetId && instSystemBounds && ( + + Available: {instSystemBounds.beginning}- + {instSystemBounds.ending >= Model.END_OF_TIME + ? "∞" + : instSystemBounds.ending} + + )} + + + updateRawInput(inst.id, "beginning", e.target.value) + } + placeholder={String(instSystemBounds?.beginning ?? 0)} + className={ + raw.beginning === "" || beginningOutOfBounds + ? "border-danger" + : "" + } + isInvalid={beginningOutOfBounds} + /> + {beginningOutOfBounds && instSystemBounds && ( + + Min: {instSystemBounds.beginning} + + )} + + + updateRawInput(inst.id, "ending", e.target.value) + } + placeholder={String(instSystemBounds?.ending ?? 10)} + className={ + raw.ending === "" || endingOutOfBounds + ? "border-danger" + : "" + } + isInvalid={endingOutOfBounds} + /> + {endingOutOfBounds && instSystemBounds && ( + + Max:{" "} + {instSystemBounds.ending >= Model.END_OF_TIME + ? "∞" + : instSystemBounds.ending} + + )} + + +
+ +
+ +
+ + )} + + {availableSystems.length === 0 && !isFiltered && ( + + No systems available. Create a System first to install this + component. + + )} + + {errors.length > 0 && ( + + Please fix the following: +
    + {errors.map((error, i) => ( +
  • {error}
  • + ))} +
+
+ )} +
+ +
+ {isFiltered + ? `${localInstallations.length} period${ + localInstallations.length !== 1 ? "s" : "" + } in this system` + : `${localInstallations.length} total installation${ + localInstallations.length !== 1 ? "s" : "" + }`} +
+
+ + +
+
+
+ ); +}; + +export default EditSystemComponentInstallation; diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index 581f075..a2b303d 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -48,12 +48,12 @@ function getOriginalId(ind: Individual): string { return ind.id; } -// Get the slot ID from an installation reference -function getSlotId(ind: Individual): string | undefined { +// Get the target ID (System for SystemComponent, SystemComponent for InstalledComponent) from an installation reference +function getTargetId(ind: Individual): string | undefined { if (isInstallationRef(ind)) { const rest = ind.id.split("__installed_in__")[1]; if (rest) { - // Format: slotId__installationId or just slotId (old format) + // Format: targetId__installationId or just targetId (old format) const parts = rest.split("__"); return parts[0]; } @@ -113,189 +113,201 @@ const SetActivity = (props: Props) => { const [showParentModal, setShowParentModal] = useState(false); const [selectedParentId, setSelectedParentId] = useState(null); - // Build the individuals list with proper labels for the Select component - const individualsWithLabels = useMemo(() => { - const individualsArray = Array.from(dataset.individuals.values()); - - // Helper to get the hierarchy label for an individual - const getHierarchyLabel = ( - ind: Individual, - installation?: { beginning: number; ending: number } - ): string => { - const entityType = ind.entityType ?? EntityType.Individual; + // Helper to get effective time bounds for a target (System or SystemComponent) + const getTargetEffectiveTimeBounds = ( + targetId: string + ): { beginning: number; ending: number } => { + const target = dataset.individuals.get(targetId); + if (!target) { + return { beginning: 0, ending: Model.END_OF_TIME }; + } - // For installation references, build full path: System - Slot - Component (time range) - if (isInstallationRef(ind)) { - const originalId = getOriginalId(ind); - const slotId = getSlotId(ind); - const installationId = getInstallationId(ind); + let beginning = target.beginning; + let ending = target.ending; - if (slotId) { - const slot = dataset.individuals.get(slotId); - const originalComponent = dataset.individuals.get(originalId); + const targetType = target.entityType ?? EntityType.Individual; - if (slot && originalComponent) { - // Get time range from the installation - let timeRange = ""; - if (installation) { - const endStr = - installation.ending >= Model.END_OF_TIME - ? "∞" - : installation.ending; - timeRange = ` (${installation.beginning}-${endStr})`; - } else if (originalComponent.installations) { - const inst = originalComponent.installations.find( - (i) => i.id === installationId - ); - if (inst) { - const endStr = - (inst.ending ?? Model.END_OF_TIME) >= Model.END_OF_TIME - ? "∞" - : inst.ending; - timeRange = ` (${inst.beginning ?? 0}-${endStr})`; - } - } + // If target is a SystemComponent, get bounds from its installation into a System + if (targetType === EntityType.SystemComponent) { + if (target.installations && target.installations.length > 0) { + // Use the union of all installation periods + const instBeginnings = target.installations.map((inst) => + Math.max(0, inst.beginning ?? 0) + ); + const instEndings = target.installations.map( + (inst) => inst.ending ?? Model.END_OF_TIME + ); + const earliestBeginning = Math.min(...instBeginnings); + const latestEnding = Math.max(...instEndings); - // Find the parent system of the slot - if (slot.parentSystemId) { - const system = dataset.individuals.get(slot.parentSystemId); - if (system) { - return `${system.name} → ${slot.name} → ${originalComponent.name}${timeRange}`; - } - } - return `${slot.name} → ${originalComponent.name}${timeRange}`; - } + if (beginning < 0) { + beginning = earliestBeginning; } - return `${ind.name} (installed component)`; - } - - // For SystemComponents, show: System → Component (slot) - if (entityType === EntityType.SystemComponent) { - if (ind.parentSystemId) { - const parent = dataset.individuals.get(ind.parentSystemId); - if (parent) { - return `${parent.name} → ${ind.name} (slot)`; - } + if (ending >= Model.END_OF_TIME && latestEnding < Model.END_OF_TIME) { + ending = latestEnding; } - return `${ind.name} (slot)`; + } else if (beginning < 0) { + beginning = 0; } + } - // For Systems - if (entityType === EntityType.System) { - return `${ind.name} (system)`; - } + // If target is a System, use its defined bounds + if (targetType === EntityType.System) { + if (beginning < 0) beginning = 0; + } - // For InstalledComponents at top level (for management, not participation) - if (entityType === EntityType.InstalledComponent) { - return `${ind.name} (component - click to manage installations)`; - } + if (beginning < 0) beginning = 0; - // For regular individuals - return ind.name; - }; + return { beginning, ending }; + }; + // Build the individuals list with proper labels for the Select component + const individualsWithLabels = useMemo(() => { const result: IndividualOption[] = []; - const visited = new Set(); - const addedVirtualIds = new Set(); - - const addWithDescendants = (ind: Individual) => { - if (visited.has(ind.id)) return; - visited.add(ind.id); - - const entityType = ind.entityType ?? EntityType.Individual; + const addedIds = new Set(); - // Skip top-level InstalledComponents - they can't participate directly - // Only their installation period rows can participate - if (entityType === EntityType.InstalledComponent) { - // Don't add - will be added as installation refs under slots - return; + // Helper to create an option + const addOption = ( + ind: Individual, + overrideId?: string, + overrideName?: string, + installation?: { beginning: number; ending: number; id: string } + ) => { + const id = overrideId || ind.id; + if (addedIds.has(id)) return; + addedIds.add(id); + + let displayLabel = overrideName || ind.name; + + // Add timing info if it's an installation + if (installation) { + const endStr = + installation.ending >= Model.END_OF_TIME ? "∞" : installation.ending; + // If the name doesn't already contain timing info, add it + if (!displayLabel.includes(`(${installation.beginning}-`)) { + displayLabel += ` (${installation.beginning}-${endStr})`; + } } result.push({ ...ind, - displayLabel: getHierarchyLabel(ind), + id: id, + name: overrideName || ind.name, // Keep name clean for display in chip + displayLabel: displayLabel, // Full label for dropdown + beginning: installation ? installation.beginning : ind.beginning, + ending: installation ? installation.ending : ind.ending, + _installationId: installation ? installation.id : undefined, }); + }; - // Find SystemComponent children - const children: Individual[] = []; - individualsArray.forEach((child) => { - const childEntityType = child.entityType ?? EntityType.Individual; - if ( - childEntityType === EntityType.SystemComponent && - child.parentSystemId === ind.id - ) { - children.push(child); - } - }); + // Collect all entities by type + const systems: Individual[] = []; + const systemComponents: Individual[] = []; + const installedComponents: Individual[] = []; + const regularIndividuals: Individual[] = []; - children - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((child) => addWithDescendants(child)); - - // Add installation references for SystemComponents - const indEntityType = ind.entityType ?? EntityType.Individual; - if (indEntityType === EntityType.SystemComponent) { - const installedHere = individualsArray.filter((ic) => { - const icType = ic.entityType ?? EntityType.Individual; - if (icType !== EntityType.InstalledComponent) return false; - if (!ic.installations || ic.installations.length === 0) return false; - return ic.installations.some((inst) => inst.targetId === ind.id); - }); + dataset.individuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + switch (entityType) { + case EntityType.System: + systems.push(ind); + break; + case EntityType.SystemComponent: + systemComponents.push(ind); + break; + case EntityType.InstalledComponent: + installedComponents.push(ind); + break; + default: + regularIndividuals.push(ind); + break; + } + }); - installedHere - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((ic) => { - // Get ALL installations for this component in this slot - const installationsInSlot = (ic.installations || []).filter( - (inst) => inst.targetId === ind.id + // Sort each group + const sortByName = (a: Individual, b: Individual) => + a.name.localeCompare(b.name); + systems.sort(sortByName); + systemComponents.sort(sortByName); + installedComponents.sort(sortByName); + regularIndividuals.sort(sortByName); + + // 1. Systems and their nested hierarchy (matching Model.getDisplayIndividuals) + systems.forEach((system) => { + // Add System + addOption(system, undefined, `${system.name} (System)`); + + // Find SystemComponents installed in this System + systemComponents.forEach((sc) => { + const installations = sc.installations || []; + const installationsInSystem = installations.filter( + (inst) => inst.targetId === system.id + ); + + installationsInSystem.sort( + (a, b) => (a.beginning ?? 0) - (b.beginning ?? 0) + ); + + installationsInSystem.forEach((inst) => { + const virtualId = `${sc.id}__installed_in__${system.id}__${inst.id}`; + const label = `${system.name} → ${sc.name}`; + + addOption(sc, virtualId, label, { + beginning: inst.beginning ?? 0, + ending: inst.ending ?? Model.END_OF_TIME, + id: inst.id, + }); + + // Under this SystemComponent virtual row, add InstalledComponents + installedComponents.forEach((ic) => { + const icInstallations = ic.installations || []; + const installationsInSlot = icInstallations.filter( + (icInst) => icInst.targetId === sc.id + ); + + installationsInSlot.sort( + (a, b) => (a.beginning ?? 0) - (b.beginning ?? 0) ); - // Create a SEPARATE virtual row for EACH installation period - installationsInSlot.forEach((inst) => { - // Format: componentId__installed_in__slotId__installationId - const virtualId = `${ic.id}__installed_in__${ind.id}__${inst.id}`; - - // Skip if already added - if (addedVirtualIds.has(virtualId)) return; - addedVirtualIds.add(virtualId); - - const installRef: IndividualOption = { - ...ic, - id: virtualId, - beginning: inst.beginning ?? 0, - ending: inst.ending ?? Model.END_OF_TIME, - _installationId: inst.id, - displayLabel: "", // Will be set below - }; - installRef.displayLabel = getHierarchyLabel(installRef, { - beginning: inst.beginning ?? 0, - ending: inst.ending ?? Model.END_OF_TIME, - }); - result.push(installRef); + installationsInSlot.forEach((icInst) => { + // Check overlap with the SystemComponent's installation in the System + const scStart = inst.beginning ?? 0; + const scEnd = inst.ending ?? Model.END_OF_TIME; + const icStart = icInst.beginning ?? 0; + const icEnd = icInst.ending ?? Model.END_OF_TIME; + + if (icStart < scEnd && icEnd > scStart) { + // Use context suffix to allow same IC installation to appear under multiple SC occurrences + const contextSuffix = `__ctx_${inst.id}`; + const icVirtualId = `${ic.id}__installed_in__${sc.id}__${icInst.id}${contextSuffix}`; + const icLabel = `${system.name} → ${sc.name} → ${ic.name}`; + + addOption(ic, icVirtualId, icLabel, { + beginning: icInst.beginning ?? 0, + ending: icInst.ending ?? Model.END_OF_TIME, + id: icInst.id, + }); + } }); }); - } - }; + }); + }); + }); - // Start with top-level items - const topLevel = individualsArray.filter((ind) => { - const entityType = ind.entityType ?? EntityType.Individual; - if (entityType === EntityType.System) return true; - if (entityType === EntityType.Individual) return true; - if (entityType === EntityType.InstalledComponent) return false; // Skip - only shown as virtual rows - if (entityType === EntityType.SystemComponent) { - if (!ind.parentSystemId) return true; - const parentExists = individualsArray.some( - (i) => i.id === ind.parentSystemId - ); - return !parentExists; - } - return false; + // 2. SystemComponents (top level) + systemComponents.forEach((sc) => { + addOption(sc, undefined, `${sc.name} (System Component - Top Level)`); }); - topLevel - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach((ind) => addWithDescendants(ind)); + // 3. InstalledComponents (top level) + installedComponents.forEach((ic) => { + addOption(ic, undefined, `${ic.name} (Installed Component - Top Level)`); + }); + + // 4. Regular Individuals + regularIndividuals.forEach((ind) => { + addOption(ind); + }); return result; }, [dataset]); @@ -332,7 +344,6 @@ const SetActivity = (props: Props) => { const latestEnding = d.lastParticipantEnding(individual.id); // Only update beginning if the individual has beginsWithParticipant enabled - // (indicated by having a non-negative beginning that should track participations) if (individual.beginsWithParticipant && individual.beginning >= 0) { individual.beginning = earliestBeginning ? earliestBeginning : -1; } @@ -497,36 +508,56 @@ const SetActivity = (props: Props) => { inputs.participations.forEach((participation, participantId) => { if (isInstallationRef({ id: participantId } as Individual)) { const originalId = participantId.split("__installed_in__")[0]; - const slotId = participantId.split("__installed_in__")[1]; - - const installedComponent = dataset.individuals.get(originalId); - if (installedComponent?.installations) { - const installation = installedComponent.installations.find( - (inst) => inst.targetId === slotId - ); + const rest = participantId.split("__installed_in__")[1]; + const parts = rest.split("__"); + const targetId = parts[0]; + const installationId = parts[1]; + + const component = dataset.individuals.get(originalId); + if (component?.installations) { + // Find the specific installation + const installation = installationId + ? component.installations.find( + (inst) => inst.id === installationId + ) + : component.installations.find( + (inst) => inst.targetId === targetId + ); if (installation) { const installStart = installation.beginning ?? 0; const installEnd = installation.ending ?? Model.END_OF_TIME; + // Get target bounds to further constrain + const targetBounds = getTargetEffectiveTimeBounds(targetId); + const effectiveStart = Math.max( + installStart, + targetBounds.beginning + ); + const effectiveEnd = Math.min( + installEnd, + targetBounds.ending < Model.END_OF_TIME + ? targetBounds.ending + : installEnd + ); + // Only error if there's NO overlap at all - // Partial overlap is allowed - the participation will be cropped visually if ( !hasOverlap( inputs.beginning, inputs.ending, - installStart, - installEnd + effectiveStart, + effectiveEnd ) ) { - const slot = dataset.individuals.get(slotId); - const slotName = slot?.name ?? slotId; + const target = dataset.individuals.get(targetId); + const targetName = target?.name ?? targetId; runningErrors.push( `Activity timing (${inputs.beginning}-${inputs.ending}) has no overlap with installation period ` + `of "${ - installedComponent.name - }" in "${slotName}" (${installStart}-${ - installEnd === Model.END_OF_TIME ? "∞" : installEnd + component.name + }" in "${targetName}" (${effectiveStart}-${ + effectiveEnd === Model.END_OF_TIME ? "∞" : effectiveEnd })` ); } @@ -677,40 +708,7 @@ const SetActivity = (props: Props) => { setShowParentModal(false); }; - // Add helper function to get slot effective bounds - const getSlotEffectiveTimeBounds = ( - slotId: string - ): { beginning: number; ending: number } => { - const slot = dataset.individuals.get(slotId); - if (!slot) { - return { beginning: 0, ending: Model.END_OF_TIME }; - } - - let beginning = slot.beginning; - let ending = slot.ending; - - if (beginning < 0 && slot.parentSystemId) { - const parentSystem = dataset.individuals.get(slot.parentSystemId); - if (parentSystem) { - beginning = parentSystem.beginning >= 0 ? parentSystem.beginning : 0; - } else { - beginning = 0; - } - } else if (beginning < 0) { - beginning = 0; - } - - if (ending >= Model.END_OF_TIME && slot.parentSystemId) { - const parentSystem = dataset.individuals.get(slot.parentSystemId); - if (parentSystem && parentSystem.ending < Model.END_OF_TIME) { - ending = parentSystem.ending; - } - } - - return { beginning, ending }; - }; - - // Update eligibleParticipants to also check slot bounds + // Update eligibleParticipants to check target bounds via installations const eligibleParticipants = useMemo(() => { const actStart = inputs.beginning; const actEnd = inputs.ending; @@ -719,14 +717,14 @@ const SetActivity = (props: Props) => { let indStart = ind.beginning >= 0 ? ind.beginning : 0; let indEnd = ind.ending < Model.END_OF_TIME ? ind.ending : Infinity; - // For installation references, also constrain to slot bounds + // For installation references, also constrain to target bounds if (isInstallationRef(ind)) { - const slotId = getSlotId(ind); - if (slotId) { - const slotBounds = getSlotEffectiveTimeBounds(slotId); - indStart = Math.max(indStart, slotBounds.beginning); - if (slotBounds.ending < Model.END_OF_TIME) { - indEnd = Math.min(indEnd, slotBounds.ending); + const targetId = getTargetId(ind); + if (targetId) { + const targetBounds = getTargetEffectiveTimeBounds(targetId); + indStart = Math.max(indStart, targetBounds.beginning); + if (targetBounds.ending < Model.END_OF_TIME) { + indEnd = Math.min(indEnd, targetBounds.ending); } } } diff --git a/editor-app/components/SetIndividual.tsx b/editor-app/components/SetIndividual.tsx index bda7b14..21e8ab0 100644 --- a/editor-app/components/SetIndividual.tsx +++ b/editor-app/components/SetIndividual.tsx @@ -49,7 +49,6 @@ const SetIndividual = (props: Props) => { beginsWithParticipant: false, endsWithParticipant: false, entityType: EntityType.Individual, - parentSystemId: undefined, installations: [], }; @@ -126,90 +125,6 @@ const SetIndividual = (props: Props) => { if (!inputs.installations) updateInputs("installations", []); }, [show]); - // Only Systems can be parents for SystemComponents (not InstalledComponents) - const availableParents = useMemo( - () => - Array.from(dataset.individuals.values()).filter( - (i) => - // Must be a System only - (i.entityType ?? EntityType.Individual) === EntityType.System && - // Cannot be the same as the item being edited - i.id !== inputs.id - ), - [dataset, inputs.id] - ); - - // Helper to get parent's time bounds for display - const getParentBounds = ( - parentSystemId?: string - ): { beginning: number; ending: number } | null => { - if (!parentSystemId) return null; - const parent = dataset.individuals.get(parentSystemId); - if (!parent) return null; - return { - beginning: parent.beginning >= 0 ? parent.beginning : 0, - ending: - parent.ending < Model.END_OF_TIME ? parent.ending : Model.END_OF_TIME, - }; - }; - - // Helper to check if slot's time bounds fit within a parent's bounds - const slotFitsInParent = ( - slotBeginning: number, - slotEnding: number, - parentSystemId?: string - ): { fits: boolean; message?: string } => { - if (!parentSystemId) return { fits: true }; - - const parentBounds = getParentBounds(parentSystemId); - if (!parentBounds) return { fits: true }; - - const effectiveSlotBeginning = slotBeginning >= 0 ? slotBeginning : 0; - const effectiveSlotEnding = - slotEnding < Model.END_OF_TIME ? slotEnding : Model.END_OF_TIME; - - if ( - parentBounds.beginning > 0 && - effectiveSlotBeginning < parentBounds.beginning - ) { - return { - fits: false, - message: `Slot begins at ${effectiveSlotBeginning} but parent starts at ${parentBounds.beginning}`, - }; - } - - if ( - parentBounds.ending < Model.END_OF_TIME && - effectiveSlotEnding > parentBounds.ending - ) { - return { - fits: false, - message: `Slot ends at ${effectiveSlotEnding} but parent ends at ${parentBounds.ending}`, - }; - } - - return { fits: true }; - }; - - // Check which parents are compatible with current slot bounds - const getParentCompatibility = ( - parentId: string - ): { compatible: boolean; reason?: string } => { - if (!isEditing || inputs.entityType !== EntityType.SystemComponent) { - return { compatible: true }; - } - - const slotBeginning = selectedIndividual!.beginning; - const slotEnding = selectedIndividual!.ending; - - const fitCheck = slotFitsInParent(slotBeginning, slotEnding, parentId); - - return { - compatible: fitCheck.fits, - reason: fitCheck.message, - }; - }; - const handleClose = () => { setShow(false); setInputs(defaultIndividual); @@ -258,19 +173,9 @@ const SetIndividual = (props: Props) => { } else { finalEnding = inputs.ending ?? Model.END_OF_TIME; } - } else if (entityType === EntityType.SystemComponent) { - // For SystemComponents: - // - When ADDING: use the inputs - // - When EDITING: preserve the original values (they're not editable) - if (isEditing) { - finalBeginning = selectedIndividual!.beginning; - finalEnding = selectedIndividual!.ending; - } else { - finalBeginning = inputs.beginning ?? -1; - finalEnding = inputs.ending ?? Model.END_OF_TIME; - } } else { - // For System and InstalledComponent, span full timeline + // For System, SystemComponent, and InstalledComponent, span full timeline + // Their actual temporal bounds come from installations finalBeginning = -1; finalEnding = Model.END_OF_TIME; } @@ -287,7 +192,6 @@ const SetIndividual = (props: Props) => { endsWithParticipant: entityType === EntityType.Individual ? endsWithParticipant : false, entityType: entityType, - parentSystemId: inputs.parentSystemId, installations: inputs.installations ?? [], }; @@ -354,53 +258,6 @@ const SetIndividual = (props: Props) => { runningErrors.push("Type field is required"); } - // Validate SystemComponent - if (inputs.entityType === EntityType.SystemComponent) { - // When ADDING: validate slot bounds fit within parent - if (!isEditing && inputs.parentSystemId) { - const parentBounds = getParentBounds(inputs.parentSystemId); - if (parentBounds) { - const slotBeginning = inputs.beginning >= 0 ? inputs.beginning : 0; - const slotEnding = - inputs.ending < Model.END_OF_TIME - ? inputs.ending - : Model.END_OF_TIME; - - if ( - parentBounds.beginning > 0 && - slotBeginning < parentBounds.beginning - ) { - runningErrors.push( - `Beginning (${slotBeginning}) cannot be before parent's beginning (${parentBounds.beginning})` - ); - } - if ( - parentBounds.ending < Model.END_OF_TIME && - slotEnding > parentBounds.ending - ) { - runningErrors.push( - `Ending (${slotEnding}) cannot be after parent's ending (${parentBounds.ending})` - ); - } - } - } - - // When EDITING: validate existing slot bounds fit within NEW parent - if (isEditing && inputs.parentSystemId) { - const slotBeginning = selectedIndividual!.beginning; - const slotEnding = selectedIndividual!.ending; - - const fitCheck = slotFitsInParent( - slotBeginning, - slotEnding, - inputs.parentSystemId - ); - if (!fitCheck.fits && fitCheck.message) { - runningErrors.push(fitCheck.message); - } - } - } - if (runningErrors.length === 0) { return true; } else { @@ -500,22 +357,6 @@ const SetIndividual = (props: Props) => { setEditingTypeValue(""); }; - // Format time for display - const formatTime = (value: number): string => { - if (value < 0) return "Start of timeline"; - if (value >= Model.END_OF_TIME) return "End of timeline"; - return String(value); - }; - - // Get current slot bounds for display when editing SystemComponent - const currentSlotBounds = - isEditing && inputs.entityType === EntityType.SystemComponent - ? { - beginning: selectedIndividual!.beginning, - ending: selectedIndividual!.ending, - } - : null; - return ( <> + + + )} + + {/* Installations section for InstalledComponents */} + {currentEntityType === EntityType.InstalledComponent && + isEditing && ( +
+
+
+ Installations +

+ {installationCount === 0 + ? "Not installed in any slot yet." + : `Installed in ${installationCount} slot${ + installationCount !== 1 ? "s" : "" + }.`} +

+
+ +
+
)} - {/* Note for InstalledComponents */} - {(inputs.entityType ?? EntityType.Individual) === - EntityType.InstalledComponent && - selectedIndividual && ( + {/* Note for new SystemComponents */} + {currentEntityType === EntityType.SystemComponent && !isEditing && ( + + + After saving, you can add installations to specify when this + component is installed in a system. + + + )} + + {/* Note for new InstalledComponents */} + {currentEntityType === EntityType.InstalledComponent && + !isEditing && ( - To manage installation periods (when this component is - installed in a slot), save this and then click on the - component in the diagram. + After saving, you can add installations to specify when this + component is installed in a slot. Note: System Components + must be installed in a System first before they can receive + Installed Components. )} From b4424e578da003094c14d87a62bab9848cdab0e2 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sat, 29 Nov 2025 13:11:20 +0000 Subject: [PATCH 48/81] feat: Refactor EditInstalledComponent and EditSystemComponentInstallation to streamline installation period management and improve UI consistency --- .../components/EditInstalledComponent.tsx | 581 ++++++++---------- .../EditSystemComponentInstallation.tsx | 407 ++++++------ 2 files changed, 449 insertions(+), 539 deletions(-) diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index 165c8c3..fa79fa7 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -585,26 +585,6 @@ const EditInstalledComponent = (props: Props) => { )} - {/* Show slot time bounds if filtering by slot */} - {isFiltered && targetSlotBounds && ( - - Slot availability: {targetSlotBounds.beginning} -{" "} - {targetSlotBounds.ending >= Model.END_OF_TIME - ? "∞" - : targetSlotBounds.ending} - {systemName && ( - <> -
- System: {systemName} - - )} -
- - Installation periods must be within these bounds. - -
- )} -

{isFiltered ? `Manage installation periods for this component in "${slotName}"${ @@ -613,312 +593,287 @@ const EditInstalledComponent = (props: Props) => { : "Manage all installation periods for this component across different slots."}

- {localInstallations.length === 0 ? ( -
-

- No installations configured. - {isFiltered - ? ` Add a period when this component is installed in "${slotName}".` - : " Add an installation to place this component in a slot."} -

- -
- ) : ( - <> - - - - + + + + ); + })} + +
- # + {/* Always show the table structure */} + + + + + {!isFiltered && ( + <> + + + + )} + + + + + + + {localInstallations.map((inst, idx) => { + const raw = rawInputs.get(inst.id) || { + beginning: String(inst.beginning), + ending: String(inst.ending), + }; + + // Get Systems where this slot is installed + const systemsForSlot = inst.targetId + ? getSystemsForSlot(inst.targetId) + : []; + + // Get slot bounds for this installation + const instSlotBounds = inst.targetId + ? getSlotTimeBoundsForSystem( + inst.targetId, + inst.systemContextId + ) + : null; + + const beginningOutOfBounds = isOutsideSlotBounds( + inst.id, + "beginning" + ); + const endingOutOfBounds = isOutsideSlotBounds(inst.id, "ending"); + + return ( + + {!isFiltered && ( <> - - - - )} - - - - - - - {localInstallations.map((inst, idx) => { - const raw = rawInputs.get(inst.id) || { - beginning: String(inst.beginning), - ending: String(inst.ending), - }; - - // Get Systems where this slot is installed - const systemsForSlot = inst.targetId - ? getSystemsForSlot(inst.targetId) - : []; - - // Get slot bounds for this installation - const instSlotBounds = inst.targetId - ? getSlotTimeBoundsForSystem( - inst.targetId, - inst.systemContextId - ) - : null; - - const beginningOutOfBounds = isOutsideSlotBounds( - inst.id, - "beginning" - ); - const endingOutOfBounds = isOutsideSlotBounds( - inst.id, - "ending" - ); - - return ( - - - {!isFiltered && ( - <> - - - - )} - - - ); - })} - -
+ # + + Target Slot * + + System Context * + From * + + Until * + + Actions +
{idx + 1} - Target Slot * - - System Context * - - From * - - Until * - - Actions -
{idx + 1} - { - const newSlotId = e.target.value; - updateInstallation( - inst.id, - "targetId", - newSlotId - ); - // Clear system context when changing slot - updateInstallation( - inst.id, - "systemContextId", - undefined - ); - updateInstallation( - inst.id, - "scInstallationContextId", - undefined - ); - // Reset times to slot defaults when changing slot - if (newSlotId) { - const newSlotBounds = - getSlotTimeBounds(newSlotId); - const newEnding = - newSlotBounds.ending < Model.END_OF_TIME - ? newSlotBounds.ending - : newSlotBounds.beginning + 10; - updateRawInput( - inst.id, - "beginning", - String(newSlotBounds.beginning) - ); - updateRawInput( - inst.id, - "ending", - String(newEnding) - ); - } - }} - className={!inst.targetId ? "border-warning" : ""} - > - - {availableSlots.map((slot) => ( - - ))} - - - { - const newSystemId = e.target.value || undefined; - updateInstallation( - inst.id, - "systemContextId", - newSystemId - ); - // Set the SC installation context ID - if (inst.targetId && newSystemId) { - const scInst = getScInstallationForSystem( - inst.targetId, - newSystemId - ); - updateInstallation( - inst.id, - "scInstallationContextId", - scInst?.id - ); - // Update times to match system context - const newBounds = getSlotTimeBoundsForSystem( - inst.targetId, - newSystemId - ); - const newEnding = - newBounds.ending < Model.END_OF_TIME - ? newBounds.ending - : newBounds.beginning + 10; - updateRawInput( - inst.id, - "beginning", - String(newBounds.beginning) - ); - updateRawInput( - inst.id, - "ending", - String(newEnding) - ); - } else { - updateInstallation( - inst.id, - "scInstallationContextId", - undefined - ); - } - }} - disabled={!inst.targetId} - className={ - inst.targetId && - systemsForSlot.length > 1 && - !inst.systemContextId - ? "border-warning" - : "" - } - > - - {systemsForSlot.length > 1 && - systemsForSlot.map((sys) => { - const scInst = getScInstallationForSystem( - inst.targetId, - sys.id - ); - const boundsStr = scInst - ? ` (${scInst.beginning ?? 0}-${ - scInst.ending ?? "∞" - })` - : ""; - return ( - - ); - })} - - {inst.targetId && - systemsForSlot.length === 1 && - !inst.systemContextId && ( - - Auto: {systemsForSlot[0].name} - - )} - - - updateRawInput(inst.id, "beginning", e.target.value) - } - placeholder={String(instSlotBounds?.beginning ?? 0)} - className={ - raw.beginning === "" || beginningOutOfBounds - ? "border-danger" - : "" - } - isInvalid={beginningOutOfBounds} - /> - {beginningOutOfBounds && instSlotBounds && ( - - Min: {instSlotBounds.beginning} - - )} + value={inst.targetId} + onChange={(e) => { + const newSlotId = e.target.value; + updateInstallation(inst.id, "targetId", newSlotId); + // Clear system context when changing slot + updateInstallation( + inst.id, + "systemContextId", + undefined + ); + updateInstallation( + inst.id, + "scInstallationContextId", + undefined + ); + // Reset times to slot defaults when changing slot + if (newSlotId) { + const newSlotBounds = + getSlotTimeBounds(newSlotId); + const newEnding = + newSlotBounds.ending < Model.END_OF_TIME + ? newSlotBounds.ending + : newSlotBounds.beginning + 10; + updateRawInput( + inst.id, + "beginning", + String(newSlotBounds.beginning) + ); + updateRawInput( + inst.id, + "ending", + String(newEnding) + ); + } + }} + className={!inst.targetId ? "border-warning" : ""} + > + + {availableSlots.map((slot) => ( + + ))} + - - updateRawInput(inst.id, "ending", e.target.value) - } - placeholder={String(instSlotBounds?.ending ?? 10)} + value={inst.systemContextId || ""} + onChange={(e) => { + const newSystemId = e.target.value || undefined; + updateInstallation( + inst.id, + "systemContextId", + newSystemId + ); + // Set the SC installation context ID + if (inst.targetId && newSystemId) { + const scInst = getScInstallationForSystem( + inst.targetId, + newSystemId + ); + updateInstallation( + inst.id, + "scInstallationContextId", + scInst?.id + ); + // Update times to match system context + const newBounds = getSlotTimeBoundsForSystem( + inst.targetId, + newSystemId + ); + const newEnding = + newBounds.ending < Model.END_OF_TIME + ? newBounds.ending + : newBounds.beginning + 10; + updateRawInput( + inst.id, + "beginning", + String(newBounds.beginning) + ); + updateRawInput( + inst.id, + "ending", + String(newEnding) + ); + } else { + updateInstallation( + inst.id, + "scInstallationContextId", + undefined + ); + } + }} + disabled={!inst.targetId} className={ - raw.ending === "" || endingOutOfBounds - ? "border-danger" + inst.targetId && + systemsForSlot.length > 1 && + !inst.systemContextId + ? "border-warning" : "" } - isInvalid={endingOutOfBounds} - /> - {endingOutOfBounds && instSlotBounds && ( - - Max:{" "} - {instSlotBounds.ending >= Model.END_OF_TIME - ? "∞" - : instSlotBounds.ending} - - )} - - + + {systemsForSlot.length > 1 && + systemsForSlot.map((sys) => { + const scInst = getScInstallationForSystem( + inst.targetId, + sys.id + ); + const boundsStr = scInst + ? ` (${scInst.beginning ?? 0}-${ + scInst.ending ?? "∞" + })` + : ""; + return ( + + ); + })} + + {inst.targetId && + systemsForSlot.length === 1 && + !inst.systemContextId && ( + + Auto: {systemsForSlot[0].name} + + )}
- -
- -
- - )} + + )} +
+ + updateRawInput(inst.id, "beginning", e.target.value) + } + placeholder={String(instSlotBounds?.beginning ?? 0)} + className={ + raw.beginning === "" || beginningOutOfBounds + ? "border-danger" + : "" + } + isInvalid={beginningOutOfBounds} + /> + {beginningOutOfBounds && instSlotBounds && ( + + Min: {instSlotBounds.beginning} + + )} + + + updateRawInput(inst.id, "ending", e.target.value) + } + placeholder={String(instSlotBounds?.ending ?? 10)} + className={ + raw.ending === "" || endingOutOfBounds + ? "border-danger" + : "" + } + isInvalid={endingOutOfBounds} + /> + {endingOutOfBounds && instSlotBounds && ( + + Max:{" "} + {instSlotBounds.ending >= Model.END_OF_TIME + ? "∞" + : instSlotBounds.ending} + + )} + + +
+ +
+ +
{availableSlots.length === 0 && !isFiltered && ( diff --git a/editor-app/components/EditSystemComponentInstallation.tsx b/editor-app/components/EditSystemComponentInstallation.tsx index 94035b8..35f73ae 100644 --- a/editor-app/components/EditSystemComponentInstallation.tsx +++ b/editor-app/components/EditSystemComponentInstallation.tsx @@ -435,241 +435,196 @@ const EditSystemComponentInstallation = (props: Props) => { )} - {/* Show system time bounds if filtering by system */} - {isFiltered && targetSystemBounds && ( - - System availability: {targetSystemBounds.beginning}{" "} - -{" "} - {targetSystemBounds.ending >= Model.END_OF_TIME - ? "∞" - : targetSystemBounds.ending} -
- - Installation periods must be within these bounds. - -
- )} -

{isFiltered ? `Manage installation periods for this system component in "${systemName}". You can have multiple non-overlapping periods.` : "Manage all installation periods for this system component across different systems."}

- {localInstallations.length === 0 ? ( -
-

- No installations configured. - {isFiltered - ? ` Add a period when this component is installed in "${systemName}".` - : " Add an installation to place this component in a system."} -

- -
- ) : ( - <> - - - - + {/* Always show the table structure */} +
- # -
+ + + + {!isFiltered && ( + + )} + + + + + + + {localInstallations.map((inst, idx) => { + const raw = rawInputs.get(inst.id) || { + beginning: String(inst.beginning), + ending: String(inst.ending ?? ""), + }; + + // Get system bounds for this installation + const instSystemBounds = inst.targetId + ? getSystemTimeBounds(inst.targetId) + : null; + + const beginningOutOfBounds = isOutsideSystemBounds( + inst.id, + "beginning" + ); + const endingOutOfBounds = isOutsideSystemBounds( + inst.id, + "ending" + ); + + return ( + + {!isFiltered && ( - + )} - - - + + + - - - {localInstallations.map((inst, idx) => { - const raw = rawInputs.get(inst.id) || { - beginning: String(inst.beginning), - ending: String(inst.ending), - }; - - // Get system bounds for this installation - const instSystemBounds = inst.targetId - ? getSystemTimeBounds(inst.targetId) - : null; - - const beginningOutOfBounds = isOutsideSystemBounds( - inst.id, - "beginning" - ); - const endingOutOfBounds = isOutsideSystemBounds( - inst.id, - "ending" - ); - - return ( - - - {!isFiltered && ( - - )} - - - - - ); - })} - -
+ # + + Target System * + + From * + Until + Actions +
{idx + 1} - Target System * - + { + updateInstallation( + inst.id, + "targetId", + e.target.value + ); + // Reset times to system defaults when changing system + if (e.target.value) { + const newSystemBounds = getSystemTimeBounds( + e.target.value + ); + updateRawInput( + inst.id, + "beginning", + String(newSystemBounds.beginning) + ); + updateRawInput(inst.id, "ending", ""); + } + }} + className={!inst.targetId ? "border-warning" : ""} + > + + {availableSystems.map((system) => { + const systemBounds = getSystemTimeBounds(system.id); + const boundsStr = + systemBounds.ending < Model.END_OF_TIME + ? ` (${systemBounds.beginning}-${systemBounds.ending})` + : systemBounds.beginning > 0 + ? ` (${systemBounds.beginning}-∞)` + : ""; + return ( + + ); + })} + + {inst.targetId && instSystemBounds && ( + + Available: {instSystemBounds.beginning}- + {instSystemBounds.ending >= Model.END_OF_TIME + ? "∞" + : instSystemBounds.ending} + + )} + - From * - - Until * - - Actions - + + updateRawInput(inst.id, "beginning", e.target.value) + } + placeholder={String(instSystemBounds?.beginning ?? 0)} + className={ + raw.beginning === "" || beginningOutOfBounds + ? "border-danger" + : "" + } + isInvalid={beginningOutOfBounds} + /> + {beginningOutOfBounds && instSystemBounds && ( + + Min: {instSystemBounds.beginning} + + )} + + + updateRawInput(inst.id, "ending", e.target.value) + } + placeholder={ + instSystemBounds && + instSystemBounds.ending < Model.END_OF_TIME + ? String(instSystemBounds.ending) + : "∞ (optional)" + } + className={endingOutOfBounds ? "border-danger" : ""} + isInvalid={endingOutOfBounds} + /> + {endingOutOfBounds && instSystemBounds && ( + + Max:{" "} + {instSystemBounds.ending >= Model.END_OF_TIME + ? "∞" + : instSystemBounds.ending} + + )} + {!endingOutOfBounds && raw.ending.trim() === "" && ( + + Will inherit from system + + )} + + +
{idx + 1} - { - updateInstallation( - inst.id, - "targetId", - e.target.value - ); - // Reset times to system defaults when changing system - if (e.target.value) { - const newSystemBounds = getSystemTimeBounds( - e.target.value - ); - const newEnding = - newSystemBounds.ending < Model.END_OF_TIME - ? newSystemBounds.ending - : newSystemBounds.beginning + 10; - updateRawInput( - inst.id, - "beginning", - String(newSystemBounds.beginning) - ); - updateRawInput( - inst.id, - "ending", - String(newEnding) - ); - } - }} - className={!inst.targetId ? "border-warning" : ""} - > - - {availableSystems.map((system) => { - const systemBounds = getSystemTimeBounds( - system.id - ); - const boundsStr = - systemBounds.ending < Model.END_OF_TIME - ? ` (${systemBounds.beginning}-${systemBounds.ending})` - : systemBounds.beginning > 0 - ? ` (${systemBounds.beginning}-∞)` - : ""; - return ( - - ); - })} - - {inst.targetId && instSystemBounds && ( - - Available: {instSystemBounds.beginning}- - {instSystemBounds.ending >= Model.END_OF_TIME - ? "∞" - : instSystemBounds.ending} - - )} - - - updateRawInput(inst.id, "beginning", e.target.value) - } - placeholder={String(instSystemBounds?.beginning ?? 0)} - className={ - raw.beginning === "" || beginningOutOfBounds - ? "border-danger" - : "" - } - isInvalid={beginningOutOfBounds} - /> - {beginningOutOfBounds && instSystemBounds && ( - - Min: {instSystemBounds.beginning} - - )} - - - updateRawInput(inst.id, "ending", e.target.value) - } - placeholder={ - instSystemBounds && - instSystemBounds.ending < Model.END_OF_TIME - ? String(instSystemBounds.ending) - : "∞ (inherit from system)" - } - className={endingOutOfBounds ? "border-danger" : ""} - isInvalid={endingOutOfBounds} - /> - {endingOutOfBounds && instSystemBounds && ( - - Max:{" "} - {instSystemBounds.ending >= Model.END_OF_TIME - ? "∞" - : instSystemBounds.ending} - - )} - {!endingOutOfBounds && raw.ending.trim() === "" && ( - - Will inherit from system - - )} - - -
- -
- -
- - )} + ); + })} + + + +
+ +
{availableSystems.length === 0 && !isFiltered && ( From cb581652333717bdea25c219d6b00509471a692c Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sat, 29 Nov 2025 14:17:49 +0000 Subject: [PATCH 49/81] feat: Update ActivityDiagramWrap and EditInstalledComponent to improve system context handling and auto-fill logic --- editor-app/components/ActivityDiagramWrap.tsx | 4 +- .../components/EditInstalledComponent.tsx | 66 ++++++++++++++----- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index 42e670f..2b19d0c 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -184,7 +184,7 @@ export default function ActivityDiagramWrap() { if (entityType === EntityType.SystemComponent) { // Virtual SystemComponent - open installation editor for this system - setSelectedIndividual(originalIndividual); + setSelectedSystemComponent(originalIndividual); setTargetSystemId(targetId); setShowSystemComponentEditor(true); } else if (entityType === EntityType.InstalledComponent) { @@ -207,7 +207,7 @@ export default function ActivityDiagramWrap() { } } - setSelectedIndividual(originalIndividual); + setSelectedInstalledComponent(originalIndividual); setTargetSlotId(targetId); setTargetSystemId(systemId); setShowInstalledComponentEditor(true); diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index fa79fa7..8eb0577 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -229,6 +229,18 @@ const EditInstalledComponent = (props: Props) => { // Validate that if a slot is selected, a System context is also selected // (if the slot is installed in multiple systems) const systemsForSlot = getSystemsForSlot(inst.targetId); + + // Auto-fill system context if only one option exists + if (systemsForSlot.length === 1 && !inst.systemContextId) { + // This will be handled during save, but we need the context for validation + const autoSystemId = systemsForSlot[0].id; + inst.systemContextId = autoSystemId; + const scInst = getScInstallationForSystem(inst.targetId, autoSystemId); + if (scInst) { + inst.scInstallationContextId = scInst.id; + } + } + if (systemsForSlot.length > 1 && !inst.systemContextId) { newErrors.push( `${slotName}: Please select which System this installation belongs to.` @@ -328,10 +340,20 @@ const EditInstalledComponent = (props: Props) => { // Get the SC installation ID for the system context let scInstallationContextId = inst.scInstallationContextId; - if (inst.targetId && inst.systemContextId && !scInstallationContextId) { + let systemContextId = inst.systemContextId; + + // Auto-fill system context if only one option + if (inst.targetId && !systemContextId) { + const systemsForSlot = getSystemsForSlot(inst.targetId); + if (systemsForSlot.length === 1) { + systemContextId = systemsForSlot[0].id; + } + } + + if (inst.targetId && systemContextId && !scInstallationContextId) { const scInst = getScInstallationForSystem( inst.targetId, - inst.systemContextId + systemContextId ); if (scInst) { scInstallationContextId = scInst.id; @@ -342,6 +364,7 @@ const EditInstalledComponent = (props: Props) => { ...inst, beginning: parseInt(raw?.beginning ?? String(inst.beginning), 10) || 0, ending: parseInt(raw?.ending ?? String(inst.ending), 10) || 10, + systemContextId, scInstallationContextId, }; }); @@ -413,10 +436,23 @@ const EditInstalledComponent = (props: Props) => { // Get the SC installation ID if we have both slot and system context let scInstallationContextId: string | undefined; - if (targetSlotId && targetSystemId) { - const scInst = getScInstallationForSystem(targetSlotId, targetSystemId); - if (scInst) { - scInstallationContextId = scInst.id; + let systemContextId: string | undefined = targetSystemId; + + if (targetSlotId) { + // If we have a target slot, check if there's only one system - auto-select it + const systemsForSlot = getSystemsForSlot(targetSlotId); + if (systemsForSlot.length === 1 && !systemContextId) { + systemContextId = systemsForSlot[0].id; + } + + if (systemContextId) { + const scInst = getScInstallationForSystem( + targetSlotId, + systemContextId + ); + if (scInst) { + scInstallationContextId = scInst.id; + } } } @@ -426,7 +462,7 @@ const EditInstalledComponent = (props: Props) => { targetId: targetSlotId || "", beginning: defaultBeginning, ending: defaultEnding, - systemContextId: targetSystemId, + systemContextId: systemContextId, scInstallationContextId, }; @@ -590,7 +626,14 @@ const EditInstalledComponent = (props: Props) => { ? `Manage installation periods for this component in "${slotName}"${ systemName ? ` within "${systemName}"` : "" }. You can have multiple non-overlapping periods.` - : "Manage all installation periods for this component across different slots."} + : `Manage all installation periods for this component across different slots.`} + {!isFiltered && availableSlots.length === 0 && ( + <> + {" "} + System Components must be installed in a System before they can + receive Installed Components.{" "} + + )}

{/* Always show the table structure */} @@ -875,13 +918,6 @@ const EditInstalledComponent = (props: Props) => { - {availableSlots.length === 0 && !isFiltered && ( - - No slots available. System Components must be installed in a System - before they can receive Installed Components. - - )} - {errors.length > 0 && ( Please fix the following: From 76ca27140ea29153322f77dc06bf0b5a89a1695f Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sat, 29 Nov 2025 14:32:15 +0000 Subject: [PATCH 50/81] feat: Add entity type selection and related UI enhancements in SetIndividual component --- editor-app/components/SetIndividual.tsx | 210 +++++++++++++++--------- 1 file changed, 135 insertions(+), 75 deletions(-) diff --git a/editor-app/components/SetIndividual.tsx b/editor-app/components/SetIndividual.tsx index 1d3a609..9c21fc1 100644 --- a/editor-app/components/SetIndividual.tsx +++ b/editor-app/components/SetIndividual.tsx @@ -30,6 +30,36 @@ interface Props { onOpenInstalledComponentInstallation?: (individual: Individual) => void; } +// Get entity type display info +const getEntityTypeInfo = (type: EntityType) => { + switch (type) { + case EntityType.System: + return { + label: "System", + icon: "▣", + description: "A container for slots/positions", + }; + case EntityType.SystemComponent: + return { + label: "System Component", + icon: "◈", + description: "A position that can be installed into Systems", + }; + case EntityType.InstalledComponent: + return { + label: "Installed Component", + icon: "⬢", + description: "A physical object that can be installed into slots", + }; + default: + return { + label: "Individual", + icon: "○", + description: "A person, place, or thing", + }; + } +}; + const SetIndividual = (props: Props) => { const { deleteIndividual, @@ -225,6 +255,11 @@ const SetIndividual = (props: Props) => { updateInputs(e.target.name, e.target.value); }; + const handleEntityTypeChange = (newType: EntityType) => { + if (isEditing) return; // Can't change entity type when editing + updateInputs("entityType", newType); + }; + const handleBeginsWithParticipant = (e: any) => { if ( inputs.entityType !== EntityType.Individual && @@ -389,6 +424,13 @@ const SetIndividual = (props: Props) => { // Get installation count for display const installationCount = inputs.installations?.length ?? 0; + // Check entity type flags + const isSystem = currentEntityType === EntityType.System; + const isSystemComponent = currentEntityType === EntityType.SystemComponent; + const isInstalledComponent = + currentEntityType === EntityType.InstalledComponent; + const isRegularIndividual = currentEntityType === EntityType.Individual; + return ( <> + ); + })} + + + {getEntityTypeInfo(currentEntityType).description} + {isEditing && ( + (Cannot change after creation) + )} + +
+ {/* Name */} Name @@ -442,7 +522,11 @@ const SetIndividual = (props: Props) => { {typeOpen && (
{
- {/* Entity type selection */} - - Entity type - - updateInputs("entityType", e.target.value as EntityType) - } - disabled={isEditing} // Can't change entity type when editing - > - - - - - - {isEditing && ( - - Entity type cannot be changed after creation. - - )} - - {/* Description */} Description @@ -597,8 +655,7 @@ const SetIndividual = (props: Props) => { {/* Begins/Ends with participant - for Individual type only */} - {(inputs.entityType === EntityType.Individual || - !inputs.entityType) && ( + {isRegularIndividual && ( <> { )} {/* Installations section for SystemComponents */} - {currentEntityType === EntityType.SystemComponent && isEditing && ( + {isSystemComponent && isEditing && (
@@ -652,56 +709,59 @@ const SetIndividual = (props: Props) => { )} {/* Installations section for InstalledComponents */} - {currentEntityType === EntityType.InstalledComponent && - isEditing && ( -
-
-
- Installations -

- {installationCount === 0 - ? "Not installed in any slot yet." - : `Installed in ${installationCount} slot${ - installationCount !== 1 ? "s" : "" - }.`} -

-
- + ? "Not installed in any slot yet." + : `Installed in ${installationCount} slot${ + installationCount !== 1 ? "s" : "" + }.`} +

+
- )} - - {/* Note for new SystemComponents */} - {currentEntityType === EntityType.SystemComponent && !isEditing && ( - - - After saving, you can add installations to specify when this - component is installed in a system. - - +
)} - {/* Note for new InstalledComponents */} - {currentEntityType === EntityType.InstalledComponent && - !isEditing && ( - - - After saving, you can add installations to specify when this - component is installed in a slot. Note: System Components - must be installed in a System first before they can receive - Installed Components. - - - )} + {/* Info for non-Individual types when adding */} + {!isRegularIndividual && !isEditing && ( + + {isSystem && ( + <> + Systems are containers. After creating this + System, you can install System Components (slots) into it. + + )} + {isSystemComponent && ( + <> + System Components (slots) can be installed + into Systems. After creating this, click on it in the + diagram to manage its installations. + + )} + {isInstalledComponent && ( + <> + Installed Components are physical objects + that can be installed into System Components (slots). After + creating this, click on it in the diagram to manage its + installations. + + )} + + )} From 1853fc90cb098e726cd949bca864df3a05fd18e4 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sat, 29 Nov 2025 15:01:18 +0000 Subject: [PATCH 51/81] feat: Implement diagonal hatch overlay for InstalledComponent installation references in drawInstallations function --- editor-app/diagram/DrawIndividuals.ts | 16 ------ editor-app/diagram/DrawInstallations.ts | 74 ++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 19 deletions(-) diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index 22e575f..d756cd9 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -476,22 +476,6 @@ function drawGroupSeparators( } } } - - // Draw the separators - separatorPositions.forEach((pos, idx) => { - svgElement - .append("line") - .attr("class", "group-separator") - .attr("id", `separator-${idx}`) - .attr("x1", pos.x1) - .attr("y1", pos.y) - .attr("x2", pos.x2) - .attr("y2", pos.y) - .attr("stroke", "#9ca3af") // Gray color - .attr("stroke-width", 1) - .attr("stroke-dasharray", "6,4") // Dashed line - .attr("opacity", 0.6); - }); } export function hoverIndividuals(ctx: DrawContext) { diff --git a/editor-app/diagram/DrawInstallations.ts b/editor-app/diagram/DrawInstallations.ts index f805064..86a96c3 100644 --- a/editor-app/diagram/DrawInstallations.ts +++ b/editor-app/diagram/DrawInstallations.ts @@ -40,7 +40,75 @@ function getInstallationId(ind: Individual): string | undefined { } export function drawInstallations(ctx: DrawContext) { - // Hatch overlay removed — legend icons indicate installed status. - // This function intentionally left as a no-op to avoid drawing hatch patterns. - return; + const { svgElement, individuals, config, dataset } = ctx; + + if (!individuals || individuals.length === 0) return; + + // Remove existing installation hatches + svgElement.selectAll(".installation-period").remove(); + + // Create or get the defs element for patterns + let defs = svgElement.select("defs"); + if (defs.empty()) { + defs = svgElement.append("defs"); + } + + // Create diagonal hatch pattern if it doesn't exist + if (defs.select("#diagonal-hatch").empty()) { + const pattern = defs + .append("pattern") + .attr("id", "diagonal-hatch") + .attr("patternUnits", "userSpaceOnUse") + .attr("width", 8) + .attr("height", 8) + .attr("patternTransform", "rotate(45)"); + + pattern + .append("rect") + .attr("width", 8) + .attr("height", 8) + .attr("fill", "white") + .attr("fill-opacity", 0.1); + + pattern + .append("line") + .attr("x1", 0) + .attr("y1", 0) + .attr("x2", 0) + .attr("y2", 8) + .attr("stroke", "#374151") + .attr("stroke-width", 1); + } + + // For each InstalledComponent installation reference row, draw a hatched overlay matching the chevron shape + individuals.forEach((ind) => { + if (!isInstallationRef(ind)) return; + + // Only apply hatch to InstalledComponent virtual rows, not SystemComponent + const entityType = ind.entityType ?? EntityType.Individual; + if (entityType !== EntityType.InstalledComponent) return; + + // Get the path data from the individual element + const escapedId = CSS.escape("i" + ind.id); + const node = svgElement + .select("#" + escapedId) + .node() as SVGPathElement | null; + if (!node) return; + + const pathData = node.getAttribute("d"); + if (!pathData) return; + + // Remove dashed border on the original element (if present) + svgElement.select("#" + escapedId).attr("stroke-dasharray", null); + + // Draw hatch overlay using the same path as the individual + svgElement + .append("path") + .attr("class", "installation-period") + .attr("d", pathData) + .attr("fill", "url(#diagonal-hatch)") + .attr("stroke", "none") + .attr("stroke-dasharray", null) + .attr("pointer-events", "none"); + }); } From 44789dd74595de36a293b0c866ea2c5cf405bb2c Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sat, 29 Nov 2025 15:59:02 +0000 Subject: [PATCH 52/81] feat: Enhance drawIndividuals and drawParticipations functions to improve time range handling and segment rect drawing --- editor-app/diagram/DrawIndividuals.ts | 24 ++- editor-app/diagram/DrawParticipations.ts | 232 ++++++++++++----------- 2 files changed, 146 insertions(+), 110 deletions(-) diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index d756cd9..c83d0b0 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -210,8 +210,25 @@ export function drawIndividuals(ctx: DrawContext) { let startOfTime = Math.min(...activities.map((a) => a.beginning)); let endOfTime = Math.max(...activities.map((a) => a.ending)); - // Expand time range to include installed components' actual installation periods + // If no activities, use a default range + if (activities.length === 0 || !isFinite(startOfTime)) { + startOfTime = 0; + } + if (activities.length === 0 || !isFinite(endOfTime)) { + endOfTime = 10; // Default end time when no activities + } + + // Expand time range to include all individuals (including virtual rows) for (const ind of individuals) { + // For regular individuals with defined bounds + if (ind.beginning >= 0 && ind.beginning < startOfTime) { + startOfTime = ind.beginning; + } + if (ind.ending < Model.END_OF_TIME && ind.ending > endOfTime) { + endOfTime = ind.ending; + } + + // For installation references, also check their installation periods if (isInstallationRef(ind)) { const originalId = getOriginalId(ind); const targetId = getTargetId(ind); @@ -238,6 +255,11 @@ export function drawIndividuals(ctx: DrawContext) { } } + // Ensure we have a valid duration (at least 1) + if (endOfTime <= startOfTime) { + endOfTime = startOfTime + 10; + } + let duration = endOfTime - startOfTime; let totalLeftMargin = config.viewPort.x * config.viewPort.zoom - diff --git a/editor-app/diagram/DrawParticipations.ts b/editor-app/diagram/DrawParticipations.ts index 7f79332..8ec9962 100644 --- a/editor-app/diagram/DrawParticipations.ts +++ b/editor-app/diagram/DrawParticipations.ts @@ -266,16 +266,17 @@ export function drawParticipations(ctx: DrawContext) { return a.participations as Participation[]; }; - const parts: { + // Build participation data with visible intervals + interface PartData { activity: Activity; participation: Participation; activityIndex: number; - lane?: number; - totalLanes?: number; - }[] = []; + visStart: number; + visEnd: number; + } + + const parts: PartData[] = []; - // Build parts but skip any participation whose individual row is missing - // or which has no visible interval after cropping to installation window. activities.forEach((a, idx) => { const pa = getParticipationArray(a); pa.forEach((p) => { @@ -294,13 +295,19 @@ export function drawParticipations(ctx: DrawContext) { ); if (vis.end <= vis.start) return; // fully outside -> skip - parts.push({ activity: a, participation: p, activityIndex: idx }); + parts.push({ + activity: a, + participation: p, + activityIndex: idx, + visStart: vis.start, + visEnd: vis.end, + }); }); }); - // --- Overlap Detection & Lane Assignment --- - // 1. Group by individual - const byIndividual = new Map(); + // --- Time-Segment Based Lane Assignment --- + // Group by individual + const byIndividual = new Map(); parts.forEach((p) => { const id = p.participation.individualId; if (!byIndividual.has(id)) { @@ -309,119 +316,126 @@ export function drawParticipations(ctx: DrawContext) { byIndividual.get(id)!.push(p); }); - // 2. Calculate lanes for each individual - byIndividual.forEach((items) => { - // Sort by start time - items.sort((a, b) => a.activity.beginning - b.activity.beginning); - - const lanes: number[] = []; // tracks the end time of the last item in each lane - - items.forEach((item) => { - let placed = false; - // Try to place in existing lane - for (let i = 0; i < lanes.length; i++) { - // If this lane is free (last item ended before this one starts) - if (lanes[i] <= item.activity.beginning) { - item.lane = i; - lanes[i] = item.activity.ending; - placed = true; - break; - } - } - // Create new lane if needed - if (!placed) { - item.lane = lanes.length; - lanes.push(item.activity.ending); - } + // For each individual, calculate time segments and draw rects + interface SegmentRect { + activity: Activity; + participation: Participation; + activityIndex: number; + segStart: number; + segEnd: number; + laneIndex: number; + totalLanes: number; + individualId: string; + } + + const segmentRects: SegmentRect[] = []; + + byIndividual.forEach((items, indId) => { + if (items.length === 0) return; + + // Collect all time boundaries for this individual + const timePoints = new Set(); + items.forEach((p) => { + timePoints.add(p.visStart); + timePoints.add(p.visEnd); }); + const sortedTimes = Array.from(timePoints).sort((a, b) => a - b); - // Store total lanes count on each item for height calculation - const totalLanes = lanes.length; - items.forEach((item) => (item.totalLanes = totalLanes)); + // For each time segment, find overlapping activities + for (let i = 0; i < sortedTimes.length - 1; i++) { + const segStart = sortedTimes[i]; + const segEnd = sortedTimes[i + 1]; + + // Find activities that overlap with this segment + const overlapping = items.filter( + (p) => p.visStart < segEnd && p.visEnd > segStart + ); + + if (overlapping.length === 0) continue; + + const totalLanes = overlapping.length; + + // Sort overlapping by activity start time for consistent lane assignment + overlapping.sort((a, b) => a.activity.beginning - b.activity.beginning); + + // Assign lanes for this segment + overlapping.forEach((p, laneIndex) => { + segmentRects.push({ + activity: p.activity, + participation: p.participation, + activityIndex: p.activityIndex, + segStart, + segEnd, + laneIndex, + totalLanes, + individualId: indId, + }); + }); + } }); // ------------------------------------------- - const maxRectHeight = Math.min(36, config.layout.individual.height); // max glass height - const rx = 4; // border-radius + const maxRectHeight = Math.min(36, config.layout.individual.height); + const rx = 4; const strokeWidth = 1; const fillOpacity = 0.85; - svgElement - .selectAll(".participation-rect") - .data(parts, (d: any) => `${d.activity.id}:${d.participation.individualId}`) - .join("rect") - .attr("class", "participation-rect") - .attr( - "id", - (d: any) => `p_${d.activity.id}_${d.participation.individualId}` - ) - .attr("x", (d: any) => { - const { start, end } = getVisibleInterval( - d.activity.beginning, - d.activity.ending, - d.participation.individualId, - dataset - ); - // If no visible portion, move offscreen - if (end <= start) return -99999; - return xBase + timeInterval * (start - startOfTime); - }) - .attr("width", (d: any) => { - const { start, end } = getVisibleInterval( - d.activity.beginning, - d.activity.ending, - d.participation.individualId, - dataset - ); - const width = (end - start) * timeInterval; - return Math.max(0, width); - }) - .attr("y", (d: any) => { - const node = svgElement - .select("#i" + CSS.escape(d.participation.individualId)) - .node(); - if (!node) return 0; - const box = (node as SVGGraphicsElement).getBBox(); + // Remove old rects + svgElement.selectAll(".participation-rect").remove(); - const totalLanes = d.totalLanes || 1; - const laneIndex = d.lane || 0; + // Draw segment rects + segmentRects.forEach((seg) => { + const node = svgElement + .select("#i" + CSS.escape(seg.individualId)) + .node() as SVGGraphicsElement | null; + if (!node) return; - // Calculate height per lane - const laneHeight = maxRectHeight / totalLanes; + const box = node.getBBox(); - // Center the group of lanes in the row - const groupY = box.y + (box.height - maxRectHeight) / 2; + // Calculate x position for this segment + const segX = xBase + timeInterval * (seg.segStart - startOfTime); + const segWidth = timeInterval * (seg.segEnd - seg.segStart); - return groupY + laneIndex * laneHeight; - }) - .attr("height", (d: any) => { - const totalLanes = d.totalLanes || 1; - // Add a tiny gap if multiple lanes, unless it makes them too small - const gap = totalLanes > 1 ? 1 : 0; - return maxRectHeight / totalLanes - gap; - }) - .attr("rx", (d: any) => (d.totalLanes && d.totalLanes > 2 ? 1 : rx)) // reduce radius if thin - .attr("ry", (d: any) => (d.totalLanes && d.totalLanes > 2 ? 1 : rx)) - .attr( - "fill", - (d: any) => - config.presentation.activity.fill[ - d.activityIndex % config.presentation.activity.fill.length - ] - ) - .attr("fill-opacity", fillOpacity) - .attr("stroke", (d: any) => - darkenHex( - config.presentation.activity.fill[ - d.activityIndex % config.presentation.activity.fill.length - ], - 0.28 + if (segWidth <= 0) return; + + // Calculate lane height for this segment + const laneHeight = maxRectHeight / seg.totalLanes; + const gap = seg.totalLanes > 1 ? 1 : 0; + + // Center the group of lanes in the row + const groupY = box.y + (box.height - maxRectHeight) / 2; + const laneY = groupY + seg.laneIndex * laneHeight; + + const colorIndex = + seg.activityIndex % config.presentation.activity.fill.length; + const fillColor = config.presentation.activity.fill[colorIndex]; + + svgElement + .append("rect") + .attr("class", "participation-rect") + .attr( + "id", + `p_${seg.activity.id}_${seg.individualId}_${seg.segStart}_${seg.segEnd}` ) - ) - .attr("stroke-width", strokeWidth) - .attr("opacity", 1); + .attr("x", segX) + .attr("y", laneY) + .attr("width", Math.max(0, segWidth)) + .attr("height", laneHeight - gap) + .attr("rx", seg.totalLanes > 2 ? 1 : rx) + .attr("ry", seg.totalLanes > 2 ? 1 : rx) + .attr("fill", fillColor) + .attr("fill-opacity", fillOpacity) + .attr("stroke", darkenHex(fillColor, 0.28)) + .attr("stroke-width", strokeWidth) + .attr("opacity", 1) + .datum({ + activity: seg.activity, + participation: seg.participation, + activityIndex: seg.activityIndex, + }); + }); - // Add hover behavior using the same pattern as original + // Add hover behavior hoverParticipations(ctx); return svgElement; From 30ed15158eeeeb2dc5823839784d46613f699d1b Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sat, 29 Nov 2025 16:07:21 +0000 Subject: [PATCH 53/81] feat: Adjust stroke width and border radius in drawParticipations for improved lane representation --- editor-app/diagram/DrawParticipations.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/editor-app/diagram/DrawParticipations.ts b/editor-app/diagram/DrawParticipations.ts index 8ec9962..cfe3373 100644 --- a/editor-app/diagram/DrawParticipations.ts +++ b/editor-app/diagram/DrawParticipations.ts @@ -377,7 +377,6 @@ export function drawParticipations(ctx: DrawContext) { const maxRectHeight = Math.min(36, config.layout.individual.height); const rx = 4; - const strokeWidth = 1; const fillOpacity = 0.85; // Remove old rects @@ -402,6 +401,9 @@ export function drawParticipations(ctx: DrawContext) { const laneHeight = maxRectHeight / seg.totalLanes; const gap = seg.totalLanes > 1 ? 1 : 0; + // Adjust stroke width based on number of lanes - thinner for split lanes + const strokeWidth = seg.totalLanes > 1 ? 0.5 : 1; + // Center the group of lanes in the row const groupY = box.y + (box.height - maxRectHeight) / 2; const laneY = groupY + seg.laneIndex * laneHeight; @@ -410,6 +412,14 @@ export function drawParticipations(ctx: DrawContext) { seg.activityIndex % config.presentation.activity.fill.length; const fillColor = config.presentation.activity.fill[colorIndex]; + // Adjust border radius based on number of lanes - smaller for split lanes + let rectRx = rx; + if (seg.totalLanes === 2) { + rectRx = 2; + } else if (seg.totalLanes > 2) { + rectRx = 1; + } + svgElement .append("rect") .attr("class", "participation-rect") @@ -421,8 +431,8 @@ export function drawParticipations(ctx: DrawContext) { .attr("y", laneY) .attr("width", Math.max(0, segWidth)) .attr("height", laneHeight - gap) - .attr("rx", seg.totalLanes > 2 ? 1 : rx) - .attr("ry", seg.totalLanes > 2 ? 1 : rx) + .attr("rx", rectRx) + .attr("ry", rectRx) .attr("fill", fillColor) .attr("fill-opacity", fillOpacity) .attr("stroke", darkenHex(fillColor, 0.28)) From 52b704bb7563200e21a091e77bf270dd524837df Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sat, 29 Nov 2025 16:34:49 +0000 Subject: [PATCH 54/81] feat: Revamp Footer and Navbar components for improved styling and layout --- editor-app/components/Footer.tsx | 155 +++++++++++++++++-------------- editor-app/components/NavBar.tsx | 49 +++++----- editor-app/pages/_app.tsx | 22 ++++- editor-app/pages/_document.tsx | 3 - editor-app/styles/globals.css | 43 +++++++++ 5 files changed, 169 insertions(+), 103 deletions(-) diff --git a/editor-app/components/Footer.tsx b/editor-app/components/Footer.tsx index f4b8115..011ac67 100644 --- a/editor-app/components/Footer.tsx +++ b/editor-app/components/Footer.tsx @@ -1,88 +1,101 @@ +// filepath: c:\Users\me1meg\Documents\4d-activity-editor\editor-app\components\Footer.tsx import Link from "next/link"; import pkg from "../package.json"; function Footer() { - const style = {}; const year = new Date().getFullYear(); return ( - <> -
-
-

+
+
+
+ {/* Left - Links */} +
+
More
+
    +
  • + + Get in touch + +
  • +
  • + + Give feedback + +
  • +
+
-
-
-
-
More
-
    -
  • - - Get in touch - -
  • -
  • - - Give feedback - -
  • -
+ {/* Center - Copyright */} +
+
+
{year} Apollo Protocol Activity Diagram Editor
+
Created by AMRC in collaboration with CIS
+
Version: v{pkg.version}
+
+
+ + {/* Right - Logos */} +
+
+
+ + + AMRC + +
-
-
-
{year} Apollo Protocol Activity Diagram Editor
-
Created by AMRC in collaboration with CIS
-
Version: v{pkg.version}
-
+
+ + CIS +
-
-
-
- - - ... - - -
-
- - ... - -
-
- -
-
- Funded by{" "} - - ... - -
-
+
+
+
+ Funded by + + Innovate UK +
-
+
- + ); } + export default Footer; diff --git a/editor-app/components/NavBar.tsx b/editor-app/components/NavBar.tsx index 4237cc4..ac7a395 100644 --- a/editor-app/components/NavBar.tsx +++ b/editor-app/components/NavBar.tsx @@ -7,9 +7,6 @@ import NavDropdown from "react-bootstrap/NavDropdown"; interface NavItemProps { href: string; - /* This disallows creating nav items containing anything but plain - * strings. But without this I can't see how to get tsc to let me pass - * the children into React.createElement below. */ children: string; linkType?: React.FunctionComponent; } @@ -24,42 +21,42 @@ function NavItem(props: NavItemProps) { ); } -/* - - - First Topic - - - Second topic - - - Second Topic - - -*/ - function CollapsibleExample() { return ( - - - - + + + + Apollo Protocol + -
-
-
-
diff --git a/editor-app/styles/globals.css b/editor-app/styles/globals.css index 02c1fb6..e060b09 100644 --- a/editor-app/styles/globals.css +++ b/editor-app/styles/globals.css @@ -23,3 +23,46 @@ .amrc-fixme { color: black; } + +/* Navbar styling */ +.navbar .nav-link { + padding: 0.5rem 1rem !important; + font-weight: 500; + color: #495057 !important; + transition: color 0.15s ease-in-out; +} + +.navbar .nav-link:hover { + color: #0d6efd !important; +} + +.navbar .dropdown-menu { + border: none; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + border-radius: 8px; +} + +.navbar .dropdown-item { + padding: 0.5rem 1rem; + font-size: 0.9rem; +} + +.navbar .dropdown-item:hover { + background-color: #f8f9fa; +} + +/* Footer link hover */ +footer a:hover { + color: #60a5fa !important; + opacity: 1 !important; +} + +/* Ensure content doesn't get hidden behind sticky navbar */ +html { + scroll-padding-top: 80px; +} + +/* Body base styling */ +body { + font-family: "Roboto", sans-serif; +} From eaa2182f2a96e410399fc3e0d587d5dae05c6df2 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sat, 29 Nov 2025 21:15:15 +0000 Subject: [PATCH 55/81] feat: Refactor layout and visibility logic in Footer, HideIndividuals, NavBar, and DrawActivityDiagram components --- editor-app/components/Footer.tsx | 8 +- editor-app/components/HideIndividuals.tsx | 140 +++++++++++++++++++--- editor-app/components/NavBar.tsx | 25 +++- editor-app/diagram/DrawActivityDiagram.ts | 118 +++++++++++++++++- editor-app/pages/_app.tsx | 6 +- editor-app/pages/_document.tsx | 11 +- editor-app/styles/globals.css | 28 ++++- 7 files changed, 296 insertions(+), 40 deletions(-) diff --git a/editor-app/components/Footer.tsx b/editor-app/components/Footer.tsx index 011ac67..324cabd 100644 --- a/editor-app/components/Footer.tsx +++ b/editor-app/components/Footer.tsx @@ -10,13 +10,9 @@ function Footer() { style={{ backgroundColor: "rgba(255, 255, 255, 0.98)", backdropFilter: "blur(10px)", - boxShadow: "0 -2px 8px rgba(0, 0, 0, 0.08)", + boxShadow: "0 2px 8px rgba(0, 0, 0, 0.08)", padding: "1.5rem 0", - position: "fixed", - bottom: 0, - left: 0, - right: 0, - zIndex: 1020, + flexShrink: 0, }} >
diff --git a/editor-app/components/HideIndividuals.tsx b/editor-app/components/HideIndividuals.tsx index 1f196ea..93bd61d 100644 --- a/editor-app/components/HideIndividuals.tsx +++ b/editor-app/components/HideIndividuals.tsx @@ -3,7 +3,7 @@ import Button from "react-bootstrap/Button"; import OverlayTrigger from "react-bootstrap/OverlayTrigger"; import Tooltip from "react-bootstrap/Tooltip"; import { Model } from "@/lib/Model"; -import { Activity } from "@/lib/Schema"; +import { Activity, EntityType, Individual } from "@/lib/Schema"; interface Props { compactMode: boolean; @@ -12,33 +12,145 @@ interface Props { activitiesInView: Activity[]; } +// Helper to get parent IDs that should be kept visible (bottom-to-top only) +// When a CHILD has activity, keep its PARENTS visible +// But NOT the reverse - if only parent has activity, don't automatically keep children +function getParentIdsToKeep( + participatingIds: Set, + dataset: Model +): Set { + const parentsToKeep = new Set(); + + participatingIds.forEach((id) => { + // Check if this is a virtual row (installation reference) + if (id.includes("__installed_in__")) { + const parts = id.split("__installed_in__"); + const rest = parts[1]; + const targetId = rest.split("__")[0]; + + // Virtual row has activity - keep its target (parent) visible + parentsToKeep.add(targetId); + + // If target is a SystemComponent, also keep its parent System + const target = dataset.individuals.get(targetId); + if (target) { + const targetType = target.entityType ?? EntityType.Individual; + if (targetType === EntityType.SystemComponent && target.installations) { + target.installations.forEach((inst) => { + if (inst.targetId) { + parentsToKeep.add(inst.targetId); + } + }); + } + } + } else { + // Regular individual - check if it's an InstalledComponent or SystemComponent + const individual = dataset.individuals.get(id); + if (individual) { + const entityType = individual.entityType ?? EntityType.Individual; + + if (entityType === EntityType.InstalledComponent) { + // InstalledComponent has activity - keep parent SystemComponents and their parent Systems + if (individual.installations) { + individual.installations.forEach((inst) => { + if (inst.targetId) { + parentsToKeep.add(inst.targetId); + + // Get the SystemComponent's parent System + const sc = dataset.individuals.get(inst.targetId); + if (sc && sc.installations) { + sc.installations.forEach((scInst) => { + if (scInst.targetId) { + parentsToKeep.add(scInst.targetId); + } + }); + } + } + }); + } + } else if (entityType === EntityType.SystemComponent) { + // SystemComponent has activity - keep parent Systems + if (individual.installations) { + individual.installations.forEach((inst) => { + if (inst.targetId) { + parentsToKeep.add(inst.targetId); + } + }); + } + } + // Note: If a System has activity, we do NOT automatically keep its children + // This is the "bottom-to-top only" behavior + } + } + }); + + return parentsToKeep; +} + +// Helper to check what should be visible +function getVisibleIds( + participatingIds: Set, + parentsToKeep: Set, + dataset: Model +): Set { + const visible = new Set(); + + // Add all direct participants + participatingIds.forEach((id) => visible.add(id)); + + // Add parents that should be kept + parentsToKeep.forEach((id) => visible.add(id)); + + return visible; +} + const HideIndividuals = ({ compactMode, setCompactMode, dataset, activitiesInView, }: Props) => { - // Find if there are individuals with no activity in the current view - const hasInactiveIndividuals = (() => { - const participating = new Set(); - activitiesInView.forEach((a) => - a.participations.forEach((p: any) => participating.add(p.individualId)) - ); + // Find all participating individual IDs (direct participants only) + const participating = new Set(); + activitiesInView.forEach((a) => + a.participations.forEach((p: any) => participating.add(p.individualId)) + ); + + // Get parent IDs that should also be kept visible (bottom-to-top only) + const parentsToKeep = getParentIdsToKeep(participating, dataset); + + // Get all IDs that should be visible + const visibleIds = getVisibleIds(participating, parentsToKeep, dataset); + + // Find if there are entities with no activity in the current view + const hasInactiveEntities = (() => { for (const i of Array.from(dataset.individuals.values())) { - if (!participating.has(i.id)) return true; + // Check if this entity should be hidden + if (!visibleIds.has(i.id)) { + // For virtual rows, check if original participates + if (i.id.includes("__installed_in__")) { + const parts = i.id.split("__installed_in__"); + const originalId = parts[0]; + if (!participating.has(originalId) && !participating.has(i.id)) { + return true; + } + } else { + return true; + } + } } return false; })(); - if (!hasInactiveIndividuals) return null; + if (!hasInactiveEntities) return null; const tooltip = compactMode ? ( - - This will show individuals with no activity. + + This will show entities with no activity. ) : ( - - This will hide individuals with no activity. + + This will hide entities with no activity. ); @@ -49,7 +161,7 @@ const HideIndividuals = ({ variant={compactMode ? "secondary" : "primary"} onClick={() => setCompactMode(!compactMode)} > - {compactMode ? "Show Individuals" : "Hide Individuals"} + {compactMode ? "Show Entities" : "Hide Entities"}
diff --git a/editor-app/components/NavBar.tsx b/editor-app/components/NavBar.tsx index ac7a395..783d695 100644 --- a/editor-app/components/NavBar.tsx +++ b/editor-app/components/NavBar.tsx @@ -1,5 +1,6 @@ import React from "react"; import Link from "next/link"; +import { useRouter } from "next/router"; import Container from "react-bootstrap/Container"; import Nav from "react-bootstrap/Nav"; import Navbar from "react-bootstrap/Navbar"; @@ -13,15 +14,29 @@ interface NavItemProps { function NavItem(props: NavItemProps) { const { href, children } = props; - const linkType = props.linkType ?? Nav.Link; + const router = useRouter(); + const isActive = router.pathname === "/" + href || router.pathname === href; + return ( - - {React.createElement(linkType, { as: "span" }, children)} + + {children} ); } function CollapsibleExample() { + const router = useRouter(); + const isActivityModellingActive = [ + "/intro", + "/crane", + "/management", + ].includes(router.pathname); + return ( - + Apollo Protocol @@ -49,6 +65,7 @@ function CollapsibleExample() { Introduction diff --git a/editor-app/diagram/DrawActivityDiagram.ts b/editor-app/diagram/DrawActivityDiagram.ts index 29f8d9a..73797da 100644 --- a/editor-app/diagram/DrawActivityDiagram.ts +++ b/editor-app/diagram/DrawActivityDiagram.ts @@ -1,4 +1,11 @@ -import { Id, Individual, Activity, Maybe, Participation } from "@/lib/Schema"; +import { + Id, + Individual, + Activity, + Maybe, + Participation, + EntityType, +} from "@/lib/Schema"; import { DrawContext, calculateViewportHeight, @@ -21,13 +28,88 @@ import { clickParticipations, drawParticipations } from "./DrawParticipations"; import { drawInstallations } from "./DrawInstallations"; import * as d3 from "d3"; import { Model } from "@/lib/Model"; -import { EntityType } from "@/lib/Schema"; export interface Plot { width: number; height: number; } +// Helper to get parent IDs that should be kept visible when filtering (bottom-to-top only) +// When a CHILD has activity, keep its PARENTS visible +// But NOT the reverse - if only parent has activity, don't automatically keep children +function getParentIdsToKeep( + participatingIds: Set, + dataset: Model +): Set { + const parentsToKeep = new Set(); + + participatingIds.forEach((id) => { + // Check if this is a virtual row (installation reference) + if (id.includes("__installed_in__")) { + const parts = id.split("__installed_in__"); + const originalId = parts[0]; + const rest = parts[1]; + const targetId = rest.split("__")[0]; + + // Virtual row has activity - keep its target (parent) visible + parentsToKeep.add(targetId); + + // If target is a SystemComponent, also keep its parent System + const target = dataset.individuals.get(targetId); + if (target) { + const targetType = target.entityType ?? EntityType.Individual; + if (targetType === EntityType.SystemComponent && target.installations) { + target.installations.forEach((inst) => { + if (inst.targetId) { + parentsToKeep.add(inst.targetId); + } + }); + } + } + } else { + // Regular individual - check if it's an InstalledComponent or SystemComponent + const individual = dataset.individuals.get(id); + if (individual) { + const entityType = individual.entityType ?? EntityType.Individual; + + if (entityType === EntityType.InstalledComponent) { + // InstalledComponent has activity - keep parent SystemComponents and their parent Systems + if (individual.installations) { + individual.installations.forEach((inst) => { + if (inst.targetId) { + parentsToKeep.add(inst.targetId); + + // Get the SystemComponent's parent System + const sc = dataset.individuals.get(inst.targetId); + if (sc && sc.installations) { + sc.installations.forEach((scInst) => { + if (scInst.targetId) { + parentsToKeep.add(scInst.targetId); + } + }); + } + } + }); + } + } else if (entityType === EntityType.SystemComponent) { + // SystemComponent has activity - keep parent Systems + if (individual.installations) { + individual.installations.forEach((inst) => { + if (inst.targetId) { + parentsToKeep.add(inst.targetId); + } + }); + } + } + // Note: If a System has activity, we do NOT automatically keep its children + // This is the "bottom-to-top only" behavior + } + } + }); + + return parentsToKeep; +} + /** * Entry point to draw an activity diagram * @param dataset The dataset to draw @@ -69,13 +151,43 @@ export function drawActivityDiagram( console.log("individualsArray before filter:", individualsArray.length); if (hideNonParticipating) { + // Get all participating IDs (only direct participants) const participating = new Set(); activitiesArray.forEach((a) => a.participations.forEach((p: Participation) => participating.add(p.individualId) ) ); - individualsArray = individualsArray.filter((i) => participating.has(i.id)); + + // Get parent IDs that should also be kept visible (because their children have activity) + const parentsToKeep = getParentIdsToKeep(participating, dataset); + + // Filter individuals - keep ONLY if: + // 1. They directly participate in an activity, OR + // 2. They are a parent of something that participates (bottom-to-top) + // Do NOT keep children just because parent participates + individualsArray = individualsArray.filter((i) => { + // Direct participation + if (participating.has(i.id)) return true; + + // Is a parent that should be kept (because child has activity) + if (parentsToKeep.has(i.id)) return true; + + // Check if this is a virtual row + if (i.id.includes("__installed_in__")) { + const parts = i.id.split("__installed_in__"); + const originalId = parts[0]; + + // Only keep virtual row if the original component directly participates + // (NOT just because the target/parent participates) + if (participating.has(originalId)) return true; + + // Or if this specific virtual row ID directly participates + if (participating.has(i.id)) return true; + } + + return false; + }); } console.log("individualsArray after filter:", individualsArray.length); diff --git a/editor-app/pages/_app.tsx b/editor-app/pages/_app.tsx index bd1e26f..4e973ac 100644 --- a/editor-app/pages/_app.tsx +++ b/editor-app/pages/_app.tsx @@ -18,8 +18,10 @@ export default function App({ Component, pageProps }: AppProps) {
diff --git a/editor-app/pages/_document.tsx b/editor-app/pages/_document.tsx index 0ece053..10b6944 100644 --- a/editor-app/pages/_document.tsx +++ b/editor-app/pages/_document.tsx @@ -19,15 +19,8 @@ export default function Document() { /> -
-
-
- -
-
+
+ ); diff --git a/editor-app/styles/globals.css b/editor-app/styles/globals.css index e060b09..43cbf05 100644 --- a/editor-app/styles/globals.css +++ b/editor-app/styles/globals.css @@ -36,6 +36,16 @@ color: #0d6efd !important; } +.navbar .nav-link.active { + color: #0d6efd !important; + font-weight: 600; +} + +.navbar .active-dropdown .dropdown-toggle { + color: #0d6efd !important; + font-weight: 600; +} + .navbar .dropdown-menu { border: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); @@ -53,8 +63,7 @@ /* Footer link hover */ footer a:hover { - color: #60a5fa !important; - opacity: 1 !important; + color: #0d6efd !important; } /* Ensure content doesn't get hidden behind sticky navbar */ @@ -66,3 +75,18 @@ html { body { font-family: "Roboto", sans-serif; } + +/* Remove default body margin and prevent extra scrollbar */ +html, +body { + margin: 0; + padding: 0; + overflow-x: hidden; +} + +/* Ensure no extra space below footer */ +#__next { + min-height: 100vh; + display: flex; + flex-direction: column; +} From 2471bd2ea957075bf29b69bb5171f353ff585ab2 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sun, 30 Nov 2025 16:40:01 +0000 Subject: [PATCH 56/81] Refactor participation handling and improve virtual row management - Enhanced SetParticipation component to include all virtual installation rows and filter participants based on activity time. - Introduced helper functions to manage ancestor IDs and check for circular references in installation hierarchies. - Updated DrawActivityDiagram and DrawParticipations to handle nested SystemComponents and InstalledComponents more effectively. - Improved the display logic in DrawIndividuals to account for deeper nesting levels. - Added context handling for installation references in the ID format. - Refined Model class to provide effective time bounds for installation targets and ensure proper display of individuals in the diagram. - Introduced new properties in the Individual interface to support parent path tracking and nesting levels. --- .../components/EditInstalledComponent.tsx | 670 ++++------ .../EditSystemComponentInstallation.tsx | 1165 +++++++++-------- editor-app/components/SetActivity.tsx | 285 +--- editor-app/components/SetParticipation.tsx | 171 +-- editor-app/diagram/DrawActivityDiagram.ts | 132 +- editor-app/diagram/DrawIndividuals.ts | 7 +- editor-app/diagram/DrawParticipations.ts | 30 +- editor-app/lib/Model.ts | 371 +++--- editor-app/lib/Schema.ts | 1 + 9 files changed, 1395 insertions(+), 1437 deletions(-) diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index 8eb0577..8909e1f 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -12,7 +12,40 @@ interface Props { dataset: Model; updateDataset?: (updater: (d: Model) => void) => void; targetSlotId?: string; - targetSystemId?: string; // NEW: The System context when opening from a nested view + targetSystemId?: string; +} + +// Interface for slot options that includes nesting info +interface SlotOption { + id: string; // Original SystemComponent ID + virtualId: string; // Virtual row ID - unique identifier for this specific slot instance + displayName: string; + bounds: { beginning: number; ending: number }; + parentPath: string; // Path of parent IDs for context + nestingLevel: number; + systemName?: string; + scInstallationId?: string; // The SC installation ID for context +} + +// Helper to extract the SC installation ID from a virtual row ID +// Format: scId__installed_in__targetId__installationId or with __ctx_ +function extractInstallationIdFromVirtualId( + virtualId: string +): string | undefined { + if (!virtualId.includes("__installed_in__")) return undefined; + const parts = virtualId.split("__installed_in__"); + if (parts.length < 2) return undefined; + let rest = parts[1]; + + // Remove context suffix if present + const ctxIndex = rest.indexOf("__ctx_"); + if (ctxIndex !== -1) { + rest = rest.substring(0, ctxIndex); + } + + const restParts = rest.split("__"); + // restParts[0] is targetId, restParts[1] is installationId + return restParts.length > 1 ? restParts[1] : undefined; } const EditInstalledComponent = (props: Props) => { @@ -40,79 +73,136 @@ const EditInstalledComponent = (props: Props) => { >(new Map()); const [showAll, setShowAll] = useState(false); - // Get all Systems - const allSystems = Array.from(dataset.individuals.values()).filter( - (ind) => (ind.entityType ?? EntityType.Individual) === EntityType.System - ); + // Get available slots - now includes ALL SystemComponent virtual rows (including nested) + const availableSlots = useMemo((): SlotOption[] => { + const slots: SlotOption[] = []; + const displayIndividuals = dataset.getDisplayIndividuals(); - // Get available slots (SystemComponents) - but only show them if they're NOT in the filtered list - // When in filtered mode (targetSlotId is set), we don't show the slot selector - const availableSlots = useMemo(() => { - if (targetSlotId) { - // In filtered mode, only return the target slot - const slot = dataset.individuals.get(targetSlotId); - return slot ? [slot] : []; - } + displayIndividuals.forEach((ind) => { + // Only consider virtual rows (installed instances) + if (!ind._isVirtualRow) return; + + // Parse the virtual ID to get the original component ID + const originalId = ind.id.split("__installed_in__")[0]; + const original = dataset.individuals.get(originalId); + if (!original) return; + + const origType = original.entityType ?? EntityType.Individual; + + // Only SystemComponents can be slots for InstalledComponents + if (origType !== EntityType.SystemComponent) return; + + // Build display name showing full hierarchy + const pathParts = ind._parentPath?.split("__") || []; + const pathNames: string[] = []; - // In unfiltered mode, return all SystemComponents that have installations - // (Only virtual SystemComponent rows can be installation targets) - return Array.from(dataset.individuals.values()).filter((ind) => { - if ( - (ind.entityType ?? EntityType.Individual) !== EntityType.SystemComponent - ) { - return false; + pathParts.forEach((partId) => { + const part = dataset.individuals.get(partId); + if (part) { + pathNames.push(part.name); + } + }); + + // The display name shows the SC name and its full path + const hierarchyStr = + pathNames.length > 0 ? pathNames.join(" → ") : "Unknown"; + const displayName = `${ind.name} (in ${hierarchyStr})`; + + // Extract the SC installation ID from the virtual ID + const scInstallationId = ind._installationId; + + slots.push({ + id: originalId, + virtualId: ind.id, + displayName: displayName, + bounds: { beginning: ind.beginning, ending: ind.ending }, + parentPath: ind._parentPath || "", + nestingLevel: ind._nestingLevel || 1, + systemName: pathNames[0], // Root system name + scInstallationId: scInstallationId, + }); + }); + + // Sort by system name, then by parent path (to maintain hierarchy), then by name + slots.sort((a, b) => { + // First by system name + if (a.systemName !== b.systemName) { + return (a.systemName || "").localeCompare(b.systemName || ""); } - // Only show SystemComponents that are installed somewhere - return ind.installations && ind.installations.length > 0; + // Then by parent path to keep hierarchy together + if (a.parentPath !== b.parentPath) { + // If one is nested under the other, parent comes first + if (a.parentPath.startsWith(b.parentPath + "__")) return 1; + if (b.parentPath.startsWith(a.parentPath + "__")) return -1; + return a.parentPath.localeCompare(b.parentPath); + } + // Then by nesting level + if (a.nestingLevel !== b.nestingLevel) { + return a.nestingLevel - b.nestingLevel; + } + // Finally by display name + return a.displayName.localeCompare(b.displayName); }); - }, [dataset, targetSlotId]); - // Helper to get Systems where a slot is installed - const getSystemsForSlot = (slotId: string): Individual[] => { - const slot = dataset.individuals.get(slotId); - if (!slot || !slot.installations) return []; + return slots; + }, [dataset]); - const systemIds = new Set(slot.installations.map((inst) => inst.targetId)); - return Array.from(systemIds) - .map((sysId) => dataset.individuals.get(sysId)) - .filter((sys): sys is Individual => !!sys); + // Group slots by system for the dropdown - MOVED BEFORE any conditional returns + const slotsBySystem = useMemo(() => { + const groups = new Map(); + availableSlots.forEach((slot) => { + const sysName = slot.systemName || "Unknown"; + if (!groups.has(sysName)) { + groups.set(sysName, []); + } + groups.get(sysName)!.push(slot); + }); + return groups; + }, [availableSlots]); + + // Helper to get slot option by virtualId (unique key) + const getSlotOptionByVirtualId = ( + virtualId: string + ): SlotOption | undefined => { + return availableSlots.find((slot) => slot.virtualId === virtualId); }; - // Helper to get the SC installation ID for a specific System - const getScInstallationForSystem = ( - slotId: string, - systemId: string - ): Installation | undefined => { - const slot = dataset.individuals.get(slotId); - if (!slot || !slot.installations) return undefined; - return slot.installations.find((inst) => inst.targetId === systemId); + // Helper to get slot option by targetId and scInstContextId + const getSlotOption = ( + targetId: string, + scInstContextId?: string + ): SlotOption | undefined => { + return availableSlots.find((slot) => { + if (slot.id !== targetId) return false; + // If we have a context ID, must match exactly + if (scInstContextId) { + return slot.scInstallationId === scInstContextId; + } + // If no context, return first matching slot + return true; + }); }; - // Helper function to get effective slot time bounds for a specific System context - const getSlotTimeBoundsForSystem = ( + // Helper function to get effective slot time bounds + const getSlotTimeBounds = ( slotId: string, - systemId?: string + scInstContextId?: string ): { beginning: number; ending: number; slotName: string } => { + const slotOption = getSlotOption(slotId, scInstContextId); + if (slotOption) { + return { + beginning: slotOption.bounds.beginning, + ending: slotOption.bounds.ending, + slotName: slotOption.displayName, + }; + } + + // Fallback to original behavior const slot = dataset.individuals.get(slotId); if (!slot) { return { beginning: 0, ending: Model.END_OF_TIME, slotName: slotId }; } - // If a specific System is provided, get bounds from that installation - if (systemId && slot.installations) { - const scInst = slot.installations.find( - (inst) => inst.targetId === systemId - ); - if (scInst) { - return { - beginning: scInst.beginning ?? 0, - ending: scInst.ending ?? Model.END_OF_TIME, - slotName: slot.name, - }; - } - } - - // Fallback: use union of all installations let beginning = slot.beginning; let ending = slot.ending; @@ -123,33 +213,21 @@ const EditInstalledComponent = (props: Props) => { const instEndings = slot.installations.map( (inst) => inst.ending ?? Model.END_OF_TIME ); - const earliestBeginning = Math.min(...instBeginnings); - const latestEnding = Math.max(...instEndings); - - if (beginning < 0) { - beginning = earliestBeginning; - } - if (ending >= Model.END_OF_TIME && latestEnding < Model.END_OF_TIME) { - ending = latestEnding; - } - } else if (beginning < 0) { - beginning = 0; + beginning = Math.min(...instBeginnings); + ending = Math.max(...instEndings); } + if (beginning < 0) beginning = 0; + return { beginning, ending, slotName: slot.name }; }; - // For backward compatibility - const getSlotTimeBounds = (slotId: string) => - getSlotTimeBoundsForSystem(slotId, undefined); - useEffect(() => { if (individual && individual.installations) { const allInst = [...individual.installations]; setAllInstallations(allInst); if (targetSlotId) { - // Filter by slot AND optionally by system context let filtered = allInst.filter((inst) => inst.targetId === targetSlotId); if (targetSystemId) { filtered = filtered.filter( @@ -198,7 +276,7 @@ const EditInstalledComponent = (props: Props) => { const endingStr = raw?.ending ?? String(inst.ending); const slotInfo = inst.targetId - ? getSlotTimeBoundsForSystem(inst.targetId, inst.systemContextId) + ? getSlotTimeBounds(inst.targetId, inst.scInstallationContextId) : { beginning: 0, ending: Model.END_OF_TIME, @@ -207,46 +285,10 @@ const EditInstalledComponent = (props: Props) => { const slotName = slotInfo.slotName; if (!inst.targetId) { - newErrors.push(`${slotName}: Please select a target slot.`); + newErrors.push(`Row ${idx + 1}: Please select a target slot.`); return; } - // Validate that a slot is selected AND it must be a virtual row (installed SystemComponent) - const slot = dataset.individuals.get(inst.targetId); - if (slot) { - const slotType = slot.entityType ?? EntityType.Individual; - if (slotType === EntityType.SystemComponent) { - // Check if this SystemComponent has any installations - if (!slot.installations || slot.installations.length === 0) { - newErrors.push( - `${slotName}: Cannot install into an uninstalled SystemComponent. The SystemComponent must be installed in a System first.` - ); - return; - } - } - } - - // Validate that if a slot is selected, a System context is also selected - // (if the slot is installed in multiple systems) - const systemsForSlot = getSystemsForSlot(inst.targetId); - - // Auto-fill system context if only one option exists - if (systemsForSlot.length === 1 && !inst.systemContextId) { - // This will be handled during save, but we need the context for validation - const autoSystemId = systemsForSlot[0].id; - inst.systemContextId = autoSystemId; - const scInst = getScInstallationForSystem(inst.targetId, autoSystemId); - if (scInst) { - inst.scInstallationContextId = scInst.id; - } - } - - if (systemsForSlot.length > 1 && !inst.systemContextId) { - newErrors.push( - `${slotName}: Please select which System this installation belongs to.` - ); - } - if (beginningStr.trim() === "") { newErrors.push(`${slotName}: "From" time is required.`); } @@ -269,29 +311,21 @@ const EditInstalledComponent = (props: Props) => { newErrors.push(`${slotName}: "From" must be less than "Until".`); } - // Validate against slot bounds (using System context if available) - const contextBounds = getSlotTimeBoundsForSystem( - inst.targetId, - inst.systemContextId - ); - if (beginning < contextBounds.beginning) { + if (beginning < slotInfo.beginning) { newErrors.push( - `${slotName}: "From" (${beginning}) cannot be before slot starts (${contextBounds.beginning}).` + `${slotName}: "From" (${beginning}) cannot be before slot starts (${slotInfo.beginning}).` ); } - if ( - contextBounds.ending < Model.END_OF_TIME && - ending > contextBounds.ending - ) { + if (slotInfo.ending < Model.END_OF_TIME && ending > slotInfo.ending) { newErrors.push( - `${slotName}: "Until" (${ending}) cannot be after slot ends (${contextBounds.ending}).` + `${slotName}: "Until" (${ending}) cannot be after slot ends (${slotInfo.ending}).` ); } } }); - // Check for overlapping periods in the same slot AND same system context - const bySlotAndSystem = new Map(); + // Check for overlapping periods in the same slot AND same context + const bySlotAndContext = new Map(); localInstallations.forEach((inst) => { if (!inst.targetId) return; const raw = rawInputs.get(inst.id); @@ -299,17 +333,19 @@ const EditInstalledComponent = (props: Props) => { const ending = parseInt(raw?.ending ?? String(inst.ending), 10); if (isNaN(beginning) || isNaN(ending)) return; - // Key includes both slot and system context - const key = `${inst.targetId}__${inst.systemContextId || "any"}`; - const list = bySlotAndSystem.get(key) || []; + const key = `${inst.targetId}__${inst.scInstallationContextId || "any"}`; + const list = bySlotAndContext.get(key) || []; list.push({ ...inst, beginning, ending }); - bySlotAndSystem.set(key, list); + bySlotAndContext.set(key, list); }); - bySlotAndSystem.forEach((installations, key) => { + bySlotAndContext.forEach((installations, key) => { if (installations.length < 2) return; - const [slotId] = key.split("__"); - const slotInfo = getSlotTimeBounds(slotId); + const [slotId, contextId] = key.split("__"); + const slotInfo = getSlotTimeBounds( + slotId, + contextId !== "any" ? contextId : undefined + ); installations.sort((a, b) => (a.beginning ?? 0) - (b.beginning ?? 0)); @@ -338,34 +374,10 @@ const EditInstalledComponent = (props: Props) => { const updatedInstallations = localInstallations.map((inst) => { const raw = rawInputs.get(inst.id); - // Get the SC installation ID for the system context - let scInstallationContextId = inst.scInstallationContextId; - let systemContextId = inst.systemContextId; - - // Auto-fill system context if only one option - if (inst.targetId && !systemContextId) { - const systemsForSlot = getSystemsForSlot(inst.targetId); - if (systemsForSlot.length === 1) { - systemContextId = systemsForSlot[0].id; - } - } - - if (inst.targetId && systemContextId && !scInstallationContextId) { - const scInst = getScInstallationForSystem( - inst.targetId, - systemContextId - ); - if (scInst) { - scInstallationContextId = scInst.id; - } - } - return { ...inst, beginning: parseInt(raw?.beginning ?? String(inst.beginning), 10) || 0, ending: parseInt(raw?.ending ?? String(inst.ending), 10) || 10, - systemContextId, - scInstallationContextId, }; }); @@ -390,18 +402,24 @@ const EditInstalledComponent = (props: Props) => { if (updateDataset) { updateDataset((d: Model) => { removedInstallations.forEach((removedInst) => { - const participationKey = `${individual.id}__installed_in__${removedInst.targetId}__${removedInst.id}`; + const participationPatterns = [ + `${individual.id}__installed_in__${removedInst.targetId}__${removedInst.id}`, + `${individual.id}__installed_in__${removedInst.targetId}`, + ]; d.activities.forEach((activity) => { const parts = activity.participations; - if (!parts) return; - - if (parts instanceof Map) { - if (parts.has(participationKey)) { - parts.delete(participationKey); - d.activities.set(activity.id, activity); - } - } + if (!parts || !(parts instanceof Map)) return; + + participationPatterns.forEach((pattern) => { + parts.forEach((_, key) => { + if (key.startsWith(pattern)) { + parts.delete(key); + } + }); + }); + + d.activities.set(activity.id, activity); }); }); @@ -423,47 +441,24 @@ const EditInstalledComponent = (props: Props) => { }; const addInstallation = () => { - // Get slot bounds if we have a target slot - const slotBounds = targetSlotId - ? getSlotTimeBoundsForSystem(targetSlotId, targetSystemId) - : { beginning: 0, ending: 10 }; + let slotOption: SlotOption | undefined; + if (targetSlotId) { + slotOption = availableSlots.find((s) => s.id === targetSlotId); + } - const defaultBeginning = slotBounds.beginning; + const defaultBeginning = slotOption?.bounds.beginning ?? 0; const defaultEnding = - slotBounds.ending < Model.END_OF_TIME - ? slotBounds.ending + slotOption && slotOption.bounds.ending < Model.END_OF_TIME + ? slotOption.bounds.ending : defaultBeginning + 10; - // Get the SC installation ID if we have both slot and system context - let scInstallationContextId: string | undefined; - let systemContextId: string | undefined = targetSystemId; - - if (targetSlotId) { - // If we have a target slot, check if there's only one system - auto-select it - const systemsForSlot = getSystemsForSlot(targetSlotId); - if (systemsForSlot.length === 1 && !systemContextId) { - systemContextId = systemsForSlot[0].id; - } - - if (systemContextId) { - const scInst = getScInstallationForSystem( - targetSlotId, - systemContextId - ); - if (scInst) { - scInstallationContextId = scInst.id; - } - } - } - const newInst: Installation = { id: uuidv4(), componentId: individual?.id || "", targetId: targetSlotId || "", beginning: defaultBeginning, ending: defaultEnding, - systemContextId: systemContextId, - scInstallationContextId, + scInstallationContextId: slotOption?.scInstallationId, }; setLocalInstallations((prev) => [...prev, newInst]); @@ -530,29 +525,6 @@ const EditInstalledComponent = (props: Props) => { } }; - // Helper to get slot name with system - const getSlotDisplayName = (slotId: string): string => { - if (!slotId) return ""; - const slot = availableSlots.find((s) => s.id === slotId); - if (!slot) return slotId; - - if (slot.installations && slot.installations.length > 0) { - const systemIds = Array.from( - new Set(slot.installations.map((inst) => inst.targetId)) - ); - const systemNames = systemIds - .map((sysId) => { - const system = dataset.individuals.get(sysId); - return system?.name ?? sysId; - }) - .join(", "); - return `${slot.name} (in ${systemNames})`; - } - - return slot.name; - }; - - // Helper to check if a value is outside slot bounds const isOutsideSlotBounds = ( instId: string, field: "beginning" | "ending" @@ -567,9 +539,9 @@ const EditInstalledComponent = (props: Props) => { ); if (isNaN(value)) return false; - const slotBounds = getSlotTimeBoundsForSystem( + const slotBounds = getSlotTimeBounds( inst.targetId, - inst.systemContextId + inst.scInstallationContextId ); if (field === "beginning") { @@ -579,24 +551,14 @@ const EditInstalledComponent = (props: Props) => { } }; + // Early return AFTER all hooks if (!individual) return null; const isFiltered = !!targetSlotId && !showAll; const totalInstallations = allInstallations.length; - const slotName = targetSlotId ? getSlotDisplayName(targetSlotId) : ""; - const systemName = targetSystemId - ? dataset.individuals.get(targetSystemId)?.name - : undefined; - - // Get slot bounds for display - const targetSlotBounds = targetSlotId - ? getSlotTimeBoundsForSystem(targetSlotId, targetSystemId) - : null; const modalTitle = isFiltered - ? systemName - ? `Edit Installation: ${individual.name} in ${slotName} (${systemName})` - : `Edit Installation: ${individual.name} in ${slotName}` + ? `Edit Installation: ${individual.name}` : `Edit All Installations: ${individual.name}`; return ( @@ -623,9 +585,7 @@ const EditInstalledComponent = (props: Props) => {

{isFiltered - ? `Manage installation periods for this component in "${slotName}"${ - systemName ? ` within "${systemName}"` : "" - }. You can have multiple non-overlapping periods.` + ? `Manage installation periods for this component. You can have multiple non-overlapping periods.` : `Manage all installation periods for this component across different slots.`} {!isFiltered && availableSlots.length === 0 && ( <> @@ -636,7 +596,6 @@ const EditInstalledComponent = (props: Props) => { )}

- {/* Always show the table structure */} @@ -644,23 +603,18 @@ const EditInstalledComponent = (props: Props) => { # {!isFiltered && ( - <> - - - + )} - - {!isFiltered && ( - <> - - - + }} + className={!inst.targetId ? "border-warning" : ""} + > + + {Array.from(slotsBySystem.entries()).map( + ([sysName, slots]) => ( + + {slots.map((slot) => { + const indent = " ".repeat( + slot.nestingLevel - 1 + ); + const boundsStr = ` (${slot.bounds.beginning}-${ + slot.bounds.ending >= Model.END_OF_TIME + ? "∞" + : slot.bounds.ending + })`; + return ( + + ); + })} + + ) + )} + + {slotOption && ( + + + Available: {slotOption.bounds.beginning}- + {slotOption.bounds.ending >= Model.END_OF_TIME + ? "∞" + : slotOption.bounds.ending} + + + )} + )} + + + ); + })} + +
- Target Slot * - - System Context * - + Target Slot * + + From * + Until * Actions @@ -674,17 +628,12 @@ const EditInstalledComponent = (props: Props) => { ending: String(inst.ending), }; - // Get Systems where this slot is installed - const systemsForSlot = inst.targetId - ? getSystemsForSlot(inst.targetId) - : []; + const slotOption = inst.targetId + ? getSlotOption(inst.targetId, inst.scInstallationContextId) + : undefined; - // Get slot bounds for this installation const instSlotBounds = inst.targetId - ? getSlotTimeBoundsForSystem( - inst.targetId, - inst.systemContextId - ) + ? getSlotTimeBounds(inst.targetId, inst.scInstallationContextId) : null; const beginningOutOfBounds = isOutsideSlotBounds( @@ -697,148 +646,93 @@ const EditInstalledComponent = (props: Props) => {
{idx + 1} - { - const newSlotId = e.target.value; - updateInstallation(inst.id, "targetId", newSlotId); - // Clear system context when changing slot + + { + const virtualId = e.target.value; + if (!virtualId) { + updateInstallation(inst.id, "targetId", ""); updateInstallation( inst.id, - "systemContextId", + "scInstallationContextId", undefined ); + return; + } + + const selectedSlot = + getSlotOptionByVirtualId(virtualId); + + if (selectedSlot) { updateInstallation( inst.id, - "scInstallationContextId", - undefined + "targetId", + selectedSlot.id ); - // Reset times to slot defaults when changing slot - if (newSlotId) { - const newSlotBounds = - getSlotTimeBounds(newSlotId); - const newEnding = - newSlotBounds.ending < Model.END_OF_TIME - ? newSlotBounds.ending - : newSlotBounds.beginning + 10; - updateRawInput( - inst.id, - "beginning", - String(newSlotBounds.beginning) - ); - updateRawInput( - inst.id, - "ending", - String(newEnding) - ); - } - }} - className={!inst.targetId ? "border-warning" : ""} - > - - {availableSlots.map((slot) => ( - - ))} - - - { - const newSystemId = e.target.value || undefined; updateInstallation( inst.id, - "systemContextId", - newSystemId + "scInstallationContextId", + selectedSlot.scInstallationId + ); + + const newEnding = + selectedSlot.bounds.ending < Model.END_OF_TIME + ? selectedSlot.bounds.ending + : selectedSlot.bounds.beginning + 10; + updateRawInput( + inst.id, + "beginning", + String(selectedSlot.bounds.beginning) + ); + updateRawInput( + inst.id, + "ending", + String(newEnding) ); - // Set the SC installation context ID - if (inst.targetId && newSystemId) { - const scInst = getScInstallationForSystem( - inst.targetId, - newSystemId - ); - updateInstallation( - inst.id, - "scInstallationContextId", - scInst?.id - ); - // Update times to match system context - const newBounds = getSlotTimeBoundsForSystem( - inst.targetId, - newSystemId - ); - const newEnding = - newBounds.ending < Model.END_OF_TIME - ? newBounds.ending - : newBounds.beginning + 10; - updateRawInput( - inst.id, - "beginning", - String(newBounds.beginning) - ); - updateRawInput( - inst.id, - "ending", - String(newEnding) - ); - } else { - updateInstallation( - inst.id, - "scInstallationContextId", - undefined - ); - } - }} - disabled={!inst.targetId} - className={ - inst.targetId && - systemsForSlot.length > 1 && - !inst.systemContextId - ? "border-warning" - : "" } - > - - {systemsForSlot.length > 1 && - systemsForSlot.map((sys) => { - const scInst = getScInstallationForSystem( - inst.targetId, - sys.id - ); - const boundsStr = scInst - ? ` (${scInst.beginning ?? 0}-${ - scInst.ending ?? "∞" - })` - : ""; - return ( - - ); - })} - - {inst.targetId && - systemsForSlot.length === 1 && - !inst.systemContextId && ( - - Auto: {systemsForSlot[0].name} - - )} - void; dataset: Model; - updateDataset?: (updater: (d: Model) => void) => void; + updateDataset: (updater: (d: Model) => void) => void; targetSystemId?: string; } -const EditSystemComponentInstallation = (props: Props) => { - const { - show, - setShow, - individual, - setIndividual, - dataset, - updateDataset, - targetSystemId, - } = props; - - const [localInstallations, setLocalInstallations] = useState( - [] - ); - const [allInstallations, setAllInstallations] = useState([]); - const [removedInstallations, setRemovedInstallations] = useState< - Installation[] - >([]); - const [errors, setErrors] = useState([]); - const [rawInputs, setRawInputs] = useState< - Map - >(new Map()); - const [showAll, setShowAll] = useState(false); - - // Helper function to get effective system time bounds - const getSystemTimeBounds = ( - systemId: string - ): { beginning: number; ending: number; systemName: string } => { - const system = dataset.individuals.get(systemId); - if (!system) { - return { beginning: 0, ending: Model.END_OF_TIME, systemName: systemId }; - } - - let beginning = system.beginning >= 0 ? system.beginning : 0; - let ending = system.ending; +interface RawInputs { + [installationId: string]: { + beginning: string; + ending: string; + }; +} - return { beginning, ending, systemName: system.name }; +interface ValidationErrors { + [installationId: string]: { + beginning?: string; + ending?: string; + overlap?: string; + target?: string; }; +} - useEffect(() => { - if (individual && individual.installations) { - const allInst = [...individual.installations]; - setAllInstallations(allInst); +// Interface for available targets (including virtual SC instances) +interface TargetOption { + id: string; // The original target ID (System or SC ID) + virtualId: string; // Unique identifier for this specific instance + displayName: string; + entityType: EntityType; + bounds: { beginning: number; ending: number }; + isVirtual: boolean; + parentSystemName?: string; + scInstallationId?: string; // The SC's installation ID for context +} - if (targetSystemId) { - const filtered = allInst.filter( - (inst) => inst.targetId === targetSystemId - ); - setLocalInstallations(filtered); - setShowAll(false); - } else { - setLocalInstallations(allInst); - setShowAll(true); - } +// Helper function to check if two time ranges overlap +function hasTimeOverlap( + start1: number, + end1: number, + start2: number, + end2: number +): boolean { + return start1 < end2 && start2 < end1; +} - const inputs = new Map(); - allInst.forEach((inst) => { - inputs.set(inst.id, { - beginning: String(inst.beginning ?? 0), - ending: String(inst.ending ?? 10), - }); +// Helper to extract the SC installation ID from a virtual row ID +function extractInstallationIdFromVirtualId( + virtualId: string +): string | undefined { + if (!virtualId.includes("__installed_in__")) return undefined; + const parts = virtualId.split("__installed_in__"); + if (parts.length < 2) return undefined; + let rest = parts[1]; + + const ctxIndex = rest.indexOf("__ctx_"); + if (ctxIndex !== -1) { + rest = rest.substring(0, ctxIndex); + } + + const restParts = rest.split("__"); + return restParts.length > 1 ? restParts[1] : undefined; +} + +export default function EditSystemComponentInstallation({ + show, + setShow, + individual, + setIndividual, + dataset, + updateDataset, + targetSystemId, +}: Props) { + const [installations, setInstallations] = useState([]); + const [rawInputs, setRawInputs] = useState({}); + const [errors, setErrors] = useState({}); + + // Initialize installations when modal opens + useEffect(() => { + if (show && individual) { + const insts = individual.installations || []; + setInstallations([...insts]); + + // Initialize raw inputs + const inputs: RawInputs = {}; + insts.forEach((inst) => { + inputs[inst.id] = { + beginning: inst.beginning?.toString() ?? "0", + ending: inst.ending?.toString() ?? "", + }; }); setRawInputs(inputs); - } else { - setLocalInstallations([]); - setAllInstallations([]); - setRawInputs(new Map()); - setShowAll(!targetSystemId); + setErrors({}); } - setRemovedInstallations([]); - setErrors([]); - }, [individual, show, targetSystemId]); - - const handleClose = () => { - setShow(false); - setRemovedInstallations([]); - setErrors([]); - setShowAll(false); - }; + }, [show, individual]); + + // Get available targets (Systems AND SystemComponent installations) + const availableTargets = useMemo((): TargetOption[] => { + const targets: TargetOption[] = []; + + // Add Systems as targets + dataset.individuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + + if (entityType === EntityType.System) { + const bounds = dataset.getTargetTimeBounds(ind.id); + targets.push({ + id: ind.id, + virtualId: ind.id, // For systems, virtualId = id + displayName: ind.name, + entityType: EntityType.System, + bounds, + isVirtual: false, + }); + } + }); - const validateInstallations = (): boolean => { - const newErrors: string[] = []; + // Add SystemComponent instances (from the display list) as targets + const displayIndividuals = dataset.getDisplayIndividuals(); - localInstallations.forEach((inst, idx) => { - const raw = rawInputs.get(inst.id); - const beginningStr = raw?.beginning ?? String(inst.beginning); - const endingStr = raw?.ending ?? String(inst.ending ?? ""); + displayIndividuals.forEach((ind) => { + if (!ind._isVirtualRow) return; - const systemInfo = inst.targetId - ? getSystemTimeBounds(inst.targetId) - : { - beginning: 0, - ending: Model.END_OF_TIME, - systemName: `Row ${idx + 1}`, - }; - const systemName = systemInfo.systemName; + const originalId = ind.id.split("__installed_in__")[0]; + const original = dataset.individuals.get(originalId); + if (!original) return; - if (!inst.targetId) { - newErrors.push(`${systemName}: Please select a target system.`); - return; - } + const origType = original.entityType ?? EntityType.Individual; + if (origType !== EntityType.SystemComponent) return; - if (beginningStr.trim() === "") { - newErrors.push(`${systemName}: "From" time is required.`); - } + // Don't allow installing into self (same original SC ID) + if (originalId === individual?.id) return; - // "Until" is now optional - if empty, it will inherit from system or use END_OF_TIME - // No validation error for empty ending + // NO circular reference check needed here! + // Different installation contexts (different systems) are independent. + // SC1 can be nested in SC2 in System A, while SC2 is nested in SC1 in System B. - const beginning = parseInt(beginningStr, 10); - const ending = - endingStr.trim() === "" ? Model.END_OF_TIME : parseInt(endingStr, 10); + // Extract parent system name for display + const pathParts = ind._parentPath?.split("__") || []; + const systemId = pathParts[0]; + const system = dataset.individuals.get(systemId); + const parentSystemName = system?.name; - if (!isNaN(beginning)) { - if (beginning < 0) { - newErrors.push(`${systemName}: "From" cannot be negative.`); + // Build a better display name showing full path + const pathNames: string[] = []; + pathParts.forEach((partId) => { + const part = dataset.individuals.get(partId); + if (part) { + pathNames.push(part.name); } + }); + const hierarchyStr = + pathNames.length > 0 ? pathNames.join(" → ") : "Unknown"; + + // Extract the SC installation ID + const scInstallationId = ind._installationId; + + targets.push({ + id: originalId, + virtualId: ind.id, // Use the FULL virtual ID as unique key + displayName: `${ind.name} (in ${hierarchyStr})`, + entityType: EntityType.SystemComponent, + bounds: { beginning: ind.beginning, ending: ind.ending }, + isVirtual: true, + parentSystemName, + scInstallationId, + }); + }); - // Validate against system bounds - if (beginning < systemInfo.beginning) { - newErrors.push( - `${systemName}: "From" (${beginning}) cannot be before system starts (${systemInfo.beginning}).` - ); - } - } + return targets; + }, [dataset, individual?.id]); - if (endingStr.trim() !== "" && !isNaN(ending)) { - if (ending < 1) { - newErrors.push(`${systemName}: "Until" must be at least 1.`); - } - if (beginning >= ending) { - newErrors.push(`${systemName}: "From" must be less than "Until".`); - } + // Helper to get target by virtualId + const getTargetByVirtualId = ( + virtualId: string + ): TargetOption | undefined => { + return availableTargets.find((t) => t.virtualId === virtualId); + }; - if ( - systemInfo.ending < Model.END_OF_TIME && - ending > systemInfo.ending - ) { - newErrors.push( - `${systemName}: "Until" (${ending}) cannot be after system ends (${systemInfo.ending}).` - ); - } - } - }); + // Helper to get target for an installation (by id + context) + const getTargetForInstallation = ( + inst: Installation + ): TargetOption | undefined => { + if (!inst.targetId) return undefined; + + // First try to find by exact context match + if (inst.scInstallationContextId) { + const match = availableTargets.find( + (t) => + t.id === inst.targetId && + t.scInstallationId === inst.scInstallationContextId + ); + if (match) return match; + } - // Check for overlapping periods in the same system - const bySystem = new Map(); - localInstallations.forEach((inst) => { - if (!inst.targetId) return; - const raw = rawInputs.get(inst.id); - const beginning = parseInt(raw?.beginning ?? String(inst.beginning), 10); - const endingStr = raw?.ending ?? String(inst.ending ?? ""); - const ending = - endingStr.trim() === "" ? Model.END_OF_TIME : parseInt(endingStr, 10); - if (isNaN(beginning)) return; + // For Systems (non-virtual), just match by ID + const systemMatch = availableTargets.find( + (t) => t.id === inst.targetId && !t.isVirtual + ); + if (systemMatch) return systemMatch; - const list = bySystem.get(inst.targetId) || []; - list.push({ ...inst, beginning, ending }); - bySystem.set(inst.targetId, list); - }); + // Fallback: return first match (shouldn't happen if data is consistent) + return availableTargets.find((t) => t.id === inst.targetId); + }; - bySystem.forEach((installations, systemId) => { - if (installations.length < 2) return; - const systemInfo = getSystemTimeBounds(systemId); - - // Sort by beginning time - installations.sort((a, b) => (a.beginning ?? 0) - (b.beginning ?? 0)); - - for (let i = 0; i < installations.length - 1; i++) { - const current = installations[i]; - const next = installations[i + 1]; - if ((current.ending ?? Model.END_OF_TIME) > (next.beginning ?? 0)) { - newErrors.push( - `${systemInfo.systemName}: Periods overlap (${current.beginning}-${ - current.ending ?? "∞" - } and ${next.beginning}-${next.ending ?? "∞"}).` - ); - } - } - }); + // Helper function to get effective target time bounds + const getTargetTimeBounds = ( + targetId: string, + scInstContextId?: string + ): { + beginning: number; + ending: number; + targetName: string; + targetType: EntityType; + } => { + // Try to find the specific target option + const targetOption = scInstContextId + ? availableTargets.find( + (t) => t.id === targetId && t.scInstallationId === scInstContextId + ) + : availableTargets.find((t) => t.id === targetId); + + if (targetOption) { + return { + beginning: targetOption.bounds.beginning, + ending: targetOption.bounds.ending, + targetName: targetOption.displayName, + targetType: targetOption.entityType, + }; + } - setErrors(newErrors); - return newErrors.length === 0; + // Fallback + const target = dataset.individuals.get(targetId); + if (!target) { + return { + beginning: 0, + ending: Model.END_OF_TIME, + targetName: targetId, + targetType: EntityType.System, + }; + } + + const targetType = target.entityType ?? EntityType.Individual; + const bounds = dataset.getTargetTimeBounds(targetId); + + return { + ...bounds, + targetName: target.name, + targetType: targetType as EntityType, + }; }; - const handleSave = () => { - if (!individual) return; + // Validate all installations for overlaps + const validateAllInstallations = ( + currentInstallations: Installation[], + currentRawInputs: RawInputs + ) => { + const newErrors: ValidationErrors = {}; - if (!validateInstallations()) { - return; - } + currentInstallations.forEach((inst) => { + newErrors[inst.id] = {}; + const raw = currentRawInputs[inst.id] || { beginning: "0", ending: "" }; - const updatedInstallations = localInstallations.map((inst) => { - const raw = rawInputs.get(inst.id); - const beginningValue = - parseInt(raw?.beginning ?? String(inst.beginning), 10) || 0; - const endingStr = raw?.ending ?? String(inst.ending ?? ""); - const endingValue = - endingStr.trim() === "" ? Model.END_OF_TIME : parseInt(endingStr, 10); + if (inst.targetId === individual?.id) { + newErrors[inst.id].target = "Cannot install into itself"; + return; + } - return { - ...inst, - beginning: beginningValue, - ending: endingValue, + // Get target bounds using context + const targetOption = getTargetForInstallation(inst); + const bounds = targetOption?.bounds || { + beginning: 0, + ending: Model.END_OF_TIME, }; - }); - let finalInstallations: Installation[]; + const beginning = raw.beginning === "" ? 0 : parseFloat(raw.beginning); + const ending = + raw.ending === "" ? Model.END_OF_TIME : parseFloat(raw.ending); + + if (isNaN(beginning)) { + newErrors[inst.id].beginning = "Must be a number"; + } else if (beginning < bounds.beginning) { + newErrors[ + inst.id + ].beginning = `Must be ≥ ${bounds.beginning} (target start)`; + } else if ( + beginning >= bounds.ending && + bounds.ending < Model.END_OF_TIME + ) { + newErrors[ + inst.id + ].beginning = `Must be < ${bounds.ending} (target end)`; + } - if (targetSystemId && !showAll) { - const keptFromOtherSystems = allInstallations.filter( - (i) => i.targetId !== targetSystemId - ); - const removedIds = new Set(removedInstallations.map((i) => i.id)); - const filteredUpdated = updatedInstallations.filter( - (i) => !removedIds.has(i.id) - ); - finalInstallations = [...keptFromOtherSystems, ...filteredUpdated]; - } else { - const removedIds = new Set(removedInstallations.map((i) => i.id)); - finalInstallations = updatedInstallations.filter( - (i) => !removedIds.has(i.id) - ); - } + if (raw.ending !== "" && isNaN(ending)) { + newErrors[inst.id].ending = "Must be a number"; + } else if (ending <= beginning) { + newErrors[inst.id].ending = "Must be > beginning"; + } else if (ending > bounds.ending && bounds.ending < Model.END_OF_TIME) { + newErrors[inst.id].ending = `Must be ≤ ${bounds.ending} (target end)`; + } - if (updateDataset) { - updateDataset((d: Model) => { - // Clean up participations for removed installations - removedInstallations.forEach((removedInst) => { - const participationKey = `${individual.id}__installed_in__${removedInst.targetId}__${removedInst.id}`; - - d.activities.forEach((activity) => { - const parts = activity.participations; - if (!parts) return; - - if (parts instanceof Map) { - if (parts.has(participationKey)) { - parts.delete(participationKey); - d.activities.set(activity.id, activity); - } - } - }); - }); + // Check for overlapping installations into the SAME target AND context + if (inst.targetId) { + const key = `${inst.targetId}__${ + inst.scInstallationContextId || "none" + }`; + const otherInstallationsInSameTarget = currentInstallations.filter( + (other) => { + if (other.id === inst.id) return false; + const otherKey = `${other.targetId}__${ + other.scInstallationContextId || "none" + }`; + return otherKey === key; + } + ); - const updated: Individual = { - ...individual, - installations: finalInstallations, - }; - d.addIndividual(updated); - }); - } else { - const updated: Individual = { - ...individual, - installations: finalInstallations, - }; - setIndividual(updated); - } + for (const other of otherInstallationsInSameTarget) { + const otherRaw = currentRawInputs[other.id] || { + beginning: "0", + ending: "", + }; + const otherBeginning = + otherRaw.beginning === "" ? 0 : parseFloat(otherRaw.beginning); + const otherEnding = + otherRaw.ending === "" + ? Model.END_OF_TIME + : parseFloat(otherRaw.ending); + + if ( + !isNaN(beginning) && + !isNaN(ending) && + !isNaN(otherBeginning) && + !isNaN(otherEnding) + ) { + if ( + hasTimeOverlap(beginning, ending, otherBeginning, otherEnding) + ) { + newErrors[ + inst.id + ].overlap = `Overlaps with another installation in the same target (${otherBeginning}-${ + otherEnding === Model.END_OF_TIME ? "∞" : otherEnding + })`; + break; + } + } + } + } + }); - handleClose(); + setErrors(newErrors); + return newErrors; }; + // Add new installation const addInstallation = () => { - // Get system bounds if we have a target system - const systemBounds = targetSystemId - ? getSystemTimeBounds(targetSystemId) - : { beginning: 0, ending: Model.END_OF_TIME }; - - const defaultBeginning = systemBounds.beginning; - // Leave ending undefined/empty so it inherits from system - const defaultEnding = undefined; - const newInst: Installation = { id: uuidv4(), - componentId: individual?.id || "", - targetId: targetSystemId || "", - beginning: defaultBeginning, - ending: defaultEnding, + componentId: individual?.id ?? "", + targetId: "", + beginning: 0, + ending: undefined, + scInstallationContextId: undefined, }; - - setLocalInstallations((prev) => [...prev, newInst]); - setRawInputs((prev) => { - const next = new Map(prev); - next.set(newInst.id, { - beginning: String(defaultBeginning), - ending: "", // Empty string means inherit from system - }); - return next; - }); + const newInstallations = [...installations, newInst]; + const newRawInputs = { + ...rawInputs, + [newInst.id]: { beginning: "0", ending: "" }, + }; + setInstallations(newInstallations); + setRawInputs(newRawInputs); + validateAllInstallations(newInstallations, newRawInputs); }; + // Remove installation const removeInstallation = (instId: string) => { - const removed = localInstallations.find((inst) => inst.id === instId); - if (removed) { - setRemovedInstallations((prev) => [...prev, removed]); - } - setLocalInstallations((prev) => prev.filter((inst) => inst.id !== instId)); - setRawInputs((prev) => { - const next = new Map(prev); - next.delete(instId); - return next; - }); - setErrors([]); - }; - - const updateInstallation = ( - instId: string, - field: keyof Installation, - value: any - ) => { - setLocalInstallations((prev) => - prev.map((inst) => - inst.id === instId ? { ...inst, [field]: value } : inst - ) - ); - if (errors.length > 0) { - setErrors([]); - } + const newInstallations = installations.filter((i) => i.id !== instId); + const newRawInputs = { ...rawInputs }; + delete newRawInputs[instId]; + setInstallations(newInstallations); + setRawInputs(newRawInputs); + validateAllInstallations(newInstallations, newRawInputs); }; + // Update raw input and validate const updateRawInput = ( instId: string, field: "beginning" | "ending", value: string ) => { - setRawInputs((prev) => { - const next = new Map(prev); - const current = next.get(instId) || { beginning: "0", ending: "10" }; - next.set(instId, { ...current, [field]: value }); - return next; - }); + const newRawInputs = { + ...rawInputs, + [instId]: { + ...rawInputs[instId], + [field]: value, + }, + }; + setRawInputs(newRawInputs); + validateAllInstallations(installations, newRawInputs); + }; - if (value !== "") { - const parsed = parseInt(value, 10); - if (!isNaN(parsed)) { - updateInstallation(instId, field, parsed); - } + // Update installation target using virtualId + const updateInstallationTarget = (instId: string, virtualId: string) => { + if (!virtualId) { + // Clear target + const newInstallations = installations.map((i) => + i.id === instId + ? { ...i, targetId: "", scInstallationContextId: undefined } + : i + ); + setInstallations(newInstallations); + validateAllInstallations(newInstallations, rawInputs); + return; } - if (errors.length > 0) { - setErrors([]); - } + const targetOption = getTargetByVirtualId(virtualId); + if (!targetOption) return; + + const newInstallations = installations.map((i) => + i.id === instId + ? { + ...i, + targetId: targetOption.id, + scInstallationContextId: targetOption.scInstallationId, + } + : i + ); + setInstallations(newInstallations); + + // Reset times to target bounds + const newRawInputs = { + ...rawInputs, + [instId]: { + beginning: String(targetOption.bounds.beginning), + ending: "", + }, + }; + setRawInputs(newRawInputs); + validateAllInstallations(newInstallations, newRawInputs); }; - // Get available systems - const availableSystems = Array.from(dataset.individuals.values()).filter( - (ind) => (ind.entityType ?? EntityType.Individual) === EntityType.System - ); + // Check if there are any validation errors + const hasErrors = () => { + return Object.values(errors).some( + (e) => e.beginning || e.ending || e.overlap || e.target + ); + }; - // Helper to get system name - const getSystemName = (systemId: string): string => { - if (!systemId) return ""; - const system = availableSystems.find((s) => s.id === systemId); - return system?.name ?? systemId; + // Check if all installations have targets + const allHaveTargets = () => { + return installations.every((i) => i.targetId); }; - // Helper to check if a value is outside system bounds - const isOutsideSystemBounds = ( - instId: string, - field: "beginning" | "ending" - ): boolean => { - const inst = localInstallations.find((i) => i.id === instId); - if (!inst || !inst.targetId) return false; + // Save changes + const handleSave = () => { + if (!individual || hasErrors() || !allHaveTargets()) return; - const raw = rawInputs.get(instId); - const valueStr = - field === "beginning" ? raw?.beginning ?? "" : raw?.ending ?? ""; + const finalErrors = validateAllInstallations(installations, rawInputs); + const hasFinalErrors = Object.values(finalErrors).some( + (e) => e.beginning || e.ending || e.overlap || e.target + ); + if (hasFinalErrors) return; - // If ending is empty, it's valid (inherits from system) - if (field === "ending" && valueStr.trim() === "") return false; + const finalInstallations = installations.map((inst) => { + const raw = rawInputs[inst.id]; + return { + ...inst, + beginning: + raw?.beginning === "" ? 0 : parseFloat(raw?.beginning ?? "0"), + ending: raw?.ending === "" ? undefined : parseFloat(raw?.ending ?? ""), + }; + }); - const value = parseInt(valueStr, 10); - if (isNaN(value)) return false; + const updated: Individual = { + ...individual, + installations: finalInstallations, + }; - const systemBounds = getSystemTimeBounds(inst.targetId); + updateDataset((d: Model) => { + d.individuals.set(individual.id, updated); + }); - if (field === "beginning") { - return value < systemBounds.beginning; - } else { - return ( - systemBounds.ending < Model.END_OF_TIME && value > systemBounds.ending - ); - } + setShow(false); }; - if (!individual) return null; - - const isFiltered = !!targetSystemId && !showAll; - const totalInstallations = allInstallations.length; - const systemName = targetSystemId ? getSystemName(targetSystemId) : ""; + const onHide = () => setShow(false); - // Get system bounds for display - const targetSystemBounds = targetSystemId - ? getSystemTimeBounds(targetSystemId) - : null; + if (!individual) return null; - const modalTitle = isFiltered - ? `Edit Installation: ${individual.name} in ${systemName}` - : `Edit All Installations: ${individual.name}`; + // Group targets by type for the dropdown + const systemTargets = availableTargets.filter( + (t) => t.entityType === EntityType.System + ); + const scTargets = availableTargets.filter( + (t) => t.entityType === EntityType.SystemComponent + ); return ( - + - {modalTitle} + + Edit Installations for {individual.name} + - {isFiltered && totalInstallations > localInstallations.length && ( -
- -
- )} -

- {isFiltered - ? `Manage installation periods for this system component in "${systemName}". You can have multiple non-overlapping periods.` - : "Manage all installation periods for this system component across different systems."} + Configure where this System Component is installed. You can install it + into Systems or other System Components (nested slots).

- {/* Always show the table structure */} - - - - - {!isFiltered && ( - - )} - - - - - - - {localInstallations.map((inst, idx) => { - const raw = rawInputs.get(inst.id) || { - beginning: String(inst.beginning), - ending: String(inst.ending ?? ""), - }; - - // Get system bounds for this installation - const instSystemBounds = inst.targetId - ? getSystemTimeBounds(inst.targetId) - : null; - - const beginningOutOfBounds = isOutsideSystemBounds( - inst.id, - "beginning" - ); - const endingOutOfBounds = isOutsideSystemBounds( - inst.id, - "ending" - ); - - return ( - - - {!isFiltered && ( + {installations.length === 0 ? ( +
+ No installations configured. Click "Add Installation" to add one. +
+ ) : ( +
- # - - Target System * - - From * - Until - Actions -
{idx + 1}
+ + + + + + + + + + {installations.map((inst) => { + const raw = rawInputs[inst.id] || { + beginning: "0", + ending: "", + }; + const err = errors[inst.id] || {}; + const targetOption = getTargetForInstallation(inst); + + return ( + + - )} - - - - - ); - })} - -
TargetBeginningEndingActions
{ - updateInstallation( - inst.id, - "targetId", - e.target.value - ); - // Reset times to system defaults when changing system - if (e.target.value) { - const newSystemBounds = getSystemTimeBounds( - e.target.value - ); - updateRawInput( - inst.id, - "beginning", - String(newSystemBounds.beginning) - ); - updateRawInput(inst.id, "ending", ""); - } - }} - className={!inst.targetId ? "border-warning" : ""} + value={targetOption?.virtualId || ""} + onChange={(e) => + updateInstallationTarget(inst.id, e.target.value) + } + className={ + !inst.targetId + ? "border-warning" + : err.target + ? "border-danger" + : "" + } > - - {availableSystems.map((system) => { - const systemBounds = getSystemTimeBounds(system.id); - const boundsStr = - systemBounds.ending < Model.END_OF_TIME - ? ` (${systemBounds.beginning}-${systemBounds.ending})` - : systemBounds.beginning > 0 - ? ` (${systemBounds.beginning}-∞)` - : ""; - return ( - - ); - })} + + + {systemTargets.map((target) => { + const boundsStr = + target.bounds.ending < Model.END_OF_TIME + ? ` (${target.bounds.beginning}-${target.bounds.ending})` + : target.bounds.beginning > 0 + ? ` (${target.bounds.beginning}-∞)` + : ""; + return ( + + ); + })} + + {scTargets.length > 0 && ( + + {scTargets.map((target) => { + const boundsStr = + target.bounds.ending < Model.END_OF_TIME + ? ` (${target.bounds.beginning}-${target.bounds.ending})` + : target.bounds.beginning > 0 + ? ` (${target.bounds.beginning}-∞)` + : ""; + return ( + + ); + })} + + )} - {inst.targetId && instSystemBounds && ( + {targetOption && ( - Available: {instSystemBounds.beginning}- - {instSystemBounds.ending >= Model.END_OF_TIME - ? "∞" - : instSystemBounds.ending} + + {targetOption.entityType === + EntityType.SystemComponent + ? "◇ " + : "🔲 "} + Available: {targetOption.bounds.beginning}- + {targetOption.bounds.ending >= Model.END_OF_TIME + ? "∞" + : targetOption.bounds.ending} + + + )} + {err.target && ( + + {err.target} + + )} + {err.overlap && ( + + {err.overlap} + + )} + + + updateRawInput(inst.id, "beginning", e.target.value) + } + isInvalid={!!err.beginning} + disabled={!inst.targetId} + /> + {err.beginning && ( + + {err.beginning} )} - - updateRawInput(inst.id, "beginning", e.target.value) - } - placeholder={String(instSystemBounds?.beginning ?? 0)} - className={ - raw.beginning === "" || beginningOutOfBounds - ? "border-danger" - : "" - } - isInvalid={beginningOutOfBounds} - /> - {beginningOutOfBounds && instSystemBounds && ( - - Min: {instSystemBounds.beginning} - - )} - - - updateRawInput(inst.id, "ending", e.target.value) - } - placeholder={ - instSystemBounds && - instSystemBounds.ending < Model.END_OF_TIME - ? String(instSystemBounds.ending) - : "∞ (optional)" - } - className={endingOutOfBounds ? "border-danger" : ""} - isInvalid={endingOutOfBounds} - /> - {endingOutOfBounds && instSystemBounds && ( - - Max:{" "} - {instSystemBounds.ending >= Model.END_OF_TIME - ? "∞" - : instSystemBounds.ending} - - )} - {!endingOutOfBounds && raw.ending.trim() === "" && ( - - Will inherit from system - - )} - - -
- -
- -
- - {availableSystems.length === 0 && !isFiltered && ( - - No systems available. Create a System first to install this - component. - +
+ + updateRawInput(inst.id, "ending", e.target.value) + } + isInvalid={!!err.ending} + placeholder="∞" + disabled={!inst.targetId} + /> + {err.ending && ( + + {err.ending} + + )} + + +
)} - {errors.length > 0 && ( - - Please fix the following: -
    - {errors.map((error, i) => ( -
  • {error}
  • - ))} -
-
- )} + - -
- {isFiltered - ? `${localInstallations.length} period${ - localInstallations.length !== 1 ? "s" : "" - } in this system` - : `${localInstallations.length} total installation${ - localInstallations.length !== 1 ? "s" : "" - }`} -
-
- - -
+ + + ); -}; - -export default EditSystemComponentInstallation; +} diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index 4c556b5..f8afeaf 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -48,12 +48,11 @@ function getOriginalId(ind: Individual): string { return ind.id; } -// Get the target ID (System for SystemComponent, SystemComponent for InstalledComponent) from an installation reference +// Get the target ID from an installation reference function getTargetId(ind: Individual): string | undefined { if (isInstallationRef(ind)) { const rest = ind.id.split("__installed_in__")[1]; if (rest) { - // Format: targetId__installationId or just targetId (old format) const parts = rest.split("__"); return parts[0]; } @@ -67,7 +66,7 @@ function getInstallationId(ind: Individual): string | undefined { const rest = ind.id.split("__installed_in__")[1]; if (rest) { const parts = rest.split("__"); - return parts[1]; // installationId is the second part + return parts[1]; } } return undefined; @@ -104,7 +103,7 @@ const SetActivity = (props: Props) => { const [errors, setErrors] = useState([]); const [dirty, setDirty] = useState(false); - // Custom activity-type selector state (search / create / inline edit) + // Custom activity-type selector state const [typeOpen, setTypeOpen] = useState(false); const [typeSearch, setTypeSearch] = useState(""); const [editingTypeId, setEditingTypeId] = useState(null); @@ -113,202 +112,88 @@ const SetActivity = (props: Props) => { const [showParentModal, setShowParentModal] = useState(false); const [selectedParentId, setSelectedParentId] = useState(null); - // Helper to get effective time bounds for a target (System or SystemComponent) + // Helper to get effective time bounds for a target const getTargetEffectiveTimeBounds = ( targetId: string ): { beginning: number; ending: number } => { - const target = dataset.individuals.get(targetId); - if (!target) { - return { beginning: 0, ending: Model.END_OF_TIME }; - } - - let beginning = target.beginning; - let ending = target.ending; - - const targetType = target.entityType ?? EntityType.Individual; - - // If target is a SystemComponent, get bounds from its installation into a System - if (targetType === EntityType.SystemComponent) { - if (target.installations && target.installations.length > 0) { - // Use the union of all installation periods - const instBeginnings = target.installations.map((inst) => - Math.max(0, inst.beginning ?? 0) - ); - const instEndings = target.installations.map( - (inst) => inst.ending ?? Model.END_OF_TIME - ); - const earliestBeginning = Math.min(...instBeginnings); - const latestEnding = Math.max(...instEndings); - - if (beginning < 0) { - beginning = earliestBeginning; - } - if (ending >= Model.END_OF_TIME && latestEnding < Model.END_OF_TIME) { - ending = latestEnding; - } - } else if (beginning < 0) { - beginning = 0; - } - } - - // If target is a System, use its defined bounds - if (targetType === EntityType.System) { - if (beginning < 0) beginning = 0; - } - - if (beginning < 0) beginning = 0; - - return { beginning, ending }; + return dataset.getTargetTimeBounds(targetId); }; - // Build the individuals list with proper labels for the Select component + // Build the individuals list using getDisplayIndividuals() which handles all nesting const individualsWithLabels = useMemo(() => { const result: IndividualOption[] = []; const addedIds = new Set(); - // Helper to create an option - const addOption = ( - ind: Individual, - overrideId?: string, - overrideName?: string, - installation?: { beginning: number; ending: number; id: string } - ) => { - const id = overrideId || ind.id; - if (addedIds.has(id)) return; - addedIds.add(id); - - let displayLabel = overrideName || ind.name; - - // Add timing info if it's an installation - if (installation) { - const endStr = - installation.ending >= Model.END_OF_TIME ? "∞" : installation.ending; - // If the name doesn't already contain timing info, add it - if (!displayLabel.includes(`(${installation.beginning}-`)) { - displayLabel += ` (${installation.beginning}-${endStr})`; - } - } - - result.push({ - ...ind, - id: id, - name: overrideName || ind.name, // Keep name clean for display in chip - displayLabel: displayLabel, // Full label for dropdown - beginning: installation ? installation.beginning : ind.beginning, - ending: installation ? installation.ending : ind.ending, - _installationId: installation ? installation.id : undefined, - }); - }; + // Get all display individuals from the Model (includes all virtual rows) + const displayIndividuals = dataset.getDisplayIndividuals(); - // Collect all entities by type - const systems: Individual[] = []; - const systemComponents: Individual[] = []; - const installedComponents: Individual[] = []; - const regularIndividuals: Individual[] = []; + displayIndividuals.forEach((ind) => { + // Skip if already added + if (addedIds.has(ind.id)) return; - dataset.individuals.forEach((ind) => { const entityType = ind.entityType ?? EntityType.Individual; - switch (entityType) { - case EntityType.System: - systems.push(ind); - break; - case EntityType.SystemComponent: - systemComponents.push(ind); - break; - case EntityType.InstalledComponent: - installedComponents.push(ind); - break; - default: - regularIndividuals.push(ind); - break; + + // Skip top-level SystemComponents/InstalledComponents that have installations + // (they will appear as virtual rows) + if (!ind._isVirtualRow) { + if (entityType === EntityType.SystemComponent) { + const original = dataset.individuals.get(ind.id); + if (original?.installations && original.installations.length > 0) { + return; + } + } + if (entityType === EntityType.InstalledComponent) { + const original = dataset.individuals.get(ind.id); + if (original?.installations && original.installations.length > 0) { + return; + } + } } - }); - // Sort each group - const sortByName = (a: Individual, b: Individual) => - a.name.localeCompare(b.name); - systems.sort(sortByName); - systemComponents.sort(sortByName); - installedComponents.sort(sortByName); - regularIndividuals.sort(sortByName); - - // 1. Systems and their nested hierarchy (matching Model.getDisplayIndividuals) - systems.forEach((system) => { - // Add System - addOption(system, undefined, `${system.name} (System)`); - - // Find SystemComponents installed in this System - systemComponents.forEach((sc) => { - const installations = sc.installations || []; - const installationsInSystem = installations.filter( - (inst) => inst.targetId === system.id - ); - - installationsInSystem.sort( - (a, b) => (a.beginning ?? 0) - (b.beginning ?? 0) - ); - - installationsInSystem.forEach((inst) => { - const virtualId = `${sc.id}__installed_in__${system.id}__${inst.id}`; - const label = `${system.name} → ${sc.name}`; - - addOption(sc, virtualId, label, { - beginning: inst.beginning ?? 0, - ending: inst.ending ?? Model.END_OF_TIME, - id: inst.id, - }); - - // Under this SystemComponent virtual row, add InstalledComponents - installedComponents.forEach((ic) => { - const icInstallations = ic.installations || []; - const installationsInSlot = icInstallations.filter( - (icInst) => icInst.targetId === sc.id - ); - - installationsInSlot.sort( - (a, b) => (a.beginning ?? 0) - (b.beginning ?? 0) - ); - - installationsInSlot.forEach((icInst) => { - // Check overlap with the SystemComponent's installation in the System - const scStart = inst.beginning ?? 0; - const scEnd = inst.ending ?? Model.END_OF_TIME; - const icStart = icInst.beginning ?? 0; - const icEnd = icInst.ending ?? Model.END_OF_TIME; - - if (icStart < scEnd && icEnd > scStart) { - // Use context suffix to allow same IC installation to appear under multiple SC occurrences - const contextSuffix = `__ctx_${inst.id}`; - const icVirtualId = `${ic.id}__installed_in__${sc.id}__${icInst.id}${contextSuffix}`; - const icLabel = `${system.name} → ${sc.name} → ${ic.name}`; - - addOption(ic, icVirtualId, icLabel, { - beginning: icInst.beginning ?? 0, - ending: icInst.ending ?? Model.END_OF_TIME, - id: icInst.id, - }); - } - }); - }); + // Build display label + let displayLabel = ind.name; + + if (ind._isVirtualRow && ind._parentPath) { + // Build hierarchical label from parent path + const pathParts = ind._parentPath.split("__").filter(Boolean); + const pathNames: string[] = []; + + pathParts.forEach((partId) => { + const part = dataset.individuals.get(partId); + if (part) { + pathNames.push(part.name); + } }); - }); - }); - // 2. DO NOT add top-level SystemComponents - they should only be selectable via their installations - // (Removed: systemComponents.forEach...) + // Get the original component name + const originalId = ind.id.split("__installed_in__")[0]; + const original = dataset.individuals.get(originalId); + const originalName = original?.name || ind.name; - // 3. DO NOT add top-level InstalledComponents - they should only be selectable via their installations - // (Removed: installedComponents.forEach...) + if (pathNames.length > 0) { + displayLabel = `${pathNames.join(" → ")} → ${originalName}`; + } else { + displayLabel = originalName; + } + + // Add timing info + const endStr = ind.ending >= Model.END_OF_TIME ? "∞" : ind.ending; + displayLabel += ` (${ind.beginning}-${endStr})`; + } else if (entityType === EntityType.System) { + displayLabel = `${ind.name} (System)`; + } - // 4. Regular Individuals - regularIndividuals.forEach((ind) => { - addOption(ind); + addedIds.add(ind.id); + result.push({ + ...ind, + displayLabel, + }); }); return result; }, [dataset]); - // Safe local ancestor check (walks partOf chain). Avoids depending on Model.isAncestor. + // Safe local ancestor check const isAncestorLocal = ( ancestorId: string, descendantId: string @@ -325,26 +210,21 @@ const SetActivity = (props: Props) => { d.individuals.forEach((individual) => { const entityType = individual.entityType ?? EntityType.Individual; - // Only update timing for regular Individuals that have participant-based timing enabled - // Do NOT update Systems, SystemComponents, or InstalledComponents - they manage their own timelines if ( entityType === EntityType.System || entityType === EntityType.SystemComponent || entityType === EntityType.InstalledComponent ) { - return; // Skip - these types have their own fixed timelines + return; } - // For regular Individuals, only update if they have the "begins/ends with participant" flags const earliestBeginning = d.earliestParticipantBeginning(individual.id); const latestEnding = d.lastParticipantEnding(individual.id); - // Only update beginning if the individual has beginsWithParticipant enabled if (individual.beginsWithParticipant && individual.beginning >= 0) { individual.beginning = earliestBeginning ? earliestBeginning : -1; } - // Only update ending if the individual has endsWithParticipant enabled if ( individual.endsWithParticipant && individual.ending < Model.END_OF_TIME @@ -417,7 +297,6 @@ const SetActivity = (props: Props) => { setActivityContext(inputs.id); }; - /* React only calls change handlers if the value has really changed. */ const updateInputs = (key: string, value: any) => { setInputs({ ...inputs, [key]: value }); setDirty(true); @@ -455,7 +334,6 @@ const SetActivity = (props: Props) => { selectedActivity.participations, ([key]) => key ); - // Find matching individuals from our labeled list const participatingIndividuals = individualsWithLabels.filter( (participant) => { return individualIds.includes(participant.id); @@ -466,22 +344,19 @@ const SetActivity = (props: Props) => { const validateInputs = () => { let runningErrors: string[] = []; - //Name + if (!inputs.name) { runningErrors.push("Name field is required"); } - //Type if (!inputs.type) { runningErrors.push("Type field is required"); } - //Ending and beginning if (inputs.ending - inputs.beginning <= 0) { runningErrors.push("Ending must be after beginning"); } if (inputs.ending >= Model.END_OF_TIME) { runningErrors.push("Ending cannot be greater than " + Model.END_OF_TIME); } - //Participant count if ( inputs.participations === undefined || inputs.participations?.size < 1 @@ -489,7 +364,6 @@ const SetActivity = (props: Props) => { runningErrors.push("Select at least one participant"); } - // Helper function to check if there's ANY overlap between two time ranges const hasOverlap = ( aStart: number, aEnd: number, @@ -499,7 +373,6 @@ const SetActivity = (props: Props) => { return aStart < iEnd && aEnd > iStart; }; - // Validate activity timing against installed component installation periods if (inputs.participations) { inputs.participations.forEach((participation, participantId) => { if (isInstallationRef({ id: participantId } as Individual)) { @@ -511,7 +384,6 @@ const SetActivity = (props: Props) => { const component = dataset.individuals.get(originalId); if (component?.installations) { - // Find the specific installation const installation = installationId ? component.installations.find( (inst) => inst.id === installationId @@ -524,7 +396,6 @@ const SetActivity = (props: Props) => { const installStart = installation.beginning ?? 0; const installEnd = installation.ending ?? Model.END_OF_TIME; - // Get target bounds to further constrain const targetBounds = getTargetEffectiveTimeBounds(targetId); const effectiveStart = Math.max( installStart, @@ -537,7 +408,6 @@ const SetActivity = (props: Props) => { : installEnd ); - // Only error if there's NO overlap at all if ( !hasOverlap( inputs.beginning, @@ -571,7 +441,7 @@ const SetActivity = (props: Props) => { } }; - // ----- New helper functions for custom activity-type selector ----- + // Activity type selector helpers const filteredTypes = dataset.activityTypes.filter((t) => t.name.toLowerCase().includes(typeSearch.toLowerCase()) ); @@ -601,7 +471,6 @@ const SetActivity = (props: Props) => { return d; }); - // Immediately select the newly created type for this form updateInputs("type", { id: newId, name, isCoreHqdm: false }); setTypeOpen(false); setTypeSearch(""); @@ -624,7 +493,6 @@ const SetActivity = (props: Props) => { const kind = d.activityTypes.find((x) => x.id === editingTypeId); if (kind) kind.name = newName; - // update activities that reference this type to use canonical Kind d.activities.forEach((a) => { if (a.type && a.type.id === editingTypeId) { const canonical = d.activityTypes.find((x) => x.id === editingTypeId); @@ -653,18 +521,15 @@ const SetActivity = (props: Props) => { setEditingTypeId(null); setEditingTypeValue(""); }; - // ----- end helpers ----- const handlePromote = () => { if (!inputs || !inputs.partOf) return; - // find current parent and then its parent (grandparent) - that's the new parent const currentParent = dataset.getParent(inputs.partOf as Id); const grandParent = currentParent ? dataset.getParent(currentParent.id) : undefined; const newParentId = grandParent ? grandParent.id : null; - // confirm if child outside of new parent's timeframe if (newParentId) { const parentAct = dataset.activities.get(newParentId); if ( @@ -690,7 +555,6 @@ const SetActivity = (props: Props) => { }; const openChangeParent = () => { - // prepare list in modal by setting default selection to current parent setSelectedParentId(inputs.partOf ? (inputs.partOf as string) : null); setShowParentModal(true); }; @@ -704,12 +568,12 @@ const SetActivity = (props: Props) => { setShowParentModal(false); }; - // Update eligibleParticipants to check target bounds via installations + // Filter eligible participants by activity time const eligibleParticipants = useMemo(() => { const actStart = inputs.beginning; const actEnd = inputs.ending; - const hasTimeOverlap = (ind: IndividualOption): boolean => { + return individualsWithLabels.filter((ind) => { let indStart = ind.beginning >= 0 ? ind.beginning : 0; let indEnd = ind.ending < Model.END_OF_TIME ? ind.ending : Infinity; @@ -727,17 +591,6 @@ const SetActivity = (props: Props) => { // Check overlap return actStart < indEnd && actEnd > indStart; - }; - - return individualsWithLabels.filter((ind) => { - if (!isInstallationRef(ind)) { - const entityType = ind.entityType ?? EntityType.Individual; - if (ind.beginning >= 0 && ind.ending < Model.END_OF_TIME) { - return hasTimeOverlap(ind); - } - return true; - } - return hasTimeOverlap(ind); }); }, [individualsWithLabels, inputs.beginning, inputs.ending, dataset]); @@ -942,7 +795,6 @@ const SetActivity = (props: Props) => { /> - {/* UPDATED: Use eligibleParticipants instead of individualsWithLabels */} Participants @@ -1060,10 +912,9 @@ const SetActivity = (props: Props) => { {Array.from(dataset.activities.values()) .filter((a) => { - // exclude self and descendants if (!inputs || !inputs.id) return false; if (a.id === inputs.id) return false; - if (isAncestorLocal(inputs.id, a.id)) return false; // avoid cycles + if (isAncestorLocal(inputs.id, a.id)) return false; return true; }) .map((a) => { diff --git a/editor-app/components/SetParticipation.tsx b/editor-app/components/SetParticipation.tsx index fc317c5..c572bee 100644 --- a/editor-app/components/SetParticipation.tsx +++ b/editor-app/components/SetParticipation.tsx @@ -48,11 +48,10 @@ const SetParticipation = (props: Props) => { const [editingRoleValue, setEditingRoleValue] = useState(""); const roleDropdownRef = useRef(null); - // Build available participants including virtual installation rows + // Build available participants including ALL virtual installation rows const availableParticipants = useMemo(() => { const result: Individual[] = []; const addedIds = new Set(); - const individuals = Array.from(dataset.individuals.values()); // Helper to add individual if not already added const addIfNew = (ind: Individual) => { @@ -62,54 +61,95 @@ const SetParticipation = (props: Props) => { } }; - // Add regular individuals (not InstalledComponents - those only appear as virtual rows) - individuals.forEach((ind) => { + // Get all display individuals (includes virtual rows for installations) + const displayIndividuals = dataset.getDisplayIndividuals(); + + displayIndividuals.forEach((ind) => { const entityType = ind.entityType ?? EntityType.Individual; - // Skip InstalledComponents - they participate via their virtual installation rows + // For virtual rows, always include them (they represent specific installation instances) + if (ind._isVirtualRow) { + addIfNew(ind); + return; + } + + // Skip top-level InstalledComponents that have installations if (entityType === EntityType.InstalledComponent) { + const hasInstallations = + ind.installations && ind.installations.length > 0; + if (hasInstallations) { + return; // Skip - will be shown as virtual rows + } + addIfNew(ind); return; } - // Add Systems, SystemComponents, and regular Individuals + // Skip top-level SystemComponents that have installations + if (entityType === EntityType.SystemComponent) { + const hasInstallations = + ind.installations && ind.installations.length > 0; + if (hasInstallations) { + return; // Skip - will be shown as virtual rows + } + addIfNew(ind); + return; + } + + // Systems and regular Individuals - always include addIfNew(ind); }); - // Add virtual installation rows for each InstalledComponent's installation periods - individuals.forEach((ind) => { - const entityType = ind.entityType ?? EntityType.Individual; + return result; + }, [dataset]); - if (entityType === EntityType.InstalledComponent && ind.installations) { - // Create a SEPARATE virtual row for EACH installation period - ind.installations.forEach((inst) => { - // Format: componentId__installed_in__slotId__installationId - const virtualId = `${ind.id}__installed_in__${inst.targetId}__${inst.id}`; + // Filter participants that overlap with activity time + const eligibleParticipants = useMemo(() => { + if (!selectedActivity) return availableParticipants; - const slot = dataset.individuals.get(inst.targetId); - const slotName = slot?.name || inst.targetId; - const endStr = - (inst.ending ?? Model.END_OF_TIME) >= Model.END_OF_TIME - ? "∞" - : inst.ending; - - const virtualIndividual: Individual = { - ...ind, - id: virtualId, - name: `${ind.name} in ${slotName} (${ - inst.beginning ?? 0 - }-${endStr})`, - beginning: inst.beginning ?? 0, - ending: inst.ending ?? Model.END_OF_TIME, - _installationId: inst.id, - }; - - addIfNew(virtualIndividual); - }); + const actStart = selectedActivity.beginning; + const actEnd = selectedActivity.ending; + + return availableParticipants.filter((ind) => { + // For virtual installation rows, use their specific time period + const indStart = ind.beginning >= 0 ? ind.beginning : 0; + const indEnd = + ind.ending < Model.END_OF_TIME ? ind.ending : Model.END_OF_TIME; + + // Check overlap + return actStart < indEnd && actEnd > indStart; + }); + }, [availableParticipants, selectedActivity]); + + // Group participants by type for display + const groupedParticipants = useMemo(() => { + const groups = { + systems: [] as Individual[], + systemComponents: [] as Individual[], + installedComponents: [] as Individual[], + individuals: [] as Individual[], + }; + + eligibleParticipants.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + + switch (entityType) { + case EntityType.System: + groups.systems.push(ind); + break; + case EntityType.SystemComponent: + groups.systemComponents.push(ind); + break; + case EntityType.InstalledComponent: + groups.installedComponents.push(ind); + break; + default: + groups.individuals.push(ind); + break; } }); - return result; - }, [dataset]); + return groups; + }, [eligibleParticipants]); // click outside to close role dropdown useEffect(() => { @@ -270,30 +310,26 @@ const SetParticipation = (props: Props) => { // Helper to get display name for an individual (including virtual rows) const getDisplayName = (ind: Individual): string => { - // Check if this is an installation reference - if (ind.id.includes("__installed_in__")) { + // Check if this is an installation reference (virtual row) + if (ind._isVirtualRow && ind.id.includes("__installed_in__")) { const originalId = ind.id.split("__installed_in__")[0]; - const rest = ind.id.split("__installed_in__")[1]; - const parts = rest.split("__"); - const targetId = parts[0]; - const installationId = parts[1]; - const originalComponent = dataset.individuals.get(originalId); - const target = dataset.individuals.get(targetId); - if (originalComponent && target) { - // Find the installation to show its time period - const installation = originalComponent.installations?.find( - (inst) => inst.id === installationId - ); - if (installation) { - const endStr = - (installation.ending ?? Model.END_OF_TIME) >= Model.END_OF_TIME - ? "∞" - : installation.ending; - return `${originalComponent.name} in ${target.name} (${installation.beginning}-${endStr})`; - } - return `${originalComponent.name} in ${target.name}`; + if (originalComponent) { + // Use the virtual row's already-computed bounds + const endStr = + ind.ending >= Model.END_OF_TIME ? "∞" : String(ind.ending); + const startStr = String(ind.beginning); + + // Get parent info from _parentPath + const parentPath = ind._parentPath || ""; + const pathParts = parentPath.split("__"); + const systemId = pathParts[0]; + const system = dataset.individuals.get(systemId); + + return `${originalComponent.name} in ${ + system?.name || "Unknown" + } (${startStr}-${endStr})`; } } return ind.name; @@ -338,27 +374,6 @@ const SetParticipation = (props: Props) => { return ind?.name || participantId; }; - // Helper to check if participant overlaps with activity time - const hasTimeOverlap = (ind: Individual): boolean => { - if (!selectedActivity) return true; - - const actStart = selectedActivity.beginning; - const actEnd = selectedActivity.ending; - - // For virtual installation rows, use their specific time period - const indStart = ind.beginning >= 0 ? ind.beginning : 0; - const indEnd = - ind.ending < Model.END_OF_TIME ? ind.ending : Model.END_OF_TIME; - - // Check overlap - return actStart < indEnd && actEnd > indStart; - }; - - // Filter participants that overlap with activity time - const eligibleParticipants = useMemo(() => { - return availableParticipants.filter(hasTimeOverlap); - }, [availableParticipants, selectedActivity]); - return ( <> diff --git a/editor-app/diagram/DrawActivityDiagram.ts b/editor-app/diagram/DrawActivityDiagram.ts index 73797da..3aa39ff 100644 --- a/editor-app/diagram/DrawActivityDiagram.ts +++ b/editor-app/diagram/DrawActivityDiagram.ts @@ -34,6 +34,45 @@ export interface Plot { height: number; } +// Helper to get all ancestor IDs from a virtual row ID (handles nested SCs) +function getAncestorIds(id: string, dataset: Model): Set { + const ancestors = new Set(); + + if (id.includes("__installed_in__")) { + const parts = id.split("__installed_in__"); + const originalId = parts[0]; + + // Add the original component ID + ancestors.add(originalId); + + // Parse through the chain of __installed_in__ to find all targets + let remaining = parts.slice(1).join("__installed_in__"); + while (remaining) { + const targetId = remaining.split("__")[0]; + ancestors.add(targetId); + + // Get the target's parents too + const target = dataset.individuals.get(targetId); + if (target && target.installations) { + target.installations.forEach((inst) => { + if (inst.targetId) { + ancestors.add(inst.targetId); + } + }); + } + + // Move to next level if exists + const nextInstalled = remaining.indexOf("__installed_in__"); + if (nextInstalled === -1) break; + remaining = remaining.substring( + nextInstalled + "__installed_in__".length + ); + } + } + + return ancestors; +} + // Helper to get parent IDs that should be kept visible when filtering (bottom-to-top only) // When a CHILD has activity, keep its PARENTS visible // But NOT the reverse - if only parent has activity, don't automatically keep children @@ -46,26 +85,9 @@ function getParentIdsToKeep( participatingIds.forEach((id) => { // Check if this is a virtual row (installation reference) if (id.includes("__installed_in__")) { - const parts = id.split("__installed_in__"); - const originalId = parts[0]; - const rest = parts[1]; - const targetId = rest.split("__")[0]; - - // Virtual row has activity - keep its target (parent) visible - parentsToKeep.add(targetId); - - // If target is a SystemComponent, also keep its parent System - const target = dataset.individuals.get(targetId); - if (target) { - const targetType = target.entityType ?? EntityType.Individual; - if (targetType === EntityType.SystemComponent && target.installations) { - target.installations.forEach((inst) => { - if (inst.targetId) { - parentsToKeep.add(inst.targetId); - } - }); - } - } + // Get all ancestors from the virtual row ID + const ancestors = getAncestorIds(id, dataset); + ancestors.forEach((ancestorId) => parentsToKeep.add(ancestorId)); } else { // Regular individual - check if it's an InstalledComponent or SystemComponent const individual = dataset.individuals.get(id); @@ -73,30 +95,67 @@ function getParentIdsToKeep( const entityType = individual.entityType ?? EntityType.Individual; if (entityType === EntityType.InstalledComponent) { - // InstalledComponent has activity - keep parent SystemComponents and their parent Systems + // InstalledComponent has activity - keep parent SystemComponents and their ancestors if (individual.installations) { individual.installations.forEach((inst) => { if (inst.targetId) { parentsToKeep.add(inst.targetId); - // Get the SystemComponent's parent System - const sc = dataset.individuals.get(inst.targetId); - if (sc && sc.installations) { - sc.installations.forEach((scInst) => { - if (scInst.targetId) { - parentsToKeep.add(scInst.targetId); - } - }); - } + // Recursively get the parent's parents + const addParentChain = (targetId: string) => { + const target = dataset.individuals.get(targetId); + if (target && target.installations) { + target.installations.forEach((parentInst) => { + if (parentInst.targetId) { + parentsToKeep.add(parentInst.targetId); + // Continue up the chain for nested SystemComponents + const parentTarget = dataset.individuals.get( + parentInst.targetId + ); + if (parentTarget) { + const parentType = + parentTarget.entityType ?? EntityType.Individual; + if (parentType === EntityType.SystemComponent) { + addParentChain(parentInst.targetId); + } + } + } + }); + } + }; + addParentChain(inst.targetId); } }); } } else if (entityType === EntityType.SystemComponent) { - // SystemComponent has activity - keep parent Systems + // SystemComponent has activity - keep parent Systems and parent SystemComponents if (individual.installations) { individual.installations.forEach((inst) => { if (inst.targetId) { parentsToKeep.add(inst.targetId); + + // If parent is also a SystemComponent, get its parents too + const addParentChain = (targetId: string) => { + const target = dataset.individuals.get(targetId); + if (target && target.installations) { + target.installations.forEach((parentInst) => { + if (parentInst.targetId) { + parentsToKeep.add(parentInst.targetId); + const parentTarget = dataset.individuals.get( + parentInst.targetId + ); + if (parentTarget) { + const parentType = + parentTarget.entityType ?? EntityType.Individual; + if (parentType === EntityType.SystemComponent) { + addParentChain(parentInst.targetId); + } + } + } + }); + } + }; + addParentChain(inst.targetId); } }); } @@ -184,6 +243,17 @@ export function drawActivityDiagram( // Or if this specific virtual row ID directly participates if (participating.has(i.id)) return true; + + // Check if this virtual row is an ancestor of a participating entity + // This handles nested SystemComponent visibility + for (const pId of Array.from(participating)) { + if ( + pId.includes(i.id) || + pId.includes(`__installed_in__${originalId}__`) + ) { + return true; + } + } } return false; diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index c83d0b0..a1f077e 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -603,6 +603,9 @@ export function labelIndividuals(ctx: DrawContext) { config.layout.individual.height / 2 + config.labels.individual.topMargin; + // Update the indent calculation for deeper nesting + const INDENT_PER_LEVEL = 14; // pixels per nesting level + // Draw icons with indentation to show hierarchy svgElement .selectAll(".individualIcon") @@ -611,8 +614,8 @@ export function labelIndividuals(ctx: DrawContext) { .attr("class", "individualIcon") .attr("id", (i: Individual) => `ii${i.id}`) .attr("x", (i: Individual) => { - const nestingLevel = getNestingLevel(i); - const indent = nestingLevel * 20; // Indent icons + const nestingLevel = i._nestingLevel ?? getNestingLevel(i); + const indent = nestingLevel * INDENT_PER_LEVEL; return ( config.layout.individual.xMargin + config.labels.individual.leftMargin + diff --git a/editor-app/diagram/DrawParticipations.ts b/editor-app/diagram/DrawParticipations.ts index cfe3373..0dd64b5 100644 --- a/editor-app/diagram/DrawParticipations.ts +++ b/editor-app/diagram/DrawParticipations.ts @@ -10,23 +10,41 @@ function isInstallationRefId(id: string): boolean { return id.includes("__installed_in__"); } -// Updated to handle format: componentId__installed_in__targetId__installationId +// Updated to handle nested format: componentId__installed_in__targetId__installationId__ctx_scInstId function getOriginalAndTarget( id: string -): { originalId: string; targetId: string; installationId?: string } | null { +): { + originalId: string; + targetId: string; + installationId?: string; + contextId?: string; +} | null { if (!isInstallationRefId(id)) return null; + const parts = id.split("__installed_in__"); - if (parts.length !== 2) return null; + if (parts.length < 2) return null; const originalId = parts[0]; - const rest = parts[1]; + let rest = parts.slice(1).join("__installed_in__"); // Handle nested __installed_in__ + + // Parse the rest which could be: + // - targetId (old format) + // - targetId__installationId (new format) + // - targetId__installationId__ctx_scInstId (with context) + + // First, extract context if present + let contextId: string | undefined; + const ctxIndex = rest.indexOf("__ctx_"); + if (ctxIndex !== -1) { + contextId = rest.substring(ctxIndex + 6); // After "__ctx_" + rest = rest.substring(0, ctxIndex); + } - // rest could be "targetId" (old format) or "targetId__installationId" (new format) const restParts = rest.split("__"); const targetId = restParts[0]; const installationId = restParts.length > 1 ? restParts[1] : undefined; - return { originalId, targetId, installationId }; + return { originalId, targetId, installationId, contextId }; } // Add helper to get target effective time bounds diff --git a/editor-app/lib/Model.ts b/editor-app/lib/Model.ts index 5397ec6..9d4cd65 100644 --- a/editor-app/lib/Model.ts +++ b/editor-app/lib/Model.ts @@ -633,195 +633,272 @@ export class Model { } /** - * Get the list of individuals to display in the diagram. + * Check for circular references in installation hierarchy + * Returns true if installing componentId into potentialTargetId would create a cycle + * + * Note: This only prevents direct self-installation. + * Cross-system nesting (e.g., SC1→SC2 in System A, SC2→SC1 in System B) is allowed + * because they are different installation contexts. */ - // ...existing code up to getDisplayIndividuals... + wouldCreateCircularReference( + componentId: string, + potentialTargetId: string + ): boolean { + // Can't install into self + return componentId === potentialTargetId; + } /** - * Get the list of individuals to display in the diagram. + * Get the effective time bounds for an installation target + * Works for both Systems and SystemComponents */ - getDisplayIndividuals(): Individual[] { - const result: Individual[] = []; - const addedVirtualIds = new Set(); + getTargetTimeBounds(targetId: string): { beginning: number; ending: number } { + const target = this.individuals.get(targetId); + if (!target) { + return { beginning: 0, ending: Model.END_OF_TIME }; + } - // Collect all entities by type - const systems: Individual[] = []; - const systemComponents: Individual[] = []; - const installedComponents: Individual[] = []; - const regularIndividuals: Individual[] = []; + const targetType = target.entityType ?? EntityType.Individual; + let beginning = target.beginning >= 0 ? target.beginning : 0; + let ending = target.ending; - this.individuals.forEach((ind) => { - const entityType = ind.entityType ?? EntityType.Individual; - switch (entityType) { - case EntityType.System: - systems.push(ind); - break; - case EntityType.SystemComponent: - systemComponents.push(ind); - break; - case EntityType.InstalledComponent: - installedComponents.push(ind); - break; - default: - regularIndividuals.push(ind); - break; + // If target is a SystemComponent, get bounds from ITS installations + if (targetType === EntityType.SystemComponent) { + if (target.installations && target.installations.length > 0) { + const instBeginnings = target.installations.map((inst) => + Math.max(0, inst.beginning ?? 0) + ); + const instEndings = target.installations.map( + (inst) => inst.ending ?? Model.END_OF_TIME + ); + beginning = Math.min(...instBeginnings); + ending = Math.max(...instEndings); } - }); + } - // Sort each group alphabetically - systems.sort((a, b) => a.name.localeCompare(b.name)); - systemComponents.sort((a, b) => a.name.localeCompare(b.name)); - installedComponents.sort((a, b) => a.name.localeCompare(b.name)); - regularIndividuals.sort((a, b) => a.name.localeCompare(b.name)); + return { beginning, ending }; + } - // 1. Add Systems with their nested VIRTUAL rows - systems.forEach((system) => { - // Add the System itself (top level) - result.push({ - ...system, - _nestingLevel: 0, - _isVirtualRow: false, - }); + /** + * Get the list of individuals to display in the diagram. + * Handles nested SystemComponent → SystemComponent → System hierarchies. + */ + getDisplayIndividuals(): Individual[] { + const result: Individual[] = []; + const processedIds = new Set(); + + // Helper to recursively add installations with proper context + const addInstallationsRecursively = ( + target: Individual, + parentPath: string, + parentBounds: { beginning: number; ending: number }, + nestingLevel: number, + parentInstallationId?: string + ) => { + const targetEntityType = target.entityType ?? EntityType.Individual; + + this.individuals.forEach((component) => { + const entityType = component.entityType ?? EntityType.Individual; + if ( + entityType !== EntityType.SystemComponent && + entityType !== EntityType.InstalledComponent + ) { + return; + } - // Find SystemComponents installed in this System and create VIRTUAL rows - systemComponents.forEach((sc) => { - const scInstallations = sc.installations || []; - const installationsInThisSystem = scInstallations.filter( - (inst) => inst.targetId === system.id - ); + if (!component.installations || component.installations.length === 0) { + return; + } - // Sort installations by time - installationsInThisSystem.sort( - (a, b) => (a.beginning ?? 0) - (b.beginning ?? 0) - ); + component.installations.forEach((installation) => { + if (installation.targetId !== target.id) return; - // Create a VIRTUAL row for each installation period of this SystemComponent in this System - installationsInThisSystem.forEach((scInst) => { - const scVirtualId = `${sc.id}__installed_in__${system.id}__${scInst.id}`; - if (addedVirtualIds.has(scVirtualId)) return; - addedVirtualIds.add(scVirtualId); - - const scStart = scInst.beginning ?? 0; - const scEnd = scInst.ending ?? Model.END_OF_TIME; - - // Create virtual row for SystemComponent in System (nested level 1) - const scVirtualRow: Individual = { - ...sc, - id: scVirtualId, - name: sc.name, - beginning: scStart, - ending: scEnd, - _installationId: scInst.id, - _nestingLevel: 1, - _isVirtualRow: true, - }; - result.push(scVirtualRow); + // If target is a SystemComponent (nested), check context matches + if ( + targetEntityType === EntityType.SystemComponent && + parentInstallationId + ) { + if (installation.scInstallationContextId) { + if ( + installation.scInstallationContextId !== parentInstallationId + ) { + return; + } + } + } - // Under this SystemComponent virtual row, add InstalledComponent VIRTUAL rows - installedComponents.forEach((ic) => { - const icInstallations = ic.installations || []; + const instStart = installation.beginning ?? 0; + const instEnd = installation.ending ?? Model.END_OF_TIME; - // Filter to only installations targeting THIS SystemComponent - const installationsInThisSC = icInstallations.filter( - (icInst) => icInst.targetId === sc.id - ); + const effectiveStart = Math.max(instStart, parentBounds.beginning); + const effectiveEnd = Math.min( + instEnd, + parentBounds.ending < Model.END_OF_TIME + ? parentBounds.ending + : instEnd + ); - installationsInThisSC.forEach((icInst) => { - // STRICT CHECK: Only show this IC under this System if: - // 1. scInstallationContextId matches this SC installation, OR - // 2. systemContextId matches this System, OR - // 3. NEITHER is set AND this is the ONLY system where this SC is installed - - const hasScInstContext = !!icInst.scInstallationContextId; - const hasSystemContext = !!icInst.systemContextId; - - if (hasScInstContext) { - // If scInstallationContextId is set, it MUST match this SC installation - if (icInst.scInstallationContextId !== scInst.id) { - return; // Skip - this IC is for a different SC installation - } - } else if (hasSystemContext) { - // If systemContextId is set (but not scInstallationContextId), it MUST match this System - if (icInst.systemContextId !== system.id) { - return; // Skip - this IC is for a different System - } - } else { - // Neither context is set - this is a legacy installation - // Only show it if this SC is installed in ONLY ONE system - // (to avoid duplicating across all systems) - const allScInstallations = sc.installations || []; - const uniqueSystemIds = new Set( - allScInstallations.map((inst) => inst.targetId) - ); - - if (uniqueSystemIds.size > 1) { - // SC is in multiple systems, and IC has no context - // Don't show it anywhere to avoid confusion - // User needs to edit the IC and set the context - return; - } - } + if (effectiveStart >= effectiveEnd) return; - const icStart = icInst.beginning ?? 0; - const icEnd = icInst.ending ?? Model.END_OF_TIME; + const virtualId = `${component.id}__installed_in__${target.id}__${installation.id}`; - // Check temporal overlap - const hasOverlap = icStart < scEnd && icEnd > scStart; + if (processedIds.has(virtualId)) return; + processedIds.add(virtualId); - if (hasOverlap) { - const icVirtualId = `${ic.id}__installed_in__${sc.id}__${icInst.id}__ctx_${scInst.id}`; + const newPath = parentPath + ? `${parentPath}__${target.id}` + : target.id; - if (addedVirtualIds.has(icVirtualId)) return; - addedVirtualIds.add(icVirtualId); + const virtualRow: Individual = { + ...component, + id: virtualId, + beginning: effectiveStart, + ending: effectiveEnd, + _isVirtualRow: true, + _parentPath: newPath, + _nestingLevel: nestingLevel, + _installationId: installation.id, + }; - const displayStart = Math.max(icStart, scStart); - const displayEnd = Math.min(icEnd, scEnd); + result.push(virtualRow); - const icVirtualRow: Individual = { - ...ic, - id: icVirtualId, - name: ic.name, - beginning: displayStart, - ending: displayEnd, - _installationId: icInst.id, - _nestingLevel: 2, - _isVirtualRow: true, - }; - result.push(icVirtualRow); - } - }); - }); + // If this component is a SystemComponent, recursively add things installed in IT + if (entityType === EntityType.SystemComponent) { + addInstallationsRecursively( + component, + newPath, + { beginning: effectiveStart, ending: effectiveEnd }, + nestingLevel + 1, + installation.id + ); + } }); }); + }; + + // STEP 1: Add all Systems and their nested components IN ORDER + // This ensures virtual rows appear immediately after their parent system + const systems: Individual[] = []; + this.individuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + if (entityType === EntityType.System) { + systems.push(ind); + } + }); + + // Sort systems by name + systems.sort((a, b) => a.name.localeCompare(b.name)); + + // Add each system and IMMEDIATELY add its nested virtual rows + systems.forEach((system) => { + if (!processedIds.has(system.id)) { + processedIds.add(system.id); + result.push({ + ...system, + _isVirtualRow: false, + _parentPath: "", + _nestingLevel: 0, + }); + + // Add virtual rows for this system IMMEDIATELY after the system + const bounds = this.getTargetTimeBounds(system.id); + addInstallationsRecursively(system, "", bounds, 1, undefined); + } }); - // 2. Add SystemComponents - actual entities (top level, NOT nested) - systemComponents.forEach((sc) => { + // STEP 2: Add ALL SystemComponents at top level (those not yet added as virtual rows) + const topLevelSCs: Individual[] = []; + this.individuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + if (entityType === EntityType.SystemComponent) { + if (!processedIds.has(ind.id)) { + topLevelSCs.push(ind); + } + } + }); + topLevelSCs.sort((a, b) => a.name.localeCompare(b.name)); + topLevelSCs.forEach((ind) => { + processedIds.add(ind.id); result.push({ - ...sc, - _nestingLevel: 0, + ...ind, _isVirtualRow: false, + _parentPath: "", + _nestingLevel: 0, }); }); - // 3. Add InstalledComponents - actual entities (top level, NOT nested) - installedComponents.forEach((ic) => { + // STEP 3: Add ALL InstalledComponents at top level + const topLevelICs: Individual[] = []; + this.individuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + if (entityType === EntityType.InstalledComponent) { + if (!processedIds.has(ind.id)) { + topLevelICs.push(ind); + } + } + }); + topLevelICs.sort((a, b) => a.name.localeCompare(b.name)); + topLevelICs.forEach((ind) => { + processedIds.add(ind.id); result.push({ - ...ic, - _nestingLevel: 0, + ...ind, _isVirtualRow: false, + _parentPath: "", + _nestingLevel: 0, }); }); - // 4. Add regular Individuals (top level) + // STEP 4: Add regular Individuals + const regularIndividuals: Individual[] = []; + this.individuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + if (entityType === EntityType.Individual) { + if (!processedIds.has(ind.id)) { + regularIndividuals.push(ind); + } + } + }); + regularIndividuals.sort((a, b) => a.name.localeCompare(b.name)); regularIndividuals.forEach((ind) => { + processedIds.add(ind.id); result.push({ ...ind, - _nestingLevel: 0, _isVirtualRow: false, + _parentPath: "", + _nestingLevel: 0, }); }); + // NO SORTING NEEDED - items are added in correct order: + // 1. Sys 1 (System) + // - Sys comp 1 (virtual row under Sys 1) + // - Inst comp 1 (virtual row under Sys comp 1 under Sys 1) + // 2. Sys 2 (System) + // - Sys comp 2 (virtual row under Sys 2) + // 3. Sys comp 1 (top-level definition) + // 4. Sys comp 2 (top-level definition) + // 5. Inst comp 1 (top-level definition) + // 6. Inst comp 2 (top-level definition) + // 7. Egg, Me, Pan... (regular Individuals) + return result; } + + // Helper to extract installation ID from virtual row ID + extractInstallationIdFromVirtualId(virtualId: string): string | undefined { + if (!virtualId.includes("__installed_in__")) return undefined; + const parts = virtualId.split("__installed_in__"); + if (parts.length < 2) return undefined; + let rest = parts[1]; + + // Remove context suffix if present + const ctxIndex = rest.indexOf("__ctx_"); + if (ctxIndex !== -1) { + rest = rest.substring(0, ctxIndex); + } + + const restParts = rest.split("__"); + // Format: targetId__installationId + return restParts.length > 1 ? restParts[1] : undefined; + } } diff --git a/editor-app/lib/Schema.ts b/editor-app/lib/Schema.ts index 960b38c..54aebea 100644 --- a/editor-app/lib/Schema.ts +++ b/editor-app/lib/Schema.ts @@ -74,6 +74,7 @@ export interface Individual { endsWithParticipant?: boolean; _nestingLevel?: number; _isVirtualRow?: boolean; + _parentPath?: string; } // Note: beginning/ending are inherited from STExtent From 45044f44f62e8a8eed2e26e2498be5f1bdc5fad2 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sun, 30 Nov 2025 17:59:09 +0000 Subject: [PATCH 57/81] feat: Update UI components for improved layout and accessibility --- .../components/EditInstalledComponent.tsx | 2 +- .../EditSystemComponentInstallation.tsx | 12 +- editor-app/components/NavBar.tsx | 3 +- editor-app/components/SetActivity.tsx | 154 +++++++++++++++--- editor-app/diagram/DrawIndividuals.ts | 39 +++-- editor-app/pages/_app.tsx | 1 + 6 files changed, 159 insertions(+), 52 deletions(-) diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index 8909e1f..4a778f9 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -698,7 +698,7 @@ const EditInstalledComponent = (props: Props) => { {Array.from(slotsBySystem.entries()).map( ([sysName, slots]) => ( - + {slots.map((slot) => { const indent = " ".repeat( slot.nestingLevel - 1 diff --git a/editor-app/components/EditSystemComponentInstallation.tsx b/editor-app/components/EditSystemComponentInstallation.tsx index 8e5cde1..f56d7f3 100644 --- a/editor-app/components/EditSystemComponentInstallation.tsx +++ b/editor-app/components/EditSystemComponentInstallation.tsx @@ -510,9 +510,7 @@ export default function EditSystemComponentInstallation({ return ( - - Edit Installations for {individual.name} - + Edit Installations for {individual.name}

@@ -561,7 +559,7 @@ export default function EditSystemComponentInstallation({ } > - + {systemTargets.map((target) => { const boundsStr = target.bounds.ending < Model.END_OF_TIME @@ -581,7 +579,7 @@ export default function EditSystemComponentInstallation({ })} {scTargets.length > 0 && ( - + {scTargets.map((target) => { const boundsStr = target.bounds.ending < Model.END_OF_TIME @@ -607,8 +605,8 @@ export default function EditSystemComponentInstallation({ {targetOption.entityType === EntityType.SystemComponent - ? "◇ " - : "🔲 "} + ? " " + : " "} Available: {targetOption.bounds.beginning}- {targetOption.bounds.ending >= Model.END_OF_TIME ? "∞" diff --git a/editor-app/components/NavBar.tsx b/editor-app/components/NavBar.tsx index 783d695..4ecc855 100644 --- a/editor-app/components/NavBar.tsx +++ b/editor-app/components/NavBar.tsx @@ -42,13 +42,12 @@ function CollapsibleExample() { collapseOnSelect expand="lg" variant="light" - sticky="top" + fixed="top" style={{ backgroundColor: "rgba(255, 255, 255, 0.98)", backdropFilter: "blur(10px)", boxShadow: "0 2px 8px rgba(0, 0, 0, 0.08)", zIndex: 1030, - flexShrink: 0, }} > diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index f8afeaf..7e717e7 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -12,7 +12,7 @@ import Form from "react-bootstrap/Form"; import ListGroup from "react-bootstrap/ListGroup"; import Alert from "react-bootstrap/Alert"; import { v4 as uuidv4 } from "uuid"; -import Select, { MultiValue } from "react-select"; +import Select, { MultiValue, GroupBase } from "react-select"; import { Individual, Id, @@ -75,6 +75,12 @@ function getInstallationId(ind: Individual): string | undefined { // Define the option type for the Select component type IndividualOption = Individual & { displayLabel: string }; +// Group type for react-select +interface OptionGroup extends GroupBase { + label: string; + options: IndividualOption[]; +} + const SetActivity = (props: Props) => { const { show, @@ -193,6 +199,92 @@ const SetActivity = (props: Props) => { return result; }, [dataset]); + // Build grouped options for react-select + const groupedOptions = useMemo(() => { + const groups: OptionGroup[] = []; + + // Get display individuals to maintain proper order + const displayIndividuals = dataset.getDisplayIndividuals(); + + // Track which systems have been processed + const systemGroups: Map = new Map(); + const topLevelSCs: IndividualOption[] = []; + const topLevelICs: IndividualOption[] = []; + const regularIndividuals: IndividualOption[] = []; + + displayIndividuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + + // Find the matching option from individualsWithLabels + const option = individualsWithLabels.find((o) => o.id === ind.id); + if (!option) return; + + if (entityType === EntityType.System && !ind._isVirtualRow) { + // System - create a new group + systemGroups.set(ind.id, [option]); + } else if (ind._isVirtualRow && ind._parentPath) { + // Virtual row (nested component) - add to parent system's group + const rootSystemId = ind._parentPath.split("__")[0]; + const systemGroup = systemGroups.get(rootSystemId); + if (systemGroup) { + systemGroup.push(option); + } + } else if ( + entityType === EntityType.SystemComponent && + !ind._isVirtualRow + ) { + // Top-level SC (not installed anywhere) + topLevelSCs.push(option); + } else if ( + entityType === EntityType.InstalledComponent && + !ind._isVirtualRow + ) { + // Top-level IC (not installed anywhere) + topLevelICs.push(option); + } else if (entityType === EntityType.Individual) { + // Regular individual + regularIndividuals.push(option); + } + }); + + // Add system groups + systemGroups.forEach((options, systemId) => { + const system = dataset.individuals.get(systemId); + if (system && options.length > 0) { + groups.push({ + label: `${system.name} (System)`, + options: options, + }); + } + }); + + // Add top-level SCs if any + if (topLevelSCs.length > 0) { + groups.push({ + label: "◇ System Components (Uninstalled)", + options: topLevelSCs, + }); + } + + // Add top-level ICs if any + if (topLevelICs.length > 0) { + groups.push({ + label: "● Installed Components (Uninstalled)", + options: topLevelICs, + }); + } + + // Add regular individuals if any + if (regularIndividuals.length > 0) { + groups.push({ + label: "○ Individuals", + options: regularIndividuals, + }); + } + + return groups; + }, [individualsWithLabels, dataset]); + // Safe local ancestor check const isAncestorLocal = ( ancestorId: string, @@ -568,31 +660,37 @@ const SetActivity = (props: Props) => { setShowParentModal(false); }; - // Filter eligible participants by activity time - const eligibleParticipants = useMemo(() => { + // Filter eligible participants by activity time - now returns grouped options + const eligibleGroupedOptions = useMemo(() => { const actStart = inputs.beginning; const actEnd = inputs.ending; - return individualsWithLabels.filter((ind) => { - let indStart = ind.beginning >= 0 ? ind.beginning : 0; - let indEnd = ind.ending < Model.END_OF_TIME ? ind.ending : Infinity; - - // For installation references, also constrain to target bounds - if (isInstallationRef(ind)) { - const targetId = getTargetId(ind); - if (targetId) { - const targetBounds = getTargetEffectiveTimeBounds(targetId); - indStart = Math.max(indStart, targetBounds.beginning); - if (targetBounds.ending < Model.END_OF_TIME) { - indEnd = Math.min(indEnd, targetBounds.ending); + // Filter each group's options based on time overlap + return groupedOptions + .map((group) => ({ + ...group, + options: group.options.filter((ind) => { + let indStart = ind.beginning >= 0 ? ind.beginning : 0; + let indEnd = ind.ending < Model.END_OF_TIME ? ind.ending : Infinity; + + // For installation references, also constrain to target bounds + if (isInstallationRef(ind)) { + const targetId = getTargetId(ind); + if (targetId) { + const targetBounds = getTargetEffectiveTimeBounds(targetId); + indStart = Math.max(indStart, targetBounds.beginning); + if (targetBounds.ending < Model.END_OF_TIME) { + indEnd = Math.min(indEnd, targetBounds.ending); + } + } } - } - } - // Check overlap - return actStart < indEnd && actEnd > indStart; - }); - }, [individualsWithLabels, inputs.beginning, inputs.ending, dataset]); + // Check overlap + return actStart < indEnd && actEnd > indStart; + }), + })) + .filter((group) => group.options.length > 0); // Remove empty groups + }, [groupedOptions, inputs.beginning, inputs.ending, dataset]); return ( <> @@ -798,23 +896,31 @@ const SetActivity = (props: Props) => { Participants - {eligibleParticipants.length < individualsWithLabels.length && ( + {eligibleGroupedOptions.reduce( + (sum, g) => sum + g.options.length, + 0 + ) < individualsWithLabels.length && ( (filtered by activity time {inputs.beginning}- {inputs.ending}) )} - + defaultValue={getSelectedIndividuals()} isMulti - options={eligibleParticipants} + options={eligibleGroupedOptions} getOptionLabel={(option) => option.displayLabel} getOptionValue={(option) => option.id} onChange={handleChangeMultiselect} noOptionsMessage={() => "No participants available for this time range" } + formatGroupLabel={(group) => ( +

+ {group.label} +
+ )} />
diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index a1f077e..27012f0 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -606,6 +606,16 @@ export function labelIndividuals(ctx: DrawContext) { // Update the indent calculation for deeper nesting const INDENT_PER_LEVEL = 14; // pixels per nesting level + // Helper to get font size based on nesting level + const getFontSize = (ind: Individual): string => { + const nestingLevel = ind._nestingLevel ?? getNestingLevel(ind); + if (nestingLevel > 0) { + // For nested entities, use smaller font (half size) + return "0.65em"; + } + return config.labels.individual.fontSize; + }; + // Draw icons with indentation to show hierarchy svgElement .selectAll(".individualIcon") @@ -633,7 +643,7 @@ export function labelIndividuals(ctx: DrawContext) { }) .attr("text-anchor", "start") .attr("font-family", "Arial, sans-serif") - .attr("font-size", config.labels.individual.fontSize) + .attr("font-size", (d: Individual) => getFontSize(d)) .attr("fill", "#374151") .text((d: Individual) => getEntityIcon(d, dataset)); // Pass dataset to get correct icon @@ -661,27 +671,20 @@ export function labelIndividuals(ctx: DrawContext) { }) .attr("text-anchor", "start") .attr("font-family", "Roboto, Arial, sans-serif") - .attr("font-size", config.labels.individual.fontSize) + .attr("font-size", (d: Individual) => getFontSize(d)) .attr("fill", "#111827") .text((d: Individual) => { - // Truncate labels for virtual SystemComponent or virtual InstalledComponent to 6 chars + "..." let label = d.name ?? ""; - if (isVirtualRow(d)) { - const originalId = getOriginalId(d); - const original = dataset.individuals.get(originalId); - const origType = original?.entityType ?? EntityType.Individual; - if ( - (origType === EntityType.SystemComponent || - origType === EntityType.InstalledComponent) && - label.length > 6 - ) { - return label.substring(0, 6) + "..."; - } - } - if (label.length > config.labels.individual.maxChars - 2) { - label = label.substring(0, config.labels.individual.maxChars - 2); - label += "..."; + // For nested entities, allow longer labels since font is smaller + const nestingLevel = d._nestingLevel ?? getNestingLevel(d); + const maxChars = + nestingLevel > 0 + ? config.labels.individual.maxChars + 10 // Allow more chars for smaller font + : config.labels.individual.maxChars; + + if (label.length > maxChars - 2) { + label = label.substring(0, maxChars - 2) + "..."; } return label; }) diff --git a/editor-app/pages/_app.tsx b/editor-app/pages/_app.tsx index 4e973ac..57aba8e 100644 --- a/editor-app/pages/_app.tsx +++ b/editor-app/pages/_app.tsx @@ -22,6 +22,7 @@ export default function App({ Component, pageProps }: AppProps) { flexDirection: "column", justifyContent: "center", padding: "1.5rem", + paddingTop: "100px", // Account for fixed navbar height (~70px + some margin) }} > From e37ec7fbac6c6a1a1a4d4615b04d698b39843757 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sun, 30 Nov 2025 18:22:09 +0000 Subject: [PATCH 58/81] feat: Update entity type icons and descriptions in EntityTypeLegend and adjust icon retrieval logic in DrawIndividuals --- editor-app/components/EntityTypeLegend.tsx | 40 +++++++---- editor-app/diagram/DrawIndividuals.ts | 79 +++++++++++++++------- 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/editor-app/components/EntityTypeLegend.tsx b/editor-app/components/EntityTypeLegend.tsx index 636435d..e4eae34 100644 --- a/editor-app/components/EntityTypeLegend.tsx +++ b/editor-app/components/EntityTypeLegend.tsx @@ -8,28 +8,41 @@ interface EntityLegendItem { } export const entityTypes: EntityLegendItem[] = [ - { icon: "▣", label: "System", description: "A system containing components" }, { - icon: "◈", + icon: "▣", + label: "System", + description: "A system containing component slots", + }, + { + icon: "◇", label: "System Component", - description: "A slot within a system", + description: "A slot/role within a system (uninstalled)", }, { - icon: "⬢", - label: "Installed Component", - description: "A physical component", + icon: "◆", + label: "SC in System", + description: "A system component installed in a system", + }, + { + icon: "◈", + label: "SC in SC", + description: "A system component nested inside another system component", }, { icon: "⬡", - label: "Installed (in system comp)", - description: "Component installed in a system component", + label: "Installed Component", + description: "A physical component (uninstalled)", }, { - icon: "◇", - label: "Installed (system)", - description: "System component installed in a system", + icon: "⬢", + label: "IC in SC", + description: "An installed component in a system component slot", + }, + { + icon: "○", + label: "Individual", + description: "A regular individual entity", }, - { icon: "O", label: "Individual", description: "A regular individual" }, ]; const EntityTypeLegend = () => { @@ -51,8 +64,7 @@ const EntityTypeLegend = () => { width: 20, height: 20, marginRight: 8, - fontSize: 20, - + fontSize: 18, fontFamily: "Arial, sans-serif", }} > diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index 27012f0..ccbface 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -8,7 +8,6 @@ import { removeLabelIfItOverlaps, } from "./DrawHelpers"; import { ConfigData } from "./config"; -import { entityTypes } from "@/components/EntityTypeLegend"; // use legend as source of truth let mouseOverElement: any | null = null; @@ -82,46 +81,63 @@ function isVirtualRow(ind: Individual): boolean { return ind._isVirtualRow === true; } -// Helper function to get icon for entity type (use EntityTypeLegend source) +/** + * Get the appropriate icon for an entity based on its type and installation context. + * + * Icon system: + * - ▣ System (square with fill - contains component slots) + * - ◇ System Component - uninstalled (empty diamond) + * - ◆ SC in System (filled diamond - installed directly in system) + * - ◈ SC in SC (diamond variant - nested in another SC) + * - ⬡ Installed Component - uninstalled (empty hexagon) + * - ⬢ IC in SC (filled hexagon - installed in a system component) + * - ○ Individual (circle) + */ function getEntityIcon(ind: Individual, dataset: Model): string { - const findIcon = (label: string) => - entityTypes.find((e) => e.label === label)?.icon ?? ""; + const entityType = ind.entityType ?? EntityType.Individual; + const isVirtual = ind._isVirtualRow === true; + const parentPath = ind._parentPath ?? ""; - let entityType = ind.entityType ?? EntityType.Individual; - - // If it's a virtual row, look up the original entity to get the correct icon - if (isVirtualRow(ind)) { - const originalId = getOriginalId(ind); + // If it's a virtual row, determine the icon based on what it is and where it's installed + if (isVirtual) { + const originalId = ind.id.split("__installed_in__")[0]; const original = dataset.individuals.get(originalId); + if (original) { const origType = original.entityType ?? EntityType.Individual; - // For a SystemComponent virtual row (system-installed instance), use the - // explicit "Installed (system)" icon from the legend. if (origType === EntityType.SystemComponent) { - return findIcon("Installed (system)"); + // SC installed somewhere + // Check if it's installed in another SC (nested) or directly in a System + const pathParts = parentPath.split("__").filter(Boolean); + + if (pathParts.length > 1) { + // Nested in another SC (path has more than just the system) + // e.g., path = "systemId__scId" means this SC is in another SC + return "◈"; // SC in SC + } else { + // Directly in a System (path only has systemId) + return "◆"; // SC in System + } } - // For an InstalledComponent virtual row (installed into a system component), - // use the "Installed (in system comp)" icon from the legend. if (origType === EntityType.InstalledComponent) { - return findIcon("Installed (in system comp)"); + // IC installed in an SC + return "⬢"; // IC in SC (filled hexagon) } - - // Otherwise fall back to the original's type below - entityType = origType; } } + // Top-level (non-virtual) entities switch (entityType) { case EntityType.System: - return findIcon("System"); + return "▣"; // System (square with fill) case EntityType.SystemComponent: - return findIcon("System Component"); + return "◇"; // SC uninstalled (empty diamond) case EntityType.InstalledComponent: - return findIcon("Installed Component"); + return "⬡"; // IC uninstalled (empty hexagon) default: - return findIcon("Individual"); + return "○"; // Individual (circle) } } @@ -539,10 +555,23 @@ function individualTooltip(individual: Individual) { if (originalType === EntityType.SystemComponent) { tip = "System Component"; tip += "
(Installation instance)"; - } else { + } else if (originalType === EntityType.InstalledComponent) { tip = "Installed Component"; tip += "
(Installation instance)"; } + } else { + const entityType = individual.entityType ?? EntityType.Individual; + switch (entityType) { + case EntityType.System: + tip = "System"; + break; + case EntityType.SystemComponent: + tip = "System Component"; + break; + case EntityType.InstalledComponent: + tip = "Installed Component"; + break; + } } if (individual.name) tip += "
Name: " + individual.name; @@ -645,7 +674,7 @@ export function labelIndividuals(ctx: DrawContext) { .attr("font-family", "Arial, sans-serif") .attr("font-size", (d: Individual) => getFontSize(d)) .attr("fill", "#374151") - .text((d: Individual) => getEntityIcon(d, dataset)); // Pass dataset to get correct icon + .text((d: Individual) => getEntityIcon(d, dataset)); // Draw labels with indentation svgElement @@ -656,7 +685,7 @@ export function labelIndividuals(ctx: DrawContext) { .attr("id", (i: Individual) => `il${i.id}`) .attr("x", (i: Individual) => { const nestingLevel = getNestingLevel(i); - const indent = nestingLevel * 20; // Indent labels + const indent = nestingLevel * INDENT_PER_LEVEL; return ( config.layout.individual.xMargin + config.labels.individual.leftMargin + From 5b5bdf29b24b0891ef99c35f255a87c4fdee2e5b Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Sun, 30 Nov 2025 19:05:24 +0000 Subject: [PATCH 59/81] feat: Enhance EntityTypeLegend with hatch support for components and improve installation drawing logic --- editor-app/components/EntityTypeLegend.tsx | 51 ++++++++++++- editor-app/diagram/DrawInstallations.ts | 83 ++++++++++++++++------ 2 files changed, 112 insertions(+), 22 deletions(-) diff --git a/editor-app/components/EntityTypeLegend.tsx b/editor-app/components/EntityTypeLegend.tsx index e4eae34..f0d6a2d 100644 --- a/editor-app/components/EntityTypeLegend.tsx +++ b/editor-app/components/EntityTypeLegend.tsx @@ -5,6 +5,7 @@ interface EntityLegendItem { icon: string; label: string; description?: string; + hasHatch?: boolean; } export const entityTypes: EntityLegendItem[] = [ @@ -28,6 +29,12 @@ export const entityTypes: EntityLegendItem[] = [ label: "SC in SC", description: "A system component nested inside another system component", }, + { + icon: "◆", + label: "SC with children", + description: "A system component that has other components installed in it", + hasHatch: true, + }, { icon: "⬡", label: "Installed Component", @@ -61,14 +68,54 @@ const EntityTypeLegend = () => { display: "inline-flex", alignItems: "center", justifyContent: "center", - width: 20, - height: 20, + width: 24, + height: 24, marginRight: 8, fontSize: 18, fontFamily: "Arial, sans-serif", + position: "relative", }} > {item.icon} + {item.hasHatch && ( + + + + + + + + + )} {item.label} diff --git a/editor-app/diagram/DrawInstallations.ts b/editor-app/diagram/DrawInstallations.ts index 86a96c3..71ae016 100644 --- a/editor-app/diagram/DrawInstallations.ts +++ b/editor-app/diagram/DrawInstallations.ts @@ -15,18 +15,6 @@ function getOriginalId(ind: Individual): string { return ind.id; } -// Get the slot ID from an installation reference -function getSlotId(ind: Individual): string | undefined { - if (isInstallationRef(ind)) { - const parts = ind.id.split("__installed_in__")[1]; - if (parts) { - const subParts = parts.split("__"); - return subParts[0]; - } - } - return undefined; -} - // Get the installation ID from an installation reference function getInstallationId(ind: Individual): string | undefined { if (isInstallationRef(ind)) { @@ -39,6 +27,58 @@ function getInstallationId(ind: Individual): string | undefined { return ind._installationId; } +/** + * Check if a SystemComponent (virtual row) has children installed in it. + * A SC has children if there are other components (SC or IC) with installations + * that target this SC AND have the matching scInstallationContextId. + */ +function scHasChildren(ind: Individual, dataset: Model): boolean { + if (!ind._isVirtualRow) return false; + + const originalId = getOriginalId(ind); + const original = dataset.individuals.get(originalId); + if (!original) return false; + + const origType = original.entityType ?? EntityType.Individual; + if (origType !== EntityType.SystemComponent) return false; + + // Get the installation ID for this virtual row + const scInstallationId = ind._installationId || getInstallationId(ind); + if (!scInstallationId) return false; + + // Check if any component has an installation targeting this SC with matching context + let hasChildren = false; + + dataset.individuals.forEach((component) => { + if (hasChildren) return; // Early exit if already found + + const compType = component.entityType ?? EntityType.Individual; + if ( + compType !== EntityType.SystemComponent && + compType !== EntityType.InstalledComponent + ) { + return; + } + + if (!component.installations || component.installations.length === 0) { + return; + } + + // Check if any installation targets this SC with matching context + for (const installation of component.installations) { + if (installation.targetId !== originalId) continue; + + // For nested installations, the scInstallationContextId must match + if (installation.scInstallationContextId === scInstallationId) { + hasChildren = true; + return; + } + } + }); + + return hasChildren; +} + export function drawInstallations(ctx: DrawContext) { const { svgElement, individuals, config, dataset } = ctx; @@ -80,13 +120,20 @@ export function drawInstallations(ctx: DrawContext) { .attr("stroke-width", 1); } - // For each InstalledComponent installation reference row, draw a hatched overlay matching the chevron shape + // For each SystemComponent virtual row that has children, draw a hatched overlay individuals.forEach((ind) => { if (!isInstallationRef(ind)) return; - // Only apply hatch to InstalledComponent virtual rows, not SystemComponent - const entityType = ind.entityType ?? EntityType.Individual; - if (entityType !== EntityType.InstalledComponent) return; + // Only apply hatch to SystemComponent virtual rows that have children + const originalId = getOriginalId(ind); + const original = dataset.individuals.get(originalId); + if (!original) return; + + const entityType = original.entityType ?? EntityType.Individual; + if (entityType !== EntityType.SystemComponent) return; + + // Check if this SC has children installed in it + if (!scHasChildren(ind, dataset)) return; // Get the path data from the individual element const escapedId = CSS.escape("i" + ind.id); @@ -98,9 +145,6 @@ export function drawInstallations(ctx: DrawContext) { const pathData = node.getAttribute("d"); if (!pathData) return; - // Remove dashed border on the original element (if present) - svgElement.select("#" + escapedId).attr("stroke-dasharray", null); - // Draw hatch overlay using the same path as the individual svgElement .append("path") @@ -108,7 +152,6 @@ export function drawInstallations(ctx: DrawContext) { .attr("d", pathData) .attr("fill", "url(#diagonal-hatch)") .attr("stroke", "none") - .attr("stroke-dasharray", null) .attr("pointer-events", "none"); }); } From 3967e7990273264c0fd5a2636452f87c51364321 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Mon, 1 Dec 2025 11:37:59 +0000 Subject: [PATCH 60/81] feat: Improve installation management UI with enhanced empty state handling and label wrapping for better readability --- .../components/EditInstalledComponent.tsx | 372 ++++++++++-------- .../EditSystemComponentInstallation.tsx | 41 +- editor-app/diagram/DrawIndividuals.ts | 256 ++++++++---- 3 files changed, 394 insertions(+), 275 deletions(-) diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index 4a778f9..c1cb0ec 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -622,186 +622,208 @@ const EditInstalledComponent = (props: Props) => { - {localInstallations.map((inst, idx) => { - const raw = rawInputs.get(inst.id) || { - beginning: String(inst.beginning), - ending: String(inst.ending), - }; - - const slotOption = inst.targetId - ? getSlotOption(inst.targetId, inst.scInstallationContextId) - : undefined; - - const instSlotBounds = inst.targetId - ? getSlotTimeBounds(inst.targetId, inst.scInstallationContextId) - : null; - - const beginningOutOfBounds = isOutsideSlotBounds( - inst.id, - "beginning" - ); - const endingOutOfBounds = isOutsideSlotBounds(inst.id, "ending"); - - return ( - - {idx + 1} - {!isFiltered && ( + {localInstallations.length === 0 ? ( + + + + No installations yet. Click "+ Add Installation Period" + below to add one. + + + + ) : ( + localInstallations.map((inst, idx) => { + const raw = rawInputs.get(inst.id) || { + beginning: String(inst.beginning), + ending: String(inst.ending), + }; + + const slotOption = inst.targetId + ? getSlotOption(inst.targetId, inst.scInstallationContextId) + : undefined; + + const instSlotBounds = inst.targetId + ? getSlotTimeBounds( + inst.targetId, + inst.scInstallationContextId + ) + : null; + + const beginningOutOfBounds = isOutsideSlotBounds( + inst.id, + "beginning" + ); + const endingOutOfBounds = isOutsideSlotBounds( + inst.id, + "ending" + ); + + return ( + + {idx + 1} + {!isFiltered && ( + + { + const virtualId = e.target.value; + if (!virtualId) { + updateInstallation(inst.id, "targetId", ""); + updateInstallation( + inst.id, + "scInstallationContextId", + undefined + ); + return; + } + + const selectedSlot = + getSlotOptionByVirtualId(virtualId); + + if (selectedSlot) { + updateInstallation( + inst.id, + "targetId", + selectedSlot.id + ); + updateInstallation( + inst.id, + "scInstallationContextId", + selectedSlot.scInstallationId + ); + + const newEnding = + selectedSlot.bounds.ending < Model.END_OF_TIME + ? selectedSlot.bounds.ending + : selectedSlot.bounds.beginning + 10; + updateRawInput( + inst.id, + "beginning", + String(selectedSlot.bounds.beginning) + ); + updateRawInput( + inst.id, + "ending", + String(newEnding) + ); + } + }} + className={!inst.targetId ? "border-warning" : ""} + > + + {Array.from(slotsBySystem.entries()).map( + ([sysName, slots]) => ( + + {slots.map((slot) => { + const indent = " ".repeat( + slot.nestingLevel - 1 + ); + const boundsStr = ` (${ + slot.bounds.beginning + }-${ + slot.bounds.ending >= Model.END_OF_TIME + ? "∞" + : slot.bounds.ending + })`; + return ( + + ); + })} + + ) + )} + + {slotOption && ( + + + Available: {slotOption.bounds.beginning}- + {slotOption.bounds.ending >= Model.END_OF_TIME + ? "∞" + : slotOption.bounds.ending} + + + )} + + )} - { - const virtualId = e.target.value; - if (!virtualId) { - updateInstallation(inst.id, "targetId", ""); - updateInstallation( - inst.id, - "scInstallationContextId", - undefined - ); - return; - } - - const selectedSlot = - getSlotOptionByVirtualId(virtualId); - - if (selectedSlot) { - updateInstallation( - inst.id, - "targetId", - selectedSlot.id - ); - updateInstallation( - inst.id, - "scInstallationContextId", - selectedSlot.scInstallationId - ); - - const newEnding = - selectedSlot.bounds.ending < Model.END_OF_TIME - ? selectedSlot.bounds.ending - : selectedSlot.bounds.beginning + 10; - updateRawInput( - inst.id, - "beginning", - String(selectedSlot.bounds.beginning) - ); - updateRawInput( - inst.id, - "ending", - String(newEnding) - ); - } - }} - className={!inst.targetId ? "border-warning" : ""} - > - - {Array.from(slotsBySystem.entries()).map( - ([sysName, slots]) => ( - - {slots.map((slot) => { - const indent = " ".repeat( - slot.nestingLevel - 1 - ); - const boundsStr = ` (${slot.bounds.beginning}-${ - slot.bounds.ending >= Model.END_OF_TIME - ? "∞" - : slot.bounds.ending - })`; - return ( - - ); - })} - - ) - )} - - {slotOption && ( - - - Available: {slotOption.bounds.beginning}- - {slotOption.bounds.ending >= Model.END_OF_TIME - ? "∞" - : slotOption.bounds.ending} - + min={instSlotBounds?.beginning ?? 0} + max={instSlotBounds?.ending ?? undefined} + value={raw.beginning} + onChange={(e) => + updateRawInput(inst.id, "beginning", e.target.value) + } + placeholder={String(instSlotBounds?.beginning ?? 0)} + className={ + raw.beginning === "" || beginningOutOfBounds + ? "border-danger" + : "" + } + isInvalid={beginningOutOfBounds} + /> + {beginningOutOfBounds && instSlotBounds && ( + + Min: {instSlotBounds.beginning} )} - )} - - - updateRawInput(inst.id, "beginning", e.target.value) - } - placeholder={String(instSlotBounds?.beginning ?? 0)} - className={ - raw.beginning === "" || beginningOutOfBounds - ? "border-danger" - : "" - } - isInvalid={beginningOutOfBounds} - /> - {beginningOutOfBounds && instSlotBounds && ( - - Min: {instSlotBounds.beginning} - - )} - - - - updateRawInput(inst.id, "ending", e.target.value) - } - placeholder={String(instSlotBounds?.ending ?? 10)} - className={ - raw.ending === "" || endingOutOfBounds - ? "border-danger" - : "" - } - isInvalid={endingOutOfBounds} - /> - {endingOutOfBounds && instSlotBounds && ( - - Max:{" "} - {instSlotBounds.ending >= Model.END_OF_TIME - ? "∞" - : instSlotBounds.ending} - - )} - - - - - - ); - })} + + + updateRawInput(inst.id, "ending", e.target.value) + } + placeholder={String(instSlotBounds?.ending ?? 10)} + className={ + raw.ending === "" || endingOutOfBounds + ? "border-danger" + : "" + } + isInvalid={endingOutOfBounds} + /> + {endingOutOfBounds && instSlotBounds && ( + + Max:{" "} + {instSlotBounds.ending >= Model.END_OF_TIME + ? "∞" + : instSlotBounds.ending} + + )} + + + + + + ); + }) + )} diff --git a/editor-app/components/EditSystemComponentInstallation.tsx b/editor-app/components/EditSystemComponentInstallation.tsx index f56d7f3..8df526f 100644 --- a/editor-app/components/EditSystemComponentInstallation.tsx +++ b/editor-app/components/EditSystemComponentInstallation.tsx @@ -518,22 +518,27 @@ export default function EditSystemComponentInstallation({ into Systems or other System Components (nested slots).

- {installations.length === 0 ? ( -
- No installations configured. Click "Add Installation" to add one. -
- ) : ( - - +
+ + + + + + + + + + {installations.length === 0 ? ( - - - - + - - - {installations.map((inst) => { + ) : ( + installations.map((inst) => { const raw = rawInputs[inst.id] || { beginning: "0", ending: "", @@ -671,10 +676,10 @@ export default function EditSystemComponentInstallation({ ); - })} - -
TargetBeginningEndingActions
TargetBeginningEndingActions + + No installations yet. Click "+ Add Installation" below to + add one. + +
- )} + }) + )} + + - Sort Individuals + Sort Entities
From 1efdc0148a48dd69ecaad88552c6f45ac5ba46d0 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Mon, 1 Dec 2025 16:01:42 +0000 Subject: [PATCH 64/81] feat: Enhance IndividualImpl and ActivityLib to support installations and entity types --- editor-app/lib/ActivityLib.ts | 353 ++++++++++++++++++++++++------- editor-app/lib/IndividualImpl.ts | 20 +- 2 files changed, 292 insertions(+), 81 deletions(-) diff --git a/editor-app/lib/ActivityLib.ts b/editor-app/lib/ActivityLib.ts index 480d391..de3dad4 100644 --- a/editor-app/lib/ActivityLib.ts +++ b/editor-app/lib/ActivityLib.ts @@ -26,6 +26,10 @@ import { import { IndividualImpl } from "./IndividualImpl"; import { Kind, Model } from "./Model"; import { EDITOR_VERSION } from "./version"; +import { EntityType, Installation } from "./Schema"; + +import { ParticipationImpl } from "./ParticipationImpl"; +// ... rest of existing imports ... /** * ActivityLib @@ -56,7 +60,10 @@ const diagramModel = new HQDMModel(); const diagramTimeUuid = "c9ecb65e-4b1f-4633-ac5c-f765e36586f2"; const diagramTimeIri = BASE + diagramTimeUuid; -const diagramTimeClass = diagramModel.createThing(class_of_event, diagramTimeIri); +const diagramTimeClass = diagramModel.createThing( + class_of_event, + diagramTimeIri +); /** * Attempts to load a model from a string containing a TTL representation of the model. @@ -127,9 +134,7 @@ const getTimeValue = (hqdm: HQDMModel, t: Thing): number => { * times we will simply lose that information in the diagram. */ if (hqdm.isMemberOf(t, diagramTimeClass)) { const n = Number.parseFloat(name); - return n < EPOCH_START ? -1 - : n >= EPOCH_END ? EPOCH_END - : n; + return n < EPOCH_START ? -1 : n >= EPOCH_END ? EPOCH_END : n; } console.log("getTimeValue: unknown class for %s", t.id); @@ -139,35 +144,41 @@ const getTimeValue = (hqdm: HQDMModel, t: Thing): number => { /** * Returns HQDM point_in_time, creating it if it doesn't already exist. */ -const createTimeValue = (hqdm: HQDMModel, modelWorld: Thing, time: number): Thing => { - +const createTimeValue = ( + hqdm: HQDMModel, + modelWorld: Thing, + time: number +): Thing => { /* Map the epoch start and end to +-Inf for output to the file. This * is cleaner than leaving magic numbers in the output. Possibly we * should simply omit the attribute altogether instead? */ - const f = time < EPOCH_START ? Number.NEGATIVE_INFINITY - : time >= EPOCH_END ? Number.POSITIVE_INFINITY - : time; + const f = + time < EPOCH_START + ? Number.NEGATIVE_INFINITY + : time >= EPOCH_END + ? Number.POSITIVE_INFINITY + : time; var exists: boolean = false; const iri = BASE + uuidv4(); var thing = new Thing(iri); // Placeholder thing as we don't know if we need to create one yet. - + // Test whether f value already exists in hqdm: HQDMModel hqdm.findByType(event).forEach((obj) => { const tVal = hqdm.getEntityName(obj); - if(tVal == f.toString()){ + if (tVal == f.toString()) { exists = true; thing = obj; } }); - if(!exists){ + if (!exists) { thing = hqdm.createThing(event, iri); hqdm.addMemberOf(thing, diagramTimeClass); hqdm.relate(ENTITY_NAME, thing, new Thing(f.toString())); hqdm.addToPossibleWorld(thing, modelWorld); } - + return thing; }; @@ -175,11 +186,21 @@ const checkReprVersion = (hqdm: HQDMModel) => { const reprVersion = hqdm.getVersionInfo(EDITOR_REPR); if (reprVersion == undefined) { hqdm.replaceIriPrefix(AMRC_BASE, BASE); - } - else if (reprVersion != currentReprVersion) + } else if (reprVersion != currentReprVersion) throw new Error("Unrecognised data version"); }; +// Custom predicates for our extensions +const ENTITY_TYPE_PREDICATE = `${EDITOR_NS}#entityType`; +const INSTALLATION_PREDICATE = `${EDITOR_NS}#hasInstallation`; +const INSTALLATION_TARGET_PREDICATE = `${EDITOR_NS}#installationTarget`; +const INSTALLATION_COMPONENT_PREDICATE = `${EDITOR_NS}#installationComponent`; +const INSTALLATION_BEGINNING_PREDICATE = `${EDITOR_NS}#installationBeginning`; +const INSTALLATION_ENDING_PREDICATE = `${EDITOR_NS}#installationEnding`; +const INSTALLATION_SC_CONTEXT_PREDICATE = `${EDITOR_NS}#installationSCContext`; +const INSTALLATION_SYSTEM_CONTEXT_PREDICATE = `${EDITOR_NS}#installationSystemContext`; +const PARTICIPATION_VIRTUAL_ROW_PREDICATE = `${EDITOR_NS}#participationVirtualRowId`; + /** * Converts an HQDMModel to a UI Model. */ @@ -188,7 +209,11 @@ export const toModel = (hqdm: HQDMModel): Model => { // Kinds are immutable so it's fine to use constant objects. // XXX These duplicate the code in lib/Model.ts. - const ordPhysObjKind = new Kind(ordinary_physical_object.id, "Resource", true); + const ordPhysObjKind = new Kind( + ordinary_physical_object.id, + "Resource", + true + ); const organizationKind = new Kind(organization.id, "Organization", true); const personKind = new Kind(person.id, "Person", true); const activityKind = new Kind(activity.id, "Task", true); @@ -196,11 +221,11 @@ export const toModel = (hqdm: HQDMModel): Model => { const communityName = new Thing(AMRC_COMMUNITY); const m = new Model(); - const isKindOfOrdinaryPhysicalObject = (x: Thing): boolean => hqdm.isKindOf(x, kind_of_ordinary_physical_object); + const isKindOfOrdinaryPhysicalObject = (x: Thing): boolean => + hqdm.isKindOf(x, kind_of_ordinary_physical_object); const kindOrDefault = (kind: Maybe, defKind: Kind) => { - if (!kind) - return defKind; + if (!kind) return defKind; return new Kind(getModelId(kind!), hqdm.getEntityName(kind!), false); }; @@ -208,27 +233,21 @@ export const toModel = (hqdm: HQDMModel): Model => { const possibleWorld = hqdm.findByType(possible_world).first(); // Assumes just one possible world if (possibleWorld) { m.name = hqdm.getIdentifications(possibleWorld, communityName).first()?.id; - m.description = hqdm.getDescriptions(possibleWorld, communityName).first()?.id; + m.description = hqdm + .getDescriptions(possibleWorld, communityName) + .first()?.id; } - /** - * TODO: It may be necessary in future to filter the ordinary_physical_objects to remove any that are not part of the model. - * - * Custom kinds of individuals are modelled as ordinary_physical_objects that are members of a kind_of_ordinary_physical_object, - * so we need to find those and add them to the model. There is also a default kind for Resources that is simple an ordinary_physical_object. - */ + // STEP 1: Load all individuals FIRST hqdm.findByType(ordinary_physical_object).forEach((obj) => { - // Find the kind of individual. const kind = hqdm .memberOfKind(obj) .filter(isKindOfOrdinaryPhysicalObject) - .first(); // Matches every element, so returns the first - + .first(); const kindOfIndividual = kindOrDefault(kind, ordPhysObjKind); addIndividual(obj, hqdm, communityName, kindOfIndividual, m); }); - // Handle person and organization kinds. hqdm.findByType(person).forEach((persona) => { addIndividual(persona, hqdm, communityName, personKind, m); }); @@ -237,24 +256,23 @@ export const toModel = (hqdm: HQDMModel): Model => { addIndividual(org, hqdm, communityName, organizationKind, m); }); - // - // Add each Activity to the model. - // + // STEP 2: Load installations BEFORE activities + // This ensures virtual rows can be created properly + loadInstallations(hqdm, m); + + // STEP 3: Now load activities with participations hqdm.findByType(activity).forEach((a) => { const id = getModelId(a); - // Get the activity name. const identifications = hqdm.getIdentifications(a, communityName); - const name = identifications.first()?.id ?? "No Name Found: " + a.id; // Assumes just one name + const name = identifications.first()?.id ?? "No Name Found: " + a.id; - // Get the activity type. const kinds = hqdm .memberOfKind(a) .filter((x) => hqdm.isKindOf(x, kind_of_activity)); - const kind = kinds.first((x) => (x ? true : false)); // Matches every element, so returns the first + const kind = kinds.first((x) => (x ? true : false)); const kindOfActivity = kindOrDefault(kind, activityKind); - // Get the temporal extent of the activity with defaults, although the defaults should never be needed. const activityFromEvent = hqdm.getBeginning(a); const activityToEvent = hqdm.getEnding(a); const beginning = activityFromEvent @@ -264,14 +282,11 @@ export const toModel = (hqdm: HQDMModel): Model => { ? getTimeValue(hqdm, activityToEvent) : EPOCH_END; - // Get the optional description of the activity. const descriptions = hqdm.getDescriptions(a, communityName); - const description = descriptions.first()?.id; // Assumes just one description + const description = descriptions.first()?.id; - // Get the parent activity, if any. const partOf = hqdm.getPartOf(a).first(); - // Create the activity and add it to the model. const newA = new ActivityImpl( id, name, @@ -279,41 +294,71 @@ export const toModel = (hqdm: HQDMModel): Model => { beginning, ending, description, - partOf ? getModelId(partOf) : undefined, + partOf ? getModelId(partOf) : undefined ); m.addActivity(newA); - // // Find the participations to the activities and add them to the model. - // const participations = hqdm.getParticipants(a); participations.forEach((p) => { - // Get the role of the participant. const participantRole = hqdm.getRole(p).first(); const roleType = kindOrDefault(participantRole, participantKind); - // Get the participant whole life object for this temporal part. + // Check if this participation has a virtual row ID saved + const virtualRowRef = hqdm + .getRelated(p, PARTICIPATION_VIRTUAL_ROW_PREDICATE) + .first(); + const participantThing = hqdm.getTemporalWhole(p); - // Get the individual from the model or create a dummy individual if the participant is not in the model. - const indiv = participantThing - ? m.individuals.get(getModelId(participantThing)) - : new IndividualImpl( - uuidv4(), - "Unknown Individual", - ordPhysObjKind, - 0, - EPOCH_END, - "Unknown Individual", - false, - false + if (virtualRowRef) { + // Use the saved virtual row ID + const virtualRowId = virtualRowRef.id; + + // Add participation directly using the virtual row ID + // Don't create an IndividualImpl - just use the ID directly in the participation + const participation = new ParticipationImpl(virtualRowId, roleType); + newA.participations.set(virtualRowId, participation); + } else { + // Regular participation + const indiv = participantThing + ? m.individuals.get(getModelId(participantThing)) + : undefined; + + if (indiv) { + newA.addParticipation(indiv, roleType); + } else if (participantThing) { + console.warn( + `Could not find individual ${getModelId( + participantThing + )} for participation` ); - if (indiv) { - newA.addParticipation(indiv, roleType); + } } }); }); + // STEP 4: Sort activities by beginning time to maintain correct order + // First, get all activities as an array and sort them + const sortedActivities = Array.from(m.activities.values()).sort((a, b) => { + // First sort by beginning time + if (a.beginning !== b.beginning) { + return a.beginning - b.beginning; + } + // If same beginning, sort by ending time + if (a.ending !== b.ending) { + return a.ending - b.ending; + } + // If same times, sort by name + return a.name.localeCompare(b.name); + }); + + // Clear and re-add activities in sorted order + m.activities.clear(); + sortedActivities.forEach((activity) => { + m.activities.set(activity.id, activity); + }); + return addRefDataToModel(hqdm, m); }; @@ -430,12 +475,16 @@ export const toHQDM = (model: Model): HQDMModel => { * @param baseKind The base kind to derive the HQDM kind from. * @returns the HQDM kind. */ - const setKindFromUI = (hqdmSt: Thing, uiKind: Maybe, baseKind: Thing) => { + const setKindFromUI = ( + hqdmSt: Thing, + uiKind: Maybe, + baseKind: Thing + ) => { if (!uiKind || uiKind.isCoreHqdm) { const stKind = uiKind ? new Thing(uiKind.id) : baseKind; hqdm.addMemberOfKind(hqdmSt, stKind); return stKind; - } + } // The type is not actually optional - it's only optional because the UI model allows it to be undefined. const stKindId = BASE + uiKind.id; @@ -495,6 +544,62 @@ export const toHQDM = (model: Model): HQDMModel => { } setKindFromUI(player, i.type, kind_of_ordinary_physical_object); + + // Save entity type if present + if (i.entityType) { + hqdm.relate(ENTITY_TYPE_PREDICATE, player, new Thing(i.entityType)); + } + + // Save installations + if (i.installations && i.installations.length > 0) { + i.installations.forEach((inst) => { + const instThing = hqdm.createThing( + ordinary_physical_object, + BASE + inst.id + ); + hqdm.addToPossibleWorld(instThing, modelWorld); + + // Link installation to component + hqdm.relate(INSTALLATION_PREDICATE, player, instThing); + + // Save installation properties + hqdm.relate( + INSTALLATION_TARGET_PREDICATE, + instThing, + new Thing(BASE + inst.targetId) + ); + hqdm.relate(INSTALLATION_COMPONENT_PREDICATE, instThing, player); + + if (inst.beginning !== undefined) { + hqdm.relate( + INSTALLATION_BEGINNING_PREDICATE, + instThing, + new Thing(String(inst.beginning)) + ); + } + if (inst.ending !== undefined) { + hqdm.relate( + INSTALLATION_ENDING_PREDICATE, + instThing, + new Thing(String(inst.ending)) + ); + } + if (inst.scInstallationContextId) { + hqdm.relate( + INSTALLATION_SC_CONTEXT_PREDICATE, + instThing, + new Thing(BASE + inst.scInstallationContextId) + ); + } + if (inst.systemContextId) { + hqdm.relate( + INSTALLATION_SYSTEM_CONTEXT_PREDICATE, + instThing, + new Thing(BASE + inst.systemContextId) + ); + } + }); + } }); // Add the activities to the model @@ -536,34 +641,46 @@ export const toHQDM = (model: Model): HQDMModel => { // Add the participations to the model a.participations.forEach((p) => { - // Create the participant and add it to the possible world. const participation = hqdm.createThing( state_of_ordinary_physical_object, BASE + uuidv4() ); hqdm.addToPossibleWorld(participation, modelWorld); - // Find or create the role (a role is a kind_of_participant). const pRole = setKindFromUI(participation, p.role, role); hqdm.addMemberOfKind(participation, participant); - // The kind_of_activity needs to define the roles it consists of, - // and the reverse relationship. However, we shouldn't define core - // HQDM kinds. if (p.role && !p.role.isCoreHqdm) { hqdm.relate(PART_OF_BY_CLASS, pRole, actKind); hqdm.relate(CONSISTS_OF_BY_CLASS, actKind, pRole); } - // Add the participant as a temporal part of the individual. - hqdm.addAsTemporalPartOf(participation, new Thing(BASE + p.individualId)); + // Handle virtual row IDs for participations + // Extract the original individual ID from virtual row format + let actualIndividualId = p.individualId; + const isVirtualRow = p.individualId.includes("__installed_in__"); + + if (isVirtualRow) { + actualIndividualId = p.individualId.split("__installed_in__")[0]; + // Save the full virtual row ID so we can restore it on load + hqdm.relate( + PARTICIPATION_VIRTUAL_ROW_PREDICATE, + participation, + new Thing(p.individualId) + ); + } + + hqdm.addAsTemporalPartOf( + participation, + new Thing(BASE + actualIndividualId) + ); hqdm.addParticipant(participation, act); - // Make the participant have gthe same temporal bounds as the activity. hqdm.beginning(participation, activityFrom); hqdm.ending(participation, activityTo); }); }); + return hqdm; }; @@ -619,7 +736,13 @@ export const loadRefDataFromTTL = (ttl: string): Model | Error => { * @param model The model to add the Individual to. * @returns void */ -const addIndividual = (thing: Thing, hqdm: HQDMModel, communityName: Thing, kind: Kind, model: Model):void => { +const addIndividual = ( + thing: Thing, + hqdm: HQDMModel, + communityName: Thing, + kind: Kind, + model: Model +): void => { const id = getModelId(thing); // The UI Model doesn't use IRIs, so remove the base. // Find the name signs recognised by the community for this individual. @@ -634,17 +757,95 @@ const addIndividual = (thing: Thing, hqdm: HQDMModel, communityName: Thing, kind const to = hqdm.getEnding(thing); if (from && to) { - // Create the individual and add it to the model. - model.addIndividual(new IndividualImpl( + // Get entity type if present - use string predicate + const entityTypeValue = hqdm + .getRelated(thing, ENTITY_TYPE_PREDICATE) + .first(); + let entityType: EntityType | undefined = undefined; + if (entityTypeValue) { + const typeStr = entityTypeValue.id; + if (Object.values(EntityType).includes(typeStr as EntityType)) { + entityType = typeStr as EntityType; + } + } + + // Create the individual with entityType in constructor + const individual = new IndividualImpl( id, name, kind, getTimeValue(hqdm, from), getTimeValue(hqdm, to), - description - )); + description, + false, // beginsWithParticipant + false, // endsWithParticipant + entityType // Pass entityType directly to constructor + ); + + // Add to model + model.addIndividual(individual); } else { console.error("Individual " + id + " has no temporal extent."); } }; +/** + * Load installations from HQDM model after all individuals are loaded + */ +const loadInstallations = (hqdm: HQDMModel, model: Model): void => { + model.individuals.forEach((individual) => { + // Find installations for this individual + const individualThing = new Thing(BASE + individual.id); + const installationRefs = hqdm.getRelated( + individualThing, + INSTALLATION_PREDICATE + ); + + const installations: Installation[] = []; + + installationRefs.forEach((instRef) => { + const instId = getModelId(instRef); + + // Get installation properties - use string predicates + const targetRef = hqdm + .getRelated(instRef, INSTALLATION_TARGET_PREDICATE) + .first(); + const beginningRef = hqdm + .getRelated(instRef, INSTALLATION_BEGINNING_PREDICATE) + .first(); + const endingRef = hqdm + .getRelated(instRef, INSTALLATION_ENDING_PREDICATE) + .first(); + const scContextRef = hqdm + .getRelated(instRef, INSTALLATION_SC_CONTEXT_PREDICATE) + .first(); + const systemContextRef = hqdm + .getRelated(instRef, INSTALLATION_SYSTEM_CONTEXT_PREDICATE) + .first(); + + if (targetRef) { + const installation: Installation = { + id: instId, + componentId: individual.id, + targetId: getModelId(targetRef), + beginning: beginningRef ? parseFloat(beginningRef.id) : undefined, + ending: endingRef ? parseFloat(endingRef.id) : undefined, + scInstallationContextId: scContextRef + ? getModelId(scContextRef) + : undefined, + systemContextId: systemContextRef + ? getModelId(systemContextRef) + : undefined, + }; + + installations.push(installation); + model.installations.set(instId, installation); + } + }); + + if (installations.length > 0) { + individual.installations = installations; + model.setIndividual(individual); + } + }); +}; diff --git a/editor-app/lib/IndividualImpl.ts b/editor-app/lib/IndividualImpl.ts index e66b1e6..583be02 100644 --- a/editor-app/lib/IndividualImpl.ts +++ b/editor-app/lib/IndividualImpl.ts @@ -1,5 +1,5 @@ -import type { Kind } from './Model.js'; -import type { Id, Individual } from './Schema.js'; +import type { Kind } from "./Model.js"; +import type { EntityType, Id, Individual, Installation } from "./Schema.js"; /** * A class that implements the Individual interface. @@ -7,22 +7,30 @@ import type { Id, Individual } from './Schema.js'; export class IndividualImpl implements Individual { id: Id; name: string; - type: Kind; + type?: Kind; description?: string; beginning: number; ending: number; beginsWithParticipant: boolean; endsWithParticipant: boolean; + entityType?: EntityType; + installations?: Installation[]; + _installationId?: string; + _nestingLevel?: number; + _isVirtualRow?: boolean; + _parentPath?: string; constructor( id: Id, name: string, - type: Kind, + type: Kind | undefined, beginning: number, ending: number, description?: string, beginsWithParticipant?: boolean, - endsWithParticipant?: boolean + endsWithParticipant?: boolean, + entityType?: EntityType, + installations?: Installation[] ) { this.id = id; this.name = name; @@ -38,5 +46,7 @@ export class IndividualImpl implements Individual { this.endsWithParticipant = endsWithParticipant ? endsWithParticipant : false; + this.entityType = entityType; + this.installations = installations; } } From 1d37ea280c6810a93f7b0af60ef2dd4a25b4bd8c Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 10:17:57 +0000 Subject: [PATCH 65/81] feat: Enhance ActivityDiagram and DrawInstallations for improved scrolling and installation period visualization --- editor-app/components/ActivityDiagram.tsx | 17 ++- editor-app/diagram/DrawInstallations.ts | 171 +++++++++++++++++----- editor-app/styles/globals.css | 31 ++++ 3 files changed, 183 insertions(+), 36 deletions(-) diff --git a/editor-app/components/ActivityDiagram.tsx b/editor-app/components/ActivityDiagram.tsx index f1edcac..add1623 100644 --- a/editor-app/components/ActivityDiagram.tsx +++ b/editor-app/components/ActivityDiagram.tsx @@ -103,11 +103,24 @@ const ActivityDiagram = (props: Props) => { return ( <> {crumbs} -
+
diff --git a/editor-app/diagram/DrawInstallations.ts b/editor-app/diagram/DrawInstallations.ts index 71ae016..a4763c4 100644 --- a/editor-app/diagram/DrawInstallations.ts +++ b/editor-app/diagram/DrawInstallations.ts @@ -1,5 +1,5 @@ import { DrawContext, keepIndividualLabels } from "./DrawHelpers"; -import { EntityType, Installation, Individual } from "@/lib/Schema"; +import { EntityType, Individual } from "@/lib/Schema"; import { Model } from "@/lib/Model"; // Helper to check if this is an "installation reference" (virtual row) @@ -28,30 +28,30 @@ function getInstallationId(ind: Individual): string | undefined { } /** - * Check if a SystemComponent (virtual row) has children installed in it. - * A SC has children if there are other components (SC or IC) with installations - * that target this SC AND have the matching scInstallationContextId. + * Get the time periods where children are installed in this SC virtual row. + * Returns an array of {start, end} objects representing when children exist. */ -function scHasChildren(ind: Individual, dataset: Model): boolean { - if (!ind._isVirtualRow) return false; +function getChildInstallationPeriods( + ind: Individual, + dataset: Model +): { start: number; end: number }[] { + if (!ind._isVirtualRow) return []; const originalId = getOriginalId(ind); const original = dataset.individuals.get(originalId); - if (!original) return false; + if (!original) return []; const origType = original.entityType ?? EntityType.Individual; - if (origType !== EntityType.SystemComponent) return false; + if (origType !== EntityType.SystemComponent) return []; // Get the installation ID for this virtual row const scInstallationId = ind._installationId || getInstallationId(ind); - if (!scInstallationId) return false; + if (!scInstallationId) return []; - // Check if any component has an installation targeting this SC with matching context - let hasChildren = false; + const periods: { start: number; end: number }[] = []; + // Find all components installed in this SC with matching context dataset.individuals.forEach((component) => { - if (hasChildren) return; // Early exit if already found - const compType = component.entityType ?? EntityType.Individual; if ( compType !== EntityType.SystemComponent && @@ -70,17 +70,18 @@ function scHasChildren(ind: Individual, dataset: Model): boolean { // For nested installations, the scInstallationContextId must match if (installation.scInstallationContextId === scInstallationId) { - hasChildren = true; - return; + const start = installation.beginning ?? 0; + const end = installation.ending ?? Model.END_OF_TIME; + periods.push({ start, end }); } } }); - return hasChildren; + return periods; } export function drawInstallations(ctx: DrawContext) { - const { svgElement, individuals, config, dataset } = ctx; + const { svgElement, individuals, config, dataset, activities } = ctx; if (!individuals || individuals.length === 0) return; @@ -120,11 +121,55 @@ export function drawInstallations(ctx: DrawContext) { .attr("stroke-width", 1); } - // For each SystemComponent virtual row that has children, draw a hatched overlay + // Calculate time range (same as in DrawParticipations) + let startOfTime = Math.min(...activities.map((a) => a.beginning)); + let endOfTime = Math.max(...activities.map((a) => a.ending)); + + if (activities.length === 0 || !isFinite(startOfTime)) { + startOfTime = 0; + } + if (activities.length === 0 || !isFinite(endOfTime)) { + endOfTime = 10; + } + + // Expand time range to include all individuals + individuals.forEach((ind) => { + if (ind.beginning >= 0 && ind.beginning < startOfTime) { + startOfTime = ind.beginning; + } + if (ind.ending < Model.END_OF_TIME && ind.ending > endOfTime) { + endOfTime = ind.ending; + } + }); + + if (endOfTime <= startOfTime) { + endOfTime = startOfTime + 10; + } + + const duration = Math.max(1, endOfTime - startOfTime); + let totalLeftMargin = + config.viewPort.x * config.viewPort.zoom - + config.layout.individual.xMargin * 2; + totalLeftMargin -= config.layout.individual.temporalMargin; + + if (config.labels.individual.enabled && keepIndividualLabels(individuals)) { + totalLeftMargin -= config.layout.individual.textLength; + } + + const timeInterval = totalLeftMargin / duration; + const xBase = + config.layout.individual.xMargin + + config.layout.individual.temporalMargin + + (config.labels.individual.enabled + ? config.layout.individual.textLength + : 0); + + // For each SystemComponent virtual row that has children, draw hatched overlays + // ONLY for the time periods where children are installed individuals.forEach((ind) => { if (!isInstallationRef(ind)) return; - // Only apply hatch to SystemComponent virtual rows that have children + // Only apply hatch to SystemComponent virtual rows const originalId = getOriginalId(ind); const original = dataset.individuals.get(originalId); if (!original) return; @@ -132,26 +177,84 @@ export function drawInstallations(ctx: DrawContext) { const entityType = original.entityType ?? EntityType.Individual; if (entityType !== EntityType.SystemComponent) return; - // Check if this SC has children installed in it - if (!scHasChildren(ind, dataset)) return; + // Get the periods where children are installed + const childPeriods = getChildInstallationPeriods(ind, dataset); + if (childPeriods.length === 0) return; - // Get the path data from the individual element + // Get the individual's row element to find Y position and height const escapedId = CSS.escape("i" + ind.id); const node = svgElement .select("#" + escapedId) - .node() as SVGPathElement | null; + .node() as SVGGraphicsElement | null; if (!node) return; - const pathData = node.getAttribute("d"); - if (!pathData) return; - - // Draw hatch overlay using the same path as the individual - svgElement - .append("path") - .attr("class", "installation-period") - .attr("d", pathData) - .attr("fill", "url(#diagonal-hatch)") - .attr("stroke", "none") - .attr("pointer-events", "none"); + const box = node.getBBox(); + const rowY = box.y; + const rowHeight = box.height; + + // Get the individual's visible time bounds (for clipping) + const indStart = ind.beginning >= 0 ? ind.beginning : startOfTime; + const indEnd = ind.ending < Model.END_OF_TIME ? ind.ending : endOfTime; + + // Draw a hatch rectangle for each child period + childPeriods.forEach((period) => { + // Clip period to the individual's visible time range AND the overall time range + const clipStart = Math.max(period.start, startOfTime, indStart); + const clipEnd = Math.min( + period.end < Model.END_OF_TIME ? period.end : endOfTime, + endOfTime, + indEnd + ); + + if (clipEnd <= clipStart) return; + + // Calculate X position and width for this period + const hatchX = xBase + timeInterval * (clipStart - startOfTime); + const hatchWidth = timeInterval * (clipEnd - clipStart); + + if (hatchWidth <= 0) return; + + // Create a unique clip path for this hatch to clip to the row's shape + const clipId = `hatch-clip-${ind.id.replace(/[^a-zA-Z0-9]/g, "_")}-${ + period.start + }-${period.end}`; + + // Remove existing clip path with same ID + defs.select(`#${clipId}`).remove(); + + // Get the path data from the individual element to use as clip + const pathData = (node as SVGPathElement).getAttribute?.("d"); + + if (pathData) { + // Create clip path using the individual's shape + const clipPath = defs.append("clipPath").attr("id", clipId); + clipPath.append("path").attr("d", pathData); + + // Draw hatch rectangle clipped to the individual's shape + svgElement + .append("rect") + .attr("class", "installation-period") + .attr("x", hatchX) + .attr("y", rowY) + .attr("width", hatchWidth) + .attr("height", rowHeight) + .attr("fill", "url(#diagonal-hatch)") + .attr("stroke", "none") + .attr("pointer-events", "none") + .attr("clip-path", `url(#${clipId})`); + } else { + // Fallback: use bounding box without clip path + svgElement + .append("rect") + .attr("class", "installation-period") + .attr("x", hatchX) + .attr("y", rowY) + .attr("width", hatchWidth) + .attr("height", rowHeight) + .attr("fill", "url(#diagonal-hatch)") + .attr("stroke", "none") + .attr("pointer-events", "none"); + } + }); }); } diff --git a/editor-app/styles/globals.css b/editor-app/styles/globals.css index 4c7b66e..efc3235 100644 --- a/editor-app/styles/globals.css +++ b/editor-app/styles/globals.css @@ -121,3 +121,34 @@ body { display: flex; flex-direction: column; } + +/* Diagram container scrolling */ +#activity-diagram-scrollable-div { + scrollbar-width: thin; + scrollbar-color: #888 #f1f1f1; +} + +#activity-diagram-scrollable-div::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +#activity-diagram-scrollable-div::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +#activity-diagram-scrollable-div::-webkit-scrollbar-thumb { + background: #888; + border-radius: 4px; +} + +#activity-diagram-scrollable-div::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Ensure the main content area doesn't overflow */ +.container-fluid { + max-height: 100vh; + overflow: hidden; +} From e32c35c12482888f7760cf9305cce5f46d9002f8 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 12:14:16 +0000 Subject: [PATCH 66/81] feat: Refactor components for improved styling and responsiveness in ActivityDiagram, DiagramLegend, and DiagramPersistence --- editor-app/components/ActivityDiagram.tsx | 4 +- editor-app/components/ActivityDiagramWrap.tsx | 2 - editor-app/components/DiagramLegend.tsx | 60 ++---- editor-app/components/DiagramPersistence.tsx | 107 ++++++---- editor-app/components/EntityTypeLegend.tsx | 39 +--- editor-app/styles/globals.css | 197 +++++++++++++++++- 6 files changed, 289 insertions(+), 120 deletions(-) diff --git a/editor-app/components/ActivityDiagram.tsx b/editor-app/components/ActivityDiagram.tsx index add1623..a80cd5c 100644 --- a/editor-app/components/ActivityDiagram.tsx +++ b/editor-app/components/ActivityDiagram.tsx @@ -1,7 +1,5 @@ import { useState, useEffect, MutableRefObject, JSX } from "react"; import Breadcrumb from "react-bootstrap/Breadcrumb"; -import Row from "react-bootstrap/Row"; -import Col from "react-bootstrap/Col"; import { drawActivityDiagram } from "@/diagram/DrawActivityDiagram"; import { ConfigData } from "@/diagram/config"; import { Model } from "@/lib/Model"; @@ -108,7 +106,7 @@ const ActivityDiagram = (props: Props) => { style={{ overflowX: "auto", overflowY: "auto", - maxHeight: "calc(100vh - 250px)", // Leave space for header, footer, and controls + maxHeight: "calc(100vh - 250px)", border: "1px solid #e0e0e0", borderRadius: "4px", backgroundColor: "#fafafa", diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index 2b19d0c..564b33c 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -388,7 +388,6 @@ export default function ActivityDiagramWrap() { - {/* InstalledComponent editor modal */} - {/* SystemComponent editor modal */} { const [hovered, setHovered] = useState(null); return ( - - - Activity Legend + + + Activity Legend {activities.map((activity, idx) => { const count = partsCount ? partsCount[activity.id] ?? 0 : 0; return (
-
+
- {count > 0 ? ( - - {activity.name}{" "} - - ({count} subtask{count !== 1 ? "s" : ""}) - - - ) : ( - {activity.name} - )} + + {count > 0 ? ( + <> + {activity.name}{" "} + + ({count} subtask{count !== 1 ? "s" : ""}) + + + ) : ( + activity.name + )} +
{/* open/edit button on the right */} -
+
{onOpenActivity ? ( onOpenActivity(activity)} aria-label={`Open ${activity.name}`} onMouseEnter={() => setHovered(activity.id)} @@ -82,21 +80,9 @@ const DiagramLegend = ({
); })} -
-
- - No Activity -
+
+ + No Activity
diff --git a/editor-app/components/DiagramPersistence.tsx b/editor-app/components/DiagramPersistence.tsx index d7bc1da..ffcdd0d 100644 --- a/editor-app/components/DiagramPersistence.tsx +++ b/editor-app/components/DiagramPersistence.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { Button, Container, Form } from "react-bootstrap"; -import Dropdown from 'react-bootstrap/Dropdown'; -import DropdownButton from 'react-bootstrap/DropdownButton'; +import Dropdown from "react-bootstrap/Dropdown"; +import DropdownButton from "react-bootstrap/DropdownButton"; import Row from "react-bootstrap/Row"; import Col from "react-bootstrap/Col"; @@ -27,25 +27,26 @@ const DiagramPersistence = (props: any) => { useEffect(() => { fetch("examples/index.json") - .then(res => { + .then((res) => { if (!res.ok) { console.log(`Fetching examples index failed: ${res.status}`); return; } return res.json(); }) - .then(json => { + .then((json) => { setExamples(json); }); }, []); function downloadTtl() { if (refDataOnly) { - saveFile(saveRefDataAsTTL(dataset), + saveFile( + saveRefDataAsTTL(dataset), dataset.filename.replace(/(\.[^.]*)?$/, "_ref_data$&"), - "text/turtle"); - } - else { + "text/turtle" + ); + } else { saveFile(save(dataset), dataset.filename, "text/turtle"); setDirty(false); } @@ -84,42 +85,62 @@ const DiagramPersistence = (props: any) => { } return ( - - - - - {examples.map(e => - loadExample(e.path)}> - {e.name} - )} - - - + {/* Load Example dropdown */} + + {examples.map((e) => ( + loadExample(e.path)}> + {e.name} + + ))} + + + {/* TTL Load/Save buttons with Reference Types toggle */} +
+ + + + {/* Reference Types Only toggle - styled as a pill/badge toggle */} +
setRefDataOnly(!refDataOnly)} > - - - {uploadText} - setRefDataOnly(!refDataOnly)} - /> - - - - - - + setRefDataOnly(!refDataOnly)} + style={{ marginRight: "8px" }} + /> + +
+
+ + {/* Error message if any */} + {uploadText && {uploadText}} +
); }; diff --git a/editor-app/components/EntityTypeLegend.tsx b/editor-app/components/EntityTypeLegend.tsx index f0d6a2d..5e8ad9b 100644 --- a/editor-app/components/EntityTypeLegend.tsx +++ b/editor-app/components/EntityTypeLegend.tsx @@ -54,40 +54,15 @@ export const entityTypes: EntityLegendItem[] = [ const EntityTypeLegend = () => { return ( - - - Entity Types + + + Entity Types {entityTypes.map((item, idx) => ( -
- +
+ {item.icon} {item.hasHatch && ( - + { )} - {item.label} + {item.label}
))} diff --git a/editor-app/styles/globals.css b/editor-app/styles/globals.css index efc3235..7181a7f 100644 --- a/editor-app/styles/globals.css +++ b/editor-app/styles/globals.css @@ -147,8 +147,199 @@ body { background: #555; } -/* Ensure the main content area doesn't overflow */ -.container-fluid { - max-height: 100vh; +/* Legend responsive styles */ +.legend-card { + min-width: 200px; + max-width: 280px; + transition: all 0.2s ease; +} + +.legend-body { + padding: 0.75rem; +} + +.legend-title { + font-size: 1rem; + margin-bottom: 0.5rem; +} + +.legend-item { + display: flex; + align-items: center; + margin-bottom: 0.25rem; + font-size: 0.875rem; +} + +.legend-icon-wrapper { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + margin-right: 6px; + font-size: 16px; + font-family: Arial, sans-serif; + position: relative; + flex-shrink: 0; +} + +.legend-hatch-overlay { + position: absolute; + top: 0; + left: 0; + width: 22px; + height: 22px; + pointer-events: none; +} + +.legend-color-box { + display: inline-block; + width: 14px; + height: 14px; + border-radius: 3px; + margin-right: 6px; + border: 1px solid #888; + flex-shrink: 0; +} + +.legend-label { + color: #111827; + white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; +} + +.legend-action-btn { + padding: 0.125rem 0.25rem; +} + +/* Medium screens (tablets, small laptops) */ +@media (max-width: 1200px) { + .legend-card { + min-width: 160px; + max-width: 220px; + } + + .legend-body { + padding: 0.5rem; + } + + .legend-title { + font-size: 0.9rem; + margin-bottom: 0.375rem; + } + + .legend-item { + font-size: 0.8rem; + margin-bottom: 0.2rem; + } + + .legend-icon-wrapper { + width: 18px; + height: 18px; + font-size: 14px; + margin-right: 4px; + } + + .legend-hatch-overlay { + width: 18px; + height: 18px; + } + + .legend-color-box { + width: 12px; + height: 12px; + margin-right: 4px; + } + + .legend-action-btn { + padding: 0.1rem 0.2rem; + } + + .legend-action-btn svg { + width: 14px; + height: 14px; + } +} + +/* Small screens (phones, very small laptops) */ +@media (max-width: 992px) { + .legend-card { + min-width: 140px; + max-width: 180px; + } + + .legend-body { + padding: 0.375rem; + } + + .legend-title { + font-size: 0.8rem; + margin-bottom: 0.25rem; + } + + .legend-item { + font-size: 0.75rem; + margin-bottom: 0.15rem; + } + + .legend-icon-wrapper { + width: 16px; + height: 16px; + font-size: 12px; + margin-right: 3px; + } + + .legend-hatch-overlay { + width: 16px; + height: 16px; + } + + .legend-color-box { + width: 10px; + height: 10px; + margin-right: 3px; + } + + .legend-action-btn { + padding: 0.05rem 0.15rem; + } + + .legend-action-btn svg { + width: 12px; + height: 12px; + } +} + +/* Extra small screens */ +@media (max-width: 768px) { + .legend-card { + min-width: 120px; + max-width: 150px; + } + + .legend-body { + padding: 0.25rem; + } + + .legend-title { + font-size: 0.75rem; + } + + .legend-item { + font-size: 0.7rem; + } + + .legend-icon-wrapper { + width: 14px; + height: 14px; + font-size: 11px; + margin-right: 2px; + } + + .legend-color-box { + width: 8px; + height: 8px; + margin-right: 2px; + } } From a19ed8a5f6453794a04130e90eb127e8fba4f81a Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 12:36:06 +0000 Subject: [PATCH 67/81] feat: Enhance ActivityDiagramWrap and DiagramLegend for improved layout and scrollable content --- editor-app/components/ActivityDiagramWrap.tsx | 30 +++-- editor-app/components/DiagramLegend.tsx | 119 ++++++++++-------- editor-app/styles/globals.css | 39 +++++- 3 files changed, 121 insertions(+), 67 deletions(-) diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index 564b33c..881e650 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -307,8 +307,10 @@ export default function ActivityDiagramWrap() { /> - - + + {/* All buttons on one row */} + + - + + {/* Center - Load/Save TTL */} + + + + + {/* Right side buttons */} + 0} undo={undo} @@ -376,16 +390,6 @@ export default function ActivityDiagramWrap() { - - - - - { const [hovered, setHovered] = useState(null); + return ( Activity Legend - {activities.map((activity, idx) => { - const count = partsCount ? partsCount[activity.id] ?? 0 : 0; - return ( -
-
- - - {count > 0 ? ( - <> - {activity.name}{" "} - - ({count} subtask{count !== 1 ? "s" : ""}) - - - ) : ( - activity.name - )} - -
- {/* open/edit button on the right */} -
- {onOpenActivity ? ( - - Open activity editor - - } - > - - - ) : null} + + + ) : null} +
-
- ); - })} + ); + })} +
+ + {/* No Activity item - outside scrollable area */}
No Activity diff --git a/editor-app/styles/globals.css b/editor-app/styles/globals.css index 7181a7f..9b9c0a0 100644 --- a/editor-app/styles/globals.css +++ b/editor-app/styles/globals.css @@ -213,7 +213,13 @@ body { padding: 0.125rem 0.25rem; } -/* Medium screens (tablets, small laptops) */ +/* Activity Legend scrollable content */ +.legend-items-container { + max-height: none; + overflow-y: visible; +} + +/* Medium screens (tablets, small laptops) - make legend scrollable */ @media (max-width: 1200px) { .legend-card { min-width: 160px; @@ -260,6 +266,28 @@ body { width: 14px; height: 14px; } + + /* Make activity legend scrollable when more than ~7 items */ + .legend-items-container { + max-height: 200px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #888 #f1f1f1; + } + + .legend-items-container::-webkit-scrollbar { + width: 6px; + } + + .legend-items-container::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 3px; + } + + .legend-items-container::-webkit-scrollbar-thumb { + background: #888; + border-radius: 3px; + } } /* Small screens (phones, very small laptops) */ @@ -309,6 +337,11 @@ body { width: 12px; height: 12px; } + + /* Smaller scroll area on small screens */ + .legend-items-container { + max-height: 160px; + } } /* Extra small screens */ @@ -342,4 +375,8 @@ body { height: 8px; margin-right: 2px; } + + .legend-items-container { + max-height: 140px; + } } From 345288a6ba0b733c74cc87b409aae950c2a4f656 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 12:42:05 +0000 Subject: [PATCH 68/81] feat: Refactor ActivityDiagramWrap and DiagramPersistence for improved layout and responsiveness; enhance visibility logic in HideIndividuals --- editor-app/components/ActivityDiagramWrap.tsx | 22 +- editor-app/components/DiagramPersistence.tsx | 78 +++--- editor-app/components/HideIndividuals.tsx | 236 ++++++++++-------- 3 files changed, 187 insertions(+), 149 deletions(-) diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index 881e650..9da34e8 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -308,9 +308,13 @@ export default function ActivityDiagramWrap() { - {/* All buttons on one row */} - - + {/* All buttons in a flex container that wraps */} +
+ {/* Left side buttons */} +
- +
{/* Center - Load/Save TTL */} - +
- +
{/* Right side buttons */} - +
0} undo={undo} @@ -388,8 +392,8 @@ export default function ActivityDiagramWrap() { /> - - +
+
{ } return ( -
+
{/* Load Example dropdown */} - + {examples.map((e) => ( loadExample(e.path)}> {e.name} @@ -95,47 +93,45 @@ const DiagramPersistence = (props: any) => { ))} - {/* TTL Load/Save buttons with Reference Types toggle */} -
- - + {/* TTL Load/Save buttons */} + + - {/* Reference Types Only toggle - styled as a pill/badge toggle */} -
setRefDataOnly(!refDataOnly)} + > + setRefDataOnly(!refDataOnly)} + style={{ marginRight: "6px" }} + /> +
+ Reference Types only +
{/* Error message if any */} diff --git a/editor-app/components/HideIndividuals.tsx b/editor-app/components/HideIndividuals.tsx index 93bd61d..0c289ae 100644 --- a/editor-app/components/HideIndividuals.tsx +++ b/editor-app/components/HideIndividuals.tsx @@ -12,96 +12,126 @@ interface Props { activitiesInView: Activity[]; } -// Helper to get parent IDs that should be kept visible (bottom-to-top only) -// When a CHILD has activity, keep its PARENTS visible -// But NOT the reverse - if only parent has activity, don't automatically keep children -function getParentIdsToKeep( +/** + * Determines which individuals should be visible based on activity participation. + * + * Rules: + * 1. If a virtual row (installation) participates, show: + * - The virtual row itself + * - Its parent System (not the top-level SC/IC definition) + * - Any intermediate SystemComponent virtual rows in the hierarchy + * + * 2. Top-level SystemComponent and InstalledComponent definitions should be HIDDEN + * if they have virtual rows that participate (since the virtual rows are shown instead) + * + * 3. Top-level SC/IC definitions should be SHOWN only if: + * - They have NO installations at all, OR + * - They directly participate (not through virtual rows) + * + * 4. Regular Individuals are shown if they participate + * + * 5. Systems are shown if any of their children (virtual rows) participate + */ +function getVisibilityInfo( participatingIds: Set, dataset: Model -): Set { - const parentsToKeep = new Set(); +): { + visibleIds: Set; + hiddenTopLevelIds: Set; +} { + const visibleIds = new Set(); + const hiddenTopLevelIds = new Set(); + // Track which top-level SC/ICs have participating virtual rows + const topLevelWithParticipatingVirtualRows = new Set(); + + // Track parent Systems that should be visible + const parentSystemsToShow = new Set(); + + // First pass: identify all participating IDs and their relationships participatingIds.forEach((id) => { - // Check if this is a virtual row (installation reference) if (id.includes("__installed_in__")) { + // This is a virtual row (installation reference) const parts = id.split("__installed_in__"); + const originalId = parts[0]; const rest = parts[1]; const targetId = rest.split("__")[0]; - // Virtual row has activity - keep its target (parent) visible - parentsToKeep.add(targetId); + // Mark the virtual row as visible + visibleIds.add(id); + + // Mark the original top-level component as having participating virtual rows + topLevelWithParticipatingVirtualRows.add(originalId); - // If target is a SystemComponent, also keep its parent System + // Find the parent System to keep visible const target = dataset.individuals.get(targetId); if (target) { const targetType = target.entityType ?? EntityType.Individual; - if (targetType === EntityType.SystemComponent && target.installations) { - target.installations.forEach((inst) => { - if (inst.targetId) { - parentsToKeep.add(inst.targetId); - } - }); + + if (targetType === EntityType.System) { + // Direct installation into System - show the System + parentSystemsToShow.add(targetId); + } else if (targetType === EntityType.SystemComponent) { + // Installation into SystemComponent - find the parent System + if (target.installations) { + target.installations.forEach((inst) => { + const parentTarget = dataset.individuals.get(inst.targetId); + if (parentTarget) { + const parentType = + parentTarget.entityType ?? EntityType.Individual; + if (parentType === EntityType.System) { + parentSystemsToShow.add(inst.targetId); + // Also show the intermediate SC virtual row + const scVirtualId = `${targetId}__installed_in__${inst.targetId}__${inst.id}`; + visibleIds.add(scVirtualId); + } + } + }); + } } } } else { - // Regular individual - check if it's an InstalledComponent or SystemComponent + // Regular individual or top-level entity participating directly + visibleIds.add(id); + + // If it's an SC or IC with installations, check if this is direct participation const individual = dataset.individuals.get(id); if (individual) { const entityType = individual.entityType ?? EntityType.Individual; - if (entityType === EntityType.InstalledComponent) { - // InstalledComponent has activity - keep parent SystemComponents and their parent Systems - if (individual.installations) { - individual.installations.forEach((inst) => { - if (inst.targetId) { - parentsToKeep.add(inst.targetId); - - // Get the SystemComponent's parent System - const sc = dataset.individuals.get(inst.targetId); - if (sc && sc.installations) { - sc.installations.forEach((scInst) => { - if (scInst.targetId) { - parentsToKeep.add(scInst.targetId); - } - }); - } - } - }); - } - } else if (entityType === EntityType.SystemComponent) { - // SystemComponent has activity - keep parent Systems - if (individual.installations) { - individual.installations.forEach((inst) => { - if (inst.targetId) { - parentsToKeep.add(inst.targetId); - } - }); - } + if (entityType === EntityType.System) { + parentSystemsToShow.add(id); } - // Note: If a System has activity, we do NOT automatically keep its children - // This is the "bottom-to-top only" behavior } } }); - return parentsToKeep; -} - -// Helper to check what should be visible -function getVisibleIds( - participatingIds: Set, - parentsToKeep: Set, - dataset: Model -): Set { - const visible = new Set(); - - // Add all direct participants - participatingIds.forEach((id) => visible.add(id)); - - // Add parents that should be kept - parentsToKeep.forEach((id) => visible.add(id)); + // Add parent Systems to visible + parentSystemsToShow.forEach((id) => visibleIds.add(id)); + + // Second pass: determine which top-level SC/ICs should be hidden + dataset.individuals.forEach((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + + if ( + entityType === EntityType.SystemComponent || + entityType === EntityType.InstalledComponent + ) { + // If this SC/IC has participating virtual rows, hide the top-level definition + if (topLevelWithParticipatingVirtualRows.has(ind.id)) { + hiddenTopLevelIds.add(ind.id); + visibleIds.delete(ind.id); // Remove from visible if it was added + } else if (ind.installations && ind.installations.length > 0) { + // Has installations but none participate - hide it + hiddenTopLevelIds.add(ind.id); + } else if (!participatingIds.has(ind.id)) { + // No installations and doesn't directly participate - hide it + hiddenTopLevelIds.add(ind.id); + } + } + }); - return visible; + return { visibleIds, hiddenTopLevelIds }; } const HideIndividuals = ({ @@ -110,61 +140,69 @@ const HideIndividuals = ({ dataset, activitiesInView, }: Props) => { - // Find all participating individual IDs (direct participants only) + // Find all participating individual IDs const participating = new Set(); activitiesInView.forEach((a) => a.participations.forEach((p: any) => participating.add(p.individualId)) ); - // Get parent IDs that should also be kept visible (bottom-to-top only) - const parentsToKeep = getParentIdsToKeep(participating, dataset); - - // Get all IDs that should be visible - const visibleIds = getVisibleIds(participating, parentsToKeep, dataset); - - // Find if there are entities with no activity in the current view - const hasInactiveEntities = (() => { - for (const i of Array.from(dataset.individuals.values())) { - // Check if this entity should be hidden - if (!visibleIds.has(i.id)) { - // For virtual rows, check if original participates - if (i.id.includes("__installed_in__")) { - const parts = i.id.split("__installed_in__"); - const originalId = parts[0]; - if (!participating.has(originalId) && !participating.has(i.id)) { - return true; - } - } else { - return true; - } + // Get visibility information + const { visibleIds, hiddenTopLevelIds } = getVisibilityInfo( + participating, + dataset + ); + + // Check if there are entities that would be hidden + const hasHideableEntities = (() => { + const displayIndividuals = dataset.getDisplayIndividuals(); + + for (const ind of displayIndividuals) { + // Skip if already visible + if (visibleIds.has(ind.id)) continue; + + // Check if this is a virtual row + if (ind.id.includes("__installed_in__")) { + // Virtual row not participating - can be hidden + return true; + } + + const entityType = ind.entityType ?? EntityType.Individual; + + // Top-level SC/IC with participating virtual rows - should be hidden + if (hiddenTopLevelIds.has(ind.id)) { + return true; + } + + // Regular individual or System not participating + if (!participating.has(ind.id)) { + return true; } } return false; })(); - if (!hasInactiveEntities) return null; + if (!hasHideableEntities) return null; const tooltip = compactMode ? ( - This will show entities with no activity. + Show all entities including those with no activity. ) : ( - This will hide entities with no activity. + Hide entities with no activity. Top-level component definitions are hidden + when their installations participate. ); return ( -
- - - -
+ + + ); }; From 33bc63fb09ef727e671281fc5caeaf81f490c106 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 13:34:29 +0000 Subject: [PATCH 69/81] feat: Implement compact mode filtering for individuals in ActivityDiagramWrap; enhance visibility logic in HideIndividuals --- editor-app/components/ActivityDiagramWrap.tsx | 178 ++++++++++-------- editor-app/components/DiagramPersistence.tsx | 4 +- editor-app/components/HideIndividuals.tsx | 161 ++-------------- editor-app/diagram/config.ts | 4 +- 4 files changed, 125 insertions(+), 222 deletions(-) diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index 9da34e8..37441f5 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -37,8 +37,83 @@ const beforeUnloadHandler = (ev: BeforeUnloadEvent) => { return; }; -/* XXX Most of this component needs refactoring into a Controller class, - * leaving the react component as just the View. */ +/** + * Filter individuals based on compact mode rules: + * 1. Top-level SC/IC with installations are ALWAYS hidden in compact mode + * 2. Virtual rows are shown only if they participate + * 3. Systems are shown if they or their children participate + * 4. Regular individuals are shown if they participate + */ +function filterIndividualsForCompactMode( + individuals: Individual[], + participatingIds: Set, + dataset: Model +): Individual[] { + // Track which Systems should be visible (because their children participate) + const parentSystemsToShow = new Set(); + + // First pass: find parent systems of participating virtual rows + participatingIds.forEach((id) => { + if (id.includes("__installed_in__")) { + const parts = id.split("__installed_in__"); + const rest = parts[1]; + const targetId = rest.split("__")[0]; + + const target = dataset.individuals.get(targetId); + if (target) { + const targetType = target.entityType ?? EntityType.Individual; + + if (targetType === EntityType.System) { + parentSystemsToShow.add(targetId); + } else if (targetType === EntityType.SystemComponent) { + // Find parent System of this SC + if (target.installations) { + target.installations.forEach((inst) => { + const parentTarget = dataset.individuals.get(inst.targetId); + if (parentTarget) { + const parentType = + parentTarget.entityType ?? EntityType.Individual; + if (parentType === EntityType.System) { + parentSystemsToShow.add(inst.targetId); + } + } + }); + } + } + } + } + }); + + return individuals.filter((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + const isVirtualRow = ind.id.includes("__installed_in__"); + + // Rule 1: Top-level SC/IC with installations - ALWAYS hidden + if ( + (entityType === EntityType.SystemComponent || + entityType === EntityType.InstalledComponent) && + !isVirtualRow && + ind.installations && + ind.installations.length > 0 + ) { + return false; + } + + // Rule 2: Virtual rows - show only if participating + if (isVirtualRow) { + return participatingIds.has(ind.id); + } + + // Rule 3: Systems - show if participating or if children participate + if (entityType === EntityType.System) { + return participatingIds.has(ind.id) || parentSystemsToShow.has(ind.id); + } + + // Rule 4: Regular individuals (and SC/IC without installations) - show if participating + return participatingIds.has(ind.id); + }); +} + export default function ActivityDiagramWrap() { // compactMode hides individuals that participate in zero activities const [compactMode, setCompactMode] = useState(false); @@ -69,7 +144,6 @@ export default function ActivityDiagramWrap() { const [selectedInstalledComponent, setSelectedInstalledComponent] = useState< Individual | undefined >(undefined); - // State for target slot ID (when clicking on a specific installation row) const [targetSlotId, setTargetSlotId] = useState( undefined ); @@ -80,45 +154,19 @@ export default function ActivityDiagramWrap() { const [selectedSystemComponent, setSelectedSystemComponent] = useState< Individual | undefined >(undefined); - // State for target system ID (when clicking on a specific installation row) const [targetSystemId, setTargetSystemId] = useState( undefined ); - // State for SystemComponent installation modal - const [ - showEditSystemComponentInstallation, - setShowEditSystemComponentInstallation, - ] = useState(false); - const [ - selectedIndividualForScInstallation, - setSelectedIndividualForScInstallation, - ] = useState(); - const [targetSystemIdForScInstallation, setTargetSystemIdForScInstallation] = - useState(); - - // State for InstalledComponent installation modal - const [showEditInstalledComponent, setShowEditInstalledComponent] = - useState(false); - const [ - selectedIndividualForIcInstallation, - setSelectedIndividualForIcInstallation, - ] = useState(); - const [targetSlotIdForIcInstallation, setTargetSlotIdForIcInstallation] = - useState(); - const [targetSystemIdForIcInstallation, setTargetSystemIdForIcInstallation] = - useState(); - - // Add callbacks for opening installation modals from SetIndividual const handleOpenSystemComponentInstallation = (individual: Individual) => { setSelectedSystemComponent(individual); - setTargetSystemId(undefined); // Show all systems + setTargetSystemId(undefined); setShowSystemComponentEditor(true); }; const handleOpenInstalledComponentInstallation = (individual: Individual) => { setSelectedInstalledComponent(individual); - setTargetSlotId(undefined); // Show all slots + setTargetSlotId(undefined); setTargetSystemId(undefined); setShowInstalledComponentEditor(true); }; @@ -135,7 +183,6 @@ export default function ActivityDiagramWrap() { setDataset(d); setDirty(true); }; - /* Callers of this function must also handle the dirty flag. */ const replaceDataset = (d: Model) => { setUndoHistory([]); setActivityContext(undefined); @@ -150,7 +197,6 @@ export default function ActivityDiagramWrap() { const svgRef = useRef(null); - // Build an array of individuals from the dataset const individualsArray = Array.from(dataset.individuals.values()); const deleteIndividual = (id: string) => { @@ -159,19 +205,14 @@ export default function ActivityDiagramWrap() { const setIndividual = (individual: Individual) => { updateDataset((d: Model) => d.addIndividual(individual)); }; - const deleteActivity = (id: string) => { - updateDataset((d: Model) => d.removeActivity(id)); - }; const setActivity = (activity: Activity) => { updateDataset((d: Model) => d.addActivity(activity)); }; const clickIndividual = (i: any) => { - // Check if this is a virtual row (installation reference) const isVirtual = i.id.includes("__installed_in__"); if (isVirtual) { - // This is a virtual row - open the installation editor const originalId = i.id.split("__installed_in__")[0]; const rest = i.id.split("__installed_in__")[1]; const parts = rest.split("__"); @@ -183,17 +224,13 @@ export default function ActivityDiagramWrap() { const entityType = originalIndividual.entityType ?? EntityType.Individual; if (entityType === EntityType.SystemComponent) { - // Virtual SystemComponent - open installation editor for this system setSelectedSystemComponent(originalIndividual); setTargetSystemId(targetId); setShowSystemComponentEditor(true); } else if (entityType === EntityType.InstalledComponent) { - // Virtual InstalledComponent - open installation editor for this slot - // Extract the system context from the virtual ID if present const contextMatch = i.id.match(/__ctx_([^_]+)$/); const contextId = contextMatch ? contextMatch[1] : undefined; - // Find the system ID from the context let systemId: string | undefined; if (contextId && targetId) { const targetSc = dataset.individuals.get(targetId); @@ -213,7 +250,6 @@ export default function ActivityDiagramWrap() { setShowInstalledComponentEditor(true); } } else { - // This is a top-level entity - open the entity editor const individual = dataset.individuals.get(i.id); if (!individual) return; @@ -243,26 +279,41 @@ export default function ActivityDiagramWrap() { ); }; - // Use the Model's getDisplayIndividuals method for sorting - const sortedIndividuals = useMemo(() => { - return dataset.getDisplayIndividuals(); - }, [dataset]); - - // Build an array of activities from the dataset so it can be filtered below const activitiesArray = Array.from(dataset.activities.values()); - // Filter activities for the current context let activitiesInView: Activity[] = []; if (activityContext) { - // Only include activities that are part of the current context activitiesInView = activitiesArray.filter( (a) => a.partOf === activityContext ); } else { - // Top-level activities (no parent) activitiesInView = activitiesArray.filter((a) => !a.partOf); } + // Get all participating IDs for current view + const participatingIds = useMemo(() => { + const ids = new Set(); + activitiesInView.forEach((a) => + a.participations.forEach((p) => ids.add(p.individualId)) + ); + return ids; + }, [activitiesInView]); + + // Get sorted individuals and apply compact mode filter + const sortedIndividuals = useMemo(() => { + const allIndividuals = dataset.getDisplayIndividuals(); + + if (compactMode) { + return filterIndividualsForCompactMode( + allIndividuals, + participatingIds, + dataset + ); + } + + return allIndividuals; + }, [dataset, compactMode, participatingIds]); + const partsCountMap: Record = {}; activitiesInView.forEach((a) => { partsCountMap[a.id] = @@ -418,26 +469,3 @@ export default function ActivityDiagramWrap() { ); } - -// Pass compactMode down to ActivityDiagram (already rendered above) -// Update ActivityDiagram invocation near top of file: - -/* - Replace the existing ActivityDiagram invocation with: - - - (The earlier invocation should be replaced so ActivityDiagram receives the prop.) -*/ diff --git a/editor-app/components/DiagramPersistence.tsx b/editor-app/components/DiagramPersistence.tsx index 04cae44..8294e65 100644 --- a/editor-app/components/DiagramPersistence.tsx +++ b/editor-app/components/DiagramPersistence.tsx @@ -85,7 +85,7 @@ const DiagramPersistence = (props: any) => { return (
{/* Load Example dropdown */} - + {examples.map((e) => ( loadExample(e.path)}> {e.name} @@ -103,7 +103,7 @@ const DiagramPersistence = (props: any) => { {/* Reference Types Only toggle */} - {/* open/edit button on the right */}
- {onOpenActivity ? ( + {onOpenActivity && ( - ) : null} + )}
); diff --git a/editor-app/components/DiagramPersistence.tsx b/editor-app/components/DiagramPersistence.tsx index 8294e65..c9368c0 100644 --- a/editor-app/components/DiagramPersistence.tsx +++ b/editor-app/components/DiagramPersistence.tsx @@ -101,14 +101,13 @@ const DiagramPersistence = (props: any) => { Save TTL - {/* Reference Types Only toggle */} -
setRefDataOnly(!refDataOnly)} > @@ -117,22 +116,20 @@ const DiagramPersistence = (props: any) => { id="refDataOnlyCheck" checked={refDataOnly} onChange={() => setRefDataOnly(!refDataOnly)} - style={{ marginRight: "6px" }} + style={{ + margin: 0, + marginRight: "0.35rem", + }} /> - -
+ + {/* Error message if any */} {uploadText && {uploadText}} diff --git a/editor-app/components/ExportJson.tsx b/editor-app/components/ExportJson.tsx index 8f10ec9..ad0ea94 100644 --- a/editor-app/components/ExportJson.tsx +++ b/editor-app/components/ExportJson.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import Button from "react-bootstrap/Button"; import { saveJSONLD } from "lib/ActivityLib"; @@ -6,16 +6,16 @@ const ExportJson = (props: any) => { const { dataset } = props; function downloadjson() { - let pom = document.createElement("a"); + const pom = document.createElement("a"); saveJSONLD(dataset, (obj) => { pom.setAttribute( "href", - "data:text/pldownloadain;charset=utf-8," + + "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(obj, null, 2)) ); pom.setAttribute("download", "activity_diagram.json"); if (document.createEvent) { - let event = document.createEvent("MouseEvents"); + const event = document.createEvent("MouseEvents"); event.initEvent("click", true, true); pom.dispatchEvent(event); } else { @@ -33,7 +33,7 @@ const ExportJson = (props: any) => { dataset.individuals.size > 0 ? "mx-1 d-block" : "mx-1 d-none" } > - Export JSON + Export JSON ); diff --git a/editor-app/components/ExportSvg.tsx b/editor-app/components/ExportSvg.tsx index 148c813..d4f07f0 100644 --- a/editor-app/components/ExportSvg.tsx +++ b/editor-app/components/ExportSvg.tsx @@ -1,25 +1,33 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import Button from "react-bootstrap/Button"; -import { saveJSONLD } from "lib/ActivityLib"; -const ExportJson = (props: any) => { +const ExportSvg = (props: any) => { const { dataset, svgRef } = props; - function serializeNode(node: any) { - var svgxml = new XMLSerializer().serializeToString(node); - return svgxml; + function serializeNode(node: SVGSVGElement | null): string { + if (!node) return ""; + const serializer = new XMLSerializer(); + return serializer.serializeToString(node); } - function downloadsvg(event: any) { - let pom = document.createElement("a"); - pom.setAttribute( - "href", - "data:image/svg+xml;base64," + btoa(serializeNode(svgRef.current)) - ); + // Safe base64 for UTF‑8 SVG + function toBase64Utf8(str: string): string { + return window.btoa(unescape(encodeURIComponent(str))); + } + + function downloadsvg() { + if (!svgRef?.current) return; + + const raw = serializeNode(svgRef.current); + + const base64 = toBase64Utf8(raw); + + const pom = document.createElement("a"); + pom.setAttribute("href", "data:image/svg+xml;base64," + base64); pom.setAttribute("download", "activity_diagram.svg"); if (document.createEvent) { - let event = document.createEvent("MouseEvents"); + const event = document.createEvent("MouseEvents"); event.initEvent("click", true, true); pom.dispatchEvent(event); } else { @@ -36,10 +44,10 @@ const ExportJson = (props: any) => { dataset.individuals.size > 0 ? "mx-1 d-block" : "mx-1 d-none" } > - Export SVG + Export SVG ); }; -export default ExportJson; +export default ExportSvg; From 2de4110480bc800ca359c1c224263880d8521914 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 14:48:21 +0000 Subject: [PATCH 72/81] feat: Enhance ExportSvg component to include activitiesInView and activityColors props for improved legend rendering --- editor-app/components/ActivityDiagramWrap.tsx | 7 +- editor-app/components/ExportSvg.tsx | 152 ++++++++++++++++-- 2 files changed, 142 insertions(+), 17 deletions(-) diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index 37441f5..c762dff 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -441,7 +441,12 @@ export default function ActivityDiagramWrap() { showConfigModal={showConfigModal} setShowConfigModal={setShowConfigModal} /> - +
diff --git a/editor-app/components/ExportSvg.tsx b/editor-app/components/ExportSvg.tsx index d4f07f0..eb5a4b9 100644 --- a/editor-app/components/ExportSvg.tsx +++ b/editor-app/components/ExportSvg.tsx @@ -1,8 +1,22 @@ import React from "react"; import Button from "react-bootstrap/Button"; +import { Activity } from "@/lib/Schema"; -const ExportSvg = (props: any) => { - const { dataset, svgRef } = props; +interface LegendResult { + content: string; + width: number; + height: number; +} + +interface Props { + dataset: any; + svgRef: React.RefObject; + activitiesInView?: Activity[]; + activityColors?: string[]; +} + +const ExportSvg = (props: Props) => { + const { dataset, svgRef, activitiesInView = [], activityColors = [] } = props; function serializeNode(node: SVGSVGElement | null): string { if (!node) return ""; @@ -10,17 +24,127 @@ const ExportSvg = (props: any) => { return serializer.serializeToString(node); } - // Safe base64 for UTF‑8 SVG + // Safe base64 for UTF-8 SVG function toBase64Utf8(str: string): string { return window.btoa(unescape(encodeURIComponent(str))); } + function escapeXml(str: string): string { + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } + + // Build legend SVG content + function buildLegendSvg(): LegendResult { + const legendWidth = 200; + const itemHeight = 20; + const padding = 10; + let y = padding; + + let legendContent = ""; + + // Entity Types Legend + const entityTypes = [ + { icon: "▣", label: "System" }, + { icon: "◇", label: "System Component" }, + { icon: "◆", label: "SC in System" }, + { icon: "◈", label: "SC in SC" }, + { icon: "⬡", label: "Installed Component" }, + { icon: "⬢", label: "IC in SC" }, + { icon: "○", label: "Individual" }, + ]; + + // Entity Types title + legendContent += `Entity Types`; + y += itemHeight + 5; + + entityTypes.forEach((item) => { + legendContent += `${ + item.icon + }`; + legendContent += `${ + item.label + }`; + y += itemHeight; + }); + + y += 15; // Gap between legends + + // Activity Legend title + legendContent += `Activity Legend`; + y += itemHeight + 5; + + // Activities + activitiesInView.forEach((activity, idx) => { + const color = activityColors[idx % activityColors.length] || "#ccc"; + legendContent += ``; + legendContent += `${escapeXml( + activity.name + )}`; + y += itemHeight; + }); + + // No Activity + legendContent += ``; + legendContent += `No Activity`; + y += itemHeight; + + const legendHeight = y + padding; + + return { content: legendContent, width: legendWidth, height: legendHeight }; + } + function downloadsvg() { if (!svgRef?.current) return; - const raw = serializeNode(svgRef.current); + const originalSvg = svgRef.current; + const viewBox = originalSvg.getAttribute("viewBox") || "0 0 1000 500"; + const [, , origWidth, origHeight] = viewBox.split(" ").map(Number); + + // Build legend + const legend = buildLegendSvg(); + const legendWidth = legend.width; + const legendHeight = legend.height; + + // Calculate new dimensions + const totalWidth = legendWidth + origWidth + 20; // 20px gap + const totalHeight = Math.max(legendHeight, origHeight); + + // Get original SVG content + const originalContent = originalSvg.innerHTML; + + // Build combined SVG + const combinedSvg = ` + + + + + ${legend.content} + + + + + ${originalContent} + +`; - const base64 = toBase64Utf8(raw); + const base64 = toBase64Utf8(combinedSvg); const pom = document.createElement("a"); pom.setAttribute("href", "data:image/svg+xml;base64," + base64); @@ -36,17 +160,13 @@ const ExportSvg = (props: any) => { } return ( - <> - - + ); }; From 88eced2c50fc3d13ee292da77e864e01bd261e58 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 15:12:46 +0000 Subject: [PATCH 73/81] feat: Refactor Footer and NavBar for improved layout; add ExportJsonLegends component for enhanced JSON export functionality --- editor-app/components/ActivityDiagramWrap.tsx | 5 ++ editor-app/components/ExportJsonLegends.tsx | 65 +++++++++++++++++++ editor-app/components/Footer.tsx | 57 ++++++++-------- editor-app/components/NavBar.tsx | 8 ++- 4 files changed, 104 insertions(+), 31 deletions(-) create mode 100644 editor-app/components/ExportJsonLegends.tsx diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index c762dff..cc27ee5 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -448,6 +448,11 @@ export default function ActivityDiagramWrap() { activityColors={config.presentation.activity.fill} /> + {/* */}
diff --git a/editor-app/components/ExportJsonLegends.tsx b/editor-app/components/ExportJsonLegends.tsx new file mode 100644 index 0000000..e6f00aa --- /dev/null +++ b/editor-app/components/ExportJsonLegends.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import Button from "react-bootstrap/Button"; +import { saveJSONLD } from "lib/ActivityLib"; +import { Activity } from "@/lib/Schema"; + +interface Props { + dataset: any; + activitiesInView?: Activity[]; + activityColors?: string[]; +} + +const ExportJson = (props: Props) => { + const { dataset, activitiesInView = [], activityColors = [] } = props; + + function downloadjson() { + const pom = document.createElement("a"); + saveJSONLD(dataset, (obj) => { + // Add legend metadata to the exported JSON + const exportData = { + ...obj, + _legend: { + entityTypes: [ + { type: "System", icon: "▣" }, + { type: "SystemComponent", icon: "◇" }, + { type: "SystemComponentInstalled", icon: "◆" }, + { type: "InstalledComponent", icon: "⬡" }, + { type: "InstalledComponentInSlot", icon: "⬢" }, + { type: "Individual", icon: "○" }, + ], + activities: activitiesInView.map((activity, idx) => ({ + id: activity.id, + name: activity.name, + color: activityColors[idx % activityColors.length] || "#ccc", + })), + }, + }; + + pom.setAttribute( + "href", + "data:text/plain;charset=utf-8," + + encodeURIComponent(JSON.stringify(exportData, null, 2)) + ); + pom.setAttribute("download", "activity_diagram.json"); + if (document.createEvent) { + const event = document.createEvent("MouseEvents"); + event.initEvent("click", true, true); + pom.dispatchEvent(event); + } else { + pom.click(); + } + }); + } + + return ( + + ); +}; + +export default ExportJson; diff --git a/editor-app/components/Footer.tsx b/editor-app/components/Footer.tsx index 324cabd..8ca0f1c 100644 --- a/editor-app/components/Footer.tsx +++ b/editor-app/components/Footer.tsx @@ -16,9 +16,9 @@ function Footer() { }} >
-
+
{/* Left - Links */} -
+
More
  • @@ -41,7 +41,7 @@ function Footer() {
{/* Center - Copyright */} -
+
{year} Apollo Protocol Activity Diagram Editor
Created by AMRC in collaboration with CIS
@@ -49,39 +49,40 @@ function Footer() {
- {/* Right - Logos */} -
-
-
- - - AMRC - - -
-
+ {/* Right - All Logos on same row */} +
+
+ CIS -
-
-
-
- Funded by + + + + CIS + + +
+ + Funded by + Innovate UK diff --git a/editor-app/components/NavBar.tsx b/editor-app/components/NavBar.tsx index 4ecc855..53717da 100644 --- a/editor-app/components/NavBar.tsx +++ b/editor-app/components/NavBar.tsx @@ -66,11 +66,13 @@ function CollapsibleExample() { id="activity-modelling-dropdown" className={isActivityModellingActive ? "active-dropdown" : ""} > - Introduction - + + Introduction + + Example Analysis - + Integrated Information Management From a34f9df173ba4e4040b1724a47de9e76059d8ea4 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 15:22:23 +0000 Subject: [PATCH 74/81] feat: Enhance NavBar dropdown styling and active item indication for improved user experience --- editor-app/components/NavBar.tsx | 19 ++++++++++++--- editor-app/styles/globals.css | 41 ++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/editor-app/components/NavBar.tsx b/editor-app/components/NavBar.tsx index 53717da..883ce19 100644 --- a/editor-app/components/NavBar.tsx +++ b/editor-app/components/NavBar.tsx @@ -65,14 +65,27 @@ function CollapsibleExample() { title="Activity Modelling" id="activity-modelling-dropdown" className={isActivityModellingActive ? "active-dropdown" : ""} + align="end" > - + Introduction - + Example Analysis - + Integrated Information Management diff --git a/editor-app/styles/globals.css b/editor-app/styles/globals.css index 6c73dc6..353a3ea 100644 --- a/editor-app/styles/globals.css +++ b/editor-app/styles/globals.css @@ -28,6 +28,7 @@ .navbar .nav-link { padding: 0.5rem 1rem !important; font-weight: 500; + font-size: 1rem; color: #495057 !important; transition: color 0.15s ease-in-out; position: relative; @@ -54,6 +55,13 @@ border-radius: 2px; } +/* Dropdown toggle styling - match nav-link */ +.navbar .dropdown-toggle { + font-weight: 500; + font-size: 1rem; + color: #495057 !important; +} + /* Dropdown styling - keep existing behavior */ .navbar .active-dropdown .dropdown-toggle { color: #495057 !important; @@ -77,19 +85,48 @@ border-radius: 2px; } +/* Dropdown menu - prevent cutting off on right edge */ .navbar .dropdown-menu { border: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); border-radius: 8px; + min-width: 250px; + right: 0; + left: auto; } +/* Dropdown items - match parent nav styling */ .navbar .dropdown-item { - padding: 0.5rem 1rem; - font-size: 0.9rem; + padding: 0.6rem 1rem; + font-size: 1rem; + font-weight: 500; + color: #495057; + position: relative; } .navbar .dropdown-item:hover { background-color: #f8f9fa; + color: #495057; +} + +/* Active dropdown item styling */ +.navbar .dropdown-item.active, +.navbar .dropdown-item:active { + background-color: transparent; + color: #495057; + font-weight: 500; +} + +/* Underline for active dropdown item */ +.navbar .dropdown-item.active::after { + content: ""; + position: absolute; + bottom: 0.3rem; + left: 1rem; + right: 1rem; + height: 3px; + background-color: #6c757d; + border-radius: 2px; } /* Footer link hover */ From 024baef968b0b59c1754ac653e7aead67a550790 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 15:57:10 +0000 Subject: [PATCH 75/81] feat: Refactor font size handling in labelIndividuals for improved scalability and readability --- .../components/EditInstalledComponent.tsx | 164 +++++++----------- editor-app/diagram/DrawIndividuals.ts | 81 ++++++--- 2 files changed, 126 insertions(+), 119 deletions(-) diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index c1cb0ec..b5e6aaf 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -17,35 +17,14 @@ interface Props { // Interface for slot options that includes nesting info interface SlotOption { - id: string; // Original SystemComponent ID - virtualId: string; // Virtual row ID - unique identifier for this specific slot instance + id: string; + virtualId: string; displayName: string; bounds: { beginning: number; ending: number }; - parentPath: string; // Path of parent IDs for context + parentPath: string; nestingLevel: number; systemName?: string; - scInstallationId?: string; // The SC installation ID for context -} - -// Helper to extract the SC installation ID from a virtual row ID -// Format: scId__installed_in__targetId__installationId or with __ctx_ -function extractInstallationIdFromVirtualId( - virtualId: string -): string | undefined { - if (!virtualId.includes("__installed_in__")) return undefined; - const parts = virtualId.split("__installed_in__"); - if (parts.length < 2) return undefined; - let rest = parts[1]; - - // Remove context suffix if present - const ctxIndex = rest.indexOf("__ctx_"); - if (ctxIndex !== -1) { - rest = rest.substring(0, ctxIndex); - } - - const restParts = rest.split("__"); - // restParts[0] is targetId, restParts[1] is installationId - return restParts.length > 1 ? restParts[1] : undefined; + scInstallationId?: string; } const EditInstalledComponent = (props: Props) => { @@ -73,26 +52,21 @@ const EditInstalledComponent = (props: Props) => { >(new Map()); const [showAll, setShowAll] = useState(false); - // Get available slots - now includes ALL SystemComponent virtual rows (including nested) + // Get available slots const availableSlots = useMemo((): SlotOption[] => { const slots: SlotOption[] = []; const displayIndividuals = dataset.getDisplayIndividuals(); displayIndividuals.forEach((ind) => { - // Only consider virtual rows (installed instances) if (!ind._isVirtualRow) return; - // Parse the virtual ID to get the original component ID const originalId = ind.id.split("__installed_in__")[0]; const original = dataset.individuals.get(originalId); if (!original) return; const origType = original.entityType ?? EntityType.Individual; - - // Only SystemComponents can be slots for InstalledComponents if (origType !== EntityType.SystemComponent) return; - // Build display name showing full hierarchy const pathParts = ind._parentPath?.split("__") || []; const pathNames: string[] = []; @@ -103,12 +77,9 @@ const EditInstalledComponent = (props: Props) => { } }); - // The display name shows the SC name and its full path const hierarchyStr = pathNames.length > 0 ? pathNames.join(" → ") : "Unknown"; const displayName = `${ind.name} (in ${hierarchyStr})`; - - // Extract the SC installation ID from the virtual ID const scInstallationId = ind._installationId; slots.push({ @@ -118,36 +89,29 @@ const EditInstalledComponent = (props: Props) => { bounds: { beginning: ind.beginning, ending: ind.ending }, parentPath: ind._parentPath || "", nestingLevel: ind._nestingLevel || 1, - systemName: pathNames[0], // Root system name + systemName: pathNames[0], scInstallationId: scInstallationId, }); }); - // Sort by system name, then by parent path (to maintain hierarchy), then by name slots.sort((a, b) => { - // First by system name if (a.systemName !== b.systemName) { return (a.systemName || "").localeCompare(b.systemName || ""); } - // Then by parent path to keep hierarchy together if (a.parentPath !== b.parentPath) { - // If one is nested under the other, parent comes first if (a.parentPath.startsWith(b.parentPath + "__")) return 1; if (b.parentPath.startsWith(a.parentPath + "__")) return -1; return a.parentPath.localeCompare(b.parentPath); } - // Then by nesting level if (a.nestingLevel !== b.nestingLevel) { return a.nestingLevel - b.nestingLevel; } - // Finally by display name return a.displayName.localeCompare(b.displayName); }); return slots; }, [dataset]); - // Group slots by system for the dropdown - MOVED BEFORE any conditional returns const slotsBySystem = useMemo(() => { const groups = new Map(); availableSlots.forEach((slot) => { @@ -160,30 +124,25 @@ const EditInstalledComponent = (props: Props) => { return groups; }, [availableSlots]); - // Helper to get slot option by virtualId (unique key) const getSlotOptionByVirtualId = ( virtualId: string ): SlotOption | undefined => { return availableSlots.find((slot) => slot.virtualId === virtualId); }; - // Helper to get slot option by targetId and scInstContextId const getSlotOption = ( targetId: string, scInstContextId?: string ): SlotOption | undefined => { return availableSlots.find((slot) => { if (slot.id !== targetId) return false; - // If we have a context ID, must match exactly if (scInstContextId) { return slot.scInstallationId === scInstContextId; } - // If no context, return first matching slot return true; }); }; - // Helper function to get effective slot time bounds const getSlotTimeBounds = ( slotId: string, scInstContextId?: string @@ -197,7 +156,6 @@ const EditInstalledComponent = (props: Props) => { }; } - // Fallback to original behavior const slot = dataset.individuals.get(slotId); if (!slot) { return { beginning: 0, ending: Model.END_OF_TIME, slotName: slotId }; @@ -246,7 +204,11 @@ const EditInstalledComponent = (props: Props) => { allInst.forEach((inst) => { inputs.set(inst.id, { beginning: String(inst.beginning ?? 0), - ending: String(inst.ending ?? 10), + // Use empty string for undefined/END_OF_TIME to show placeholder + ending: + inst.ending === undefined || inst.ending >= Model.END_OF_TIME + ? "" + : String(inst.ending), }); }); setRawInputs(inputs); @@ -273,7 +235,7 @@ const EditInstalledComponent = (props: Props) => { localInstallations.forEach((inst, idx) => { const raw = rawInputs.get(inst.id); const beginningStr = raw?.beginning ?? String(inst.beginning); - const endingStr = raw?.ending ?? String(inst.ending); + const endingStr = raw?.ending ?? ""; const slotInfo = inst.targetId ? getSlotTimeBounds(inst.targetId, inst.scInstallationContextId) @@ -293,29 +255,32 @@ const EditInstalledComponent = (props: Props) => { newErrors.push(`${slotName}: "From" time is required.`); } - if (endingStr.trim() === "") { - newErrors.push(`${slotName}: "Until" time is required.`); - } + // "Until" is now optional - empty means infinity const beginning = parseInt(beginningStr, 10); - const ending = parseInt(endingStr, 10); + // Parse ending: empty string means END_OF_TIME + const ending = + endingStr.trim() === "" ? Model.END_OF_TIME : parseInt(endingStr, 10); - if (!isNaN(beginning) && !isNaN(ending)) { + if (!isNaN(beginning)) { if (beginning < 0) { newErrors.push(`${slotName}: "From" cannot be negative.`); } - if (ending < 1) { - newErrors.push(`${slotName}: "Until" must be at least 1.`); - } - if (beginning >= ending) { - newErrors.push(`${slotName}: "From" must be less than "Until".`); - } if (beginning < slotInfo.beginning) { newErrors.push( `${slotName}: "From" (${beginning}) cannot be before slot starts (${slotInfo.beginning}).` ); } + } + + if (endingStr.trim() !== "" && !isNaN(ending)) { + if (ending < 1) { + newErrors.push(`${slotName}: "Until" must be at least 1.`); + } + if (!isNaN(beginning) && beginning >= ending) { + newErrors.push(`${slotName}: "From" must be less than "Until".`); + } if (slotInfo.ending < Model.END_OF_TIME && ending > slotInfo.ending) { newErrors.push( `${slotName}: "Until" (${ending}) cannot be after slot ends (${slotInfo.ending}).` @@ -330,8 +295,10 @@ const EditInstalledComponent = (props: Props) => { if (!inst.targetId) return; const raw = rawInputs.get(inst.id); const beginning = parseInt(raw?.beginning ?? String(inst.beginning), 10); - const ending = parseInt(raw?.ending ?? String(inst.ending), 10); - if (isNaN(beginning) || isNaN(ending)) return; + const endingStr = raw?.ending ?? ""; + const ending = + endingStr.trim() === "" ? Model.END_OF_TIME : parseInt(endingStr, 10); + if (isNaN(beginning)) return; const key = `${inst.targetId}__${inst.scInstallationContextId || "any"}`; const list = bySlotAndContext.get(key) || []; @@ -352,9 +319,17 @@ const EditInstalledComponent = (props: Props) => { for (let i = 0; i < installations.length - 1; i++) { const current = installations[i]; const next = installations[i + 1]; - if ((current.ending ?? 0) > (next.beginning ?? 0)) { + const currentEnding = current.ending ?? Model.END_OF_TIME; + const nextBeginning = next.beginning ?? 0; + if (currentEnding > nextBeginning) { + const currentEndingStr = + currentEnding >= Model.END_OF_TIME ? "∞" : currentEnding; + const nextEndingStr = + (next.ending ?? Model.END_OF_TIME) >= Model.END_OF_TIME + ? "∞" + : next.ending; newErrors.push( - `${slotInfo.slotName}: Periods overlap (${current.beginning}-${current.ending} and ${next.beginning}-${next.ending}).` + `${slotInfo.slotName}: Periods overlap (${current.beginning}-${currentEndingStr} and ${nextBeginning}-${nextEndingStr}).` ); } } @@ -373,11 +348,13 @@ const EditInstalledComponent = (props: Props) => { const updatedInstallations = localInstallations.map((inst) => { const raw = rawInputs.get(inst.id); + const endingStr = raw?.ending ?? ""; return { ...inst, beginning: parseInt(raw?.beginning ?? String(inst.beginning), 10) || 0, - ending: parseInt(raw?.ending ?? String(inst.ending), 10) || 10, + // Empty ending means undefined (infinity) + ending: endingStr.trim() === "" ? undefined : parseInt(endingStr, 10), }; }); @@ -447,17 +424,13 @@ const EditInstalledComponent = (props: Props) => { } const defaultBeginning = slotOption?.bounds.beginning ?? 0; - const defaultEnding = - slotOption && slotOption.bounds.ending < Model.END_OF_TIME - ? slotOption.bounds.ending - : defaultBeginning + 10; const newInst: Installation = { id: uuidv4(), componentId: individual?.id || "", targetId: targetSlotId || "", beginning: defaultBeginning, - ending: defaultEnding, + ending: undefined, // Default to undefined (infinity) scInstallationContextId: slotOption?.scInstallationId, }; @@ -466,7 +439,7 @@ const EditInstalledComponent = (props: Props) => { const next = new Map(prev); next.set(newInst.id, { beginning: String(defaultBeginning), - ending: String(defaultEnding), + ending: "", // Empty string for infinity }); return next; }); @@ -508,7 +481,7 @@ const EditInstalledComponent = (props: Props) => { ) => { setRawInputs((prev) => { const next = new Map(prev); - const current = next.get(instId) || { beginning: "0", ending: "10" }; + const current = next.get(instId) || { beginning: "0", ending: "" }; next.set(instId, { ...current, [field]: value }); return next; }); @@ -518,6 +491,9 @@ const EditInstalledComponent = (props: Props) => { if (!isNaN(parsed)) { updateInstallation(instId, field, parsed); } + } else if (field === "ending") { + // Empty ending means undefined + updateInstallation(instId, field, undefined); } if (errors.length > 0) { @@ -533,10 +509,14 @@ const EditInstalledComponent = (props: Props) => { if (!inst || !inst.targetId) return false; const raw = rawInputs.get(instId); - const value = parseInt( - field === "beginning" ? raw?.beginning ?? "" : raw?.ending ?? "", - 10 - ); + const rawValue = field === "beginning" ? raw?.beginning : raw?.ending; + + // Empty ending is valid (infinity) + if (field === "ending" && (!rawValue || rawValue.trim() === "")) { + return false; + } + + const value = parseInt(rawValue ?? "", 10); if (isNaN(value)) return false; const slotBounds = getSlotTimeBounds( @@ -551,7 +531,6 @@ const EditInstalledComponent = (props: Props) => { } }; - // Early return AFTER all hooks if (!individual) return null; const isFiltered = !!targetSlotId && !showAll; @@ -585,8 +564,8 @@ const EditInstalledComponent = (props: Props) => {

{isFiltered - ? `Manage installation periods for this component. You can have multiple non-overlapping periods.` - : `Manage all installation periods for this component across different slots.`} + ? `Manage installation periods for this component. You can have multiple non-overlapping periods. Leave "Until" empty for indefinite.` + : `Manage all installation periods for this component across different slots. Leave "Until" empty for indefinite (∞).`} {!isFiltered && availableSlots.length === 0 && ( <> {" "} @@ -610,9 +589,7 @@ const EditInstalledComponent = (props: Props) => { From * - - Until * - + Until { localInstallations.map((inst, idx) => { const raw = rawInputs.get(inst.id) || { beginning: String(inst.beginning), - ending: String(inst.ending), + ending: "", }; const slotOption = inst.targetId @@ -696,20 +673,13 @@ const EditInstalledComponent = (props: Props) => { selectedSlot.scInstallationId ); - const newEnding = - selectedSlot.bounds.ending < Model.END_OF_TIME - ? selectedSlot.bounds.ending - : selectedSlot.bounds.beginning + 10; updateRawInput( inst.id, "beginning", String(selectedSlot.bounds.beginning) ); - updateRawInput( - inst.id, - "ending", - String(newEnding) - ); + // Leave ending empty (infinity) by default + updateRawInput(inst.id, "ending", ""); } }} className={!inst.targetId ? "border-warning" : ""} @@ -794,12 +764,8 @@ const EditInstalledComponent = (props: Props) => { onChange={(e) => updateRawInput(inst.id, "ending", e.target.value) } - placeholder={String(instSlotBounds?.ending ?? 10)} - className={ - raw.ending === "" || endingOutOfBounds - ? "border-danger" - : "" - } + placeholder="∞" + className={endingOutOfBounds ? "border-danger" : ""} isInvalid={endingOutOfBounds} /> {endingOutOfBounds && instSlotBounds && ( diff --git a/editor-app/diagram/DrawIndividuals.ts b/editor-app/diagram/DrawIndividuals.ts index 0b6c169..ea635f8 100644 --- a/editor-app/diagram/DrawIndividuals.ts +++ b/editor-app/diagram/DrawIndividuals.ts @@ -647,6 +647,65 @@ export function labelIndividuals(ctx: DrawContext) { const CHAR_WIDTH_ESTIMATE = 6; // Approximate width per character in pixels const MIN_CHARS = 6; // Minimum characters to show before truncating + // Parse the base font size from config (handles formats like "0.8em", "12px", "12") + const parseConfigFontSize = ( + fontSize: string + ): { value: number; unit: string } => { + const match = fontSize.match(/^([\d.]+)(em|px|rem|pt)?$/i); + if (match) { + return { + value: parseFloat(match[1]), + unit: match[2]?.toLowerCase() || "px", + }; + } + // Fallback for unrecognized formats + return { value: 12, unit: "px" }; + }; + + const baseFontConfig = parseConfigFontSize(config.labels.individual.fontSize); + + // Nested elements use 75% of the base font size + const NESTED_FONT_SCALE = 0.75; + + // Helper to get font size based on nesting level (derived from config) + const getFontSize = (ind: Individual): string => { + const nestingLevel = ind._nestingLevel ?? getNestingLevel(ind); + if (nestingLevel > 0) { + // For nested entities, use scaled-down version of config font size + const nestedValue = baseFontConfig.value * NESTED_FONT_SCALE; + return `${nestedValue}${baseFontConfig.unit}`; + } + return config.labels.individual.fontSize; + }; + + // Helper to get numeric font size in pixels for calculations + const getNumericFontSize = (ind: Individual): number => { + const nestingLevel = ind._nestingLevel ?? getNestingLevel(ind); + + // Convert config font size to approximate pixel value for calculations + let basePx: number; + switch (baseFontConfig.unit) { + case "em": + case "rem": + // Assume 1em ≈ 16px (browser default) + basePx = baseFontConfig.value * 16; + break; + case "pt": + // 1pt ≈ 1.333px + basePx = baseFontConfig.value * 1.333; + break; + case "px": + default: + basePx = baseFontConfig.value; + break; + } + + if (nestingLevel > 0) { + return basePx * NESTED_FONT_SCALE; + } + return basePx; + }; + // Calculate time range (same as in drawIndividuals) let startOfTime = Math.min(...activities.map((a) => a.beginning)); let endOfTime = Math.max(...activities.map((a) => a.ending)); @@ -721,25 +780,6 @@ export function labelIndividuals(ctx: DrawContext) { return activityX; }; - // Helper to get font size based on nesting level - const getFontSize = (ind: Individual): string => { - const nestingLevel = ind._nestingLevel ?? getNestingLevel(ind); - if (nestingLevel > 0) { - // For nested entities, use smaller font - return "0.6em"; - } - return config.labels.individual.fontSize; - }; - - // Helper to get numeric font size for calculations - const getNumericFontSize = (ind: Individual): number => { - const nestingLevel = ind._nestingLevel ?? getNestingLevel(ind); - if (nestingLevel > 0) { - return 9; // Smaller font for nested - } - return 12; // Default font size - }; - // Helper to wrap text into multiple lines (handles long words without spaces) const wrapText = (text: string, maxCharsPerLine: number): string[] => { const words = text.split(/\s+/); @@ -880,7 +920,8 @@ export function labelIndividuals(ctx: DrawContext) { } else { // Nested: wrap text to multiple lines const lines = wrapText(label, MAX_CHARS_PER_LINE_NESTED); - const lineHeight = getNumericFontSize(ind) * 1.2; + const numericFontSize = getNumericFontSize(ind); + const lineHeight = numericFontSize * 1.2; const totalHeight = lines.length * lineHeight; // Center the text block vertically within the row From 6208884d8120d7929dca00a92a984debb7f39282 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Tue, 2 Dec 2025 16:20:53 +0000 Subject: [PATCH 76/81] feat: Enhance SortIndividuals component to support sorting of regular Individuals and preserve user-defined order in Model --- editor-app/components/SortIndividuals.tsx | 150 ++++++++++++++++++---- editor-app/lib/Model.ts | 41 ++---- 2 files changed, 136 insertions(+), 55 deletions(-) diff --git a/editor-app/components/SortIndividuals.tsx b/editor-app/components/SortIndividuals.tsx index 7c3b28e..658d808 100644 --- a/editor-app/components/SortIndividuals.tsx +++ b/editor-app/components/SortIndividuals.tsx @@ -1,22 +1,49 @@ -import { Individual } from "@/lib/Schema"; +import { Individual, EntityType } from "@/lib/Schema"; import React, { useState, useEffect } from "react"; import { SortableList } from "@/components/SortableList/SortableList"; import Button from "react-bootstrap/Button"; import Modal from "react-bootstrap/Modal"; +import { Model } from "@/lib/Model"; -const SortIndividuals = (props: any) => { +interface Props { + dataset: Model; + updateDataset: (updater: (d: Model) => void) => void; + showSortIndividuals: boolean; + setShowSortIndividuals: (show: boolean) => void; +} + +const SortIndividuals = (props: Props) => { const { dataset, updateDataset, showSortIndividuals, setShowSortIndividuals, } = props; - const [items, setItems] = useState([]); + + // Only regular Individuals (not System, SC, IC) + const [sortableItems, setSortableItems] = useState([]); + + // Items that won't be sorted (hierarchical entities) + const [fixedItems, setFixedItems] = useState([]); useEffect(() => { - const individualsArray: Individual[] = []; - dataset.individuals.forEach((i: Individual) => individualsArray.push(i)); - setItems(individualsArray); + const sortable: Individual[] = []; + const fixed: Individual[] = []; + + dataset.individuals.forEach((ind: Individual) => { + const entityType = ind.entityType ?? EntityType.Individual; + + if (entityType === EntityType.Individual) { + // Regular individuals can be sorted + sortable.push(ind); + } else { + // Systems, SystemComponents, InstalledComponents maintain hierarchy + fixed.push(ind); + } + }); + + setSortableItems(sortable); + setFixedItems(fixed); }, [dataset]); const handleClose = () => { @@ -24,11 +51,23 @@ const SortIndividuals = (props: any) => { }; const handleSave = () => { - let individualsMap = new Map(); - items.forEach((i) => { - individualsMap.set(i.id, i); + // Rebuild the individuals map preserving the order: + // 1. Fixed items (Systems, SCs, ICs) in their original order + // 2. Sortable items (Individuals) in the new user-defined order + updateDataset((d: Model) => { + // Clear the existing individuals map + d.individuals.clear(); + + // First add fixed items in their original order + fixedItems.forEach((ind) => { + d.individuals.set(ind.id, ind); + }); + + // Then add sortable items in the new order + sortableItems.forEach((ind) => { + d.individuals.set(ind.id, ind); + }); }); - updateDataset((d: any) => (d.individuals = individualsMap)); }; const handleSaveAndClose = () => { @@ -36,42 +75,99 @@ const SortIndividuals = (props: any) => { handleClose(); }; + // Only show button if there are multiple sortable individuals + const showButton = sortableItems.length > 1; + return ( <> - Sort Entities + Sort Individuals -

- ( - - {item.name} - - - )} - /> -
+ {sortableItems.length === 0 ? ( +

+ No regular Individuals to sort. Systems, System Components, and + Installed Components are organized hierarchically and cannot be + manually sorted. +

+ ) : ( + <> +

+ Drag to reorder regular Individuals. Systems, System Components, + and Installed Components maintain their hierarchical order. +

+
+ ( + + + {item.name} + + + )} + /> +
+ + )} + + {fixedItems.length > 0 && ( +
+

+ Hierarchical entities (not sortable): +

+
    + {fixedItems.slice(0, 5).map((ind) => { + const entityType = ind.entityType ?? EntityType.Individual; + let icon = "○"; + if (entityType === EntityType.System) icon = "▣"; + else if (entityType === EntityType.SystemComponent) + icon = "◇"; + else if (entityType === EntityType.InstalledComponent) + icon = "⬡"; + + return ( +
  • + {icon} + {ind.name} +
  • + ); + })} + {fixedItems.length > 5 && ( +
  • + ...and {fixedItems.length - 5} more +
  • + )} +
+
+ )} - - diff --git a/editor-app/lib/Model.ts b/editor-app/lib/Model.ts index 88f2a51..d884141 100644 --- a/editor-app/lib/Model.ts +++ b/editor-app/lib/Model.ts @@ -632,8 +632,6 @@ export class Model { this.installations.delete(id); } - // ... existing code ... - /** * Check for circular references in installation hierarchy. * @@ -760,6 +758,7 @@ export class Model { /** * Get the list of individuals to display in the diagram. * Handles nested SystemComponent → SystemComponent → System hierarchies. + * Preserves user-defined order for regular Individuals. */ getDisplayIndividuals(): Individual[] { const result: Individual[] = []; @@ -864,7 +863,7 @@ export class Model { } }); - // Sort systems by name + // Sort systems by name (hierarchical entities are sorted alphabetically) systems.sort((a, b) => a.name.localeCompare(b.name)); // Add each system and IMMEDIATELY add its nested virtual rows @@ -894,6 +893,7 @@ export class Model { } } }); + // Sort SCs alphabetically (hierarchical entities) topLevelSCs.sort((a, b) => a.name.localeCompare(b.name)); topLevelSCs.forEach((ind) => { processedIds.add(ind.id); @@ -915,6 +915,7 @@ export class Model { } } }); + // Sort ICs alphabetically (hierarchical entities) topLevelICs.sort((a, b) => a.name.localeCompare(b.name)); topLevelICs.forEach((ind) => { processedIds.add(ind.id); @@ -926,38 +927,22 @@ export class Model { }); }); - // STEP 4: Add regular Individuals - const regularIndividuals: Individual[] = []; + // STEP 4: Add regular Individuals - PRESERVE MAP ORDER (user-defined sort) + // Do NOT sort alphabetically - the Map order is the user's custom order this.individuals.forEach((ind) => { const entityType = ind.entityType ?? EntityType.Individual; if (entityType === EntityType.Individual) { if (!processedIds.has(ind.id)) { - regularIndividuals.push(ind); + processedIds.add(ind.id); + result.push({ + ...ind, + _isVirtualRow: false, + _parentPath: "", + _nestingLevel: 0, + }); } } }); - regularIndividuals.sort((a, b) => a.name.localeCompare(b.name)); - regularIndividuals.forEach((ind) => { - processedIds.add(ind.id); - result.push({ - ...ind, - _isVirtualRow: false, - _parentPath: "", - _nestingLevel: 0, - }); - }); - - // NO SORTING NEEDED - items are added in correct order: - // 1. Sys 1 (System) - // - Sys comp 1 (virtual row under Sys 1) - // - Inst comp 1 (virtual row under Sys comp 1 under Sys 1) - // 2. Sys 2 (System) - // - Sys comp 2 (virtual row under Sys 2) - // 3. Sys comp 1 (top-level definition) - // 4. Sys comp 2 (top-level definition) - // 5. Inst comp 1 (top-level definition) - // 6. Inst comp 2 (top-level definition) - // 7. Egg, Me, Pan... (regular Individuals) return result; } From a799639817ddbf461475f5d0110fe549c11ad974 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Wed, 3 Dec 2025 09:26:29 +0000 Subject: [PATCH 77/81] feat: Implement autosave functionality for ActivityDiagramWrap; add load and save methods to manage localStorage --- editor-app/components/ActivityDiagramWrap.tsx | 40 ++++++++++++++++ editor-app/lib/ActivityLib.ts | 48 +++++++++++++++++-- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index cc27ee5..5621f3c 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -30,6 +30,7 @@ import DiagramLegend from "./DiagramLegend"; import EditInstalledComponent from "./EditInstalledComponent"; import EditSystemComponentInstallation from "./EditSystemComponentInstallation"; import EntityTypeLegend from "./EntityTypeLegend"; +import { load, save } from "@/lib/ActivityLib"; const beforeUnloadHandler = (ev: BeforeUnloadEvent) => { ev.returnValue = ""; @@ -119,6 +120,8 @@ export default function ActivityDiagramWrap() { const [compactMode, setCompactMode] = useState(false); const model = new Model(); const [dataset, setDataset] = useState(model); + // Add a state to track initialization + const [isInitialized, setIsInitialized] = useState(false); const [dirty, setDirty] = useState(false); const [activityContext, setActivityContext] = useState>(undefined); const [undoHistory, setUndoHistory] = useState([]); @@ -176,6 +179,43 @@ export default function ActivityDiagramWrap() { else window.removeEventListener("beforeunload", beforeUnloadHandler); }, [dirty]); + // 1. Load diagram from localStorage on mount + useEffect(() => { + // Ensure we are in the browser + if (typeof window !== "undefined") { + const savedTtl = localStorage.getItem("4d-activity-editor-autosave"); + if (savedTtl) { + try { + const loadedModel = load(savedTtl); + if (loadedModel instanceof Model) { + setDataset(loadedModel); + setUndoHistory([]); // Clear undo history on load + } + } catch (err) { + console.error("Failed to load autosave:", err); + } + } + setIsInitialized(true); + } + }, []); + + // 2. Save diagram to localStorage whenever dataset changes + useEffect(() => { + if (!isInitialized) return; + + // Debounce save to avoid performance hit on every small change + const timer = setTimeout(() => { + try { + const ttl = save(dataset); + localStorage.setItem("4d-activity-editor-autosave", ttl); + } catch (err) { + console.error("Failed to autosave:", err); + } + }, 1000); + + return () => clearTimeout(timer); + }, [dataset, isInitialized]); + const updateDataset = (updater: Dispatch) => { setUndoHistory([dataset, ...undoHistory.slice(0, 5)]); const d = dataset.clone(); diff --git a/editor-app/lib/ActivityLib.ts b/editor-app/lib/ActivityLib.ts index de3dad4..10d571a 100644 --- a/editor-app/lib/ActivityLib.ts +++ b/editor-app/lib/ActivityLib.ts @@ -200,6 +200,7 @@ const INSTALLATION_ENDING_PREDICATE = `${EDITOR_NS}#installationEnding`; const INSTALLATION_SC_CONTEXT_PREDICATE = `${EDITOR_NS}#installationSCContext`; const INSTALLATION_SYSTEM_CONTEXT_PREDICATE = `${EDITOR_NS}#installationSystemContext`; const PARTICIPATION_VIRTUAL_ROW_PREDICATE = `${EDITOR_NS}#participationVirtualRowId`; +const SORT_INDEX_PREDICATE = `${EDITOR_NS}#sortIndex`; /** * Converts an HQDMModel to a UI Model. @@ -239,21 +240,57 @@ export const toModel = (hqdm: HQDMModel): Model => { } // STEP 1: Load all individuals FIRST + // Collect all candidates first so we can sort them by index + const candidates: { thing: Thing; kind: Kind }[] = []; + hqdm.findByType(ordinary_physical_object).forEach((obj) => { const kind = hqdm .memberOfKind(obj) .filter(isKindOfOrdinaryPhysicalObject) .first(); const kindOfIndividual = kindOrDefault(kind, ordPhysObjKind); - addIndividual(obj, hqdm, communityName, kindOfIndividual, m); + candidates.push({ thing: obj, kind: kindOfIndividual }); }); hqdm.findByType(person).forEach((persona) => { - addIndividual(persona, hqdm, communityName, personKind, m); + candidates.push({ thing: persona, kind: personKind }); }); hqdm.findByType(organization).forEach((org) => { - addIndividual(org, hqdm, communityName, organizationKind, m); + candidates.push({ thing: org, kind: organizationKind }); + }); + + // Helper to get sort index + const getSortIndex = (thing: Thing): number | undefined => { + const ref = hqdm.getRelated(thing, SORT_INDEX_PREDICATE).first(); + if (ref) { + const val = parseInt(ref.id, 10); + return isNaN(val) ? undefined : val; + } + return undefined; + }; + + // Sort candidates by index + candidates.sort((a, b) => { + const idxA = getSortIndex(a.thing); + const idxB = getSortIndex(b.thing); + + if (idxA !== undefined && idxB !== undefined) { + return idxA - idxB; + } + // If only one has index, prioritize it (though usually all or none will have it) + if (idxA !== undefined) return -1; + if (idxB !== undefined) return 1; + + // Fallback to name for deterministic order if no index exists + const nameA = hqdm.getEntityName(a.thing) || ""; + const nameB = hqdm.getEntityName(b.thing) || ""; + return nameA.localeCompare(nameB); + }); + + // Add sorted individuals to model + candidates.forEach((c) => { + addIndividual(c.thing, hqdm, communityName, c.kind, m); }); // STEP 2: Load installations BEFORE activities @@ -500,6 +537,7 @@ export const toHQDM = (model: Model): HQDMModel => { }; // Add the individuals to the model + let sortIndex = 0; model.individuals.forEach((i) => { // Create the individual and add it to the possible world, add the name and description. let playerEntityType; @@ -516,6 +554,10 @@ export const toHQDM = (model: Model): HQDMModel => { const player = hqdm.createThing(playerEntityType, BASE + i.id); hqdm.addToPossibleWorld(player, modelWorld); + // Save sort index to preserve order + hqdm.relate(SORT_INDEX_PREDICATE, player, new Thing(sortIndex.toString())); + sortIndex++; + const individualStart = createTimeValue(hqdm, modelWorld, i.beginning); const individualEnd = createTimeValue(hqdm, modelWorld, i.ending); From de548acc5efd22db3795ee8905bedf589a6e2998 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Wed, 3 Dec 2025 09:57:17 +0000 Subject: [PATCH 78/81] feat: Add activity highlighting feature in ActivityDiagram and DiagramLegend; update ActivityDiagramWrap to manage highlighted activity state --- editor-app/components/ActivityDiagram.tsx | 38 ++++++ editor-app/components/ActivityDiagramWrap.tsx | 8 ++ editor-app/components/DiagramLegend.tsx | 38 +++++- editor-app/components/SetActivity.tsx | 112 +++++++----------- 4 files changed, 125 insertions(+), 71 deletions(-) diff --git a/editor-app/components/ActivityDiagram.tsx b/editor-app/components/ActivityDiagram.tsx index a80cd5c..be94bd4 100644 --- a/editor-app/components/ActivityDiagram.tsx +++ b/editor-app/components/ActivityDiagram.tsx @@ -19,6 +19,7 @@ interface Props { svgRef: MutableRefObject; hideNonParticipating: boolean; sortedIndividuals?: Individual[]; + highlightedActivityId?: string | null; } const ActivityDiagram = (props: Props) => { @@ -36,6 +37,7 @@ const ActivityDiagram = (props: Props) => { svgRef, hideNonParticipating, sortedIndividuals, + highlightedActivityId, } = props; const [plot, setPlot] = useState({ @@ -60,6 +62,41 @@ const ActivityDiagram = (props: Props) => { sortedIndividuals ) ); + + // Apply highlighting logic directly to the SVG elements + const svg = svgRef.current; + if (svg) { + const allActivities = svg.querySelectorAll(".activity"); + + if (highlightedActivityId) { + // Dim all activities + allActivities.forEach((el: SVGElement) => { + el.style.opacity = "0.15"; + el.style.stroke = ""; + el.style.strokeWidth = ""; + }); + + // Highlight the selected one + // IDs are typically "a" + UUID + const targetId = "a" + highlightedActivityId; + const target = svg.querySelector("#" + CSS.escape(targetId)); + + if (target) { + target.style.opacity = "1"; + target.style.stroke = "#000"; + target.style.strokeWidth = "2px"; + // Bring to front + target.parentNode?.appendChild(target); + } + } else { + // Reset styles if nothing highlighted + allActivities.forEach((el: SVGElement) => { + el.style.opacity = ""; + el.style.stroke = ""; + el.style.strokeWidth = ""; + }); + } + } }, [ dataset, configData, @@ -73,6 +110,7 @@ const ActivityDiagram = (props: Props) => { rightClickParticipation, hideNonParticipating, sortedIndividuals, + highlightedActivityId, // Added dependency ]); const buildCrumbs = () => { diff --git a/editor-app/components/ActivityDiagramWrap.tsx b/editor-app/components/ActivityDiagramWrap.tsx index 5621f3c..16222f0 100644 --- a/editor-app/components/ActivityDiagramWrap.tsx +++ b/editor-app/components/ActivityDiagramWrap.tsx @@ -141,6 +141,11 @@ export default function ActivityDiagramWrap() { const [showConfigModal, setShowConfigModal] = useState(false); const [showSortIndividuals, setShowSortIndividuals] = useState(false); + // NEW: State for highlighting activity from legend + const [highlightedActivityId, setHighlightedActivityId] = useState< + string | null + >(null); + // State for the InstalledComponent editor const [showInstalledComponentEditor, setShowInstalledComponentEditor] = useState(false); @@ -378,6 +383,8 @@ export default function ActivityDiagramWrap() { setSelectedActivity(a); setShowActivity(true); }} + highlightedActivityId={highlightedActivityId} + onHighlightActivity={setHighlightedActivityId} /> @@ -395,6 +402,7 @@ export default function ActivityDiagramWrap() { svgRef={svgRef} hideNonParticipating={compactMode} sortedIndividuals={sortedIndividuals} + highlightedActivityId={highlightedActivityId} /> diff --git a/editor-app/components/DiagramLegend.tsx b/editor-app/components/DiagramLegend.tsx index f004466..1a108cf 100644 --- a/editor-app/components/DiagramLegend.tsx +++ b/editor-app/components/DiagramLegend.tsx @@ -12,16 +12,17 @@ interface Props { activityColors: string[]; partsCount?: Record; onOpenActivity?: (a: Activity) => void; + highlightedActivityId?: string | null; + onHighlightActivity?: (id: string | null) => void; } -////typescript -// filepath: c:\Users\me1meg\Documents\4d-activity-editor\editor-app\components\DiagramLegend.tsx -// ...existing code... const DiagramLegend = ({ activities, activityColors, partsCount, onOpenActivity, + highlightedActivityId, + onHighlightActivity, }: Props) => { const [hovered, setHovered] = useState(null); const [searchTerm, setSearchTerm] = useState(""); @@ -60,7 +61,12 @@ const DiagramLegend = ({ return ( - Activity Legend + + Activity Legend{" "} + + (click for diagram highlight) + + {/* Search input - only show if there are more than 5 activities */} {activities.length > 5 && ( @@ -91,10 +97,29 @@ const DiagramLegend = ({ (a) => a.id === activity.id ); const count = partsCount ? partsCount[activity.id] ?? 0 : 0; + const isHighlighted = highlightedActivityId === activity.id; + return (
+ onHighlightActivity && + onHighlightActivity(isHighlighted ? null : activity.id) + } + title="Click to highlight in diagram" >
onOpenActivity(activity)} + onClick={(e) => { + e.stopPropagation(); // Prevent triggering highlight + onOpenActivity(activity); + }} aria-label={`Open ${activity.name}`} onMouseEnter={() => setHovered(activity.id)} onMouseLeave={() => setHovered(null)} diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index 7e717e7..3a1d2d6 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -925,80 +925,60 @@ const SetActivity = (props: Props) => { - -
-
- - - + + {selectedActivity && ( +
+
+
+ + +
+
+ + + +
+
-
- - + )} + +
+
+ {errors.length > 0 && ( + + {errors.map((error, i) => ( +
{error}
+ ))} +
+ )}
-
-
-
- -
-
- {errors.length > 0 && ( - - {errors.map((error, i) => ( -

- {error} -

- ))} -
- )} -
From b2edd466a562db1c6db2981ccdc834073500cda3 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Wed, 3 Dec 2025 13:32:41 +0000 Subject: [PATCH 79/81] feat: Implement fixed axis rendering in ActivityDiagram; add scroll tracking and wrapper height measurement --- editor-app/components/ActivityDiagram.tsx | 222 ++++++++++++++++++++-- editor-app/diagram/DrawActivities.ts | 3 +- editor-app/diagram/DrawAxis.ts | 114 ++--------- 3 files changed, 227 insertions(+), 112 deletions(-) diff --git a/editor-app/components/ActivityDiagram.tsx b/editor-app/components/ActivityDiagram.tsx index be94bd4..c49ce2c 100644 --- a/editor-app/components/ActivityDiagram.tsx +++ b/editor-app/components/ActivityDiagram.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, MutableRefObject, JSX } from "react"; +import { useState, useEffect, MutableRefObject, JSX, useRef } from "react"; import Breadcrumb from "react-bootstrap/Breadcrumb"; import { drawActivityDiagram } from "@/diagram/DrawActivityDiagram"; import { ConfigData } from "@/diagram/config"; @@ -45,6 +45,49 @@ const ActivityDiagram = (props: Props) => { height: 0, }); + const scrollContainerRef = useRef(null); + const wrapperRef = useRef(null); // NEW: Ref for the outer wrapper + const [wrapperHeight, setWrapperHeight] = useState(0); // NEW: State for wrapper height + const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 }); + + // Track scroll position for axis positioning + const handleScroll = () => { + if (scrollContainerRef.current) { + setScrollPosition({ + x: scrollContainerRef.current.scrollLeft, + y: scrollContainerRef.current.scrollTop, + }); + } + }; + + useEffect(() => { + const container = scrollContainerRef.current; + if (container) { + container.addEventListener("scroll", handleScroll); + return () => container.removeEventListener("scroll", handleScroll); + } + }, []); + + // NEW: Measure wrapper height to draw axis correctly + useEffect(() => { + if (!wrapperRef.current) return; + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + setWrapperHeight(entry.contentRect.height); + } + }); + resizeObserver.observe(wrapperRef.current); + return () => resizeObserver.disconnect(); + }, []); + + useEffect(() => { + const container = scrollContainerRef.current; + if (container) { + container.addEventListener("scroll", handleScroll); + return () => container.removeEventListener("scroll", handleScroll); + } + }, []); + useEffect(() => { setPlot( drawActivityDiagram( @@ -77,7 +120,6 @@ const ActivityDiagram = (props: Props) => { }); // Highlight the selected one - // IDs are typically "a" + UUID const targetId = "a" + highlightedActivityId; const target = svg.querySelector("#" + CSS.escape(targetId)); @@ -85,7 +127,6 @@ const ActivityDiagram = (props: Props) => { target.style.opacity = "1"; target.style.stroke = "#000"; target.style.strokeWidth = "2px"; - // Bring to front target.parentNode?.appendChild(target); } } else { @@ -110,7 +151,7 @@ const ActivityDiagram = (props: Props) => { rightClickParticipation, hideNonParticipating, sortedIndividuals, - highlightedActivityId, // Added dependency + highlightedActivityId, ]); const buildCrumbs = () => { @@ -136,28 +177,183 @@ const ActivityDiagram = (props: Props) => { }; const crumbs: JSX.Element[] = buildCrumbs(); + // Axis configuration + const axisMargin = configData.presentation.axis.margin; + const axisWidth = configData.presentation.axis.width; + const axisColour = configData.presentation.axis.colour; + const axisEndMargin = configData.presentation.axis.endMargin; + + // Calculate visible dimensions + // Use measured wrapper height, fallback to calculation if 0 (initial render) + const containerHeight = + wrapperHeight || Math.min(plot.height, window.innerHeight - 250); + const bottomAxisHeight = axisMargin + 30; + return ( <> {crumbs}
- + + {/* Y-Axis arrow */} + + + + + + + {/* Y-Axis label "Space" */} + + Space + + +
+ + {/* Fixed X-Axis (Time) - bottom */} +
+ + {/* X-Axis arrow */} + + + + + + + {/* X-Axis label "Time" */} + + Time + + +
+ + {/* Corner piece to cover overlap */} +
+ + {/* Scrollable diagram content */} +
+ +
); diff --git a/editor-app/diagram/DrawActivities.ts b/editor-app/diagram/DrawActivities.ts index 7dff5ca..b673b9a 100644 --- a/editor-app/diagram/DrawActivities.ts +++ b/editor-app/diagram/DrawActivities.ts @@ -91,7 +91,8 @@ export function drawActivities(ctx: DrawContext) { svgElement .selectAll(".activity") - .data(validActivities) + // FIX: Add key function (a.id) to ensure data binds to correct element even if reordered + .data(validActivities, (a: Activity) => a.id) .join("rect") .attr("class", "activity") .attr("id", (a: Activity) => "a" + a["id"]) diff --git a/editor-app/diagram/DrawAxis.ts b/editor-app/diagram/DrawAxis.ts index 92f912b..f0f69e4 100644 --- a/editor-app/diagram/DrawAxis.ts +++ b/editor-app/diagram/DrawAxis.ts @@ -2,103 +2,21 @@ import { ConfigData } from "./config"; import { DrawContext } from "./DrawHelpers"; export function drawAxisArrows(ctx: DrawContext, viewPortHeight: number) { - const { config, svgElement } = ctx; - //Define arrow head - svgElement - .append("svg:defs") - .attr("class", "axisTriangle") - .append("svg:marker") - .attr("id", "triangle") - .attr("refX", 0) - .attr("refY", 10) - .attr("markerWidth", 20) - .attr("markerHeight", 20) - .attr("markerUnits", "userSpaceOnUse") - .attr("orient", "auto") - .attr("viewbox", "0 0 20 20") - .append("path") - .attr("d", "M 0 0 L 20 10 L 0 20 z") - .style("fill", config.presentation.axis.colour); - - //X Axis arrow - svgElement - .append("line") - .attr("class", "axisLine") - .attr("x1", config.presentation.axis.margin) - .attr("y1", viewPortHeight - config.presentation.axis.margin) - .attr( - "x2", - config.viewPort.x * config.viewPort.zoom - - config.presentation.axis.endMargin - ) - .attr("y2", viewPortHeight - config.presentation.axis.margin) - .attr("marker-end", "url(#triangle)") - .attr("stroke", config.presentation.axis.colour) - .attr("stroke-width", config.presentation.axis.width); - - //X Axis text - svgElement - .append("text") - .attr("class", "axisLable") - .attr( - "x", - (config.viewPort.x * config.viewPort.zoom) / 2 - - config.presentation.axis.endMargin - ) - .attr( - "y", - viewPortHeight - - config.presentation.axis.margin + - config.presentation.axis.textOffsetX - ) - .attr("stroke", "white") - .attr("fill", "white") - .attr("font-size", "0.8em") - .attr("font-weight", "200") - .attr("text-anchor", "start") - .attr("font-family", "Roboto, Arial, sans-serif") - .text("Time"); - - //Y Axis arrow - svgElement - .append("line") - .attr("class", "axisLine") - .attr("x1", config.presentation.axis.margin) - .attr( - "y1", - viewPortHeight - - config.presentation.axis.margin + - config.presentation.axis.width / 2 - ) - .attr("x2", config.presentation.axis.margin) - .attr("y2", config.presentation.axis.endMargin) - .attr("marker-end", "url(#triangle)") - .attr("stroke", config.presentation.axis.colour) - .attr("stroke-width", config.presentation.axis.width); + // Axis arrows are now rendered as fixed overlays in the React component + // This function is kept for API compatibility but does nothing + return; +} - //Y Axis text - svgElement - .append("text") - .attr("class", "axisLable") - .attr( - "x", - config.presentation.axis.margin * 2 + config.presentation.axis.textOffsetY - ) - .attr("y", viewPortHeight / 2 + config.presentation.axis.margin) - .attr("stroke", "white") - .attr("fill", "white") - .attr("font-size", "0.8em") - .attr("font-weight", "200") - .attr("text-anchor", "middle") - .attr("font-family", "Roboto, Arial, sans-serif") - .attr( - "transform", - "rotate(270 " + - (config.presentation.axis.margin + - config.presentation.axis.textOffsetY) + - " " + - (viewPortHeight / 2 + config.presentation.axis.margin) + - ")" - ) - .text("Space"); +// New function to get axis configuration for the fixed overlay +export function getAxisConfig(config: ConfigData, viewPortHeight: number) { + return { + margin: config.presentation.axis.margin, + endMargin: config.presentation.axis.endMargin, + width: config.presentation.axis.width, + colour: config.presentation.axis.colour, + textOffsetX: config.presentation.axis.textOffsetX, + textOffsetY: config.presentation.axis.textOffsetY, + viewPortWidth: config.viewPort.x * config.viewPort.zoom, + viewPortHeight, + }; } From 06c8b6eed9f25e6dfc84b63b8c57db756b29df3d Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Wed, 3 Dec 2025 15:01:33 +0000 Subject: [PATCH 80/81] feat: Enhance activity highlighting in ActivityDiagram; improve participation rect handling and add bounding box visualization --- editor-app/components/ActivityDiagram.tsx | 78 +++++++++++++++---- .../components/EditInstalledComponent.tsx | 47 ++++++++--- .../EditSystemComponentInstallation.tsx | 24 +++++- editor-app/components/ExportSvg.tsx | 69 ++++++++++++++-- editor-app/components/SetActivity.tsx | 5 +- editor-app/diagram/DrawActivities.ts | 22 ++++-- 6 files changed, 206 insertions(+), 39 deletions(-) diff --git a/editor-app/components/ActivityDiagram.tsx b/editor-app/components/ActivityDiagram.tsx index c49ce2c..aaeb3c1 100644 --- a/editor-app/components/ActivityDiagram.tsx +++ b/editor-app/components/ActivityDiagram.tsx @@ -106,36 +106,88 @@ const ActivityDiagram = (props: Props) => { ) ); - // Apply highlighting logic directly to the SVG elements + // Apply highlighting logic to participation rects (the actual visible colored blocks) const svg = svgRef.current; if (svg) { - const allActivities = svg.querySelectorAll(".activity"); + // Target participation-rect elements (the visible colored blocks) + const allParticipationRects = svg.querySelectorAll(".participation-rect"); if (highlightedActivityId) { - // Dim all activities - allActivities.forEach((el: SVGElement) => { + // Dim all participation rects + allParticipationRects.forEach((el: SVGElement) => { el.style.opacity = "0.15"; el.style.stroke = ""; el.style.strokeWidth = ""; }); - // Highlight the selected one - const targetId = "a" + highlightedActivityId; - const target = svg.querySelector("#" + CSS.escape(targetId)); + // Track bounding box of all highlighted rects + let minX = Infinity, + minY = Infinity, + maxX = -Infinity, + maxY = -Infinity; + let foundHighlighted = false; - if (target) { - target.style.opacity = "1"; - target.style.stroke = "#000"; - target.style.strokeWidth = "2px"; - target.parentNode?.appendChild(target); + // Highlight participation rects belonging to the selected activity + // Participation rect IDs are in format: p_{activityId}_{individualId}_{segStart}_{segEnd} + allParticipationRects.forEach((el: SVGElement) => { + const elId = el.getAttribute("id") || ""; + + // Check if this participation rect belongs to the highlighted activity + // ID format: p_{activityId}_{rest...} + if (elId.startsWith("p_" + highlightedActivityId + "_")) { + el.style.opacity = "1"; + el.style.stroke = "#000"; + el.style.strokeWidth = "2px"; + // Bring to front + el.parentNode?.appendChild(el); + + // Calculate bounding box from this element + const rect = el as SVGGraphicsElement; + const bbox = rect.getBBox?.(); + if (bbox) { + foundHighlighted = true; + minX = Math.min(minX, bbox.x); + minY = Math.min(minY, bbox.y); + maxX = Math.max(maxX, bbox.x + bbox.width); + maxY = Math.max(maxY, bbox.y + bbox.height); + } + } + }); + + // Remove any existing highlight borders first + svg + .querySelectorAll(".highlight-border") + .forEach((el: Element) => el.remove()); + + // Draw a dashed border around the calculated bounding box + if (foundHighlighted && minX < Infinity) { + const ns = "http://www.w3.org/2000/svg"; + const highlightRect = document.createElementNS(ns, "rect"); + highlightRect.setAttribute("class", "highlight-border"); + highlightRect.setAttribute("x", String(minX - 3)); + highlightRect.setAttribute("y", String(minY - 3)); + highlightRect.setAttribute("width", String(maxX - minX + 6)); + highlightRect.setAttribute("height", String(maxY - minY + 6)); + highlightRect.setAttribute("fill", "none"); + highlightRect.setAttribute("stroke", "#000000"); + highlightRect.setAttribute("stroke-width", "2"); + highlightRect.setAttribute("stroke-dasharray", "6,3"); + highlightRect.setAttribute("rx", "6"); + highlightRect.setAttribute("pointer-events", "none"); + svg.appendChild(highlightRect); } } else { // Reset styles if nothing highlighted - allActivities.forEach((el: SVGElement) => { + allParticipationRects.forEach((el: SVGElement) => { el.style.opacity = ""; el.style.stroke = ""; el.style.strokeWidth = ""; }); + + // Remove any highlight borders + svg + .querySelectorAll(".highlight-border") + .forEach((el: Element) => el.remove()); } } }, [ diff --git a/editor-app/components/EditInstalledComponent.tsx b/editor-app/components/EditInstalledComponent.tsx index b5e6aaf..58fc6a8 100644 --- a/editor-app/components/EditInstalledComponent.tsx +++ b/editor-app/components/EditInstalledComponent.tsx @@ -255,12 +255,12 @@ const EditInstalledComponent = (props: Props) => { newErrors.push(`${slotName}: "From" time is required.`); } - // "Until" is now optional - empty means infinity + // "Until" is now optional - empty means inherit from parent const beginning = parseInt(beginningStr, 10); - // Parse ending: empty string means END_OF_TIME + // Parse ending: empty string means inherit from parent slot const ending = - endingStr.trim() === "" ? Model.END_OF_TIME : parseInt(endingStr, 10); + endingStr.trim() === "" ? slotInfo.ending : parseInt(endingStr, 10); if (!isNaN(beginning)) { if (beginning < 0) { @@ -281,6 +281,7 @@ const EditInstalledComponent = (props: Props) => { if (!isNaN(beginning) && beginning >= ending) { newErrors.push(`${slotName}: "From" must be less than "Until".`); } + // Allow ending to be equal to slot ending (changed from > to >) if (slotInfo.ending < Model.END_OF_TIME && ending > slotInfo.ending) { newErrors.push( `${slotName}: "Until" (${ending}) cannot be after slot ends (${slotInfo.ending}).` @@ -296,8 +297,15 @@ const EditInstalledComponent = (props: Props) => { const raw = rawInputs.get(inst.id); const beginning = parseInt(raw?.beginning ?? String(inst.beginning), 10); const endingStr = raw?.ending ?? ""; + + // Get slot bounds for this installation + const slotInfo = getSlotTimeBounds( + inst.targetId, + inst.scInstallationContextId + ); + // If ending is empty, use slot ending const ending = - endingStr.trim() === "" ? Model.END_OF_TIME : parseInt(endingStr, 10); + endingStr.trim() === "" ? slotInfo.ending : parseInt(endingStr, 10); if (isNaN(beginning)) return; const key = `${inst.targetId}__${inst.scInstallationContextId || "any"}`; @@ -319,15 +327,15 @@ const EditInstalledComponent = (props: Props) => { for (let i = 0; i < installations.length - 1; i++) { const current = installations[i]; const next = installations[i + 1]; - const currentEnding = current.ending ?? Model.END_OF_TIME; + const currentEnding = current.ending ?? slotInfo.ending; const nextBeginning = next.beginning ?? 0; if (currentEnding > nextBeginning) { const currentEndingStr = currentEnding >= Model.END_OF_TIME ? "∞" : currentEnding; const nextEndingStr = - (next.ending ?? Model.END_OF_TIME) >= Model.END_OF_TIME + (next.ending ?? slotInfo.ending) >= Model.END_OF_TIME ? "∞" - : next.ending; + : next.ending ?? slotInfo.ending; newErrors.push( `${slotInfo.slotName}: Periods overlap (${current.beginning}-${currentEndingStr} and ${nextBeginning}-${nextEndingStr}).` ); @@ -350,11 +358,25 @@ const EditInstalledComponent = (props: Props) => { const raw = rawInputs.get(inst.id); const endingStr = raw?.ending ?? ""; + // Get slot bounds for this installation + const slotInfo = inst.targetId + ? getSlotTimeBounds(inst.targetId, inst.scInstallationContextId) + : { beginning: 0, ending: Model.END_OF_TIME, slotName: "" }; + + // If ending is empty, inherit from parent slot + let endingValue: number | undefined; + if (endingStr.trim() === "") { + // Inherit from parent - use parent's ending if it's not infinity + endingValue = + slotInfo.ending < Model.END_OF_TIME ? slotInfo.ending : undefined; + } else { + endingValue = parseInt(endingStr, 10); + } + return { ...inst, beginning: parseInt(raw?.beginning ?? String(inst.beginning), 10) || 0, - // Empty ending means undefined (infinity) - ending: endingStr.trim() === "" ? undefined : parseInt(endingStr, 10), + ending: endingValue, }; }); @@ -764,7 +786,12 @@ const EditInstalledComponent = (props: Props) => { onChange={(e) => updateRawInput(inst.id, "ending", e.target.value) } - placeholder="∞" + placeholder={ + instSlotBounds && + instSlotBounds.ending < Model.END_OF_TIME + ? String(instSlotBounds.ending) + : "∞" + } className={endingOutOfBounds ? "border-danger" : ""} isInvalid={endingOutOfBounds} /> diff --git a/editor-app/components/EditSystemComponentInstallation.tsx b/editor-app/components/EditSystemComponentInstallation.tsx index 3d9afa0..b68d0f1 100644 --- a/editor-app/components/EditSystemComponentInstallation.tsx +++ b/editor-app/components/EditSystemComponentInstallation.tsx @@ -300,8 +300,8 @@ export default function EditSystemComponentInstallation({ }; const beginning = raw.beginning === "" ? 0 : parseFloat(raw.beginning); - const ending = - raw.ending === "" ? Model.END_OF_TIME : parseFloat(raw.ending); + // If ending is empty, inherit from parent bounds + const ending = raw.ending === "" ? bounds.ending : parseFloat(raw.ending); if (isNaN(beginning)) { newErrors[inst.id].beginning = "Must be a number"; @@ -323,6 +323,7 @@ export default function EditSystemComponentInstallation({ } else if (ending <= beginning) { newErrors[inst.id].ending = "Must be > beginning"; } else if (ending > bounds.ending && bounds.ending < Model.END_OF_TIME) { + // Changed from > to > (allow equal) newErrors[inst.id].ending = `Must be ≤ ${bounds.ending} (target end)`; } @@ -348,9 +349,10 @@ export default function EditSystemComponentInstallation({ }; const otherBeginning = otherRaw.beginning === "" ? 0 : parseFloat(otherRaw.beginning); + // If other ending is empty, inherit from bounds const otherEnding = otherRaw.ending === "" - ? Model.END_OF_TIME + ? bounds.ending : parseFloat(otherRaw.ending); if ( @@ -489,11 +491,25 @@ export default function EditSystemComponentInstallation({ const finalInstallations = installations.map((inst) => { const raw = rawInputs[inst.id]; + const targetOption = getTargetForInstallation(inst); + const bounds = targetOption?.bounds || { + beginning: 0, + ending: Model.END_OF_TIME, + }; + + // If ending is empty, use parent bounds ending + const endingValue = + raw?.ending === "" + ? bounds.ending < Model.END_OF_TIME + ? bounds.ending + : undefined + : parseFloat(raw?.ending ?? ""); + return { ...inst, beginning: raw?.beginning === "" ? 0 : parseFloat(raw?.beginning ?? "0"), - ending: raw?.ending === "" ? undefined : parseFloat(raw?.ending ?? ""), + ending: endingValue, }; }); diff --git a/editor-app/components/ExportSvg.tsx b/editor-app/components/ExportSvg.tsx index eb5a4b9..da52b3d 100644 --- a/editor-app/components/ExportSvg.tsx +++ b/editor-app/components/ExportSvg.tsx @@ -117,30 +117,89 @@ const ExportSvg = (props: Props) => { const viewBox = originalSvg.getAttribute("viewBox") || "0 0 1000 500"; const [, , origWidth, origHeight] = viewBox.split(" ").map(Number); + // Axis configuration + const AXIS_SIZE = 40; + const AXIS_COLOR = "#9ca3af"; + const AXIS_STROKE_WIDTH = 4; + // Build legend const legend = buildLegendSvg(); const legendWidth = legend.width; const legendHeight = legend.height; - // Calculate new dimensions - const totalWidth = legendWidth + origWidth + 20; // 20px gap - const totalHeight = Math.max(legendHeight, origHeight); + // Calculate dimensions for the diagram section (Diagram + Axes) + const diagramSectionWidth = AXIS_SIZE + origWidth; + const diagramSectionHeight = origHeight + AXIS_SIZE; + + // Total SVG dimensions + const totalWidth = legendWidth + 20 + diagramSectionWidth; + const totalHeight = Math.max(legendHeight, diagramSectionHeight); // Get original SVG content const originalContent = originalSvg.innerHTML; + // Create Axis SVG parts + const defs = ` + + + + + + `; + + const yAxis = ` + + + + + Space + + `; + + const xAxis = ` + + + + + Time + + `; + // Build combined SVG const combinedSvg = ` + ${defs} + ${legend.content} - + - ${originalContent} + + + ${yAxis} + + + + ${originalContent} + + + + ${xAxis} + `; diff --git a/editor-app/components/SetActivity.tsx b/editor-app/components/SetActivity.tsx index 3a1d2d6..0d2b468 100644 --- a/editor-app/components/SetActivity.tsx +++ b/editor-app/components/SetActivity.tsx @@ -945,15 +945,16 @@ const SetActivity = (props: Props) => { variant="secondary" onClick={handlePromote} title="Promote (move up one level)" + disabled={!inputs.partOf} > Promote
diff --git a/editor-app/diagram/DrawActivities.ts b/editor-app/diagram/DrawActivities.ts index b673b9a..7cf21cf 100644 --- a/editor-app/diagram/DrawActivities.ts +++ b/editor-app/diagram/DrawActivities.ts @@ -227,14 +227,20 @@ export function clickActivities( function calculateLengthOfNewActivity(svgElement: any, activity: Activity) { let highestY = 0; + let foundAny = false; + activity?.participations?.forEach((a: Participation) => { - const node = svgElement.select("#i" + CSS.escape(a.individualId)).node(); + // Use CSS.escape to handle special characters in virtual row IDs (like __) + const escapedId = CSS.escape(a.individualId); + const node = svgElement.select("#i" + escapedId).node(); if (node) { + foundAny = true; const element = node.getBBox(); - highestY = Math.max(highestY, element.y); + highestY = Math.max(highestY, element.y + element.height); } }); - return highestY > 0 ? highestY : null; + + return foundAny ? highestY : null; } function calculateTopPositionOfNewActivity( @@ -242,14 +248,20 @@ function calculateTopPositionOfNewActivity( activity: Activity ) { let lowestY = Number.MAX_VALUE; + let foundAny = false; + activity?.participations?.forEach((a: Participation) => { - const node = svgElement.select("#i" + CSS.escape(a.individualId)).node(); + // Use CSS.escape to handle special characters in virtual row IDs (like __) + const escapedId = CSS.escape(a.individualId); + const node = svgElement.select("#i" + escapedId).node(); if (node) { + foundAny = true; const element = node.getBBox(); lowestY = Math.min(lowestY, element.y); } }); - return lowestY === Number.MAX_VALUE ? 0 : lowestY; + + return foundAny ? lowestY : 0; } function getBoxOfExistingActivity(svgElement: any, activity: Activity) { From ca4c990abc260c8b27c1a1513d505abd01cd3369 Mon Sep 17 00:00:00 2001 From: M E Gyamfi Date: Wed, 3 Dec 2025 16:22:47 +0000 Subject: [PATCH 81/81] fix: Ensure window is defined before accessing innerHeight in ActivityDiagram --- editor-app/components/ActivityDiagram.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/editor-app/components/ActivityDiagram.tsx b/editor-app/components/ActivityDiagram.tsx index aaeb3c1..f1bb5fb 100644 --- a/editor-app/components/ActivityDiagram.tsx +++ b/editor-app/components/ActivityDiagram.tsx @@ -237,8 +237,13 @@ const ActivityDiagram = (props: Props) => { // Calculate visible dimensions // Use measured wrapper height, fallback to calculation if 0 (initial render) + // FIX: Check if window is defined before accessing innerHeight const containerHeight = - wrapperHeight || Math.min(plot.height, window.innerHeight - 250); + wrapperHeight || + Math.min( + plot.height, + (typeof window !== "undefined" ? window.innerHeight : 800) - 250 + ); const bottomAxisHeight = axisMargin + 30; return (